Compare commits
43 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c9f7aeacc | ||
|
|
a94e5d9e68 | ||
|
|
50e4e81fd3 | ||
|
|
5edb2403c2 | ||
|
|
fc9d8afead | ||
|
|
a5106fb9f5 | ||
|
|
e61a62066a | ||
|
|
289adf87ac | ||
|
|
1291f3f820 | ||
|
|
e7e209d900 | ||
|
|
5716496239 | ||
|
|
0f3d8c2574 | ||
|
|
75d807788c | ||
|
|
d9ca6351dd | ||
|
|
2ff2732a78 | ||
|
|
38cdb6744d | ||
|
|
7d6dca0d0a | ||
|
|
5dfb43531c | ||
|
|
8042731da9 | ||
|
|
848a49112a | ||
|
|
496d155d7b | ||
|
|
40c63073a9 | ||
|
|
4cccfb4190 | ||
|
|
259da93567 | ||
|
|
ff6b2d4574 | ||
|
|
314a948373 | ||
|
|
10a2d20e26 | ||
|
|
3b8c0f8885 | ||
|
|
c8beb665dc | ||
|
|
98a27b1ec7 | ||
|
|
90a981a03a | ||
|
|
c1e5525fc6 | ||
|
|
d53c73adaa | ||
|
|
dd1ee39426 | ||
|
|
08e574eec4 | ||
|
|
8a86c8d48b | ||
|
|
381baf783d | ||
|
|
61ef8af1e2 | ||
|
|
41fb25349a | ||
|
|
9d0fb0f815 | ||
|
|
59044862a9 | ||
|
|
780c21ab2d | ||
|
|
d8273c3857 |
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -91,6 +91,7 @@ enum class Class : ClassType {
|
||||
Service_PM, ///< The PM service
|
||||
Service_PREPO, ///< The PREPO (Play report) service
|
||||
Service_PSC, ///< The PSC service
|
||||
Service_PSM, ///< The PSM service
|
||||
Service_SET, ///< The SET (Settings) service
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -331,6 +331,8 @@ add_library(core STATIC
|
||||
hle/service/prepo/prepo.h
|
||||
hle/service/psc/psc.cpp
|
||||
hle/service/psc/psc.h
|
||||
hle/service/ptm/psm.cpp
|
||||
hle/service/ptm/psm.h
|
||||
hle/service/service.cpp
|
||||
hle/service/service.h
|
||||
hle/service/set/set.cpp
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -189,14 +189,15 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
|
||||
CASCADE_RESULT(client_session, client_port->Connect());
|
||||
|
||||
// Return the client session
|
||||
CASCADE_RESULT(*out_handle, kernel.HandleTable().Create(client_session));
|
||||
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
CASCADE_RESULT(*out_handle, handle_table.Create(client_session));
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Makes a blocking IPC call to an OS service.
|
||||
static ResultCode SendSyncRequest(Handle handle) {
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<ClientSession> session = kernel.HandleTable().Get<ClientSession>(handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
SharedPtr<ClientSession> session = handle_table.Get<ClientSession>(handle);
|
||||
if (!session) {
|
||||
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
|
||||
return ERR_INVALID_HANDLE;
|
||||
@@ -215,8 +216,8 @@ static ResultCode SendSyncRequest(Handle handle) {
|
||||
static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -229,8 +230,8 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
|
||||
static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
|
||||
if (!process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -273,11 +274,11 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
|
||||
|
||||
using ObjectPtr = Thread::ThreadWaitObjects::value_type;
|
||||
Thread::ThreadWaitObjects objects(handle_count);
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
|
||||
for (u64 i = 0; i < handle_count; ++i) {
|
||||
const Handle handle = Memory::Read32(handles_address + i * sizeof(Handle));
|
||||
const auto object = kernel.HandleTable().Get<WaitObject>(handle);
|
||||
const auto object = handle_table.Get<WaitObject>(handle);
|
||||
|
||||
if (object == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
@@ -325,8 +326,8 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
|
||||
static ResultCode CancelSynchronization(Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -354,7 +355,7 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
|
||||
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
|
||||
requesting_thread_handle);
|
||||
}
|
||||
@@ -374,9 +375,19 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
return Mutex::Release(mutex_addr);
|
||||
}
|
||||
|
||||
enum class BreakType : u32 {
|
||||
Panic = 0,
|
||||
AssertionFailed = 1,
|
||||
PreNROLoad = 3,
|
||||
PostNROLoad = 4,
|
||||
PreNROUnload = 5,
|
||||
PostNROUnload = 6,
|
||||
};
|
||||
|
||||
struct BreakReason {
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<0, 30, BreakType> break_type;
|
||||
BitField<31, 1, u32> signal_debugger;
|
||||
};
|
||||
};
|
||||
@@ -384,12 +395,48 @@ struct BreakReason {
|
||||
/// Break program execution
|
||||
static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
BreakReason break_reason{reason};
|
||||
if (break_reason.signal_debugger) {
|
||||
LOG_ERROR(
|
||||
|
||||
switch (break_reason.break_type) {
|
||||
case BreakType::Panic:
|
||||
LOG_CRITICAL(Debug_Emulated, "Signalling debugger, PANIC! info1=0x{:016X}, info2=0x{:016X}",
|
||||
info1, info2);
|
||||
break;
|
||||
case BreakType::AssertionFailed:
|
||||
LOG_CRITICAL(Debug_Emulated,
|
||||
"Signalling debugger, Assertion failed! info1=0x{:016X}, info2=0x{:016X}",
|
||||
info1, info2);
|
||||
break;
|
||||
case BreakType::PreNROLoad:
|
||||
LOG_WARNING(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
} else {
|
||||
"Signalling debugger, Attempting to load an NRO at 0x{:016X} with size 0x{:016X}",
|
||||
info1, info2);
|
||||
break;
|
||||
case BreakType::PostNROLoad:
|
||||
LOG_WARNING(Debug_Emulated,
|
||||
"Signalling debugger, Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
|
||||
info2);
|
||||
break;
|
||||
case BreakType::PreNROUnload:
|
||||
LOG_WARNING(
|
||||
Debug_Emulated,
|
||||
"Signalling debugger, Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}",
|
||||
info1, info2);
|
||||
break;
|
||||
case BreakType::PostNROUnload:
|
||||
LOG_WARNING(Debug_Emulated,
|
||||
"Signalling debugger, Unloaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
|
||||
info2);
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(
|
||||
Debug_Emulated,
|
||||
"Signalling debugger, Unknown break reason {}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
static_cast<u32>(break_reason.break_type.Value()), info1, info2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!break_reason.signal_debugger) {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
@@ -499,13 +546,12 @@ static ResultCode SetThreadActivity(Handle handle, u32 unknown) {
|
||||
static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, context=0x{:08X}, thread=0x{:X}", thread_context, handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
|
||||
const auto* current_process = Core::CurrentProcess();
|
||||
const SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto* current_process = Core::CurrentProcess();
|
||||
if (thread->GetOwnerProcess() != current_process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -531,10 +577,11 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
|
||||
/// Gets the priority for the specified thread
|
||||
static ResultCode GetThreadPriority(u32* priority, Handle handle) {
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
|
||||
if (!thread)
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
*priority = thread->GetPriority();
|
||||
return RESULT_SUCCESS;
|
||||
@@ -546,14 +593,15 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
|
||||
if (!thread)
|
||||
const auto* const current_process = Core::CurrentProcess();
|
||||
SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Note: The kernel uses the current process's resource limit instead of
|
||||
// the one from the thread owner's resource limit.
|
||||
const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit();
|
||||
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
|
||||
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
@@ -595,15 +643,13 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
|
||||
return ERR_INVALID_MEMORY_PERMISSIONS;
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle);
|
||||
if (!shared_memory) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
if (!vm_manager.IsWithinASLRRegion(addr, size)) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
@@ -627,15 +673,13 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle);
|
||||
if (!shared_memory) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
if (!vm_manager.IsWithinASLRRegion(addr, size)) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
@@ -646,9 +690,8 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
|
||||
/// Query process memory
|
||||
static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/,
|
||||
Handle process_handle, u64 addr) {
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
|
||||
if (!process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -695,20 +738,19 @@ static void ExitProcess() {
|
||||
/// Creates a new thread
|
||||
static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top,
|
||||
u32 priority, s32 processor_id) {
|
||||
std::string name = fmt::format("thread-{:X}", entry_point);
|
||||
|
||||
if (priority > THREADPRIO_LOWEST) {
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit();
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
|
||||
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
if (processor_id == THREADPROCESSORID_DEFAULT) {
|
||||
// Set the target CPU to the one specified in the process' exheader.
|
||||
processor_id = Core::CurrentProcess()->GetDefaultProcessorID();
|
||||
processor_id = current_process->GetDefaultProcessorID();
|
||||
ASSERT(processor_id != THREADPROCESSORID_DEFAULT);
|
||||
}
|
||||
|
||||
@@ -723,11 +765,13 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
return ERR_INVALID_PROCESSOR_ID;
|
||||
}
|
||||
|
||||
const std::string name = fmt::format("thread-{:X}", entry_point);
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
CASCADE_RESULT(SharedPtr<Thread> thread,
|
||||
Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
|
||||
*Core::CurrentProcess()));
|
||||
const auto new_guest_handle = kernel.HandleTable().Create(thread);
|
||||
*current_process));
|
||||
|
||||
const auto new_guest_handle = current_process->GetHandleTable().Create(thread);
|
||||
if (new_guest_handle.Failed()) {
|
||||
return new_guest_handle.Code();
|
||||
}
|
||||
@@ -748,8 +792,8 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
static ResultCode StartThread(Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -796,8 +840,8 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
|
||||
"called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
|
||||
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
ASSERT(thread);
|
||||
|
||||
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||
@@ -908,9 +952,9 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
mutex_val | Mutex::MutexHasWaitersFlag));
|
||||
|
||||
// The mutex is already owned by some other thread, make this thread wait on it.
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
|
||||
const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
auto owner = handle_table.Get<Thread>(owner_handle);
|
||||
ASSERT(owner);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->InvalidateWakeupCallback();
|
||||
@@ -989,16 +1033,16 @@ static u64 GetSystemTick() {
|
||||
static ResultCode CloseHandle(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
return kernel.HandleTable().Close(handle);
|
||||
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
return handle_table.Close(handle);
|
||||
}
|
||||
|
||||
/// Reset an event
|
||||
static ResultCode ResetSignal(Handle handle) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto event = kernel.HandleTable().Get<Event>(handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
auto event = handle_table.Get<Event>(handle);
|
||||
|
||||
ASSERT(event != nullptr);
|
||||
|
||||
@@ -1017,8 +1061,8 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32
|
||||
static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) {
|
||||
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -1033,8 +1077,8 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle,
|
||||
mask, core);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -1095,7 +1139,7 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto& handle_table = kernel.HandleTable();
|
||||
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
auto shared_mem_handle =
|
||||
SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
|
||||
local_perms, remote_perms);
|
||||
@@ -1107,10 +1151,12 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
|
||||
static ResultCode ClearEvent(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<Event> evt = kernel.HandleTable().Get<Event>(handle);
|
||||
if (evt == nullptr)
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
SharedPtr<Event> evt = handle_table.Get<Event>(handle);
|
||||
if (evt == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
evt->Clear();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -1123,8 +1169,8 @@ static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
|
||||
Status,
|
||||
};
|
||||
|
||||
const auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const auto process = kernel.HandleTable().Get<Process>(process_handle);
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
const auto process = handle_table.Get<Process>(process_handle);
|
||||
if (!process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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 GetNpadId(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, 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_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(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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
71
src/core/hle/service/ptm/psm.cpp
Normal file
71
src/core/hle/service/ptm/psm.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/ptm/psm.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::PSM {
|
||||
|
||||
constexpr u32 BATTERY_FULLY_CHARGED = 100; // 100% Full
|
||||
constexpr u32 BATTERY_CURRENTLY_CHARGING = 1; // Plugged into an official dock
|
||||
|
||||
class PSM final : public ServiceFramework<PSM> {
|
||||
public:
|
||||
explicit PSM() : ServiceFramework{"psm"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"},
|
||||
{1, &PSM::GetChargerType, "GetChargerType"},
|
||||
{2, nullptr, "EnableBatteryCharging"},
|
||||
{3, nullptr, "DisableBatteryCharging"},
|
||||
{4, nullptr, "IsBatteryChargingEnabled"},
|
||||
{5, nullptr, "AcquireControllerPowerSupply"},
|
||||
{6, nullptr, "ReleaseControllerPowerSupply"},
|
||||
{7, nullptr, "OpenSession"},
|
||||
{8, nullptr, "EnableEnoughPowerChargeEmulation"},
|
||||
{9, nullptr, "DisableEnoughPowerChargeEmulation"},
|
||||
{10, nullptr, "EnableFastBatteryCharging"},
|
||||
{11, nullptr, "DisableFastBatteryCharging"},
|
||||
{12, nullptr, "GetBatteryVoltageState"},
|
||||
{13, nullptr, "GetRawBatteryChargePercentage"},
|
||||
{14, nullptr, "IsEnoughPowerSupplied"},
|
||||
{15, nullptr, "GetBatteryAgePercentage"},
|
||||
{16, nullptr, "GetBatteryChargeInfoEvent"},
|
||||
{17, nullptr, "GetBatteryChargeInfoFields"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
~PSM() override = default;
|
||||
|
||||
private:
|
||||
void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_PSM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(BATTERY_FULLY_CHARGED);
|
||||
}
|
||||
|
||||
void GetChargerType(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_PSM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(BATTERY_CURRENTLY_CHARGING);
|
||||
}
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& sm) {
|
||||
std::make_shared<PSM>()->InstallAsService(sm);
|
||||
}
|
||||
|
||||
} // namespace Service::PSM
|
||||
15
src/core/hle/service/ptm/psm.h
Normal file
15
src/core/hle/service/ptm/psm.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::PSM {
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& sm);
|
||||
|
||||
} // namespace Service::PSM
|
||||
@@ -58,6 +58,7 @@
|
||||
#include "core/hle/service/pm/pm.h"
|
||||
#include "core/hle/service/prepo/prepo.h"
|
||||
#include "core/hle/service/psc/psc.h"
|
||||
#include "core/hle/service/ptm/psm.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/set/settings.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
@@ -246,6 +247,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs)
|
||||
PlayReport::InstallInterfaces(*sm);
|
||||
PM::InstallInterfaces(*sm);
|
||||
PSC::InstallInterfaces(*sm);
|
||||
PSM::InstallInterfaces(*sm);
|
||||
Set::InstallInterfaces(*sm);
|
||||
Sockets::InstallInterfaces(*sm);
|
||||
SPL::InstallInterfaces(*sm);
|
||||
|
||||
@@ -74,10 +74,6 @@ double PerfStats::GetLastFrameTimeScale() {
|
||||
}
|
||||
|
||||
void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
|
||||
// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
|
||||
// values increase the time needed to recover and limit framerate again after spikes.
|
||||
constexpr microseconds MAX_LAG_TIME_US = 25000us;
|
||||
|
||||
if (!Settings::values.use_frame_limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -563,6 +563,10 @@ union Instruction {
|
||||
BitField<48, 1, u64> negate_b;
|
||||
} fmul;
|
||||
|
||||
union {
|
||||
BitField<55, 1, u64> saturate;
|
||||
} fmul32;
|
||||
|
||||
union {
|
||||
BitField<48, 1, u64> is_signed;
|
||||
} shift;
|
||||
@@ -778,6 +782,14 @@ union Instruction {
|
||||
BitField<45, 2, PredOperation> op;
|
||||
} 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;
|
||||
@@ -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
|
||||
@@ -163,10 +163,11 @@ private:
|
||||
const ExitMethod jmp = Scan(target, end, labels);
|
||||
return exit_method = ParallelExit(no_jmp, jmp);
|
||||
}
|
||||
case OpCode::Id::SSY: {
|
||||
// The SSY instruction uses a similar encoding as the BRA instruction.
|
||||
case OpCode::Id::SSY:
|
||||
case OpCode::Id::PBK: {
|
||||
// The SSY and PBK use a similar encoding as the BRA instruction.
|
||||
ASSERT_MSG(instr.bra.constant_buffer == 0,
|
||||
"Constant buffer SSY is not supported");
|
||||
"Constant buffer branching is not supported");
|
||||
const u32 target = offset + instr.bra.GetBranchTarget();
|
||||
labels.insert(target);
|
||||
// Continue scanning for an exit method.
|
||||
@@ -378,8 +379,8 @@ public:
|
||||
* @param reg The destination register to use.
|
||||
* @param elem The element to use for the operation.
|
||||
* @param value The code representing the value to assign. Type has to be half float.
|
||||
* @param type Half float kind of assignment.
|
||||
* @param dest_num_components Number of components in the destionation.
|
||||
* @param merge Half float kind of assignment.
|
||||
* @param dest_num_components Number of components in the destination.
|
||||
* @param value_num_components Number of components in the value.
|
||||
* @param is_saturated Optional, when True, saturates the provided value.
|
||||
* @param dest_elem Optional, the destination element to use for the operation.
|
||||
@@ -422,6 +423,7 @@ public:
|
||||
* @param reg The destination register to use.
|
||||
* @param elem The element to use for the operation.
|
||||
* @param attribute The input attribute to use as the source value.
|
||||
* @param input_mode The input mode.
|
||||
* @param vertex The register that decides which vertex to read from (used in GS).
|
||||
*/
|
||||
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
|
||||
@@ -951,7 +953,7 @@ private:
|
||||
// Can't assign to the constant predicate.
|
||||
ASSERT(pred != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
const std::string variable = 'p' + std::to_string(pred) + '_' + suffix;
|
||||
std::string variable = 'p' + std::to_string(pred) + '_' + suffix;
|
||||
shader.AddLine(variable + " = " + value + ';');
|
||||
declr_predicates.insert(std::move(variable));
|
||||
}
|
||||
@@ -1058,7 +1060,7 @@ private:
|
||||
/*
|
||||
* Transforms the input string GLSL operand into an unpacked half float pair.
|
||||
* @note This function returns a float type pair instead of a half float pair. This is because
|
||||
* real half floats are not standarized in GLSL but unpackHalf2x16 (which returns a vec2) is.
|
||||
* real half floats are not standardized in GLSL but unpackHalf2x16 (which returns a vec2) is.
|
||||
* @param operand Input operand. It has to be an unsigned integer.
|
||||
* @param type How to unpack the unsigned integer to a half float pair.
|
||||
* @param abs Get the absolute value of unpacked half floats.
|
||||
@@ -1232,27 +1234,27 @@ private:
|
||||
}
|
||||
|
||||
/*
|
||||
* Emits code to push the input target address to the SSY address stack, incrementing the stack
|
||||
* Emits code to push the input target address to the flow address stack, incrementing the stack
|
||||
* top.
|
||||
*/
|
||||
void EmitPushToSSYStack(u32 target) {
|
||||
void EmitPushToFlowStack(u32 target) {
|
||||
shader.AddLine('{');
|
||||
++shader.scope;
|
||||
shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;");
|
||||
shader.AddLine("ssy_stack_top++;");
|
||||
shader.AddLine("flow_stack[flow_stack_top] = " + std::to_string(target) + "u;");
|
||||
shader.AddLine("flow_stack_top++;");
|
||||
--shader.scope;
|
||||
shader.AddLine('}');
|
||||
}
|
||||
|
||||
/*
|
||||
* Emits code to pop an address from the SSY address stack, setting the jump address to the
|
||||
* Emits code to pop an address from the flow address stack, setting the jump address to the
|
||||
* popped address and decrementing the stack top.
|
||||
*/
|
||||
void EmitPopFromSSYStack() {
|
||||
void EmitPopFromFlowStack() {
|
||||
shader.AddLine('{');
|
||||
++shader.scope;
|
||||
shader.AddLine("ssy_stack_top--;");
|
||||
shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];");
|
||||
shader.AddLine("flow_stack_top--;");
|
||||
shader.AddLine("jmp_to = flow_stack[flow_stack_top];");
|
||||
shader.AddLine("break;");
|
||||
--shader.scope;
|
||||
shader.AddLine('}');
|
||||
@@ -1310,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.
|
||||
@@ -1479,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: {
|
||||
@@ -3283,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();
|
||||
EmitPushToSSYStack(target);
|
||||
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();
|
||||
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: {
|
||||
@@ -3302,87 +3378,51 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::VMAD: {
|
||||
const bool signed_a = instr.vmad.signed_a == 1;
|
||||
const bool signed_b = instr.vmad.signed_b == 1;
|
||||
const bool result_signed = signed_a || signed_b;
|
||||
boost::optional<std::string> forced_result;
|
||||
|
||||
auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed,
|
||||
Tegra::Shader::VmadType type, u64 byte_height) {
|
||||
const std::string value = [&]() {
|
||||
if (!is_chunk) {
|
||||
const auto offset = static_cast<u32>(byte_height * 8);
|
||||
return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)";
|
||||
}
|
||||
const std::string zero = "0";
|
||||
|
||||
switch (type) {
|
||||
case Tegra::Shader::VmadType::Size16_Low:
|
||||
return '(' + op + " & 0xffff)";
|
||||
case Tegra::Shader::VmadType::Size16_High:
|
||||
return '(' + op + " >> 16)";
|
||||
case Tegra::Shader::VmadType::Size32:
|
||||
// TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when
|
||||
// this type is used (1 * 1 + 0 == 0x5b800000). Until a better
|
||||
// explanation is found: assert.
|
||||
UNREACHABLE_MSG("Unimplemented");
|
||||
return zero;
|
||||
case Tegra::Shader::VmadType::Invalid:
|
||||
// Note(Rodrigo): This flag is invalid according to nvdisasm. From my
|
||||
// testing (even though it's invalid) this makes the whole instruction
|
||||
// assign zero to target register.
|
||||
forced_result = boost::make_optional(zero);
|
||||
return zero;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return zero;
|
||||
}
|
||||
}();
|
||||
|
||||
if (is_signed) {
|
||||
return "int(" + value + ')';
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false),
|
||||
instr.vmad.is_byte_chunk_a != 0, signed_a,
|
||||
instr.vmad.type_a, instr.vmad.byte_height_a);
|
||||
|
||||
std::string op_b;
|
||||
if (instr.vmad.use_register_b) {
|
||||
op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
|
||||
instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b,
|
||||
instr.vmad.byte_height_b);
|
||||
} else {
|
||||
op_b = '(' +
|
||||
std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16())
|
||||
: instr.alu.GetImm20_16()) +
|
||||
')';
|
||||
}
|
||||
|
||||
const bool result_signed = instr.video.signed_a == 1 || instr.video.signed_b == 1;
|
||||
const std::string op_a = GetVideoOperandA(instr);
|
||||
const std::string op_b = GetVideoOperandB(instr);
|
||||
const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed);
|
||||
|
||||
std::string result;
|
||||
if (forced_result) {
|
||||
result = *forced_result;
|
||||
} else {
|
||||
result = '(' + op_a + " * " + op_b + " + " + op_c + ')';
|
||||
std::string result = '(' + op_a + " * " + op_b + " + " + op_c + ')';
|
||||
|
||||
switch (instr.vmad.shr) {
|
||||
case Tegra::Shader::VmadShr::Shr7:
|
||||
result = '(' + result + " >> 7)";
|
||||
break;
|
||||
case Tegra::Shader::VmadShr::Shr15:
|
||||
result = '(' + result + " >> 15)";
|
||||
break;
|
||||
}
|
||||
switch (instr.vmad.shr) {
|
||||
case Tegra::Shader::VmadShr::Shr7:
|
||||
result = '(' + result + " >> 7)";
|
||||
break;
|
||||
case Tegra::Shader::VmadShr::Shr15:
|
||||
result = '(' + result + " >> 15)";
|
||||
break;
|
||||
}
|
||||
|
||||
regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1,
|
||||
instr.vmad.saturate == 1, 0, Register::Size::Word,
|
||||
instr.vmad.cc);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::VSETP: {
|
||||
const std::string op_a = GetVideoOperandA(instr);
|
||||
const std::string op_b = GetVideoOperandB(instr);
|
||||
|
||||
// We can't use the constant predicate as destination.
|
||||
ASSERT(instr.vsetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
const std::string second_pred = GetPredicateCondition(instr.vsetp.pred39, false);
|
||||
|
||||
const std::string combiner = GetPredicateCombiner(instr.vsetp.op);
|
||||
|
||||
const std::string predicate = GetPredicateComparison(instr.vsetp.cond, op_a, op_b);
|
||||
// Set the primary predicate to the result of Predicate OP SecondPredicate
|
||||
SetPredicate(instr.vsetp.pred3,
|
||||
'(' + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
|
||||
if (instr.vsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
|
||||
// Set the secondary predicate to the result of !Predicate OP SecondPredicate,
|
||||
// if enabled
|
||||
SetPredicate(instr.vsetp.pred0,
|
||||
"!(" + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
|
||||
UNREACHABLE();
|
||||
@@ -3446,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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -121,7 +121,7 @@ target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
|
||||
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
|
||||
add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING)
|
||||
target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING)
|
||||
endif()
|
||||
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
<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="use_docked_mode">
|
||||
<property name="text">
|
||||
<string>Enable docked mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enable_nfc">
|
||||
<property name="text">
|
||||
<string>Enable NFC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user