Compare commits
82 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca142f35c0 | ||
|
|
abefe29398 | ||
|
|
29f748a658 | ||
|
|
69b35d7615 | ||
|
|
b723390ab1 | ||
|
|
ce2403d975 | ||
|
|
d9590d7dfa | ||
|
|
2694b43d3a | ||
|
|
e6e17a3fc6 | ||
|
|
ddff188c65 | ||
|
|
d14ba122e2 | ||
|
|
2eff8336f4 | ||
|
|
cdd499c261 | ||
|
|
e65f5e4d66 | ||
|
|
030847d5fa | ||
|
|
77e705a8fa | ||
|
|
e7ac42677b | ||
|
|
a1c85b8c55 | ||
|
|
47f081d513 | ||
|
|
2cbc284c2b | ||
|
|
6d27614994 | ||
|
|
93596d03ec | ||
|
|
7c9f7aeacc | ||
|
|
a3d1ede25f | ||
|
|
c7c594a6b8 | ||
|
|
c6529688fc | ||
|
|
257b7bbfee | ||
|
|
a97cdb5eb4 | ||
|
|
f80b80b922 | ||
|
|
a94e5d9e68 | ||
|
|
bfad41b0c1 | ||
|
|
45f2a2fe29 | ||
|
|
e408bbceed | ||
|
|
702622b8f1 | ||
|
|
19c5cf9c63 | ||
|
|
466960c8ab | ||
|
|
b2a8209c5b | ||
|
|
d3fbf45705 | ||
|
|
aeffd4b436 | ||
|
|
e7e3d5898e | ||
|
|
50e4e81fd3 | ||
|
|
5edb2403c2 | ||
|
|
fc9d8afead | ||
|
|
a5106fb9f5 | ||
|
|
e61a62066a | ||
|
|
ed8ca608a0 | ||
|
|
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()
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count)
|
||||
: m_sample_rate(sample_rate), m_channel_count(channel_count) {
|
||||
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count) : m_sample_rate{sample_rate} {
|
||||
m_sound_touch.setChannels(channel_count);
|
||||
m_sound_touch.setSampleRate(sample_rate);
|
||||
m_sound_touch.setPitch(1.0);
|
||||
|
||||
@@ -27,7 +27,6 @@ public:
|
||||
|
||||
private:
|
||||
u32 m_sample_rate;
|
||||
u32 m_channel_count;
|
||||
soundtouch::SoundTouch m_sound_touch;
|
||||
double m_stretch_ratio = 1.0;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -196,6 +196,7 @@ void FileBackend::Write(const Entry& entry) {
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NIM) \
|
||||
SUB(Service, NPNS) \
|
||||
SUB(Service, NS) \
|
||||
SUB(Service, NVDRV) \
|
||||
SUB(Service, PCIE) \
|
||||
@@ -204,10 +205,12 @@ void FileBackend::Write(const Entry& entry) {
|
||||
SUB(Service, PM) \
|
||||
SUB(Service, PREPO) \
|
||||
SUB(Service, PSC) \
|
||||
SUB(Service, PSM) \
|
||||
SUB(Service, SET) \
|
||||
SUB(Service, SM) \
|
||||
SUB(Service, SPL) \
|
||||
SUB(Service, SSL) \
|
||||
SUB(Service, TCAP) \
|
||||
SUB(Service, Time) \
|
||||
SUB(Service, USB) \
|
||||
SUB(Service, VI) \
|
||||
|
||||
@@ -83,6 +83,7 @@ enum class Class : ClassType {
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NIM, ///< The NIM service
|
||||
Service_NPNS, ///< The NPNS service
|
||||
Service_NS, ///< The NS services
|
||||
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
||||
Service_PCIE, ///< The PCIe service
|
||||
@@ -91,10 +92,12 @@ 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
|
||||
Service_SSL, ///< The SSL service
|
||||
Service_TCAP, ///< The TCAP service.
|
||||
Service_Time, ///< The time service
|
||||
Service_USB, ///< The USB (Universal Serial Bus) service
|
||||
Service_VI, ///< The VI (Video interface) service
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -713,7 +713,6 @@ void KeyManager::DeriveBase() {
|
||||
|
||||
const auto sbk = GetKey(S128KeyType::SecureBoot);
|
||||
const auto tsec = GetKey(S128KeyType::TSEC);
|
||||
const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master));
|
||||
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!revisions[i])
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -472,10 +472,14 @@ bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t blo
|
||||
std::vector<u8> temp(std::min(block_size, src->GetSize()));
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
|
||||
const auto read = std::min(block_size, src->GetSize() - i);
|
||||
const auto block = src->Read(temp.data(), read, i);
|
||||
|
||||
if (dest->Write(temp.data(), read, i) != read)
|
||||
if (src->Read(temp.data(), read, i) != read) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dest->Write(temp.data(), read, i) != read) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
@@ -16,6 +20,9 @@
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
constexpr u32 MAX_JPEG_IMAGE_SIZE = 0x20000;
|
||||
|
||||
// TODO: RE this structure
|
||||
struct UserData {
|
||||
INSERT_PADDING_WORDS(1);
|
||||
@@ -27,6 +34,11 @@ struct UserData {
|
||||
};
|
||||
static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
|
||||
|
||||
static std::string GetImagePath(UUID uuid) {
|
||||
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
|
||||
}
|
||||
|
||||
class IProfile final : public ServiceFramework<IProfile> {
|
||||
public:
|
||||
explicit IProfile(UUID user_id, ProfileManager& profile_manager)
|
||||
@@ -73,11 +85,11 @@ private:
|
||||
}
|
||||
|
||||
void LoadImage(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
// smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
|
||||
// TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000
|
||||
constexpr u32 jpeg_size = 107;
|
||||
static constexpr std::array<u8, jpeg_size> jpeg{
|
||||
// used as a backup should the one on disk not exist
|
||||
constexpr u32 backup_jpeg_size = 107;
|
||||
static constexpr std::array<u8, backup_jpeg_size> backup_jpeg{
|
||||
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03,
|
||||
0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a,
|
||||
@@ -87,18 +99,42 @@ private:
|
||||
0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
|
||||
0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
|
||||
};
|
||||
ctx.WriteBuffer(jpeg);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(jpeg_size);
|
||||
|
||||
const FileUtil::IOFile image(GetImagePath(user_id), "rb");
|
||||
|
||||
if (!image.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC,
|
||||
"Failed to load user provided image! Falling back to built-in backup...");
|
||||
ctx.WriteBuffer(backup_jpeg);
|
||||
rb.Push<u32>(backup_jpeg_size);
|
||||
} else {
|
||||
const auto size = std::min<u32>(image.GetSize(), MAX_JPEG_IMAGE_SIZE);
|
||||
std::vector<u8> buffer(size);
|
||||
image.ReadBytes(buffer.data(), buffer.size());
|
||||
|
||||
ctx.WriteBuffer(buffer.data(), buffer.size());
|
||||
rb.Push<u32>(buffer.size());
|
||||
}
|
||||
}
|
||||
|
||||
void GetImageSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
constexpr u32 jpeg_size = 107;
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
constexpr u32 backup_jpeg_size = 107;
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(jpeg_size);
|
||||
|
||||
const FileUtil::IOFile image(GetImagePath(user_id), "rb");
|
||||
|
||||
if (!image.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC,
|
||||
"Failed to load user provided image! Falling back to built-in backup...");
|
||||
rb.Push<u32>(backup_jpeg_size);
|
||||
} else {
|
||||
rb.Push<u32>(std::min<u32>(image.GetSize(), MAX_JPEG_IMAGE_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
const ProfileManager& profile_manager;
|
||||
|
||||
@@ -4,32 +4,57 @@
|
||||
|
||||
#include <random>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
struct UserRaw {
|
||||
UUID uuid;
|
||||
UUID uuid2;
|
||||
u64 timestamp;
|
||||
ProfileUsername username;
|
||||
INSERT_PADDING_BYTES(0x80);
|
||||
};
|
||||
static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size.");
|
||||
|
||||
struct ProfileDataRaw {
|
||||
INSERT_PADDING_BYTES(0x10);
|
||||
std::array<UserRaw, MAX_USERS> users;
|
||||
};
|
||||
static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size.");
|
||||
|
||||
// TODO(ogniK): Get actual error codes
|
||||
constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1);
|
||||
constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2);
|
||||
constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
|
||||
|
||||
const UUID& UUID::Generate() {
|
||||
constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/";
|
||||
|
||||
UUID UUID::Generate() {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
|
||||
uuid[0] = distribution(gen);
|
||||
uuid[1] = distribution(gen);
|
||||
return *this;
|
||||
return UUID{distribution(gen), distribution(gen)};
|
||||
}
|
||||
|
||||
ProfileManager::ProfileManager() {
|
||||
// TODO(ogniK): Create the default user we have for now until loading/saving users is added
|
||||
auto user_uuid = UUID{1, 0};
|
||||
ASSERT(CreateNewUser(user_uuid, Settings::values.username).IsSuccess());
|
||||
OpenUser(user_uuid);
|
||||
ParseUserSaveFile();
|
||||
|
||||
if (user_count == 0)
|
||||
CreateNewUser(UUID::Generate(), "yuzu");
|
||||
|
||||
auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1);
|
||||
if (UserExistsIndex(current))
|
||||
current = 0;
|
||||
|
||||
OpenUser(*GetUser(current));
|
||||
}
|
||||
|
||||
ProfileManager::~ProfileManager() = default;
|
||||
ProfileManager::~ProfileManager() {
|
||||
WriteUserSaveFile();
|
||||
}
|
||||
|
||||
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
|
||||
/// internal management of the users profiles
|
||||
@@ -101,6 +126,12 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username)
|
||||
return CreateNewUser(uuid, username_output);
|
||||
}
|
||||
|
||||
boost::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
|
||||
if (index >= MAX_USERS)
|
||||
return boost::none;
|
||||
return profiles[index].user_uuid;
|
||||
}
|
||||
|
||||
/// Returns a users profile index based on their user id.
|
||||
boost::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
|
||||
if (!uuid) {
|
||||
@@ -164,6 +195,12 @@ bool ProfileManager::UserExists(UUID uuid) const {
|
||||
return (GetUserIndex(uuid) != boost::none);
|
||||
}
|
||||
|
||||
bool ProfileManager::UserExistsIndex(std::size_t index) const {
|
||||
if (index >= MAX_USERS)
|
||||
return false;
|
||||
return profiles[index].user_uuid.uuid != INVALID_UUID;
|
||||
}
|
||||
|
||||
/// Opens a specific user
|
||||
void ProfileManager::OpenUser(UUID uuid) {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
@@ -239,4 +276,96 @@ bool ProfileManager::CanSystemRegisterUser() const {
|
||||
// emulate qlaunch. Update this to dynamically change.
|
||||
}
|
||||
|
||||
bool ProfileManager::RemoveUser(UUID uuid) {
|
||||
auto index = GetUserIndex(uuid);
|
||||
if (index == boost::none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
profiles[*index] = ProfileInfo{};
|
||||
std::stable_partition(profiles.begin(), profiles.end(),
|
||||
[](const ProfileInfo& profile) { return profile.user_uuid; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
|
||||
auto index = GetUserIndex(uuid);
|
||||
if (profile_new.user_uuid == UUID(INVALID_UUID) || index == boost::none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& profile = profiles[*index];
|
||||
profile.user_uuid = profile_new.user_uuid;
|
||||
profile.username = profile_new.username;
|
||||
profile.creation_time = profile_new.timestamp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProfileManager::ParseUserSaveFile() {
|
||||
FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat",
|
||||
"rb");
|
||||
|
||||
if (!save.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
|
||||
"user 'yuzu' with random UUID.");
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileDataRaw data;
|
||||
if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) {
|
||||
LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user "
|
||||
"'yuzu' with random UUID.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < MAX_USERS; ++i) {
|
||||
const auto& user = data.users[i];
|
||||
|
||||
if (user.uuid != UUID(INVALID_UUID))
|
||||
AddUser({user.uuid, user.username, user.timestamp, {}, false});
|
||||
}
|
||||
|
||||
std::stable_partition(profiles.begin(), profiles.end(),
|
||||
[](const ProfileInfo& profile) { return profile.user_uuid; });
|
||||
}
|
||||
|
||||
void ProfileManager::WriteUserSaveFile() {
|
||||
ProfileDataRaw raw{};
|
||||
|
||||
for (std::size_t i = 0; i < MAX_USERS; ++i) {
|
||||
raw.users[i].username = profiles[i].username;
|
||||
raw.users[i].uuid2 = profiles[i].user_uuid;
|
||||
raw.users[i].uuid = profiles[i].user_uuid;
|
||||
raw.users[i].timestamp = profiles[i].creation_time;
|
||||
}
|
||||
|
||||
const auto raw_path =
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
|
||||
if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
|
||||
FileUtil::Delete(raw_path);
|
||||
|
||||
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
|
||||
|
||||
if (!FileUtil::CreateFullPath(path)) {
|
||||
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
|
||||
"nand/system/save/8000000000000010/su/avators to mitigate this "
|
||||
"issue.");
|
||||
return;
|
||||
}
|
||||
|
||||
FileUtil::IOFile save(path, "wb");
|
||||
|
||||
if (!save.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
|
||||
"made in current session will be saved.");
|
||||
return;
|
||||
}
|
||||
|
||||
save.Resize(sizeof(ProfileDataRaw));
|
||||
save.WriteBytes(&raw, sizeof(ProfileDataRaw));
|
||||
}
|
||||
|
||||
}; // namespace Service::Account
|
||||
|
||||
@@ -36,7 +36,7 @@ struct UUID {
|
||||
}
|
||||
|
||||
// TODO(ogniK): Properly generate uuids based on RFC-4122
|
||||
const UUID& Generate();
|
||||
static UUID Generate();
|
||||
|
||||
// Set the UUID to {0,0} to be considered an invalid user
|
||||
void Invalidate() {
|
||||
@@ -45,6 +45,15 @@ struct UUID {
|
||||
std::string Format() const {
|
||||
return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
|
||||
}
|
||||
|
||||
std::string FormatSwitch() const {
|
||||
std::array<u8, 16> s{};
|
||||
std::memcpy(s.data(), uuid.data(), sizeof(u128));
|
||||
return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
|
||||
":02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
|
||||
s[12], s[13], s[14], s[15]);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
|
||||
|
||||
@@ -81,12 +90,13 @@ static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size");
|
||||
/// objects
|
||||
class ProfileManager {
|
||||
public:
|
||||
ProfileManager(); // TODO(ogniK): Load from system save
|
||||
ProfileManager();
|
||||
~ProfileManager();
|
||||
|
||||
ResultCode AddUser(const ProfileInfo& user);
|
||||
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
|
||||
ResultCode CreateNewUser(UUID uuid, const std::string& username);
|
||||
boost::optional<UUID> GetUser(std::size_t index) const;
|
||||
boost::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
|
||||
boost::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
|
||||
bool GetProfileBase(boost::optional<std::size_t> index, ProfileBase& profile) const;
|
||||
@@ -100,6 +110,7 @@ public:
|
||||
std::size_t GetUserCount() const;
|
||||
std::size_t GetOpenUserCount() const;
|
||||
bool UserExists(UUID uuid) const;
|
||||
bool UserExistsIndex(std::size_t index) const;
|
||||
void OpenUser(UUID uuid);
|
||||
void CloseUser(UUID uuid);
|
||||
UserIDArray GetOpenUsers() const;
|
||||
@@ -108,7 +119,13 @@ public:
|
||||
|
||||
bool CanSystemRegisterUser() const;
|
||||
|
||||
bool RemoveUser(UUID uuid);
|
||||
bool SetProfileBase(UUID uuid, const ProfileBase& profile);
|
||||
|
||||
private:
|
||||
void ParseUserSaveFile();
|
||||
void WriteUserSaveFile();
|
||||
|
||||
std::array<ProfileInfo, MAX_USERS> profiles{};
|
||||
std::size_t user_count = 0;
|
||||
boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <stack>
|
||||
#include "core/core.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applet_ae.h"
|
||||
#include "core/hle/service/am/applet_oe.h"
|
||||
@@ -26,6 +28,16 @@
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
|
||||
|
||||
struct LaunchParameters {
|
||||
u32_le magic;
|
||||
u32_le is_account_selected;
|
||||
u128 current_user;
|
||||
INSERT_PADDING_BYTES(0x70);
|
||||
};
|
||||
static_assert(sizeof(LaunchParameters) == 0x88);
|
||||
|
||||
IWindowController::IWindowController() : ServiceFramework("IWindowController") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -724,20 +736,23 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
|
||||
}
|
||||
|
||||
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
|
||||
constexpr std::array<u8, 0x88> data{{
|
||||
0xca, 0x97, 0x94, 0xc7, // Magic
|
||||
1, 0, 0, 0, // IsAccountSelected (bool)
|
||||
1, 0, 0, 0, // User Id (word 0)
|
||||
0, 0, 0, 0, // User Id (word 1)
|
||||
0, 0, 0, 0, // User Id (word 2)
|
||||
0, 0, 0, 0 // User Id (word 3)
|
||||
}};
|
||||
LaunchParameters params{};
|
||||
|
||||
std::vector<u8> buffer(data.begin(), data.end());
|
||||
params.magic = POP_LAUNCH_PARAMETER_MAGIC;
|
||||
params.is_account_selected = 1;
|
||||
|
||||
Account::ProfileManager profile_manager{};
|
||||
const auto uuid = profile_manager.GetUser(Settings::values.current_user);
|
||||
ASSERT(uuid != boost::none);
|
||||
params.current_user = uuid->uuid;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
std::vector<u8> buffer(sizeof(LaunchParameters));
|
||||
std::memcpy(buffer.data(), ¶ms, buffer.size());
|
||||
|
||||
rb.PushIpcInterface<AM::IStorage>(buffer);
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace Service::AOC {
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
|
||||
|
||||
static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) {
|
||||
return (aoc & DLC_BASE_TITLE_ID_MASK) == base;
|
||||
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
|
||||
return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
|
||||
}
|
||||
|
||||
static std::vector<u64> AccumulateAOCTitleIDs() {
|
||||
@@ -74,7 +74,7 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
rb.Push<u32>(static_cast<u32>(
|
||||
std::count_if(add_on_content.begin(), add_on_content.end(),
|
||||
[¤t](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; })));
|
||||
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
|
||||
}
|
||||
|
||||
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -108,9 +108,10 @@ void Controller_NPad::OnInit() {
|
||||
styleset_changed_event =
|
||||
Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged");
|
||||
|
||||
if (!IsControllerActivated())
|
||||
if (!IsControllerActivated()) {
|
||||
return;
|
||||
std::size_t controller{};
|
||||
}
|
||||
|
||||
if (style.raw == 0) {
|
||||
// We want to support all controllers
|
||||
style.handheld.Assign(1);
|
||||
|
||||
@@ -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,7 +113,8 @@ static const std::array<const char*, NumAnalogs> mapping = {{
|
||||
struct Values {
|
||||
// System
|
||||
bool use_docked_mode;
|
||||
std::string username;
|
||||
bool enable_nfc;
|
||||
int current_user;
|
||||
int language_index;
|
||||
|
||||
// Controls
|
||||
|
||||
@@ -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;
|
||||
@@ -156,7 +155,6 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
|
||||
"Units other than CROP are unimplemented");
|
||||
|
||||
u32 value = Memory::Read32(*address);
|
||||
u64 result = 0;
|
||||
|
||||
// TODO(Subv): Support the other query variables
|
||||
@@ -408,5 +406,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
|
||||
|
||||
@@ -79,6 +79,7 @@ union Attribute {
|
||||
constexpr explicit Attribute(u64 value) : value(value) {}
|
||||
|
||||
enum class Index : u64 {
|
||||
PointSize = 6,
|
||||
Position = 7,
|
||||
Attribute_0 = 8,
|
||||
Attribute_31 = 39,
|
||||
@@ -207,6 +208,16 @@ enum class UniformType : u64 {
|
||||
Double = 5,
|
||||
};
|
||||
|
||||
enum class StoreType : u64 {
|
||||
Unsigned8 = 0,
|
||||
Signed8 = 1,
|
||||
Unsigned16 = 2,
|
||||
Signed16 = 3,
|
||||
Bytes32 = 4,
|
||||
Bytes64 = 5,
|
||||
Bytes128 = 6,
|
||||
};
|
||||
|
||||
enum class IMinMaxExchange : u64 {
|
||||
None = 0,
|
||||
XLo = 1,
|
||||
@@ -214,7 +225,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 +574,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;
|
||||
@@ -742,6 +757,18 @@ union Instruction {
|
||||
BitField<44, 2, u64> unknown;
|
||||
} ld_c;
|
||||
|
||||
union {
|
||||
BitField<48, 3, StoreType> type;
|
||||
} ldst_sl;
|
||||
|
||||
union {
|
||||
BitField<44, 2, u64> unknown;
|
||||
} ld_l;
|
||||
|
||||
union {
|
||||
BitField<44, 2, u64> unknown;
|
||||
} st_l;
|
||||
|
||||
union {
|
||||
BitField<0, 3, u64> pred0;
|
||||
BitField<3, 3, u64> pred3;
|
||||
@@ -778,6 +805,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 +1185,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;
|
||||
@@ -1194,6 +1231,7 @@ union Instruction {
|
||||
BitField<61, 1, u64> is_b_imm;
|
||||
BitField<60, 1, u64> is_b_gpr;
|
||||
BitField<59, 1, u64> is_c_gpr;
|
||||
BitField<20, 24, s64> smem_imm;
|
||||
|
||||
Attribute attribute;
|
||||
Sampler sampler;
|
||||
@@ -1209,14 +1247,20 @@ public:
|
||||
KIL,
|
||||
SSY,
|
||||
SYNC,
|
||||
BRK,
|
||||
DEPBAR,
|
||||
BFE_C,
|
||||
BFE_R,
|
||||
BFE_IMM,
|
||||
BRA,
|
||||
PBK,
|
||||
LD_A,
|
||||
LD_L,
|
||||
LD_S,
|
||||
LD_C,
|
||||
ST_A,
|
||||
ST_L,
|
||||
ST_S,
|
||||
LDG, // Load from global memory
|
||||
STG, // Store in global memory
|
||||
TEX,
|
||||
@@ -1232,6 +1276,7 @@ public:
|
||||
OUT_R, // Emit vertex/primitive
|
||||
ISBERD,
|
||||
VMAD,
|
||||
VSETP,
|
||||
FFMA_IMM, // Fused Multiply and Add
|
||||
FFMA_CR,
|
||||
FFMA_RC,
|
||||
@@ -1370,7 +1415,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,12 +1511,18 @@ 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("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
|
||||
INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
|
||||
INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
|
||||
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
|
||||
INST("1110111101011---", Id::ST_S, Type::Memory, "ST_S"),
|
||||
INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
|
||||
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
|
||||
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
|
||||
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
|
||||
@@ -1487,6 +1538,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 +1658,4 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Tegra::Shader
|
||||
} // namespace Tegra::Shader
|
||||
@@ -96,6 +96,11 @@ struct Header {
|
||||
}
|
||||
} ps;
|
||||
};
|
||||
|
||||
u64 GetLocalMemorySize() {
|
||||
return (common1.shader_local_memory_low_size |
|
||||
(common2.shader_local_memory_high_size << 24));
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == 0x50, "Incorrect structure size");
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <fmt/format.h>
|
||||
@@ -163,10 +164,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.
|
||||
@@ -275,7 +277,8 @@ public:
|
||||
GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
|
||||
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix,
|
||||
const Tegra::Shader::Header& header)
|
||||
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} {
|
||||
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header},
|
||||
fixed_pipeline_output_attributes_used{}, local_memory_size{0} {
|
||||
BuildRegisterList();
|
||||
BuildInputList();
|
||||
}
|
||||
@@ -378,8 +381,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 +425,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,
|
||||
@@ -432,6 +436,25 @@ public:
|
||||
shader.AddLine(dest + " = " + src + ';');
|
||||
}
|
||||
|
||||
std::string GetLocalMemoryAsFloat(const std::string& index) {
|
||||
return "lmem[" + index + ']';
|
||||
}
|
||||
|
||||
std::string GetLocalMemoryAsInteger(const std::string& index, bool is_signed = false) {
|
||||
const std::string func{is_signed ? "floatToIntBits" : "floatBitsToUint"};
|
||||
return func + "(lmem[" + index + "])";
|
||||
}
|
||||
|
||||
void SetLocalMemoryAsFloat(const std::string& index, const std::string& value) {
|
||||
shader.AddLine("lmem[" + index + "] = " + value + ';');
|
||||
}
|
||||
|
||||
void SetLocalMemoryAsInteger(const std::string& index, const std::string& value,
|
||||
bool is_signed = false) {
|
||||
const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"};
|
||||
shader.AddLine("lmem[" + index + "] = " + func + '(' + value + ");");
|
||||
}
|
||||
|
||||
std::string GetControlCode(const Tegra::Shader::ControlCode cc) const {
|
||||
switch (cc) {
|
||||
case Tegra::Shader::ControlCode::NEU:
|
||||
@@ -478,7 +501,12 @@ public:
|
||||
std::to_string(static_cast<u32>(attribute)) + ']' +
|
||||
GetSwizzle(elem) + " = " + src + ';');
|
||||
} else {
|
||||
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
|
||||
if (attribute == Attribute::Index::PointSize) {
|
||||
fixed_pipeline_output_attributes_used.insert(attribute);
|
||||
shader.AddLine(dest + " = " + src + ';');
|
||||
} else {
|
||||
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -522,7 +550,9 @@ public:
|
||||
|
||||
/// Add declarations.
|
||||
void GenerateDeclarations(const std::string& suffix) {
|
||||
GenerateVertex();
|
||||
GenerateRegisters(suffix);
|
||||
GenerateLocalMemory();
|
||||
GenerateInternalFlags();
|
||||
GenerateInputAttrs();
|
||||
GenerateOutputAttrs();
|
||||
@@ -568,6 +598,10 @@ public:
|
||||
return entry.GetName();
|
||||
}
|
||||
|
||||
void SetLocalMemory(u64 lmem) {
|
||||
local_memory_size = lmem;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Generates declarations for registers.
|
||||
void GenerateRegisters(const std::string& suffix) {
|
||||
@@ -578,6 +612,15 @@ private:
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Generates declarations for local memory.
|
||||
void GenerateLocalMemory() {
|
||||
if (local_memory_size > 0) {
|
||||
declarations.AddLine("float lmem[" + std::to_string((local_memory_size - 1 + 4) / 4) +
|
||||
"];");
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates declarations for internal flags.
|
||||
void GenerateInternalFlags() {
|
||||
for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
|
||||
@@ -681,6 +724,20 @@ private:
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
void GenerateVertex() {
|
||||
if (stage != Maxwell3D::Regs::ShaderStage::Vertex)
|
||||
return;
|
||||
declarations.AddLine("out gl_PerVertex {");
|
||||
++declarations.scope;
|
||||
declarations.AddLine("vec4 gl_Position;");
|
||||
for (auto& o : fixed_pipeline_output_attributes_used) {
|
||||
if (o == Attribute::Index::PointSize)
|
||||
declarations.AddLine("float gl_PointSize;");
|
||||
}
|
||||
--declarations.scope;
|
||||
declarations.AddLine("};");
|
||||
}
|
||||
|
||||
/// Generates code representing a temporary (GPR) register.
|
||||
std::string GetRegister(const Register& reg, unsigned elem) {
|
||||
if (reg == Register::ZeroIndex) {
|
||||
@@ -834,6 +891,8 @@ private:
|
||||
/// Generates code representing the declaration name of an output attribute register.
|
||||
std::string GetOutputAttribute(Attribute::Index attribute) {
|
||||
switch (attribute) {
|
||||
case Attribute::Index::PointSize:
|
||||
return "gl_PointSize";
|
||||
case Attribute::Index::Position:
|
||||
return "position";
|
||||
default:
|
||||
@@ -868,6 +927,8 @@ private:
|
||||
const Maxwell3D::Regs::ShaderStage& stage;
|
||||
const std::string& suffix;
|
||||
const Tegra::Shader::Header& header;
|
||||
std::unordered_set<Attribute::Index> fixed_pipeline_output_attributes_used;
|
||||
u64 local_memory_size;
|
||||
};
|
||||
|
||||
class GLSLGenerator {
|
||||
@@ -877,6 +938,8 @@ public:
|
||||
: subroutines(subroutines), program_code(program_code), main_offset(main_offset),
|
||||
stage(stage), suffix(suffix) {
|
||||
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
|
||||
local_memory_size = header.GetLocalMemorySize();
|
||||
regs.SetLocalMemory(local_memory_size);
|
||||
Generate(suffix);
|
||||
}
|
||||
|
||||
@@ -951,7 +1014,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 +1121,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 +1295,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 +1373,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 +1599,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: {
|
||||
@@ -2239,6 +2360,39 @@ private:
|
||||
shader.AddLine("}");
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::LD_L: {
|
||||
// Add an extra scope and declare the index register inside to prevent
|
||||
// overwriting it in case it is used as an output of the LD instruction.
|
||||
shader.AddLine('{');
|
||||
++shader.scope;
|
||||
|
||||
std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
|
||||
std::to_string(instr.smem_imm.Value()) + ')';
|
||||
|
||||
shader.AddLine("uint index = (" + op + " / 4);");
|
||||
|
||||
const std::string op_a = regs.GetLocalMemoryAsFloat("index");
|
||||
|
||||
if (instr.ld_l.unknown != 1) {
|
||||
LOG_CRITICAL(HW_GPU, "LD_L Unhandled mode: {}",
|
||||
static_cast<unsigned>(instr.ld_l.unknown.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
switch (instr.ldst_sl.type.Value()) {
|
||||
case Tegra::Shader::StoreType::Bytes32:
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "LD_L Unhandled type: {}",
|
||||
static_cast<unsigned>(instr.ldst_sl.type.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine('}');
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::ST_A: {
|
||||
ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex,
|
||||
"Indirect attribute loads are not supported");
|
||||
@@ -2267,6 +2421,37 @@ private:
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::ST_L: {
|
||||
// Add an extra scope and declare the index register inside to prevent
|
||||
// overwriting it in case it is used as an output of the LD instruction.
|
||||
shader.AddLine('{');
|
||||
++shader.scope;
|
||||
|
||||
std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
|
||||
std::to_string(instr.smem_imm.Value()) + ')';
|
||||
|
||||
shader.AddLine("uint index = (" + op + " / 4);");
|
||||
|
||||
if (instr.st_l.unknown != 0) {
|
||||
LOG_CRITICAL(HW_GPU, "ST_L Unhandled mode: {}",
|
||||
static_cast<unsigned>(instr.st_l.unknown.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
switch (instr.ldst_sl.type.Value()) {
|
||||
case Tegra::Shader::StoreType::Bytes32:
|
||||
regs.SetLocalMemoryAsFloat("index", regs.GetRegisterAsFloat(instr.gpr0));
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "ST_L Unhandled type: {}",
|
||||
static_cast<unsigned>(instr.ldst_sl.type.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine('}');
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEX: {
|
||||
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
|
||||
std::string coord;
|
||||
@@ -3283,16 +3468,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 +3503,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 +3611,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;
|
||||
@@ -3510,6 +3675,7 @@ private:
|
||||
const u32 main_offset;
|
||||
Maxwell3D::Regs::ShaderStage stage;
|
||||
const std::string& suffix;
|
||||
u64 local_memory_size;
|
||||
|
||||
ShaderWriter shader;
|
||||
ShaderWriter declarations;
|
||||
|
||||
@@ -19,9 +19,6 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
|
||||
out += Decompiler::GetCommonDeclarations();
|
||||
|
||||
out += R"(
|
||||
out gl_PerVertex {
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
layout (location = 0) out vec4 position;
|
||||
|
||||
|
||||
@@ -142,7 +142,6 @@ void SwizzledData(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle,
|
||||
const u32 blocks_on_x = div_ceil(width, block_x_elements);
|
||||
const u32 blocks_on_y = div_ceil(height, block_y_elements);
|
||||
const u32 blocks_on_z = div_ceil(depth, block_z_elements);
|
||||
const u32 blocks = blocks_on_x * blocks_on_y * blocks_on_z;
|
||||
const u32 gob_size = gob_x_bytes * gob_elements_y * gob_elements_z;
|
||||
const u32 xy_block_size = gob_size * block_height;
|
||||
const u32 block_size = xy_block_size * block_depth;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/settings.h"
|
||||
@@ -107,9 +106,8 @@ private:
|
||||
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||
: QWidget(parent), child(nullptr), emu_thread(emu_thread) {
|
||||
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
setWindowTitle(QString::fromStdString(window_title));
|
||||
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
|
||||
InputCommon::Init();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <QSettings>
|
||||
#include "common/file_util.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "input_common/main.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
@@ -122,7 +123,11 @@ void Config::ReadValues() {
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
|
||||
Settings::values.username = qt_config->value("username", "yuzu").toString().toStdString();
|
||||
Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
|
||||
|
||||
Settings::values.current_user = std::clamp<int>(qt_config->value("current_user", 0).toInt(), 0,
|
||||
Service::Account::MAX_USERS - 1);
|
||||
|
||||
Settings::values.language_index = qt_config->value("language_index", 1).toInt();
|
||||
qt_config->endGroup();
|
||||
|
||||
@@ -258,7 +263,9 @@ void Config::SaveValues() {
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
|
||||
qt_config->setValue("username", QString::fromStdString(Settings::values.username));
|
||||
qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
|
||||
qt_config->setValue("current_user", Settings::values.current_user);
|
||||
|
||||
qt_config->setValue("language_index", Settings::values.language_index);
|
||||
qt_config->endGroup();
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,13 +2,30 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <QFileDialog>
|
||||
#include <QGraphicsItem>
|
||||
#include <QGraphicsScene>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_system.h"
|
||||
#include "yuzu/configuration/configure_system.h"
|
||||
#include "yuzu/main.h"
|
||||
|
||||
static std::string GetImagePath(Service::Account::UUID uuid) {
|
||||
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
|
||||
}
|
||||
|
||||
static const std::array<int, 12> days_in_month = {{
|
||||
31,
|
||||
29,
|
||||
@@ -24,7 +41,20 @@ static const std::array<int, 12> days_in_month = {{
|
||||
31,
|
||||
}};
|
||||
|
||||
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
|
||||
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
|
||||
static constexpr std::array<u8, 107> backup_jpeg{
|
||||
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
|
||||
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
|
||||
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
|
||||
0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
|
||||
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
|
||||
0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
|
||||
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
|
||||
};
|
||||
|
||||
ConfigureSystem::ConfigureSystem(QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::ConfigureSystem),
|
||||
profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
|
||||
ui->setupUi(this);
|
||||
connect(ui->combo_birthmonth,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
@@ -32,6 +62,45 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
|
||||
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
|
||||
&ConfigureSystem::refreshConsoleID);
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
tree_view = new QTreeView;
|
||||
item_model = new QStandardItemModel(tree_view);
|
||||
tree_view->setModel(item_model);
|
||||
|
||||
tree_view->setAlternatingRowColors(true);
|
||||
tree_view->setSelectionMode(QHeaderView::SingleSelection);
|
||||
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
|
||||
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
|
||||
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
|
||||
tree_view->setSortingEnabled(true);
|
||||
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
|
||||
tree_view->setUniformRowHeights(true);
|
||||
tree_view->setIconSize({64, 64});
|
||||
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
|
||||
item_model->insertColumns(0, 1);
|
||||
item_model->setHeaderData(0, Qt::Horizontal, "Users");
|
||||
|
||||
// We must register all custom types with the Qt Automoc system so that we are able to use it
|
||||
// with signals/slots. In this case, QList falls under the umbrells of custom types.
|
||||
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
|
||||
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(tree_view);
|
||||
|
||||
ui->scrollArea->setLayout(layout);
|
||||
|
||||
connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser);
|
||||
|
||||
connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser);
|
||||
connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser);
|
||||
connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser);
|
||||
connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage);
|
||||
|
||||
scene = new QGraphicsScene;
|
||||
ui->current_user_icon->setScene(scene);
|
||||
|
||||
this->setConfiguration();
|
||||
}
|
||||
|
||||
@@ -39,16 +108,74 @@ ConfigureSystem::~ConfigureSystem() = default;
|
||||
|
||||
void ConfigureSystem::setConfiguration() {
|
||||
enabled = !Core::System::GetInstance().IsPoweredOn();
|
||||
ui->edit_username->setText(QString::fromStdString(Settings::values.username));
|
||||
|
||||
ui->combo_language->setCurrentIndex(Settings::values.language_index);
|
||||
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
list_items.clear();
|
||||
|
||||
PopulateUserList();
|
||||
UpdateCurrentUser();
|
||||
}
|
||||
|
||||
static QPixmap GetIcon(Service::Account::UUID uuid) {
|
||||
const auto icon_url = QString::fromStdString(GetImagePath(uuid));
|
||||
QPixmap icon{icon_url};
|
||||
|
||||
if (!icon) {
|
||||
icon.fill(Qt::black);
|
||||
icon.loadFromData(backup_jpeg.data(), backup_jpeg.size());
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
void ConfigureSystem::PopulateUserList() {
|
||||
const auto& profiles = profile_manager->GetAllUsers();
|
||||
for (const auto& user : profiles) {
|
||||
Service::Account::ProfileBase profile;
|
||||
if (!profile_manager->GetProfileBase(user, profile))
|
||||
continue;
|
||||
|
||||
const auto username = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
|
||||
|
||||
list_items.push_back(QList<QStandardItem*>{new QStandardItem{
|
||||
GetIcon(user).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
||||
QString::fromStdString(username + '\n' + user.FormatSwitch())}});
|
||||
}
|
||||
|
||||
for (const auto& item : list_items)
|
||||
item_model->appendRow(item);
|
||||
}
|
||||
|
||||
void ConfigureSystem::UpdateCurrentUser() {
|
||||
ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
|
||||
|
||||
const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
|
||||
ASSERT(current_user != boost::none);
|
||||
const auto username = GetAccountUsername(*current_user);
|
||||
|
||||
scene->clear();
|
||||
scene->addPixmap(
|
||||
GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
ui->current_user_username->setText(QString::fromStdString(username));
|
||||
}
|
||||
|
||||
void ConfigureSystem::ReadSystemSettings() {}
|
||||
|
||||
std::string ConfigureSystem::GetAccountUsername(Service::Account::UUID uuid) const {
|
||||
Service::Account::ProfileBase profile;
|
||||
if (!profile_manager->GetProfileBase(uuid, profile))
|
||||
return "";
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
|
||||
}
|
||||
|
||||
void ConfigureSystem::applyConfiguration() {
|
||||
if (!enabled)
|
||||
return;
|
||||
Settings::values.username = ui->edit_username->text().toStdString();
|
||||
|
||||
Settings::values.language_index = ui->combo_language->currentIndex();
|
||||
Settings::Apply();
|
||||
}
|
||||
@@ -92,3 +219,130 @@ void ConfigureSystem::refreshConsoleID() {
|
||||
ui->label_console_id->setText(
|
||||
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
|
||||
}
|
||||
|
||||
void ConfigureSystem::SelectUser(const QModelIndex& index) {
|
||||
Settings::values.current_user =
|
||||
std::clamp<std::size_t>(index.row(), 0, profile_manager->GetUserCount() - 1);
|
||||
|
||||
UpdateCurrentUser();
|
||||
|
||||
ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
|
||||
ui->pm_rename->setEnabled(true);
|
||||
ui->pm_set_image->setEnabled(true);
|
||||
}
|
||||
|
||||
void ConfigureSystem::AddUser() {
|
||||
Service::Account::UUID uuid;
|
||||
uuid.Generate();
|
||||
|
||||
bool ok = false;
|
||||
const auto username =
|
||||
QInputDialog::getText(this, tr("Enter Username"), tr("Enter a username for the new user:"),
|
||||
QLineEdit::Normal, QString(), &ok);
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
profile_manager->CreateNewUser(uuid, username.toStdString());
|
||||
|
||||
item_model->appendRow(new QStandardItem{
|
||||
GetIcon(uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
||||
QString::fromStdString(username.toStdString() + '\n' + uuid.FormatSwitch())});
|
||||
}
|
||||
|
||||
void ConfigureSystem::RenameUser() {
|
||||
const auto user = tree_view->currentIndex().row();
|
||||
const auto uuid = profile_manager->GetUser(user);
|
||||
ASSERT(uuid != boost::none);
|
||||
const auto username = GetAccountUsername(*uuid);
|
||||
|
||||
Service::Account::ProfileBase profile;
|
||||
if (!profile_manager->GetProfileBase(*uuid, profile))
|
||||
return;
|
||||
|
||||
bool ok = false;
|
||||
const auto new_username =
|
||||
QInputDialog::getText(this, tr("Enter Username"), tr("Enter a new username:"),
|
||||
QLineEdit::Normal, QString::fromStdString(username), &ok);
|
||||
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
std::fill(profile.username.begin(), profile.username.end(), '\0');
|
||||
const auto username_std = new_username.toStdString();
|
||||
if (username_std.size() > profile.username.size()) {
|
||||
std::copy_n(username_std.begin(), std::min(profile.username.size(), username_std.size()),
|
||||
profile.username.begin());
|
||||
} else {
|
||||
std::copy(username_std.begin(), username_std.end(), profile.username.begin());
|
||||
}
|
||||
|
||||
profile_manager->SetProfileBase(*uuid, profile);
|
||||
|
||||
item_model->setItem(
|
||||
user, 0,
|
||||
new QStandardItem{
|
||||
GetIcon(*uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
||||
tr("%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. "
|
||||
"00112233-4455-6677-8899-AABBCCDDEEFF))")
|
||||
.arg(QString::fromStdString(username_std),
|
||||
QString::fromStdString(uuid->FormatSwitch()))});
|
||||
UpdateCurrentUser();
|
||||
}
|
||||
|
||||
void ConfigureSystem::DeleteUser() {
|
||||
const auto index = tree_view->currentIndex().row();
|
||||
const auto uuid = profile_manager->GetUser(index);
|
||||
ASSERT(uuid != boost::none);
|
||||
const auto username = GetAccountUsername(*uuid);
|
||||
|
||||
const auto confirm =
|
||||
QMessageBox::question(this, tr("Confirm Delete"),
|
||||
tr("You are about to delete user with name %1. Are you sure?")
|
||||
.arg(QString::fromStdString(username)));
|
||||
|
||||
if (confirm == QMessageBox::No)
|
||||
return;
|
||||
|
||||
if (Settings::values.current_user == tree_view->currentIndex().row())
|
||||
Settings::values.current_user = 0;
|
||||
UpdateCurrentUser();
|
||||
|
||||
if (!profile_manager->RemoveUser(*uuid))
|
||||
return;
|
||||
|
||||
item_model->removeRows(tree_view->currentIndex().row(), 1);
|
||||
tree_view->clearSelection();
|
||||
|
||||
ui->pm_remove->setEnabled(false);
|
||||
ui->pm_rename->setEnabled(false);
|
||||
}
|
||||
|
||||
void ConfigureSystem::SetUserImage() {
|
||||
const auto index = tree_view->currentIndex().row();
|
||||
const auto uuid = profile_manager->GetUser(index);
|
||||
ASSERT(uuid != boost::none);
|
||||
const auto username = GetAccountUsername(*uuid);
|
||||
|
||||
const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
|
||||
"JPEG Images (*.jpg *.jpeg)");
|
||||
|
||||
if (file.isEmpty())
|
||||
return;
|
||||
|
||||
FileUtil::Delete(GetImagePath(*uuid));
|
||||
|
||||
const auto raw_path =
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
|
||||
if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
|
||||
FileUtil::Delete(raw_path);
|
||||
|
||||
FileUtil::CreateFullPath(GetImagePath(*uuid));
|
||||
FileUtil::Copy(file.toStdString(), GetImagePath(*uuid));
|
||||
|
||||
item_model->setItem(
|
||||
index, 0,
|
||||
new QStandardItem{
|
||||
GetIcon(*uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
||||
QString::fromStdString(username + '\n' + uuid->FormatSwitch())});
|
||||
UpdateCurrentUser();
|
||||
}
|
||||
|
||||
@@ -5,8 +5,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
namespace Service::Account {
|
||||
class ProfileManager;
|
||||
struct UUID;
|
||||
} // namespace Service::Account
|
||||
|
||||
class QGraphicsScene;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureSystem;
|
||||
}
|
||||
@@ -21,18 +34,36 @@ public:
|
||||
void applyConfiguration();
|
||||
void setConfiguration();
|
||||
|
||||
void PopulateUserList();
|
||||
void UpdateCurrentUser();
|
||||
|
||||
public slots:
|
||||
void updateBirthdayComboBox(int birthmonth_index);
|
||||
void refreshConsoleID();
|
||||
|
||||
void SelectUser(const QModelIndex& index);
|
||||
void AddUser();
|
||||
void RenameUser();
|
||||
void DeleteUser();
|
||||
void SetUserImage();
|
||||
|
||||
private:
|
||||
void ReadSystemSettings();
|
||||
std::string GetAccountUsername(Service::Account::UUID uuid) const;
|
||||
|
||||
QVBoxLayout* layout;
|
||||
QTreeView* tree_view;
|
||||
QStandardItemModel* item_model;
|
||||
QGraphicsScene* scene;
|
||||
|
||||
std::vector<QList<QStandardItem*>> list_items;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureSystem> ui;
|
||||
bool enabled;
|
||||
|
||||
std::u16string username;
|
||||
int birthmonth, birthday;
|
||||
int language_index;
|
||||
int sound_index;
|
||||
|
||||
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>377</height>
|
||||
<height>483</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -22,34 +22,28 @@
|
||||
<string>System Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_username">
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="edit_username">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>32</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_language">
|
||||
<property name="text">
|
||||
<string>Language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_birthday">
|
||||
<property name="text">
|
||||
<string>Birthday</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_console_id">
|
||||
<property name="text">
|
||||
<string>Console ID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_birthday2">
|
||||
<item>
|
||||
<widget class="QComboBox" name="combo_birthmonth">
|
||||
@@ -120,14 +114,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_language">
|
||||
<property name="text">
|
||||
<string>Language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="combo_language">
|
||||
<property name="toolTip">
|
||||
<string>Note: this can be overridden when region setting is auto-select</string>
|
||||
@@ -187,31 +174,31 @@
|
||||
<string>Russian (Русский)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Taiwanese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>British English</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Canadian French</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Latin American Spanish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simplified Chinese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Taiwanese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>British English</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Canadian French</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Latin American Spanish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simplified Chinese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Traditional Chinese (正體中文)</string>
|
||||
@@ -219,14 +206,14 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_sound">
|
||||
<property name="text">
|
||||
<string>Sound output mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="combo_sound">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -245,14 +232,7 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_console_id">
|
||||
<property name="text">
|
||||
<string>Console ID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="button_regenerate_console_id">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
@@ -271,6 +251,143 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gridGroupBox">
|
||||
<property name="title">
|
||||
<string>Profile Manager</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetNoConstraint</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Current User</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGraphicsView" name="current_user_icon">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="interactive">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="current_user_username">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pm_set_image">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Image</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pm_add">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pm_rename">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rename</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pm_remove">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_disable_info">
|
||||
<property name="text">
|
||||
@@ -281,19 +398,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <QLabel>
|
||||
#include <QMetaType>
|
||||
#include <QPushButton>
|
||||
@@ -32,21 +31,8 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
if (index.column() == 0) {
|
||||
static const std::map<Tegra::DebugContext::Event, QString> map = {
|
||||
{Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
|
||||
{Tegra::DebugContext::Event::MaxwellCommandProcessed,
|
||||
tr("Maxwell command processed")},
|
||||
{Tegra::DebugContext::Event::IncomingPrimitiveBatch,
|
||||
tr("Incoming primitive batch")},
|
||||
{Tegra::DebugContext::Event::FinishedPrimitiveBatch,
|
||||
tr("Finished primitive batch")},
|
||||
};
|
||||
|
||||
DEBUG_ASSERT(map.size() ==
|
||||
static_cast<std::size_t>(Tegra::DebugContext::Event::NumEvents));
|
||||
return (map.find(event) != map.end()) ? map.at(event) : QString();
|
||||
return DebugContextEventToString(event);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -128,6 +114,23 @@ void BreakPointModel::OnResumed() {
|
||||
active_breakpoint = context->active_breakpoint;
|
||||
}
|
||||
|
||||
QString BreakPointModel::DebugContextEventToString(Tegra::DebugContext::Event event) {
|
||||
switch (event) {
|
||||
case Tegra::DebugContext::Event::MaxwellCommandLoaded:
|
||||
return tr("Maxwell command loaded");
|
||||
case Tegra::DebugContext::Event::MaxwellCommandProcessed:
|
||||
return tr("Maxwell command processed");
|
||||
case Tegra::DebugContext::Event::IncomingPrimitiveBatch:
|
||||
return tr("Incoming primitive batch");
|
||||
case Tegra::DebugContext::Event::FinishedPrimitiveBatch:
|
||||
return tr("Finished primitive batch");
|
||||
case Tegra::DebugContext::Event::NumEvents:
|
||||
break;
|
||||
}
|
||||
|
||||
return tr("Unknown debug context event");
|
||||
}
|
||||
|
||||
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
|
||||
: QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
|
||||
|
||||
@@ -29,6 +29,8 @@ public:
|
||||
void OnResumed();
|
||||
|
||||
private:
|
||||
static QString DebugContextEventToString(Tegra::DebugContext::Event event);
|
||||
|
||||
std::weak_ptr<Tegra::DebugContext> context_weak;
|
||||
bool at_breakpoint;
|
||||
Tegra::DebugContext::Event active_breakpoint;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
@@ -217,11 +216,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
|
||||
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
item_model->insertColumns(0, COLUMN_COUNT);
|
||||
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
|
||||
item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility");
|
||||
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, "Add-ons");
|
||||
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
|
||||
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
|
||||
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
|
||||
item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
|
||||
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
|
||||
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
|
||||
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
|
||||
|
||||
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
|
||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||
@@ -387,9 +386,9 @@ void GameList::LoadCompatibilityList() {
|
||||
}
|
||||
|
||||
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
|
||||
if (!FileUtil::Exists(dir_path.toStdString()) ||
|
||||
!FileUtil::IsDirectory(dir_path.toStdString())) {
|
||||
LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toLocal8Bit().data());
|
||||
const QFileInfo dir_info{dir_path};
|
||||
if (!dir_info.exists() || !dir_info.isDir()) {
|
||||
LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString());
|
||||
search_field->setFilterResult(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
|
||||
// defines.
|
||||
@@ -60,6 +61,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/fsp_ldr.h"
|
||||
#include "core/hle/service/nfp/nfp.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
@@ -100,6 +103,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
|
||||
/**
|
||||
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
|
||||
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
|
||||
@@ -422,6 +427,7 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
|
||||
[this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); });
|
||||
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
|
||||
connect(ui.action_Load_Amiibo, &QAction::triggered, this, &GMainWindow::OnLoadAmiibo);
|
||||
|
||||
// Emulation
|
||||
connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
|
||||
@@ -690,6 +696,7 @@ void GMainWindow::ShutdownGame() {
|
||||
ui.action_Stop->setEnabled(false);
|
||||
ui.action_Restart->setEnabled(false);
|
||||
ui.action_Report_Compatibility->setEnabled(false);
|
||||
ui.action_Load_Amiibo->setEnabled(false);
|
||||
render_window->hide();
|
||||
game_list->show();
|
||||
game_list->setFilterFocus();
|
||||
@@ -751,12 +758,43 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||
open_target = "Save Data";
|
||||
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||
ASSERT(program_id != 0);
|
||||
// TODO(tech4me): Update this to work with arbitrary user profile
|
||||
// Refer to core/hle/service/acc/profile_manager.cpp ProfileManager constructor
|
||||
constexpr u128 user_id = {1, 0};
|
||||
|
||||
Service::Account::ProfileManager manager{};
|
||||
const auto user_ids = manager.GetAllUsers();
|
||||
QStringList list;
|
||||
for (const auto& user_id : user_ids) {
|
||||
if (user_id == Service::Account::UUID{})
|
||||
continue;
|
||||
Service::Account::ProfileBase base;
|
||||
if (!manager.GetProfileBase(user_id, base))
|
||||
continue;
|
||||
|
||||
list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(base.username.data()), base.username.size())));
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const auto index_string =
|
||||
QInputDialog::getItem(this, tr("Select User"),
|
||||
tr("Please select the user's save data you would like to open."),
|
||||
list, Settings::values.current_user, false, &ok);
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
const auto index = list.indexOf(index_string);
|
||||
ASSERT(index != -1 && index < 8);
|
||||
|
||||
const auto user_id = manager.GetUser(index);
|
||||
ASSERT(user_id != boost::none);
|
||||
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
|
||||
FileSys::SaveDataType::SaveData,
|
||||
program_id, user_id, 0);
|
||||
program_id, user_id->uuid, 0);
|
||||
|
||||
if (!FileUtil::Exists(path)) {
|
||||
FileUtil::CreateFullPath(path);
|
||||
FileUtil::CreateDir(path);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case GameListOpenTarget::ModData: {
|
||||
@@ -823,14 +861,10 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
|
||||
}
|
||||
|
||||
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
|
||||
const auto path = fmt::format("{}{:016X}/romfs",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
|
||||
|
||||
const auto failed = [this, &path] {
|
||||
const auto failed = [this] {
|
||||
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
|
||||
tr("There was an error copying the RomFS files or the user "
|
||||
"cancelled the operation."));
|
||||
vfs->DeleteDirectory(path);
|
||||
};
|
||||
|
||||
const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
|
||||
@@ -845,10 +879,24 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||
return;
|
||||
}
|
||||
|
||||
const auto romfs =
|
||||
loader->IsRomFSUpdatable()
|
||||
? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
|
||||
: file;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id);
|
||||
|
||||
if (!romfs_title_id) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto path = fmt::format(
|
||||
"{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id);
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
|
||||
if (*romfs_title_id == program_id) {
|
||||
romfs = file;
|
||||
} else {
|
||||
romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
|
||||
}
|
||||
|
||||
const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
|
||||
if (extracted == nullptr) {
|
||||
@@ -860,6 +908,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||
|
||||
if (out == nullptr) {
|
||||
failed();
|
||||
vfs->DeleteDirectory(path);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -870,8 +919,11 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||
"files into the new directory while <br>skeleton will only create the directory "
|
||||
"structure."),
|
||||
{"Full", "Skeleton"}, 0, false, &ok);
|
||||
if (!ok)
|
||||
if (!ok) {
|
||||
failed();
|
||||
vfs->DeleteDirectory(path);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto full = res == "Full";
|
||||
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
|
||||
@@ -888,6 +940,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||
} else {
|
||||
progress.close();
|
||||
failed();
|
||||
vfs->DeleteDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1174,6 +1227,7 @@ void GMainWindow::OnStartGame() {
|
||||
ui.action_Report_Compatibility->setEnabled(true);
|
||||
|
||||
discord_rpc->Update();
|
||||
ui.action_Load_Amiibo->setEnabled(true);
|
||||
}
|
||||
|
||||
void GMainWindow::OnPauseGame() {
|
||||
@@ -1278,6 +1332,27 @@ void GMainWindow::OnConfigure() {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnLoadAmiibo() {
|
||||
const QString extensions{"*.bin"};
|
||||
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
|
||||
const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter);
|
||||
if (!filename.isEmpty()) {
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
Service::SM::ServiceManager& sm = system.ServiceManager();
|
||||
auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
|
||||
if (nfc != nullptr) {
|
||||
auto nfc_file = FileUtil::IOFile(filename.toStdString(), "rb");
|
||||
if (!nfc_file.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
std::vector<u8> amiibo_buffer(nfc_file.GetSize());
|
||||
nfc_file.ReadBytes(amiibo_buffer.data(), amiibo_buffer.size());
|
||||
nfc_file.Close();
|
||||
nfc->LoadAmiibo(amiibo_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnAbout() {
|
||||
AboutDialog aboutDialog(this);
|
||||
aboutDialog.exec();
|
||||
@@ -1318,15 +1393,17 @@ void GMainWindow::UpdateStatusBar() {
|
||||
void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
|
||||
QMessageBox::StandardButton answer;
|
||||
QString status_message;
|
||||
const QString common_message = tr(
|
||||
"The game you are trying to load requires additional files from your Switch to be dumped "
|
||||
"before playing.<br/><br/>For more information on dumping these files, please see the "
|
||||
"following wiki page: <a "
|
||||
"href='https://yuzu-emu.org/wiki/"
|
||||
"dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
|
||||
"Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit "
|
||||
"back to the game list? Continuing emulation may result in crashes, corrupted save "
|
||||
"data, or other bugs.");
|
||||
const QString common_message =
|
||||
tr("The game you are trying to load requires additional files from your Switch to be "
|
||||
"dumped "
|
||||
"before playing.<br/><br/>For more information on dumping these files, please see the "
|
||||
"following wiki page: <a "
|
||||
"href='https://yuzu-emu.org/wiki/"
|
||||
"dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
|
||||
"Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to "
|
||||
"quit "
|
||||
"back to the game list? Continuing emulation may result in crashes, corrupted save "
|
||||
"data, or other bugs.");
|
||||
switch (result) {
|
||||
case Core::System::ResultStatus::ErrorSystemFiles: {
|
||||
QString message = "yuzu was unable to locate a Switch system archive";
|
||||
@@ -1357,9 +1434,12 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||
this, tr("Fatal Error"),
|
||||
tr("yuzu has encountered a fatal error, please see the log for more details. "
|
||||
"For more information on accessing the log, please see the following page: "
|
||||
"<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to "
|
||||
"Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? "
|
||||
"Continuing emulation may result in crashes, corrupted save data, or other bugs."),
|
||||
"<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
|
||||
"to "
|
||||
"Upload the Log File</a>.<br/><br/>Would you like to quit back to the game "
|
||||
"list? "
|
||||
"Continuing emulation may result in crashes, corrupted save data, or other "
|
||||
"bugs."),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
status_message = "Fatal Error encountered";
|
||||
break;
|
||||
@@ -1459,6 +1539,42 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<u64> GMainWindow::SelectRomFSDumpTarget(
|
||||
const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
|
||||
const auto dlc_entries =
|
||||
installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
std::vector<FileSys::RegisteredCacheEntry> dlc_match;
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
|
||||
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
|
||||
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
|
||||
});
|
||||
|
||||
std::vector<u64> romfs_tids;
|
||||
romfs_tids.push_back(program_id);
|
||||
for (const auto& entry : dlc_match)
|
||||
romfs_tids.push_back(entry.title_id);
|
||||
|
||||
if (romfs_tids.size() > 1) {
|
||||
QStringList list{"Base"};
|
||||
for (std::size_t i = 1; i < romfs_tids.size(); ++i)
|
||||
list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
|
||||
|
||||
bool ok;
|
||||
const auto res = QInputDialog::getItem(
|
||||
this, tr("Select RomFS Dump Target"),
|
||||
tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
|
||||
if (!ok) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
return romfs_tids[list.indexOf(res)];
|
||||
}
|
||||
|
||||
return program_id;
|
||||
}
|
||||
|
||||
bool GMainWindow::ConfirmClose() {
|
||||
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
|
||||
return true;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/main.h"
|
||||
#include "yuzu_cmd/config.h"
|
||||
@@ -125,10 +126,11 @@ void Config::ReadValues() {
|
||||
|
||||
// System
|
||||
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
|
||||
Settings::values.username = sdl2_config->Get("System", "username", "yuzu");
|
||||
if (Settings::values.username.empty()) {
|
||||
Settings::values.username = "yuzu";
|
||||
}
|
||||
Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true);
|
||||
const auto size = sdl2_config->GetInteger("System", "users_size", 0);
|
||||
|
||||
Settings::values.current_user = std::clamp<int>(
|
||||
sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
|
||||
|
||||
// Miscellaneous
|
||||
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
|
||||
|
||||
@@ -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