diff options
43 files changed, 869 insertions, 569 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6021a8054..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 @@ -170,7 +170,7 @@ endif() # On modern Unixes, this is typically already the case. The lone exception is # glibc, which may default to 32 bits. glibc allows this to be configured # by setting _FILE_OFFSET_BITS. -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) add_definitions(-D_FILE_OFFSET_BITS=64) endif() @@ -178,10 +178,6 @@ endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>) - -math(EXPR EMU_ARCH_BITS ${CMAKE_SIZEOF_VOID_P}*8) -add_definitions(-DEMU_ARCH_BITS=${EMU_ARCH_BITS}) - # System imported libraries # ====================== @@ -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/file_util.cpp b/src/common/file_util.cpp index 548463787..b52492da6 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -15,21 +15,24 @@ #ifdef _WIN32 #include <windows.h> // windows.h needs to be included before other windows headers -#include <commdlg.h> // for GetSaveFileName -#include <direct.h> // getcwd +#include <direct.h> // getcwd #include <io.h> #include <shellapi.h> #include <shlobj.h> // for SHGetFolderPath #include <tchar.h> #include "common/string_util.h" -// 64 bit offsets for windows +#ifdef _MSC_VER +// 64 bit offsets for MSVC #define fseeko _fseeki64 #define ftello _ftelli64 -#define atoll _atoi64 +#define fileno _fileno +#endif + +// 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64 #define stat _stat64 #define fstat _fstat64 -#define fileno _fileno + #else #ifdef __APPLE__ #include <sys/param.h> diff --git a/src/common/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/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/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 61b9cfdc1..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); } @@ -545,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; } @@ -577,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; @@ -592,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; } @@ -641,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; } @@ -673,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; } @@ -692,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; } @@ -741,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); } @@ -769,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(); } @@ -794,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; } @@ -842,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)); @@ -954,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(); @@ -1035,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); @@ -1063,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; } @@ -1079,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; } @@ -1141,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); @@ -1153,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; } @@ -1169,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/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/settings.h b/src/core/settings.h index 8f2da01c8..ca80718e2 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -113,6 +113,7 @@ static const std::array<const char*, NumAnalogs> mapping = {{ struct Values { // System bool use_docked_mode; + bool enable_nfc; std::string username; int language_index; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 8afd26fe9..bca014a4a 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -13,8 +13,7 @@ #include "video_core/renderer_base.h" #include "video_core/textures/texture.h" -namespace Tegra { -namespace Engines { +namespace Tegra::Engines { /// First register id that is actually a Macro call. constexpr u32 MacroRegistersStart = 0xE00; @@ -408,5 +407,4 @@ void Maxwell3D::ProcessClearBuffers() { rasterizer.Clear(); } -} // namespace Engines -} // namespace Tegra +} // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index c8af1c6b6..0e09a7ee5 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -643,8 +643,10 @@ public: u32 d3d_cull_mode; ComparisonOp depth_test_func; + float alpha_test_ref; + ComparisonOp alpha_test_func; - INSERT_PADDING_WORDS(0xB); + INSERT_PADDING_WORDS(0x9); struct { u32 separate_alpha; diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp index 59e28b22d..8b5f08351 100644 --- a/src/video_core/engines/maxwell_compute.cpp +++ b/src/video_core/engines/maxwell_compute.cpp @@ -6,8 +6,7 @@ #include "core/core.h" #include "video_core/engines/maxwell_compute.h" -namespace Tegra { -namespace Engines { +namespace Tegra::Engines { void MaxwellCompute::WriteReg(u32 method, u32 value) { ASSERT_MSG(method < Regs::NUM_REGS, @@ -26,5 +25,4 @@ void MaxwellCompute::WriteReg(u32 method, u32 value) { } } -} // namespace Engines -} // namespace Tegra +} // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 103cd110e..b8a78cf82 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -7,8 +7,7 @@ #include "video_core/rasterizer_interface.h" #include "video_core/textures/decoders.h" -namespace Tegra { -namespace Engines { +namespace Tegra::Engines { MaxwellDMA::MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) : memory_manager(memory_manager), rasterizer{rasterizer} {} @@ -78,9 +77,9 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.exec.enable_2d == 1); - std::size_t copy_size = regs.x_count * regs.y_count; + const std::size_t copy_size = regs.x_count * regs.y_count; - const auto FlushAndInvalidate = [&](u32 src_size, u32 dst_size) { + const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) { // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated // copying. rasterizer.FlushRegion(source_cpu, src_size); @@ -91,14 +90,11 @@ void MaxwellDMA::HandleCopy() { rasterizer.InvalidateRegion(dest_cpu, dst_size); }; - u8* src_buffer = Memory::GetPointer(source_cpu); - u8* dst_buffer = Memory::GetPointer(dest_cpu); - if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { ASSERT(regs.src_params.size_z == 1); // If the input is tiled and the output is linear, deswizzle the input and copy it over. - u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; + const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; FlushAndInvalidate(regs.src_pitch * regs.src_params.size_y, copy_size * src_bytes_per_pixel); @@ -111,7 +107,7 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.dst_params.size_z == 1); ASSERT(regs.src_pitch == regs.x_count); - u32 src_bpp = regs.src_pitch / regs.x_count; + const u32 src_bpp = regs.src_pitch / regs.x_count; FlushAndInvalidate(regs.src_pitch * regs.y_count, regs.dst_params.size_x * regs.dst_params.size_y * src_bpp); @@ -122,5 +118,4 @@ void MaxwellDMA::HandleCopy() { } } -} // namespace Engines -} // namespace Tegra +} // namespace Tegra::Engines diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 67501cf0a..6cd08d28b 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -214,7 +214,7 @@ enum class IMinMaxExchange : u64 { XHi = 3, }; -enum class VmadType : u64 { +enum class VideoType : u64 { Size16_Low = 0, Size16_High = 1, Size32 = 2, @@ -564,6 +564,10 @@ union Instruction { } fmul; union { + BitField<55, 1, u64> saturate; + } fmul32; + + union { BitField<48, 1, u64> is_signed; } shift; @@ -779,6 +783,14 @@ union Instruction { } psetp; union { + BitField<43, 4, PredCondition> cond; + BitField<45, 2, PredOperation> op; + BitField<3, 3, u64> pred3; + BitField<0, 3, u64> pred0; + BitField<39, 3, u64> pred39; + } vsetp; + + union { BitField<12, 3, u64> pred12; BitField<15, 1, u64> neg_pred12; BitField<24, 2, PredOperation> cond; @@ -1150,15 +1162,17 @@ union Instruction { union { BitField<48, 1, u64> signed_a; BitField<38, 1, u64> is_byte_chunk_a; - BitField<36, 2, VmadType> type_a; + BitField<36, 2, VideoType> type_a; BitField<36, 2, u64> byte_height_a; BitField<49, 1, u64> signed_b; BitField<50, 1, u64> use_register_b; BitField<30, 1, u64> is_byte_chunk_b; - BitField<28, 2, VmadType> type_b; + BitField<28, 2, VideoType> type_b; BitField<28, 2, u64> byte_height_b; + } video; + union { BitField<51, 2, VmadShr> shr; BitField<55, 1, u64> saturate; // Saturates the result (a * b + c) BitField<47, 1, u64> cc; @@ -1209,11 +1223,13 @@ public: KIL, SSY, SYNC, + BRK, DEPBAR, BFE_C, BFE_R, BFE_IMM, BRA, + PBK, LD_A, LD_C, ST_A, @@ -1232,6 +1248,7 @@ public: OUT_R, // Emit vertex/primitive ISBERD, VMAD, + VSETP, FFMA_IMM, // Fused Multiply and Add FFMA_CR, FFMA_RC, @@ -1370,7 +1387,7 @@ public: /// conditionally executed). static bool IsPredicatedInstruction(Id opcode) { // TODO(Subv): Add the rest of unpredicated instructions. - return opcode != Id::SSY; + return opcode != Id::SSY && opcode != Id::PBK; } class Matcher { @@ -1466,9 +1483,11 @@ private: #define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name) INST("111000110011----", Id::KIL, Type::Flow, "KIL"), INST("111000101001----", Id::SSY, Type::Flow, "SSY"), + INST("111000101010----", Id::PBK, Type::Flow, "PBK"), INST("111000100100----", Id::BRA, Type::Flow, "BRA"), + INST("1111000011111---", Id::SYNC, Type::Flow, "SYNC"), + INST("111000110100---", Id::BRK, Type::Flow, "BRK"), INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"), - INST("1111000011111---", Id::SYNC, Type::Synch, "SYNC"), INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"), INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"), INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"), @@ -1487,6 +1506,7 @@ private: INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"), INST("01011111--------", Id::VMAD, Type::Trivial, "VMAD"), + INST("0101000011110---", Id::VSETP, Type::Trivial, "VSETP"), INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"), INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"), @@ -1606,4 +1626,4 @@ private: } }; -} // namespace Tegra::Shader +} // namespace Tegra::Shader
\ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 3daccf82f..be51c5215 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -570,10 +570,11 @@ void RasterizerOpenGL::DrawArrays() { SyncBlendState(); SyncLogicOpState(); SyncCullMode(); - SyncAlphaTest(); SyncScissorTest(); + // Alpha Testing is synced on shaders. SyncTransformFeedback(); SyncPointState(); + CheckAlphaTests(); // TODO(bunnei): Sync framebuffer_scale uniform here // TODO(bunnei): Sync scissorbox uniform(s) here @@ -1007,17 +1008,6 @@ void RasterizerOpenGL::SyncLogicOpState() { state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); } -void RasterizerOpenGL::SyncAlphaTest() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - - // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be - // implemented with a test+discard in fragment shaders. - if (regs.alpha_test_enabled != 0) { - LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented"); - UNREACHABLE(); - } -} - void RasterizerOpenGL::SyncScissorTest() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; @@ -1052,4 +1042,15 @@ void RasterizerOpenGL::SyncPointState() { state.point.size = regs.point_size == 0 ? 1 : regs.point_size; } +void RasterizerOpenGL::CheckAlphaTests() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + + if (regs.alpha_test_enabled != 0 && regs.rt_control.count > 1) { + LOG_CRITICAL( + Render_OpenGL, + "Alpha Testing is enabled with Multiple Render Targets, this behavior is undefined."); + UNREACHABLE(); + } +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index b1f7ccc7e..0e90a31f5 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -162,9 +162,6 @@ private: /// Syncs the LogicOp state to match the guest state void SyncLogicOpState(); - /// Syncs the alpha test state to match the guest state - void SyncAlphaTest(); - /// Syncs the scissor test state to match the guest state void SyncScissorTest(); @@ -174,6 +171,9 @@ private: /// Syncs the point state to match the guest state void SyncPointState(); + /// Check asserts for alpha testing. + void CheckAlphaTests(); + bool has_ARB_direct_state_access = false; bool has_ARB_multi_bind = false; bool has_ARB_separate_shader_objects = false; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 09db58ab6..fe4d1bd83 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -163,10 +163,11 @@ private: const ExitMethod jmp = Scan(target, end, labels); return exit_method = ParallelExit(no_jmp, jmp); } - case OpCode::Id::SSY: { - // The SSY instruction uses a similar encoding as the BRA instruction. + case OpCode::Id::SSY: + case OpCode::Id::PBK: { + // The SSY and PBK use a similar encoding as the BRA instruction. ASSERT_MSG(instr.bra.constant_buffer == 0, - "Constant buffer SSY is not supported"); + "Constant buffer branching is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); labels.insert(target); // Continue scanning for an exit method. @@ -1233,27 +1234,27 @@ private: } /* - * Emits code to push the input target address to the SSY address stack, incrementing the stack + * Emits code to push the input target address to the flow address stack, incrementing the stack * top. */ - void EmitPushToSSYStack(u32 target) { + void EmitPushToFlowStack(u32 target) { shader.AddLine('{'); ++shader.scope; - shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;"); - shader.AddLine("ssy_stack_top++;"); + shader.AddLine("flow_stack[flow_stack_top] = " + std::to_string(target) + "u;"); + shader.AddLine("flow_stack_top++;"); --shader.scope; shader.AddLine('}'); } /* - * Emits code to pop an address from the SSY address stack, setting the jump address to the + * Emits code to pop an address from the flow address stack, setting the jump address to the * popped address and decrementing the stack top. */ - void EmitPopFromSSYStack() { + void EmitPopFromFlowStack() { shader.AddLine('{'); ++shader.scope; - shader.AddLine("ssy_stack_top--;"); - shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];"); + shader.AddLine("flow_stack_top--;"); + shader.AddLine("jmp_to = flow_stack[flow_stack_top];"); shader.AddLine("break;"); --shader.scope; shader.AddLine('}'); @@ -1265,9 +1266,29 @@ private: ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented"); + shader.AddLine("if (alpha_test[0] != 0) {"); + ++shader.scope; + // We start on the register containing the alpha value in the first RT. + u32 current_reg = 3; + for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets; + ++render_target) { + // TODO(Blinkhawk): verify the behavior of alpha testing on hardware when + // multiple render targets are used. + if (header.ps.IsColorComponentOutputEnabled(render_target, 0) || + header.ps.IsColorComponentOutputEnabled(render_target, 1) || + header.ps.IsColorComponentOutputEnabled(render_target, 2) || + header.ps.IsColorComponentOutputEnabled(render_target, 3)) { + shader.AddLine(fmt::format("if (!AlphaFunc({})) discard;", + regs.GetRegisterAsFloat(current_reg))); + current_reg += 4; + } + } + --shader.scope; + shader.AddLine('}'); + // Write the color outputs using the data in the shader registers, disabled // rendertargets/components are skipped in the register assignment. - u32 current_reg = 0; + current_reg = 0; for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets; ++render_target) { // TODO(Subv): Figure out how dual-source blending is configured in the Switch. @@ -1291,6 +1312,63 @@ private: } } + /// Unpacks a video instruction operand (e.g. VMAD). + std::string GetVideoOperand(const std::string& op, bool is_chunk, bool is_signed, + Tegra::Shader::VideoType type, u64 byte_height) { + const std::string value = [&]() { + if (!is_chunk) { + const auto offset = static_cast<u32>(byte_height * 8); + return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)"; + } + const std::string zero = "0"; + + switch (type) { + case Tegra::Shader::VideoType::Size16_Low: + return '(' + op + " & 0xffff)"; + case Tegra::Shader::VideoType::Size16_High: + return '(' + op + " >> 16)"; + case Tegra::Shader::VideoType::Size32: + // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when + // this type is used (1 * 1 + 0 == 0x5b800000). Until a better + // explanation is found: assert. + UNIMPLEMENTED(); + return zero; + case Tegra::Shader::VideoType::Invalid: + UNREACHABLE_MSG("Invalid instruction encoding"); + return zero; + default: + UNREACHABLE(); + return zero; + } + }(); + + if (is_signed) { + return "int(" + value + ')'; + } + return value; + }; + + /// Gets the A operand for a video instruction. + std::string GetVideoOperandA(Instruction instr) { + return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr8, 0, false), + instr.video.is_byte_chunk_a != 0, instr.video.signed_a, + instr.video.type_a, instr.video.byte_height_a); + } + + /// Gets the B operand for a video instruction. + std::string GetVideoOperandB(Instruction instr) { + if (instr.video.use_register_b) { + return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr20, 0, false), + instr.video.is_byte_chunk_b != 0, instr.video.signed_b, + instr.video.type_b, instr.video.byte_height_b); + } else { + return '(' + + std::to_string(instr.video.signed_b ? static_cast<s16>(instr.alu.GetImm20_16()) + : instr.alu.GetImm20_16()) + + ')'; + } + } + /** * Compiles a single instruction from Tegra to GLSL. * @param offset the offset of the Tegra shader instruction. @@ -1460,9 +1538,10 @@ private: break; } case OpCode::Id::FMUL32_IMM: { - regs.SetRegisterToFloat( - instr.gpr0, 0, - regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); + regs.SetRegisterToFloat(instr.gpr0, 0, + regs.GetRegisterAsFloat(instr.gpr8) + " * " + + GetImmediate32(instr), + 1, 1, instr.fmul32.saturate); break; } case OpCode::Id::FADD32I: { @@ -3264,16 +3343,32 @@ private: // The SSY opcode tells the GPU where to re-converge divergent execution paths, it // sets the target of the jump that the SYNC instruction will make. The SSY opcode // has a similar structure to the BRA opcode. - ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported"); + ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer flow is not supported"); + + const u32 target = offset + instr.bra.GetBranchTarget(); + EmitPushToFlowStack(target); + break; + } + case OpCode::Id::PBK: { + // PBK pushes to a stack the address where BRK will jump to. This shares stack with + // SSY but using SYNC on a PBK address will kill the shader execution. We don't + // emulate this because it's very unlikely a driver will emit such invalid shader. + ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer PBK is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); - EmitPushToSSYStack(target); + EmitPushToFlowStack(target); break; } case OpCode::Id::SYNC: { // The SYNC opcode jumps to the address previously set by the SSY opcode ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); - EmitPopFromSSYStack(); + EmitPopFromFlowStack(); + break; + } + case OpCode::Id::BRK: { + // The BRK opcode jumps to the address previously set by the PBK opcode + ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); + EmitPopFromFlowStack(); break; } case OpCode::Id::DEPBAR: { @@ -3283,87 +3378,51 @@ private: break; } case OpCode::Id::VMAD: { - const bool signed_a = instr.vmad.signed_a == 1; - const bool signed_b = instr.vmad.signed_b == 1; - const bool result_signed = signed_a || signed_b; - boost::optional<std::string> forced_result; - - auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed, - Tegra::Shader::VmadType type, u64 byte_height) { - const std::string value = [&]() { - if (!is_chunk) { - const auto shift = static_cast<u32>(byte_height * 8); - return "((" + op + " >> " + std::to_string(shift) + ") & 0xff)"; - } - const std::string zero = "0"; - - switch (type) { - case Tegra::Shader::VmadType::Size16_Low: - return '(' + op + " & 0xffff)"; - case Tegra::Shader::VmadType::Size16_High: - return '(' + op + " >> 16)"; - case Tegra::Shader::VmadType::Size32: - // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when - // this type is used (1 * 1 + 0 == 0x5b800000). Until a better - // explanation is found: assert. - UNREACHABLE_MSG("Unimplemented"); - return zero; - case Tegra::Shader::VmadType::Invalid: - // Note(Rodrigo): This flag is invalid according to nvdisasm. From my - // testing (even though it's invalid) this makes the whole instruction - // assign zero to target register. - forced_result = boost::make_optional(zero); - return zero; - default: - UNREACHABLE(); - return zero; - } - }(); - - if (is_signed) { - return "int(" + value + ')'; - } - return value; - }; - - const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false), - instr.vmad.is_byte_chunk_a != 0, signed_a, - instr.vmad.type_a, instr.vmad.byte_height_a); - - std::string op_b; - if (instr.vmad.use_register_b) { - op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false), - instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b, - instr.vmad.byte_height_b); - } else { - op_b = '(' + - std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16()) - : instr.alu.GetImm20_16()) + - ')'; - } - + const bool result_signed = instr.video.signed_a == 1 || instr.video.signed_b == 1; + const std::string op_a = GetVideoOperandA(instr); + const std::string op_b = GetVideoOperandB(instr); const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed); - std::string result; - if (forced_result) { - result = *forced_result; - } else { - result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; + std::string result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; - switch (instr.vmad.shr) { - case Tegra::Shader::VmadShr::Shr7: - result = '(' + result + " >> 7)"; - break; - case Tegra::Shader::VmadShr::Shr15: - result = '(' + result + " >> 15)"; - break; - } + switch (instr.vmad.shr) { + case Tegra::Shader::VmadShr::Shr7: + result = '(' + result + " >> 7)"; + break; + case Tegra::Shader::VmadShr::Shr15: + result = '(' + result + " >> 15)"; + break; } + regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1, instr.vmad.saturate == 1, 0, Register::Size::Word, instr.vmad.cc); break; } + case OpCode::Id::VSETP: { + const std::string op_a = GetVideoOperandA(instr); + const std::string op_b = GetVideoOperandB(instr); + + // We can't use the constant predicate as destination. + ASSERT(instr.vsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); + + const std::string second_pred = GetPredicateCondition(instr.vsetp.pred39, false); + + const std::string combiner = GetPredicateCombiner(instr.vsetp.op); + + const std::string predicate = GetPredicateComparison(instr.vsetp.cond, op_a, op_b); + // Set the primary predicate to the result of Predicate OP SecondPredicate + SetPredicate(instr.vsetp.pred3, + '(' + predicate + ") " + combiner + " (" + second_pred + ')'); + + if (instr.vsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + // Set the secondary predicate to the result of !Predicate OP SecondPredicate, + // if enabled + SetPredicate(instr.vsetp.pred0, + "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + } + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName()); UNREACHABLE(); @@ -3427,11 +3486,11 @@ private: labels.insert(subroutine.begin); shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;"); - // TODO(Subv): Figure out the actual depth of the SSY stack, for now it seems - // unlikely that shaders will use 20 nested SSYs. - constexpr u32 SSY_STACK_SIZE = 20; - shader.AddLine("uint ssy_stack[" + std::to_string(SSY_STACK_SIZE) + "];"); - shader.AddLine("uint ssy_stack_top = 0u;"); + // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems + // unlikely that shaders will use 20 nested SSYs and PBKs. + constexpr u32 FLOW_STACK_SIZE = 20; + shader.AddLine("uint flow_stack[" + std::to_string(FLOW_STACK_SIZE) + "];"); + shader.AddLine("uint flow_stack_top = 0u;"); shader.AddLine("while (true) {"); ++shader.scope; @@ -3498,7 +3557,7 @@ private: // Declarations std::set<std::string> declr_predicates; -}; // namespace Decompiler +}; // namespace OpenGL::GLShader::Decompiler std::string GetCommonDeclarations() { return fmt::format("#define MAX_CONSTBUFFER_ELEMENTS {}\n", diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index ecbc9d8ed..e883ffb1d 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -29,6 +29,7 @@ layout(std140) uniform vs_config { vec4 viewport_flip; uvec4 instance_id; uvec4 flip_stage; + uvec4 alpha_test; }; )"; @@ -105,6 +106,7 @@ layout (std140) uniform gs_config { vec4 viewport_flip; uvec4 instance_id; uvec4 flip_stage; + uvec4 alpha_test; }; void main() { @@ -142,8 +144,33 @@ layout (std140) uniform fs_config { vec4 viewport_flip; uvec4 instance_id; uvec4 flip_stage; + uvec4 alpha_test; }; +bool AlphaFunc(in float value) { + float ref = uintBitsToFloat(alpha_test[2]); + switch (alpha_test[1]) { + case 1: + return false; + case 2: + return value < ref; + case 3: + return value == ref; + case 4: + return value <= ref; + case 5: + return value > ref; + case 6: + return value != ref; + case 7: + return value >= ref; + case 8: + return true; + default: + return false; + } +} + void main() { exec_fragment(); } @@ -152,4 +179,4 @@ void main() { out += program.first; return {out, program.second}; } -} // namespace OpenGL::GLShader
\ No newline at end of file +} // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 010857ec6..8b8869ecb 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -16,6 +16,17 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f; viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f; + u32 func = static_cast<u32>(regs.alpha_test_func); + // Normalize the gl variants of opCompare to be the same as the normal variants + u32 op_gl_variant_base = static_cast<u32>(Tegra::Engines::Maxwell3D::Regs::ComparisonOp::Never); + if (func >= op_gl_variant_base) { + func = func - op_gl_variant_base + 1U; + } + + alpha_test.enabled = regs.alpha_test_enabled; + alpha_test.func = func; + alpha_test.ref = regs.alpha_test_ref; + // We only assign the instance to the first component of the vector, the rest is just padding. instance_id[0] = state.current_instance; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index b3a191cf2..36fe1f04c 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -22,8 +22,14 @@ struct MaxwellUniformData { alignas(16) GLvec4 viewport_flip; alignas(16) GLuvec4 instance_id; alignas(16) GLuvec4 flip_stage; + struct alignas(16) { + GLuint enabled; + GLuint func; + GLfloat ref; + GLuint padding; + } alpha_test; }; -static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) == 64, "MaxwellUniformData structure size is incorrect"); static_assert(sizeof(MaxwellUniformData) < 16384, "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); diff --git a/src/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/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 71c6ebb41..d029590ff 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -122,6 +122,7 @@ void Config::ReadValues() { qt_config->beginGroup("System"); Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool(); + Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool(); Settings::values.username = qt_config->value("username", "yuzu").toString().toStdString(); Settings::values.language_index = qt_config->value("language_index", 1).toInt(); qt_config->endGroup(); @@ -258,6 +259,7 @@ void Config::SaveValues() { qt_config->beginGroup("System"); qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); + qt_config->setValue("enable_nfc", Settings::values.enable_nfc); qt_config->setValue("username", QString::fromStdString(Settings::values.username)); 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/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..be9896614 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -60,6 +60,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 +102,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 +426,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 +695,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(); @@ -823,14 +829,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 +847,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 +876,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa if (out == nullptr) { failed(); + vfs->DeleteDirectory(path); return; } @@ -870,8 +887,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 +908,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } else { progress.close(); failed(); + vfs->DeleteDirectory(path); } } @@ -1174,6 +1195,7 @@ void GMainWindow::OnStartGame() { ui.action_Report_Compatibility->setEnabled(true); discord_rpc->Update(); + ui.action_Load_Amiibo->setEnabled(true); } void GMainWindow::OnPauseGame() { @@ -1278,6 +1300,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 +1361,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 +1402,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 +1507,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..654a15a5c 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -125,6 +125,7 @@ void Config::ReadValues() { // System Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); + Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true); Settings::values.username = sdl2_config->Get("System", "username", "yuzu"); if (Settings::values.username.empty()) { Settings::values.username = "yuzu"; 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 |