Compare commits
398 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4ac3bed6c | ||
|
|
da3da6be90 | ||
|
|
2a472ff54d | ||
|
|
c4ed0b16b1 | ||
|
|
c7f2fb2151 | ||
|
|
232b0d9d2a | ||
|
|
74e08b4800 | ||
|
|
a1bdc597e9 | ||
|
|
c5ea6db02d | ||
|
|
c7c4e6dcba | ||
|
|
c5c0da41b4 | ||
|
|
f835349364 | ||
|
|
12ba80a86c | ||
|
|
1fd979f50a | ||
|
|
b2ca8089ce | ||
|
|
e70a3c5a5d | ||
|
|
d1b1c42c07 | ||
|
|
dd35b4b18a | ||
|
|
4877e6c2f6 | ||
|
|
8e8326595f | ||
|
|
8ce02d85e9 | ||
|
|
b38d67d940 | ||
|
|
cea627b0fc | ||
|
|
5abf71fe65 | ||
|
|
eef0c93643 | ||
|
|
125d7122ac | ||
|
|
ad1220e1b3 | ||
|
|
92b85fad70 | ||
|
|
cb8b371570 | ||
|
|
38517241ec | ||
|
|
15cc34b93e | ||
|
|
99fc32428a | ||
|
|
d63b1d21f1 | ||
|
|
ac68c8a605 | ||
|
|
c2695aa2eb | ||
|
|
16b83fac9b | ||
|
|
a769d8c913 | ||
|
|
a0e2bd85a5 | ||
|
|
29ac15d1b8 | ||
|
|
0057a47e41 | ||
|
|
5a53d75313 | ||
|
|
8dd9cb98ce | ||
|
|
c95c4442e9 | ||
|
|
37f2ec6fc2 | ||
|
|
624239ed6b | ||
|
|
5678ec0dd0 | ||
|
|
3f4fb4b037 | ||
|
|
bfb28c5b3f | ||
|
|
f2d5b100c2 | ||
|
|
6923ecee3a | ||
|
|
36090521ce | ||
|
|
cc71832b19 | ||
|
|
bf89a99839 | ||
|
|
79243b6fa0 | ||
|
|
b0f7713fce | ||
|
|
8c9abe1d41 | ||
|
|
ca58929eb0 | ||
|
|
523e4be02c | ||
|
|
fde3b1b6f2 | ||
|
|
477eee3993 | ||
|
|
93a4097e9d | ||
|
|
e3bddf8137 | ||
|
|
c4ce7e456a | ||
|
|
e33452f7e8 | ||
|
|
5aaee2ff8d | ||
|
|
2ae88feea7 | ||
|
|
16db8b9d9f | ||
|
|
948002635f | ||
|
|
ea99819f37 | ||
|
|
eac3cf301c | ||
|
|
fc5b489b0f | ||
|
|
19b05c3f55 | ||
|
|
2788144f46 | ||
|
|
96463d0a55 | ||
|
|
dd70ddad7e | ||
|
|
c0fb321935 | ||
|
|
34f3d58470 | ||
|
|
b5fb246a99 | ||
|
|
c6fda4c758 | ||
|
|
609cb04f3f | ||
|
|
eb88fedc5d | ||
|
|
f5b132676f | ||
|
|
0fcdf37917 | ||
|
|
350f6e0aa4 | ||
|
|
9d8f19d7bf | ||
|
|
38cd4e9c61 | ||
|
|
f9a26d468c | ||
|
|
1277556c69 | ||
|
|
dfdf4a46fe | ||
|
|
69dd37d874 | ||
|
|
f13a66b963 | ||
|
|
2b9eee4d1e | ||
|
|
b1d238bbb8 | ||
|
|
f24ab6d9e6 | ||
|
|
46ef072cf9 | ||
|
|
6bcdf37d4f | ||
|
|
bc16f7f3cc | ||
|
|
ba8ff096fd | ||
|
|
7784ce1854 | ||
|
|
e8cb6f5c9b | ||
|
|
9e9a4bb3a7 | ||
|
|
d7c68fbb12 | ||
|
|
3fe77be392 | ||
|
|
028d90eb79 | ||
|
|
296e57fa0e | ||
|
|
b20ed93884 | ||
|
|
185b35bfcd | ||
|
|
943771e703 | ||
|
|
ce4b77bd7d | ||
|
|
6ee8b15abe | ||
|
|
23d45715dc | ||
|
|
ffd60ee476 | ||
|
|
8a88110060 | ||
|
|
6cf719a4ab | ||
|
|
51ddb130c5 | ||
|
|
9b17486be6 | ||
|
|
0a1d4fbc5c | ||
|
|
f7edbcd7a3 | ||
|
|
b0eb580931 | ||
|
|
85da529f15 | ||
|
|
7fb406c3fc | ||
|
|
3ef4b3d4b4 | ||
|
|
73b937b190 | ||
|
|
656758fd81 | ||
|
|
29d4f8c2dd | ||
|
|
9baf5de90c | ||
|
|
d6cb22b0df | ||
|
|
1b92ae136f | ||
|
|
731701a2d2 | ||
|
|
706fc5d2d6 | ||
|
|
27da7bc9da | ||
|
|
9b1c49a9cf | ||
|
|
e0f66c1fbf | ||
|
|
8335b2f115 | ||
|
|
367feaefa0 | ||
|
|
71cc482bbd | ||
|
|
ff358d97e8 | ||
|
|
2e95ba2e9c | ||
|
|
6eba539f4a | ||
|
|
63dff47e22 | ||
|
|
504cff2b7a | ||
|
|
804aebf7c7 | ||
|
|
2003771789 | ||
|
|
f246fd778d | ||
|
|
1db7839f11 | ||
|
|
224be09d66 | ||
|
|
e341d868ee | ||
|
|
da7226442f | ||
|
|
727136a9c9 | ||
|
|
0d9b3e425e | ||
|
|
ce56b8e1fa | ||
|
|
cef35e7c9c | ||
|
|
928e78dced | ||
|
|
3fd78f4d24 | ||
|
|
9791f0d590 | ||
|
|
2a3d7128d1 | ||
|
|
aac807fd3a | ||
|
|
a0ce6de913 | ||
|
|
94329038b6 | ||
|
|
24a759de4a | ||
|
|
b39cd70cd4 | ||
|
|
c594ec3417 | ||
|
|
89c3d6a2a3 | ||
|
|
c00b374e78 | ||
|
|
69236e5aff | ||
|
|
1dd27aff47 | ||
|
|
cee6a7ab55 | ||
|
|
a2fa37b499 | ||
|
|
f96de510ee | ||
|
|
91140f6c0a | ||
|
|
38592a3b5e | ||
|
|
40ecdda19e | ||
|
|
5ef447cc0e | ||
|
|
11c221cc62 | ||
|
|
40f83fee6a | ||
|
|
0bca5743ab | ||
|
|
aac5792a2b | ||
|
|
5752454629 | ||
|
|
87d8a9c986 | ||
|
|
0769aa594e | ||
|
|
c8e3f98c27 | ||
|
|
b74df62959 | ||
|
|
e0b0f4eece | ||
|
|
b1148d269d | ||
|
|
c5284efd4f | ||
|
|
8599e1e4fc | ||
|
|
3aad82b1a3 | ||
|
|
2a42dea568 | ||
|
|
c8cd1785e6 | ||
|
|
991eb4824c | ||
|
|
301baaa942 | ||
|
|
3db1b8e0cd | ||
|
|
8f9c49f7ee | ||
|
|
f009ed63f3 | ||
|
|
18dfa99030 | ||
|
|
6eda9ebbdb | ||
|
|
ad7815a28d | ||
|
|
409d2e07c2 | ||
|
|
8dc4407586 | ||
|
|
96c0b81a51 | ||
|
|
25d71454d1 | ||
|
|
5e66a24423 | ||
|
|
290439a6a5 | ||
|
|
dc876fd63a | ||
|
|
d8fd3ef4fe | ||
|
|
1c31cbad72 | ||
|
|
2e715ef70d | ||
|
|
319dbc5843 | ||
|
|
60f476cd8f | ||
|
|
6d549abb4e | ||
|
|
0ce0905380 | ||
|
|
11895d54af | ||
|
|
d1520410a3 | ||
|
|
4dacb8a4b1 | ||
|
|
5b32594fbe | ||
|
|
882ce44986 | ||
|
|
bc7bfd96f0 | ||
|
|
6bfcf13187 | ||
|
|
309564abe3 | ||
|
|
b6c47b578f | ||
|
|
9d09d92c56 | ||
|
|
57d007e545 | ||
|
|
6e52f37d5b | ||
|
|
59d18ef55b | ||
|
|
46fbf6dd92 | ||
|
|
f19b4fab5f | ||
|
|
875d52a81f | ||
|
|
9bf9c71c88 | ||
|
|
fcc5155601 | ||
|
|
45cc022ea9 | ||
|
|
eab35c8235 | ||
|
|
01d199965a | ||
|
|
4b44b8b4fb | ||
|
|
56300f2928 | ||
|
|
e67630b51e | ||
|
|
bd14653417 | ||
|
|
2e89719d3e | ||
|
|
41b77c4e0a | ||
|
|
baaafbd5ea | ||
|
|
3476f5b4d3 | ||
|
|
bdf17fe0cc | ||
|
|
54ef9302a2 | ||
|
|
76fad8410d | ||
|
|
92492ee23b | ||
|
|
e56a444da9 | ||
|
|
8fe118bcaa | ||
|
|
c56a0e3c34 | ||
|
|
fecffeb0dd | ||
|
|
9608f51cde | ||
|
|
e4ed5bc836 | ||
|
|
de5d431eec | ||
|
|
8da753ab81 | ||
|
|
d923766042 | ||
|
|
a9877c8f65 | ||
|
|
2e7802ad7d | ||
|
|
3a338d9286 | ||
|
|
84b542c386 | ||
|
|
0135b328ed | ||
|
|
a970709d5d | ||
|
|
534abf9d97 | ||
|
|
5224cc49c4 | ||
|
|
b82b093108 | ||
|
|
cf0a7cd1c1 | ||
|
|
424e90f0f5 | ||
|
|
e12a07079e | ||
|
|
35e4a47be0 | ||
|
|
fcc5ffdfdd | ||
|
|
4cafc24a4e | ||
|
|
68c44ca0ee | ||
|
|
e858a72a22 | ||
|
|
4db8acd30a | ||
|
|
b8c1dca62f | ||
|
|
e850ff63bc | ||
|
|
11470f331a | ||
|
|
55c73e10a7 | ||
|
|
0eb39922f6 | ||
|
|
0af7e93763 | ||
|
|
6ff7906ddc | ||
|
|
ce722e317b | ||
|
|
6f6bba3ff1 | ||
|
|
d7298ec262 | ||
|
|
66f4f86a82 | ||
|
|
63a70c253e | ||
|
|
9e74d6238e | ||
|
|
5926fbd3d7 | ||
|
|
75bba25009 | ||
|
|
7b6519741b | ||
|
|
d6a1a43854 | ||
|
|
eb2633f3ef | ||
|
|
639ebb39f6 | ||
|
|
cb3c50eacc | ||
|
|
094f6003e0 | ||
|
|
98b940052c | ||
|
|
e5ee0afe6f | ||
|
|
a70ad9b5bb | ||
|
|
3f81c38c6d | ||
|
|
c68aa65226 | ||
|
|
ecfbe7d9c8 | ||
|
|
a1fb8a331f | ||
|
|
6b76b77400 | ||
|
|
fdf27bf390 | ||
|
|
8f06a0f898 | ||
|
|
dda8ef11c7 | ||
|
|
149bda980a | ||
|
|
893447b6b0 | ||
|
|
22bdddd6f0 | ||
|
|
62e859c6c7 | ||
|
|
f78a6e752f | ||
|
|
3b3c919e20 | ||
|
|
10812f8407 | ||
|
|
e5504a060d | ||
|
|
167bfddafa | ||
|
|
bfb945c243 | ||
|
|
b67e751ccb | ||
|
|
a91983b11c | ||
|
|
9aab787122 | ||
|
|
ab8acce645 | ||
|
|
9b0e3556ed | ||
|
|
c0257cf52f | ||
|
|
70a510bd8f | ||
|
|
95bb1067c1 | ||
|
|
5b4119fa7f | ||
|
|
42114e1df4 | ||
|
|
a27ec24c0f | ||
|
|
b70a831608 | ||
|
|
10aac376d1 | ||
|
|
a921d22545 | ||
|
|
ee07041b3a | ||
|
|
9c977d2215 | ||
|
|
f2c7b5dcd6 | ||
|
|
d37da52cb3 | ||
|
|
969326bd58 | ||
|
|
224071a652 | ||
|
|
2dad1204e8 | ||
|
|
249341d08f | ||
|
|
261a4f0311 | ||
|
|
ca4bf671ce | ||
|
|
28e90fa0e0 | ||
|
|
0a93b45b6a | ||
|
|
403dfd68fc | ||
|
|
c519354506 | ||
|
|
3d486fffed | ||
|
|
436acbb630 | ||
|
|
0b668d5ff3 | ||
|
|
bc286c169f | ||
|
|
670a2c1f80 | ||
|
|
88ffa422d4 | ||
|
|
0471976b48 | ||
|
|
c1ad973881 | ||
|
|
305a05f820 | ||
|
|
d64303d185 | ||
|
|
b8b9f41b6b | ||
|
|
dfcde52f39 | ||
|
|
10f494eefe | ||
|
|
448290bee4 | ||
|
|
2592e41301 | ||
|
|
0b6f8ba51e | ||
|
|
d0b2950434 | ||
|
|
42431d2aa6 | ||
|
|
b8e70faa2d | ||
|
|
662218e997 | ||
|
|
c3013c7c9c | ||
|
|
acff922762 | ||
|
|
dfea525cbe | ||
|
|
82fa0bcea7 | ||
|
|
4fa4d80c68 | ||
|
|
6aa8ee6943 | ||
|
|
4f0818144e | ||
|
|
b76ddb7647 | ||
|
|
2a3b335b15 | ||
|
|
20c2928c2b | ||
|
|
f380496728 | ||
|
|
2e80e7480d | ||
|
|
8eb97706b8 | ||
|
|
aaf671a309 | ||
|
|
be53097577 | ||
|
|
0a003efde4 | ||
|
|
2e8620c877 | ||
|
|
6b0bc48a42 | ||
|
|
a5b65df9cf | ||
|
|
57626fda7b | ||
|
|
6313d54cef | ||
|
|
7e6a73963e | ||
|
|
2156cb3cbe | ||
|
|
ef41983c84 | ||
|
|
8069fbd37f | ||
|
|
ec3bef7b4c | ||
|
|
b8c43b6080 | ||
|
|
3a67876252 | ||
|
|
6828c25498 | ||
|
|
e8c52d4c89 | ||
|
|
4e1471ef21 | ||
|
|
e9978fd4f5 | ||
|
|
75169c7570 | ||
|
|
03d7faf583 | ||
|
|
6f691e71bf | ||
|
|
5f8d253ce0 | ||
|
|
ed8b2a0254 |
@@ -1,5 +1,5 @@
|
||||
# CMake 3.6 required for FindBoost to define IMPORTED libs properly on unknown Boost versions
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
include(DownloadExternals)
|
||||
@@ -66,10 +66,12 @@ if (NOT ENABLE_GENERIC)
|
||||
detect_architecture("_M_AMD64" x86_64)
|
||||
detect_architecture("_M_IX86" x86)
|
||||
detect_architecture("_M_ARM" ARM)
|
||||
detect_architecture("_M_ARM64" ARM64)
|
||||
else()
|
||||
detect_architecture("__x86_64__" x86_64)
|
||||
detect_architecture("__i386__" x86)
|
||||
detect_architecture("__arm__" ARM)
|
||||
detect_architecture("__aarch64__" ARM64)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -187,8 +189,8 @@ find_package(Threads REQUIRED)
|
||||
if (ENABLE_SDL2)
|
||||
if (YUZU_USE_BUNDLED_SDL2)
|
||||
# Detect toolchain and platform
|
||||
if (MSVC14 AND ARCHITECTURE_x86_64)
|
||||
set(SDL2_VER "SDL2-2.0.5")
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
|
||||
set(SDL2_VER "SDL2-2.0.8")
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
||||
endif()
|
||||
@@ -220,7 +222,7 @@ if (YUZU_USE_BUNDLED_UNICORN)
|
||||
if (MSVC)
|
||||
message(STATUS "unicorn not found, falling back to bundled")
|
||||
# Detect toolchain and platform
|
||||
if (MSVC14 AND ARCHITECTURE_x86_64)
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
|
||||
set(UNICORN_VER "unicorn-yuzu")
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled Unicorn binaries for your toolchain. Disable YUZU_USE_BUNDLED_UNICORN and provide your own.")
|
||||
@@ -279,7 +281,7 @@ endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
if (YUZU_USE_BUNDLED_QT)
|
||||
if (MSVC14 AND ARCHITECTURE_x86_64)
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
|
||||
set(QT_VER qt-5.10.0-msvc2015_64)
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
|
||||
@@ -303,7 +305,7 @@ endif()
|
||||
# ======================================
|
||||
|
||||
IF (APPLE)
|
||||
FIND_LIBRARY(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
|
||||
find_library(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
|
||||
2
externals/boost
vendored
2
externals/boost
vendored
Submodule externals/boost updated: d80e506e17...0b920df1c9
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 4f96c63025...a42f301c28
2
externals/fmt
vendored
2
externals/fmt
vendored
Submodule externals/fmt updated: c2ce7e4f07...62010520ed
@@ -1,4 +1,8 @@
|
||||
add_library(audio_core STATIC
|
||||
algorithm/filter.cpp
|
||||
algorithm/filter.h
|
||||
algorithm/interpolate.cpp
|
||||
algorithm/interpolate.h
|
||||
audio_out.cpp
|
||||
audio_out.h
|
||||
audio_renderer.cpp
|
||||
@@ -7,12 +11,12 @@ add_library(audio_core STATIC
|
||||
codec.cpp
|
||||
codec.h
|
||||
null_sink.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
sink.h
|
||||
sink_details.cpp
|
||||
sink_details.h
|
||||
sink_stream.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
|
||||
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
|
||||
)
|
||||
|
||||
79
src/audio_core/algorithm/filter.cpp
Normal file
79
src/audio_core/algorithm/filter.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include "audio_core/algorithm/filter.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
Filter Filter::LowPass(double cutoff, double Q) {
|
||||
const double w0 = 2.0 * M_PI * cutoff;
|
||||
const double sin_w0 = std::sin(w0);
|
||||
const double cos_w0 = std::cos(w0);
|
||||
const double alpha = sin_w0 / (2 * Q);
|
||||
|
||||
const double a0 = 1 + alpha;
|
||||
const double a1 = -2.0 * cos_w0;
|
||||
const double a2 = 1 - alpha;
|
||||
const double b0 = 0.5 * (1 - cos_w0);
|
||||
const double b1 = 1.0 * (1 - cos_w0);
|
||||
const double b2 = 0.5 * (1 - cos_w0);
|
||||
|
||||
return {a0, a1, a2, b0, b1, b2};
|
||||
}
|
||||
|
||||
Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {}
|
||||
|
||||
Filter::Filter(double a0, double a1, double a2, double b0, double b1, double b2)
|
||||
: a1(a1 / a0), a2(a2 / a0), b0(b0 / a0), b1(b1 / a0), b2(b2 / a0) {}
|
||||
|
||||
void Filter::Process(std::vector<s16>& signal) {
|
||||
const size_t num_frames = signal.size() / 2;
|
||||
for (size_t i = 0; i < num_frames; i++) {
|
||||
std::rotate(in.begin(), in.end() - 1, in.end());
|
||||
std::rotate(out.begin(), out.end() - 1, out.end());
|
||||
|
||||
for (size_t ch = 0; ch < channel_count; ch++) {
|
||||
in[0][ch] = signal[i * channel_count + ch];
|
||||
|
||||
out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] -
|
||||
a2 * out[2][ch];
|
||||
|
||||
signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the appropriate Q for each biquad in a cascading filter.
|
||||
/// @param total_count The total number of biquads to be cascaded.
|
||||
/// @param index 0-index of the biquad to calculate the Q value for.
|
||||
static double CascadingBiquadQ(size_t total_count, size_t index) {
|
||||
const double pole = M_PI * (2 * index + 1) / (4.0 * total_count);
|
||||
return 1.0 / (2.0 * std::cos(pole));
|
||||
}
|
||||
|
||||
CascadingFilter CascadingFilter::LowPass(double cutoff, size_t cascade_size) {
|
||||
std::vector<Filter> cascade(cascade_size);
|
||||
for (size_t i = 0; i < cascade_size; i++) {
|
||||
cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i));
|
||||
}
|
||||
return CascadingFilter{std::move(cascade)};
|
||||
}
|
||||
|
||||
CascadingFilter::CascadingFilter() = default;
|
||||
CascadingFilter::CascadingFilter(std::vector<Filter> filters) : filters(std::move(filters)) {}
|
||||
|
||||
void CascadingFilter::Process(std::vector<s16>& signal) {
|
||||
for (auto& filter : filters) {
|
||||
filter.Process(signal);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
62
src/audio_core/algorithm/filter.h
Normal file
62
src/audio_core/algorithm/filter.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/// Digital biquad filter:
|
||||
///
|
||||
/// b0 + b1 z^-1 + b2 z^-2
|
||||
/// H(z) = ------------------------
|
||||
/// a0 + a1 z^-1 + b2 z^-2
|
||||
class Filter {
|
||||
public:
|
||||
/// Creates a low-pass filter.
|
||||
/// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
|
||||
/// @param Q Determines the quality factor of this filter.
|
||||
static Filter LowPass(double cutoff, double Q = 0.7071);
|
||||
|
||||
/// Passthrough filter.
|
||||
Filter();
|
||||
|
||||
Filter(double a0, double a1, double a2, double b0, double b1, double b2);
|
||||
|
||||
void Process(std::vector<s16>& signal);
|
||||
|
||||
private:
|
||||
static constexpr size_t channel_count = 2;
|
||||
|
||||
/// Coefficients are in normalized form (a0 = 1.0).
|
||||
double a1, a2, b0, b1, b2;
|
||||
/// Input History
|
||||
std::array<std::array<double, channel_count>, 3> in;
|
||||
/// Output History
|
||||
std::array<std::array<double, channel_count>, 3> out;
|
||||
};
|
||||
|
||||
/// Cascade filters to build up higher-order filters from lower-order ones.
|
||||
class CascadingFilter {
|
||||
public:
|
||||
/// Creates a cascading low-pass filter.
|
||||
/// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
|
||||
/// @param cascade_size Number of biquads in cascade.
|
||||
static CascadingFilter LowPass(double cutoff, size_t cascade_size);
|
||||
|
||||
/// Passthrough.
|
||||
CascadingFilter();
|
||||
|
||||
explicit CascadingFilter(std::vector<Filter> filters);
|
||||
|
||||
void Process(std::vector<s16>& signal);
|
||||
|
||||
private:
|
||||
std::vector<Filter> filters;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
71
src/audio_core/algorithm/interpolate.cpp
Normal file
71
src/audio_core/algorithm/interpolate.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/// The Lanczos kernel
|
||||
static double Lanczos(size_t a, double x) {
|
||||
if (x == 0.0)
|
||||
return 1.0;
|
||||
const double px = M_PI * x;
|
||||
return a * std::sin(px) * std::sin(px / a) / (px * px);
|
||||
}
|
||||
|
||||
std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio) {
|
||||
if (input.size() < 2)
|
||||
return {};
|
||||
|
||||
if (ratio <= 0) {
|
||||
LOG_CRITICAL(Audio, "Nonsensical interpolation ratio {}", ratio);
|
||||
ratio = 1.0;
|
||||
}
|
||||
|
||||
if (ratio != state.current_ratio) {
|
||||
const double cutoff_frequency = std::min(0.5 / ratio, 0.5 * ratio);
|
||||
state.nyquist = CascadingFilter::LowPass(std::clamp(cutoff_frequency, 0.0, 0.4), 3);
|
||||
state.current_ratio = ratio;
|
||||
}
|
||||
state.nyquist.Process(input);
|
||||
|
||||
constexpr size_t taps = InterpolationState::lanczos_taps;
|
||||
const size_t num_frames = input.size() / 2;
|
||||
|
||||
std::vector<s16> output;
|
||||
output.reserve(static_cast<size_t>(input.size() / ratio + 4));
|
||||
|
||||
double& pos = state.position;
|
||||
auto& h = state.history;
|
||||
for (size_t i = 0; i < num_frames; ++i) {
|
||||
std::rotate(h.begin(), h.end() - 1, h.end());
|
||||
h[0][0] = input[i * 2 + 0];
|
||||
h[0][1] = input[i * 2 + 1];
|
||||
|
||||
while (pos <= 1.0) {
|
||||
double l = 0.0;
|
||||
double r = 0.0;
|
||||
for (size_t j = 0; j < h.size(); j++) {
|
||||
l += Lanczos(taps, pos + j - taps + 1) * h[j][0];
|
||||
r += Lanczos(taps, pos + j - taps + 1) * h[j][1];
|
||||
}
|
||||
output.emplace_back(static_cast<s16>(std::clamp(l, -32768.0, 32767.0)));
|
||||
output.emplace_back(static_cast<s16>(std::clamp(r, -32768.0, 32767.0)));
|
||||
|
||||
pos += ratio;
|
||||
}
|
||||
pos -= 1.0;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
43
src/audio_core/algorithm/interpolate.h
Normal file
43
src/audio_core/algorithm/interpolate.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "audio_core/algorithm/filter.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
struct InterpolationState {
|
||||
static constexpr size_t lanczos_taps = 4;
|
||||
static constexpr size_t history_size = lanczos_taps * 2 - 1;
|
||||
|
||||
double current_ratio = 0.0;
|
||||
CascadingFilter nyquist;
|
||||
std::array<std::array<s16, 2>, history_size> history = {};
|
||||
double position = 0;
|
||||
};
|
||||
|
||||
/// Interpolates input signal to produce output signal.
|
||||
/// @param input The signal to interpolate.
|
||||
/// @param ratio Interpolation ratio.
|
||||
/// ratio > 1.0 results in fewer output samples.
|
||||
/// ratio < 1.0 results in more output samples.
|
||||
/// @returns Output signal.
|
||||
std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio);
|
||||
|
||||
/// Interpolates input signal to produce output signal.
|
||||
/// @param input The signal to interpolate.
|
||||
/// @param input_rate The sample rate of input.
|
||||
/// @param output_rate The desired sample rate of the output.
|
||||
/// @returns Output signal.
|
||||
inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
|
||||
u32 input_rate, u32 output_rate) {
|
||||
const double ratio = static_cast<double>(input_rate) / static_cast<double>(output_rate);
|
||||
return Interpolate(state, std::move(input), ratio);
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -26,6 +27,18 @@ AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
QueueMixedBuffer(2);
|
||||
}
|
||||
|
||||
u32 AudioRenderer::GetSampleRate() const {
|
||||
return worker_params.sample_rate;
|
||||
}
|
||||
|
||||
u32 AudioRenderer::GetSampleCount() const {
|
||||
return worker_params.sample_count;
|
||||
}
|
||||
|
||||
u32 AudioRenderer::GetMixBufferCount() const {
|
||||
return worker_params.mix_buffer_count;
|
||||
}
|
||||
|
||||
std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
|
||||
// Copy UpdateDataHeader struct
|
||||
UpdateDataHeader config{};
|
||||
@@ -187,6 +200,8 @@ void AudioRenderer::VoiceState::RefreshBuffer() {
|
||||
break;
|
||||
}
|
||||
|
||||
samples = Interpolate(interp_state, std::move(samples), Info().sample_rate, STREAM_SAMPLE_RATE);
|
||||
|
||||
is_refresh_pending = false;
|
||||
}
|
||||
|
||||
@@ -212,7 +227,7 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
break;
|
||||
}
|
||||
|
||||
samples_remaining -= samples.size();
|
||||
samples_remaining -= samples.size() / stream->GetNumChannels();
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
const s32 buffer_sample{buffer[offset]};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "audio_core/codec.h"
|
||||
#include "audio_core/stream.h"
|
||||
@@ -26,7 +27,7 @@ enum class PlayState : u8 {
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le unknown_8;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le unknown_c;
|
||||
u32_le voice_count;
|
||||
u32_le sink_count;
|
||||
@@ -160,6 +161,9 @@ public:
|
||||
std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params);
|
||||
void QueueMixedBuffer(Buffer::Tag tag);
|
||||
void ReleaseAndQueueBuffers();
|
||||
u32 GetSampleRate() const;
|
||||
u32 GetSampleCount() const;
|
||||
u32 GetMixBufferCount() const;
|
||||
|
||||
private:
|
||||
class VoiceState {
|
||||
@@ -191,6 +195,7 @@ private:
|
||||
size_t wave_index{};
|
||||
size_t offset{};
|
||||
Codec::ADPCMState adpcm_state{};
|
||||
InterpolationState interp_state{};
|
||||
std::vector<s16> samples;
|
||||
VoiceOutStatus out_status{};
|
||||
VoiceInfo info{};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#include "audio_core/stream.h"
|
||||
@@ -66,6 +67,8 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock{queue_mutex};
|
||||
|
||||
queue.reserve(queue.size() + samples.size() * GetNumChannels());
|
||||
|
||||
if (is_6_channel) {
|
||||
@@ -94,6 +97,7 @@ private:
|
||||
u32 num_channels{};
|
||||
bool is_6_channel{};
|
||||
|
||||
std::mutex queue_mutex;
|
||||
std::vector<s16> queue;
|
||||
|
||||
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
@@ -153,6 +157,8 @@ long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const v
|
||||
return {};
|
||||
}
|
||||
|
||||
std::lock_guard lock{impl->queue_mutex};
|
||||
|
||||
const size_t frames_to_write{
|
||||
std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))};
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@ add_library(common STATIC
|
||||
assert.h
|
||||
bit_field.h
|
||||
bit_set.h
|
||||
break_points.cpp
|
||||
break_points.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
color.h
|
||||
@@ -40,6 +38,8 @@ add_library(common STATIC
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
hash.h
|
||||
hex_util.cpp
|
||||
hex_util.h
|
||||
logging/backend.cpp
|
||||
logging/backend.h
|
||||
logging/filter.cpp
|
||||
|
||||
@@ -178,8 +178,7 @@ public:
|
||||
return ExtractValue(storage);
|
||||
}
|
||||
|
||||
// TODO: we may want to change this to explicit operator bool() if it's bug-free in VS2015
|
||||
constexpr FORCE_INLINE bool ToBool() const {
|
||||
constexpr explicit operator bool() const {
|
||||
return Value() != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,90 +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 <algorithm>
|
||||
#include <sstream>
|
||||
#include "common/break_points.h"
|
||||
|
||||
bool BreakPoints::IsAddressBreakPoint(u32 iAddress) const {
|
||||
auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress; };
|
||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||
return it != m_BreakPoints.end();
|
||||
}
|
||||
|
||||
bool BreakPoints::IsTempBreakPoint(u32 iAddress) const {
|
||||
auto cond = [&iAddress](const TBreakPoint& bp) {
|
||||
return bp.iAddress == iAddress && bp.bTemporary;
|
||||
};
|
||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||
return it != m_BreakPoints.end();
|
||||
}
|
||||
|
||||
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const {
|
||||
TBreakPointsStr bps;
|
||||
for (auto breakpoint : m_BreakPoints) {
|
||||
if (!breakpoint.bTemporary) {
|
||||
std::stringstream bp;
|
||||
bp << std::hex << breakpoint.iAddress << " " << (breakpoint.bOn ? "n" : "");
|
||||
bps.push_back(bp.str());
|
||||
}
|
||||
}
|
||||
|
||||
return bps;
|
||||
}
|
||||
|
||||
void BreakPoints::AddFromStrings(const TBreakPointsStr& bps) {
|
||||
for (auto bps_item : bps) {
|
||||
TBreakPoint bp;
|
||||
std::stringstream bpstr;
|
||||
bpstr << std::hex << bps_item;
|
||||
bpstr >> bp.iAddress;
|
||||
bp.bOn = bps_item.find("n") != bps_item.npos;
|
||||
bp.bTemporary = false;
|
||||
Add(bp);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakPoints::Add(const TBreakPoint& bp) {
|
||||
if (!IsAddressBreakPoint(bp.iAddress)) {
|
||||
m_BreakPoints.push_back(bp);
|
||||
// if (jit)
|
||||
// jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakPoints::Add(u32 em_address, bool temp) {
|
||||
if (!IsAddressBreakPoint(em_address)) // only add new addresses
|
||||
{
|
||||
TBreakPoint pt; // breakpoint settings
|
||||
pt.bOn = true;
|
||||
pt.bTemporary = temp;
|
||||
pt.iAddress = em_address;
|
||||
|
||||
m_BreakPoints.push_back(pt);
|
||||
|
||||
// if (jit)
|
||||
// jit->GetBlockCache()->InvalidateICache(em_address, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakPoints::Remove(u32 em_address) {
|
||||
auto cond = [&em_address](const TBreakPoint& bp) { return bp.iAddress == em_address; };
|
||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||
if (it != m_BreakPoints.end())
|
||||
m_BreakPoints.erase(it);
|
||||
}
|
||||
|
||||
void BreakPoints::Clear() {
|
||||
// if (jit)
|
||||
//{
|
||||
// std::for_each(m_BreakPoints.begin(), m_BreakPoints.end(),
|
||||
// [](const TBreakPoint& bp)
|
||||
// {
|
||||
// jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4);
|
||||
// }
|
||||
// );
|
||||
//}
|
||||
|
||||
m_BreakPoints.clear();
|
||||
}
|
||||
@@ -1,49 +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 <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
class DebugInterface;
|
||||
|
||||
struct TBreakPoint {
|
||||
u32 iAddress;
|
||||
bool bOn;
|
||||
bool bTemporary;
|
||||
};
|
||||
|
||||
// Code breakpoints.
|
||||
class BreakPoints {
|
||||
public:
|
||||
typedef std::vector<TBreakPoint> TBreakPoints;
|
||||
typedef std::vector<std::string> TBreakPointsStr;
|
||||
|
||||
const TBreakPoints& GetBreakPoints() {
|
||||
return m_BreakPoints;
|
||||
}
|
||||
|
||||
TBreakPointsStr GetStrings() const;
|
||||
void AddFromStrings(const TBreakPointsStr& bps);
|
||||
|
||||
// is address breakpoint
|
||||
bool IsAddressBreakPoint(u32 iAddress) const;
|
||||
bool IsTempBreakPoint(u32 iAddress) const;
|
||||
|
||||
// Add BreakPoint
|
||||
void Add(u32 em_address, bool temp = false);
|
||||
void Add(const TBreakPoint& bp);
|
||||
|
||||
// Remove Breakpoint
|
||||
void Remove(u32 iAddress);
|
||||
void Clear();
|
||||
|
||||
void DeleteByAddress(u32 Address);
|
||||
|
||||
private:
|
||||
TBreakPoints m_BreakPoints;
|
||||
u32 m_iBreakOnCount;
|
||||
};
|
||||
@@ -750,6 +750,12 @@ std::string GetHactoolConfigurationPath() {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GetNANDRegistrationDir(bool system) {
|
||||
if (system)
|
||||
return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/";
|
||||
return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
|
||||
}
|
||||
|
||||
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
|
||||
return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
|
||||
}
|
||||
|
||||
@@ -129,6 +129,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
|
||||
|
||||
std::string GetHactoolConfigurationPath();
|
||||
|
||||
std::string GetNANDRegistrationDir(bool system = false);
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
std::string GetSysDirectory();
|
||||
|
||||
|
||||
31
src/common/hex_util.cpp
Normal file
31
src/common/hex_util.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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/hex_util.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
u8 ToHexNibble(char c1) {
|
||||
if (c1 >= 65 && c1 <= 70)
|
||||
return c1 - 55;
|
||||
if (c1 >= 97 && c1 <= 102)
|
||||
return c1 - 87;
|
||||
if (c1 >= 48 && c1 <= 57)
|
||||
return c1 - 48;
|
||||
throw std::logic_error("Invalid hex digit");
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
|
||||
if (len != 32)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<16>(str);
|
||||
}
|
||||
|
||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
|
||||
if (len != 64)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<32>(str);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
41
src/common/hex_util.h
Normal file
41
src/common/hex_util.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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 <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
u8 ToHexNibble(char c1);
|
||||
|
||||
template <size_t Size, bool le = false>
|
||||
std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
std::array<u8, Size> out{};
|
||||
if constexpr (le) {
|
||||
for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
} else {
|
||||
for (size_t i = 0; i < 2 * Size; i += 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
|
||||
std::string out;
|
||||
for (u8 c : array)
|
||||
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
|
||||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
|
||||
|
||||
} // namespace Common
|
||||
@@ -302,13 +302,14 @@ Backend* GetBackend(std::string_view backend_name) {
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
const fmt::format_args& args) {
|
||||
auto filter = Impl::Instance().GetGlobalFilter();
|
||||
auto& instance = Impl::Instance();
|
||||
const auto& filter = instance.GetGlobalFilter();
|
||||
if (!filter.CheckMessage(log_class, log_level))
|
||||
return;
|
||||
|
||||
Entry entry =
|
||||
CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
|
||||
|
||||
Impl::Instance().PushEntry(std::move(entry));
|
||||
instance.PushEntry(std::move(entry));
|
||||
}
|
||||
} // namespace Log
|
||||
|
||||
@@ -42,7 +42,7 @@ void PrintColoredMessage(const Entry& entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFO original_info = {0};
|
||||
CONSOLE_SCREEN_BUFFER_INFO original_info = {};
|
||||
GetConsoleScreenBufferInfo(console_handle, &original_info);
|
||||
|
||||
WORD color = 0;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
@@ -3,8 +3,15 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/telemetry.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
|
||||
namespace Telemetry {
|
||||
|
||||
void FieldCollection::Accept(VisitorInterface& visitor) const {
|
||||
@@ -37,4 +44,62 @@ template class Field<std::string>;
|
||||
template class Field<const char*>;
|
||||
template class Field<std::chrono::microseconds>;
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
static const char* CpuVendorToStr(Common::CPUVendor vendor) {
|
||||
switch (vendor) {
|
||||
case Common::CPUVendor::INTEL:
|
||||
return "Intel";
|
||||
case Common::CPUVendor::AMD:
|
||||
return "Amd";
|
||||
case Common::CPUVendor::OTHER:
|
||||
return "Other";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
#endif
|
||||
|
||||
void AppendBuildInfo(FieldCollection& fc) {
|
||||
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
|
||||
fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty);
|
||||
fc.AddField(FieldType::App, "Git_Branch", Common::g_scm_branch);
|
||||
fc.AddField(FieldType::App, "Git_Revision", Common::g_scm_rev);
|
||||
fc.AddField(FieldType::App, "BuildDate", Common::g_build_date);
|
||||
fc.AddField(FieldType::App, "BuildName", Common::g_build_name);
|
||||
}
|
||||
|
||||
void AppendCPUInfo(FieldCollection& fc) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Vendor", CpuVendorToStr(Common::GetCPUCaps().vendor));
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSSE3", Common::GetCPUCaps().ssse3);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE41", Common::GetCPUCaps().sse4_1);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE42", Common::GetCPUCaps().sse4_2);
|
||||
#else
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Model", "Other");
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppendOSInfo(FieldCollection& fc) {
|
||||
#ifdef __APPLE__
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Apple");
|
||||
#elif defined(_WIN32)
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Windows");
|
||||
#elif defined(__linux__) || defined(linux) || defined(__linux)
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Linux");
|
||||
#else
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Unknown");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
|
||||
@@ -180,4 +180,16 @@ struct NullVisitor : public VisitorInterface {
|
||||
void Complete() override {}
|
||||
};
|
||||
|
||||
/// Appends build-specific information to the given FieldCollection,
|
||||
/// such as branch name, revision hash, etc.
|
||||
void AppendBuildInfo(FieldCollection& fc);
|
||||
|
||||
/// Appends CPU-specific information to the given FieldCollection,
|
||||
/// such as instruction set extensions, etc.
|
||||
void AppendCPUInfo(FieldCollection& fc);
|
||||
|
||||
/// Appends OS-specific information to the given FieldCollection,
|
||||
/// such as platform name, etc.
|
||||
void AppendOSInfo(FieldCollection& fc);
|
||||
|
||||
} // namespace Telemetry
|
||||
|
||||
@@ -16,7 +16,7 @@ struct ThreadQueueList {
|
||||
// (dynamically resizable) circular buffers to remove their overhead when
|
||||
// inserting and popping.
|
||||
|
||||
typedef unsigned int Priority;
|
||||
using Priority = unsigned int;
|
||||
|
||||
// Number of priority levels. (Valid levels are [0..NUM_QUEUES).)
|
||||
static const Priority NUM_QUEUES = N;
|
||||
@@ -26,9 +26,9 @@ struct ThreadQueueList {
|
||||
}
|
||||
|
||||
// Only for debugging, returns priority level.
|
||||
Priority contains(const T& uid) {
|
||||
Priority contains(const T& uid) const {
|
||||
for (Priority i = 0; i < NUM_QUEUES; ++i) {
|
||||
Queue& cur = queues[i];
|
||||
const Queue& cur = queues[i];
|
||||
if (std::find(cur.data.cbegin(), cur.data.cend(), uid) != cur.data.cend()) {
|
||||
return i;
|
||||
}
|
||||
@@ -37,8 +37,8 @@ struct ThreadQueueList {
|
||||
return -1;
|
||||
}
|
||||
|
||||
T get_first() {
|
||||
Queue* cur = first;
|
||||
T get_first() const {
|
||||
const Queue* cur = first;
|
||||
while (cur != nullptr) {
|
||||
if (!cur->data.empty()) {
|
||||
return cur->data.front();
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_set.h"
|
||||
|
||||
namespace Common {
|
||||
namespace X64 {
|
||||
namespace Common::X64 {
|
||||
|
||||
int RegToIndex(const Xbyak::Reg& reg) {
|
||||
inline int RegToIndex(const Xbyak::Reg& reg) {
|
||||
using Kind = Xbyak::Reg::Kind;
|
||||
ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,
|
||||
"RegSet only support GPRs and XMM registers.");
|
||||
@@ -152,8 +151,8 @@ constexpr size_t ABI_SHADOW_SPACE = 0;
|
||||
|
||||
#endif
|
||||
|
||||
void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size,
|
||||
s32* out_subtraction, s32* out_xmm_offset) {
|
||||
inline void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size,
|
||||
s32* out_subtraction, s32* out_xmm_offset) {
|
||||
int count = (regs & ABI_ALL_GPRS).Count();
|
||||
rsp_alignment -= count * 8;
|
||||
size_t subtraction = 0;
|
||||
@@ -174,8 +173,8 @@ void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_f
|
||||
*out_xmm_offset = (s32)(subtraction - xmm_base_subtraction);
|
||||
}
|
||||
|
||||
size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
s32 subtraction, xmm_offset;
|
||||
ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset);
|
||||
|
||||
@@ -195,8 +194,8 @@ size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs
|
||||
return ABI_SHADOW_SPACE;
|
||||
}
|
||||
|
||||
void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, size_t rsp_alignment,
|
||||
size_t needed_frame_size = 0) {
|
||||
inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
s32 subtraction, xmm_offset;
|
||||
ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset);
|
||||
|
||||
@@ -217,5 +216,4 @@ void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, s
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include <xbyak.h>
|
||||
#include "common/x64/xbyak_abi.h"
|
||||
|
||||
namespace Common {
|
||||
namespace X64 {
|
||||
namespace Common::X64 {
|
||||
|
||||
// Constants for use with cmpps/cmpss
|
||||
enum {
|
||||
@@ -45,5 +44,4 @@ inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
||||
@@ -20,6 +20,8 @@ add_library(core STATIC
|
||||
crypto/key_manager.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
crypto/ctr_encryption_layer.h
|
||||
file_sys/bis_factory.cpp
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/content_archive.cpp
|
||||
@@ -29,10 +31,14 @@ add_library(core STATIC
|
||||
file_sys/directory.h
|
||||
file_sys/errors.h
|
||||
file_sys/mode.h
|
||||
file_sys/nca_metadata.cpp
|
||||
file_sys/nca_metadata.h
|
||||
file_sys/partition_filesystem.cpp
|
||||
file_sys/partition_filesystem.h
|
||||
file_sys/program_metadata.cpp
|
||||
file_sys/program_metadata.h
|
||||
file_sys/registered_cache.cpp
|
||||
file_sys/registered_cache.h
|
||||
file_sys/romfs.cpp
|
||||
file_sys/romfs.h
|
||||
file_sys/romfs_factory.cpp
|
||||
@@ -43,6 +49,8 @@ add_library(core STATIC
|
||||
file_sys/sdmc_factory.h
|
||||
file_sys/vfs.cpp
|
||||
file_sys/vfs.h
|
||||
file_sys/vfs_concat.cpp
|
||||
file_sys/vfs_concat.h
|
||||
file_sys/vfs_offset.cpp
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
@@ -114,6 +122,8 @@ add_library(core STATIC
|
||||
hle/service/acc/acc_u0.h
|
||||
hle/service/acc/acc_u1.cpp
|
||||
hle/service/acc/acc_u1.h
|
||||
hle/service/acc/profile_manager.cpp
|
||||
hle/service/acc/profile_manager.h
|
||||
hle/service/am/am.cpp
|
||||
hle/service/am/am.h
|
||||
hle/service/am/applet_ae.cpp
|
||||
@@ -249,6 +259,10 @@ add_library(core STATIC
|
||||
hle/service/nvdrv/devices/nvhost_gpu.h
|
||||
hle/service/nvdrv/devices/nvhost_nvdec.cpp
|
||||
hle/service/nvdrv/devices/nvhost_nvdec.h
|
||||
hle/service/nvdrv/devices/nvhost_nvjpg.cpp
|
||||
hle/service/nvdrv/devices/nvhost_nvjpg.h
|
||||
hle/service/nvdrv/devices/nvhost_vic.cpp
|
||||
hle/service/nvdrv/devices/nvhost_vic.h
|
||||
hle/service/nvdrv/devices/nvmap.cpp
|
||||
hle/service/nvdrv/devices/nvmap.h
|
||||
hle/service/nvdrv/interface.cpp
|
||||
|
||||
@@ -86,7 +86,16 @@ public:
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
CoreTiming::AddTicks(ticks - num_interpreted_instructions);
|
||||
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
|
||||
// rough approximation of the amount of executed ticks in the system, it may be thrown off
|
||||
// if not all cores are doing a similar amount of work. Instead of doing this, we should
|
||||
// device a way so that timing is consistent across all cores without increasing the ticks 4
|
||||
// times.
|
||||
u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES;
|
||||
// Always execute at least one tick.
|
||||
amortized_ticks = std::max<u64>(amortized_ticks, 1);
|
||||
|
||||
CoreTiming::AddTicks(amortized_ticks);
|
||||
num_interpreted_instructions = 0;
|
||||
}
|
||||
u64 GetTicksRemaining() override {
|
||||
@@ -125,6 +134,9 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
config.dczid_el0 = 4;
|
||||
config.ctr_el0 = 0x8444c004;
|
||||
|
||||
// Unpredictable instructions
|
||||
config.define_unpredictable_behaviour = true;
|
||||
|
||||
return std::make_unique<Dynarmic::A64::Jit>(config);
|
||||
}
|
||||
|
||||
@@ -234,9 +246,7 @@ void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) {
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::PrepareReschedule() {
|
||||
if (jit->IsExecuting()) {
|
||||
jit->HaltExecution();
|
||||
}
|
||||
jit->HaltExecution();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::ClearInstructionCache() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
@@ -17,6 +18,7 @@
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
#include "file_sys/vfs_concat.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
@@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read));
|
||||
static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
const std::string& path) {
|
||||
// To account for split 00+01+etc files.
|
||||
std::string dir_name;
|
||||
std::string filename;
|
||||
Common::SplitPath(path, &dir_name, &filename, nullptr);
|
||||
if (filename == "00") {
|
||||
const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
|
||||
std::vector<FileSys::VirtualFile> concat;
|
||||
for (u8 i = 0; i < 0x10; ++i) {
|
||||
auto next = dir->GetFile(fmt::format("{:02X}", i));
|
||||
if (next != nullptr)
|
||||
concat.push_back(std::move(next));
|
||||
else {
|
||||
next = dir->GetFile(fmt::format("{:02x}", i));
|
||||
if (next != nullptr)
|
||||
concat.push_back(std::move(next));
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (concat.empty())
|
||||
return nullptr;
|
||||
|
||||
return FileSys::ConcatenateFiles(concat, dir->GetName());
|
||||
}
|
||||
|
||||
return vfs->OpenFile(path, FileSys::Mode::Read);
|
||||
}
|
||||
|
||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
|
||||
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
@@ -102,18 +135,8 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
|
||||
static_cast<int>(system_mode.second));
|
||||
|
||||
switch (system_mode.second) {
|
||||
case Loader::ResultStatus::ErrorMissingKeys:
|
||||
return ResultStatus::ErrorLoader_ErrorMissingKeys;
|
||||
case Loader::ResultStatus::ErrorDecrypting:
|
||||
return ResultStatus::ErrorLoader_ErrorDecrypting;
|
||||
case Loader::ResultStatus::ErrorInvalidFormat:
|
||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||
case Loader::ResultStatus::ErrorUnsupportedArch:
|
||||
return ResultStatus::ErrorUnsupportedArch;
|
||||
default:
|
||||
if (system_mode.second != Loader::ResultStatus::Success)
|
||||
return ResultStatus::ErrorSystemMode;
|
||||
}
|
||||
}
|
||||
|
||||
ResultStatus init_result{Init(emu_window)};
|
||||
@@ -129,17 +152,9 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
||||
System::Shutdown();
|
||||
|
||||
switch (load_result) {
|
||||
case Loader::ResultStatus::ErrorMissingKeys:
|
||||
return ResultStatus::ErrorLoader_ErrorMissingKeys;
|
||||
case Loader::ResultStatus::ErrorDecrypting:
|
||||
return ResultStatus::ErrorLoader_ErrorDecrypting;
|
||||
case Loader::ResultStatus::ErrorInvalidFormat:
|
||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||
case Loader::ResultStatus::ErrorUnsupportedArch:
|
||||
return ResultStatus::ErrorUnsupportedArch;
|
||||
default:
|
||||
return ResultStatus::ErrorLoader;
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
}
|
||||
status = ResultStatus::Success;
|
||||
@@ -169,7 +184,7 @@ Cpu& System::CpuCore(size_t core_index) {
|
||||
return *cpu_cores[core_index];
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(EmuWindow& emu_window) {
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
CoreTiming::Init();
|
||||
|
||||
@@ -22,9 +22,12 @@
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
class EmuWindow;
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
@@ -37,6 +40,12 @@ namespace Core {
|
||||
|
||||
class System {
|
||||
public:
|
||||
System(const System&) = delete;
|
||||
System& operator=(const System&) = delete;
|
||||
|
||||
System(System&&) = delete;
|
||||
System& operator=(System&&) = delete;
|
||||
|
||||
~System();
|
||||
|
||||
/**
|
||||
@@ -49,21 +58,15 @@ public:
|
||||
|
||||
/// Enumeration representing the return values of the System Initialize and Load process.
|
||||
enum class ResultStatus : u32 {
|
||||
Success, ///< Succeeded
|
||||
ErrorNotInitialized, ///< Error trying to use core prior to initialization
|
||||
ErrorGetLoader, ///< Error finding the correct application loader
|
||||
ErrorSystemMode, ///< Error determining the system mode
|
||||
ErrorLoader, ///< Error loading the specified application
|
||||
ErrorLoader_ErrorMissingKeys, ///< Error because the key/keys needed to run could not be
|
||||
///< found.
|
||||
ErrorLoader_ErrorDecrypting, ///< Error loading the specified application due to encryption
|
||||
ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an
|
||||
/// invalid format
|
||||
ErrorSystemFiles, ///< Error in finding system files
|
||||
ErrorSharedFont, ///< Error in finding shared font
|
||||
ErrorVideoCore, ///< Error in the video core
|
||||
ErrorUnsupportedArch, ///< Unsupported Architecture (32-Bit ROMs)
|
||||
ErrorUnknown ///< Any other error
|
||||
Success, ///< Succeeded
|
||||
ErrorNotInitialized, ///< Error trying to use core prior to initialization
|
||||
ErrorGetLoader, ///< Error finding the correct application loader
|
||||
ErrorSystemMode, ///< Error determining the system mode
|
||||
ErrorSystemFiles, ///< Error in finding system files
|
||||
ErrorSharedFont, ///< Error in finding shared font
|
||||
ErrorVideoCore, ///< Error in the video core
|
||||
ErrorUnknown, ///< Any other error
|
||||
ErrorLoader, ///< The base for loader errors (too many to repeat)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -105,7 +108,7 @@ public:
|
||||
* @param filepath String path to the executable application to load on the host file system.
|
||||
* @returns ResultStatus code, indicating if the operation succeeded.
|
||||
*/
|
||||
ResultStatus Load(EmuWindow& emu_window, const std::string& filepath);
|
||||
ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath);
|
||||
|
||||
/**
|
||||
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an
|
||||
@@ -233,7 +236,7 @@ private:
|
||||
* input.
|
||||
* @return ResultStatus code, indicating if the operation succeeded.
|
||||
*/
|
||||
ResultStatus Init(EmuWindow& emu_window);
|
||||
ResultStatus Init(Frontend::EmuWindow& emu_window);
|
||||
|
||||
/// RealVfsFilesystem instance
|
||||
FileSys::VirtualFilesystem virtual_filesystem;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -90,6 +91,7 @@ void Cpu::RunLoop(bool tight_loop) {
|
||||
LOG_TRACE(Core, "Core-{} idling", core_index);
|
||||
|
||||
if (IsMainCore()) {
|
||||
// TODO(Subv): Only let CoreTiming idle if all 4 cores are idling.
|
||||
CoreTiming::Idle();
|
||||
CoreTiming::Advance();
|
||||
}
|
||||
@@ -125,6 +127,8 @@ void Cpu::Reschedule() {
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
// Lock the global kernel mutex when we manipulate the HLE state
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
scheduler->Reschedule();
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ private:
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::shared_ptr<Kernel::Scheduler> scheduler;
|
||||
|
||||
bool reschedule_pending{};
|
||||
std::atomic<bool> reschedule_pending = false;
|
||||
size_t core_index;
|
||||
};
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ static u64 event_fifo_id;
|
||||
// to the event_queue by the emu thread
|
||||
static Common::MPSCQueue<Event, false> ts_queue;
|
||||
|
||||
// the queue for unscheduling the events from other threads threadsafe
|
||||
static Common::MPSCQueue<std::pair<const EventType*, u64>, false> unschedule_queue;
|
||||
|
||||
constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
|
||||
static s64 idled_cycles;
|
||||
@@ -135,11 +138,9 @@ void ClearPendingEvents() {
|
||||
void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
|
||||
ASSERT(event_type != nullptr);
|
||||
s64 timeout = GetTicks() + cycles_into_future;
|
||||
|
||||
// If this event needs to be scheduled before the next advance(), force one early
|
||||
if (!is_global_timer_sane)
|
||||
ForceExceptionCheck(cycles_into_future);
|
||||
|
||||
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
@@ -160,6 +161,10 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
}
|
||||
}
|
||||
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) {
|
||||
unschedule_queue.Push(std::make_pair(event_type, userdata));
|
||||
}
|
||||
|
||||
void RemoveEvent(const EventType* event_type) {
|
||||
auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type; });
|
||||
@@ -196,6 +201,9 @@ void MoveEvents() {
|
||||
|
||||
void Advance() {
|
||||
MoveEvents();
|
||||
for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) {
|
||||
UnscheduleEvent(ev.first, ev.second);
|
||||
}
|
||||
|
||||
int cycles_executed = slice_length - downcount;
|
||||
global_timer += cycles_executed;
|
||||
|
||||
@@ -65,6 +65,7 @@ void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 user
|
||||
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata);
|
||||
|
||||
void UnscheduleEvent(const EventType* event_type, u64 userdata);
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);
|
||||
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const EventType* event_type);
|
||||
|
||||
@@ -10,44 +10,13 @@
|
||||
#include <string_view>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
static u8 ToHexNibble(char c1) {
|
||||
if (c1 >= 65 && c1 <= 70)
|
||||
return c1 - 55;
|
||||
if (c1 >= 97 && c1 <= 102)
|
||||
return c1 - 87;
|
||||
if (c1 >= 48 && c1 <= 57)
|
||||
return c1 - 48;
|
||||
throw std::logic_error("Invalid hex digit");
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
static std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
std::array<u8, Size> out{};
|
||||
for (size_t i = 0; i < 2 * Size; i += 2) {
|
||||
auto d1 = str[i];
|
||||
auto d2 = str[i + 1];
|
||||
out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
|
||||
if (len != 32)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<16>(str);
|
||||
}
|
||||
|
||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
|
||||
if (len != 64)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<32>(str);
|
||||
}
|
||||
|
||||
KeyManager::KeyManager() {
|
||||
// Initialize keys
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
@@ -83,20 +52,20 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
|
||||
|
||||
if (is_title_keys) {
|
||||
auto rights_id_raw = HexStringToArray<16>(out[0]);
|
||||
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
|
||||
u128 rights_id{};
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
|
||||
Key128 key = HexStringToArray<16>(out[1]);
|
||||
Key128 key = Common::HexStringToArray<16>(out[1]);
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
} else {
|
||||
std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
|
||||
if (s128_file_id.find(out[0]) != s128_file_id.end()) {
|
||||
const auto index = s128_file_id.at(out[0]);
|
||||
Key128 key = HexStringToArray<16>(out[1]);
|
||||
Key128 key = Common::HexStringToArray<16>(out[1]);
|
||||
SetKey(index.type, key, index.field1, index.field2);
|
||||
} else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
|
||||
const auto index = s256_file_id.at(out[0]);
|
||||
Key256 key = HexStringToArray<32>(out[1]);
|
||||
Key256 key = Common::HexStringToArray<32>(out[1]);
|
||||
SetKey(index.type, key, index.field1, index.field2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> {
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
|
||||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
|
||||
|
||||
class KeyManager {
|
||||
public:
|
||||
KeyManager();
|
||||
|
||||
31
src/core/file_sys/bis_factory.cpp
Normal file
31
src/core/file_sys/bis_factory.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
|
||||
const auto res = dir->GetDirectoryRelative(path);
|
||||
if (res == nullptr)
|
||||
return dir->CreateDirectoryRelative(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
BISFactory::BISFactory(VirtualDir nand_root_)
|
||||
: nand_root(std::move(nand_root_)),
|
||||
sysnand_cache(std::make_shared<RegisteredCache>(
|
||||
GetOrCreateDirectory(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_shared<RegisteredCache>(
|
||||
GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {}
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache;
|
||||
}
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
30
src/core/file_sys/bis_factory.h
Normal file
30
src/core/file_sys/bis_factory.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/loader/loader.h"
|
||||
#include "registered_cache.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the Built-In Storage
|
||||
/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
|
||||
/// registered caches.
|
||||
class BISFactory {
|
||||
public:
|
||||
explicit BISFactory(VirtualDir nand_root);
|
||||
|
||||
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
|
||||
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
|
||||
|
||||
private:
|
||||
VirtualDir nand_root;
|
||||
|
||||
std::shared_ptr<RegisteredCache> sysnand_cache;
|
||||
std::shared_ptr<RegisteredCache> usrnand_cache;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -4,22 +4,27 @@
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <core/loader/loader.h>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
|
||||
|
||||
XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
status = Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
status = Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,9 +36,6 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure",
|
||||
"logo"};
|
||||
|
||||
for (XCIPartition partition :
|
||||
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
|
||||
auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]);
|
||||
@@ -94,6 +96,10 @@ VirtualDir XCI::GetLogoPartition() const {
|
||||
return GetPartition(XCIPartition::Logo);
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
|
||||
return ncas;
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
|
||||
const auto iter =
|
||||
std::find_if(ncas.begin(), ncas.end(),
|
||||
@@ -108,19 +114,19 @@ VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const {
|
||||
std::vector<VirtualFile> XCI::GetFiles() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const {
|
||||
return std::vector<std::shared_ptr<VfsDirectory>>();
|
||||
std::vector<VirtualDir> XCI::GetSubdirectories() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string XCI::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const {
|
||||
VirtualDir XCI::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
@@ -130,15 +136,21 @@ bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
|
||||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
if (partitions[static_cast<size_t>(part)] == nullptr) {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
return Loader::ResultStatus::ErrorXCIMissingPartition;
|
||||
}
|
||||
|
||||
for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) {
|
||||
if (file->GetExtension() != "nca")
|
||||
continue;
|
||||
auto nca = std::make_shared<NCA>(file);
|
||||
if (nca->GetStatus() == Loader::ResultStatus::Success)
|
||||
if (nca->GetStatus() == Loader::ResultStatus::Success) {
|
||||
ncas.push_back(std::move(nca));
|
||||
} else {
|
||||
const u16 error_id = static_cast<u16>(nca->GetStatus());
|
||||
LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
|
||||
partition_names[static_cast<size_t>(part)], nca->GetName(), error_id,
|
||||
nca->GetStatus());
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
|
||||
@@ -68,16 +68,17 @@ public:
|
||||
VirtualDir GetUpdatePartition() const;
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
|
||||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
|
||||
VirtualFile GetNCAFileByType(NCAContentType type) const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
@@ -113,17 +113,27 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
|
||||
return out;
|
||||
}
|
||||
|
||||
boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() const {
|
||||
boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
|
||||
const auto master_key_id = GetCryptoRevision();
|
||||
|
||||
u128 rights_id{};
|
||||
memcpy(rights_id.data(), header.rights_id.data(), 16);
|
||||
if (rights_id == u128{})
|
||||
if (rights_id == u128{}) {
|
||||
status = Loader::ResultStatus::ErrorInvalidRightsID;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
|
||||
if (titlekey == Core::Crypto::Key128{})
|
||||
if (titlekey == Core::Crypto::Key128{}) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekek;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
|
||||
keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB);
|
||||
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt);
|
||||
@@ -131,7 +141,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() const {
|
||||
return titlekey;
|
||||
}
|
||||
|
||||
VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) const {
|
||||
VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) {
|
||||
if (!encrypted)
|
||||
return in;
|
||||
|
||||
@@ -143,15 +153,22 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
|
||||
{
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
if (std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
|
||||
[](char c) { return c == 0; }) == header.rights_id.end()) {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
|
||||
} else {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
if (status == Loader::ResultStatus::Success)
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == boost::none)
|
||||
return nullptr;
|
||||
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
|
||||
std::move(in), key.value(), starting_offset);
|
||||
std::vector<u8> iv(16);
|
||||
@@ -170,16 +187,31 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header))
|
||||
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header)) {
|
||||
LOG_ERROR(Loader, "File reader errored out during header read.");
|
||||
status = Loader::ResultStatus::ErrorBadNCAHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
encrypted = false;
|
||||
|
||||
if (!IsValidNCA(header)) {
|
||||
if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return;
|
||||
}
|
||||
if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return;
|
||||
}
|
||||
|
||||
NCAHeader dec_header{};
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
@@ -189,14 +221,26 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
header = dec_header;
|
||||
encrypted = true;
|
||||
} else {
|
||||
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return;
|
||||
}
|
||||
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
|
||||
status = Loader::ResultStatus::ErrorMissingKeys;
|
||||
status = Loader::ResultStatus::ErrorMissingHeaderKey;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorDecrypting;
|
||||
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
|
||||
[](char c) { return c == '\0'; }) != header.rights_id.end();
|
||||
|
||||
const std::ptrdiff_t number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
@@ -229,7 +273,12 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
status = Loader::ResultStatus::ErrorMissingKeys;
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
@@ -249,7 +298,12 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
exefs = dirs.back();
|
||||
}
|
||||
} else {
|
||||
status = Loader::ResultStatus::ErrorMissingKeys;
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ enum class NCAContentType : u8 {
|
||||
Control = 2,
|
||||
Manual = 3,
|
||||
Data = 4,
|
||||
Data_Unknown5 = 5, ///< Seems to be used on some system archives
|
||||
};
|
||||
|
||||
enum class NCASectionCryptoType : u8 {
|
||||
@@ -98,8 +99,8 @@ protected:
|
||||
private:
|
||||
u8 GetCryptoRevision() const;
|
||||
boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
|
||||
boost::optional<Core::Crypto::Key128> GetTitlekey() const;
|
||||
VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const;
|
||||
boost::optional<Core::Crypto::Key128> GetTitlekey();
|
||||
VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
|
||||
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::vector<VirtualFile> files;
|
||||
@@ -109,6 +110,7 @@ private:
|
||||
VirtualFile file;
|
||||
|
||||
NCAHeader header{};
|
||||
bool has_rights_id{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
|
||||
}
|
||||
|
||||
NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) {
|
||||
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
|
||||
file->ReadObject(raw.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ public:
|
||||
std::string GetVersionString() const;
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
std::unique_ptr<RawNACP> raw;
|
||||
};
|
||||
|
||||
|
||||
131
src/core/file_sys/nca_metadata.cpp
Normal file
131
src/core/file_sys/nca_metadata.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
bool operator>=(TitleType lhs, TitleType rhs) {
|
||||
return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs);
|
||||
}
|
||||
|
||||
bool operator<=(TitleType lhs, TitleType rhs) {
|
||||
return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs);
|
||||
}
|
||||
|
||||
CNMT::CNMT(VirtualFile file) {
|
||||
if (file->ReadObject(&header) != sizeof(CNMTHeader))
|
||||
return;
|
||||
|
||||
// If type is {Application, Update, AOC} has opt-header.
|
||||
if (header.type >= TitleType::Application && header.type <= TitleType::AOC) {
|
||||
if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) {
|
||||
LOG_WARNING(Loader, "Failed to read optional header.");
|
||||
}
|
||||
}
|
||||
|
||||
for (u16 i = 0; i < header.number_content_entries; ++i) {
|
||||
auto& next = content_records.emplace_back(ContentRecord{});
|
||||
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) +
|
||||
header.table_offset) != sizeof(ContentRecord)) {
|
||||
content_records.erase(content_records.end() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (u16 i = 0; i < header.number_meta_entries; ++i) {
|
||||
auto& next = meta_records.emplace_back(MetaRecord{});
|
||||
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) +
|
||||
header.table_offset) != sizeof(MetaRecord)) {
|
||||
meta_records.erase(meta_records.end() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
|
||||
std::vector<MetaRecord> meta_records)
|
||||
: header(std::move(header)), opt_header(std::move(opt_header)),
|
||||
content_records(std::move(content_records)), meta_records(std::move(meta_records)) {}
|
||||
|
||||
u64 CNMT::GetTitleID() const {
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
u32 CNMT::GetTitleVersion() const {
|
||||
return header.title_version;
|
||||
}
|
||||
|
||||
TitleType CNMT::GetType() const {
|
||||
return header.type;
|
||||
}
|
||||
|
||||
const std::vector<ContentRecord>& CNMT::GetContentRecords() const {
|
||||
return content_records;
|
||||
}
|
||||
|
||||
const std::vector<MetaRecord>& CNMT::GetMetaRecords() const {
|
||||
return meta_records;
|
||||
}
|
||||
|
||||
bool CNMT::UnionRecords(const CNMT& other) {
|
||||
bool change = false;
|
||||
for (const auto& rec : other.content_records) {
|
||||
const auto iter = std::find_if(content_records.begin(), content_records.end(),
|
||||
[&rec](const ContentRecord& r) {
|
||||
return r.nca_id == rec.nca_id && r.type == rec.type;
|
||||
});
|
||||
if (iter == content_records.end()) {
|
||||
content_records.emplace_back(rec);
|
||||
++header.number_content_entries;
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
for (const auto& rec : other.meta_records) {
|
||||
const auto iter =
|
||||
std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) {
|
||||
return r.title_id == rec.title_id && r.title_version == rec.title_version &&
|
||||
r.type == rec.type;
|
||||
});
|
||||
if (iter == meta_records.end()) {
|
||||
meta_records.emplace_back(rec);
|
||||
++header.number_meta_entries;
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
std::vector<u8> CNMT::Serialize() const {
|
||||
const bool has_opt_header =
|
||||
header.type >= TitleType::Application && header.type <= TitleType::AOC;
|
||||
const auto dead_zone = header.table_offset + sizeof(CNMTHeader);
|
||||
std::vector<u8> out(
|
||||
std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) +
|
||||
content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord));
|
||||
memcpy(out.data(), &header, sizeof(CNMTHeader));
|
||||
|
||||
// Optional Header
|
||||
if (has_opt_header) {
|
||||
memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
|
||||
}
|
||||
|
||||
auto offset = header.table_offset;
|
||||
|
||||
for (const auto& rec : content_records) {
|
||||
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
|
||||
offset += sizeof(ContentRecord);
|
||||
}
|
||||
|
||||
for (const auto& rec : meta_records) {
|
||||
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord));
|
||||
offset += sizeof(MetaRecord);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace FileSys
|
||||
112
src/core/file_sys/nca_metadata.h
Normal file
112
src/core/file_sys/nca_metadata.h
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
class CNMT;
|
||||
|
||||
struct CNMTHeader;
|
||||
struct OptionalHeader;
|
||||
|
||||
enum class TitleType : u8 {
|
||||
SystemProgram = 0x01,
|
||||
SystemDataArchive = 0x02,
|
||||
SystemUpdate = 0x03,
|
||||
FirmwarePackageA = 0x04,
|
||||
FirmwarePackageB = 0x05,
|
||||
Application = 0x80,
|
||||
Update = 0x81,
|
||||
AOC = 0x82,
|
||||
DeltaTitle = 0x83,
|
||||
};
|
||||
|
||||
bool operator>=(TitleType lhs, TitleType rhs);
|
||||
bool operator<=(TitleType lhs, TitleType rhs);
|
||||
|
||||
enum class ContentRecordType : u8 {
|
||||
Meta = 0,
|
||||
Program = 1,
|
||||
Data = 2,
|
||||
Control = 3,
|
||||
Manual = 4,
|
||||
Legal = 5,
|
||||
Patch = 6,
|
||||
};
|
||||
|
||||
struct ContentRecord {
|
||||
std::array<u8, 0x20> hash;
|
||||
std::array<u8, 0x10> nca_id;
|
||||
std::array<u8, 0x6> size;
|
||||
ContentRecordType type;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
};
|
||||
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
|
||||
|
||||
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
|
||||
|
||||
struct MetaRecord {
|
||||
u64_le title_id;
|
||||
u32_le title_version;
|
||||
TitleType type;
|
||||
u8 install_byte;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
};
|
||||
static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size.");
|
||||
|
||||
struct OptionalHeader {
|
||||
u64_le title_id;
|
||||
u64_le minimum_version;
|
||||
};
|
||||
static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size.");
|
||||
|
||||
struct CNMTHeader {
|
||||
u64_le title_id;
|
||||
u32_le title_version;
|
||||
TitleType type;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u16_le table_offset;
|
||||
u16_le number_content_entries;
|
||||
u16_le number_meta_entries;
|
||||
INSERT_PADDING_BYTES(12);
|
||||
};
|
||||
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
|
||||
|
||||
// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or
|
||||
// meta0.ncd. These describe which NCA's belong with which titles in the registered cache.
|
||||
class CNMT {
|
||||
public:
|
||||
explicit CNMT(VirtualFile file);
|
||||
CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
|
||||
std::vector<MetaRecord> meta_records);
|
||||
|
||||
u64 GetTitleID() const;
|
||||
u32 GetTitleVersion() const;
|
||||
TitleType GetType() const;
|
||||
|
||||
const std::vector<ContentRecord>& GetContentRecords() const;
|
||||
const std::vector<MetaRecord>& GetMetaRecords() const;
|
||||
|
||||
bool UnionRecords(const CNMT& other);
|
||||
std::vector<u8> Serialize() const;
|
||||
|
||||
private:
|
||||
CNMTHeader header;
|
||||
OptionalHeader opt_header;
|
||||
std::vector<ContentRecord> content_records;
|
||||
std::vector<MetaRecord> meta_records;
|
||||
|
||||
// TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data
|
||||
// after the table. This is not documented, unfortunately.
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -24,19 +24,19 @@ bool PartitionFilesystem::Header::HasValidMagicValue() const {
|
||||
PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
|
||||
// At least be as large as the header
|
||||
if (file->GetSize() < sizeof(Header)) {
|
||||
status = Loader::ResultStatus::Error;
|
||||
status = Loader::ResultStatus::ErrorBadPFSHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
// For cartridges, HFSs can get very large, so we need to calculate the size up to
|
||||
// the actual content itself instead of just blindly reading in the entire file.
|
||||
if (sizeof(Header) != file->ReadObject(&pfs_header)) {
|
||||
status = Loader::ResultStatus::Error;
|
||||
status = Loader::ResultStatus::ErrorBadPFSHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pfs_header.HasValidMagicValue()) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
status = Loader::ResultStatus::ErrorBadPFSHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
|
||||
const size_t total_size = file_data.size();
|
||||
|
||||
if (total_size != metadata_size) {
|
||||
status = Loader::ResultStatus::Error;
|
||||
status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -12,26 +12,26 @@ namespace FileSys {
|
||||
Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
||||
size_t total_size = static_cast<size_t>(file->GetSize());
|
||||
if (total_size < sizeof(Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
return Loader::ResultStatus::ErrorBadNPDMHeader;
|
||||
|
||||
// TODO(DarkLordZach): Use ReadObject when Header/AcidHeader becomes trivially copyable.
|
||||
std::vector<u8> npdm_header_data = file->ReadBytes(sizeof(Header));
|
||||
if (sizeof(Header) != npdm_header_data.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
return Loader::ResultStatus::ErrorBadNPDMHeader;
|
||||
std::memcpy(&npdm_header, npdm_header_data.data(), sizeof(Header));
|
||||
|
||||
std::vector<u8> acid_header_data = file->ReadBytes(sizeof(AcidHeader), npdm_header.acid_offset);
|
||||
if (sizeof(AcidHeader) != acid_header_data.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
return Loader::ResultStatus::ErrorBadACIDHeader;
|
||||
std::memcpy(&acid_header, acid_header_data.data(), sizeof(AcidHeader));
|
||||
|
||||
if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset))
|
||||
return Loader::ResultStatus::Error;
|
||||
return Loader::ResultStatus::ErrorBadACIHeader;
|
||||
|
||||
if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset))
|
||||
return Loader::ResultStatus::Error;
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
|
||||
return Loader::ResultStatus::Error;
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "partition_filesystem.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
479
src/core/file_sys/registered_cache.cpp
Normal file
479
src/core/file_sys/registered_cache.cpp
Normal file
@@ -0,0 +1,479 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <regex>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
|
||||
namespace FileSys {
|
||||
std::string RegisteredCacheEntry::DebugInfo() const {
|
||||
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
return std::regex_match(name.begin(), name.end(), two_digit_regex);
|
||||
}
|
||||
|
||||
static bool FollowsNcaIdFormat(std::string_view name) {
|
||||
static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
|
||||
std::regex_constants::icase);
|
||||
return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex);
|
||||
}
|
||||
|
||||
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
|
||||
bool within_two_digit) {
|
||||
if (!within_two_digit)
|
||||
return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper));
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
|
||||
return fmt::format("/000000{:02X}/{}.nca", hash[0],
|
||||
Common::HexArrayToString(nca_id, second_hex_upper));
|
||||
}
|
||||
|
||||
static std::string GetCNMTName(TitleType type, u64 title_id) {
|
||||
constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{
|
||||
"SystemProgram",
|
||||
"SystemData",
|
||||
"SystemUpdate",
|
||||
"BootImagePackage",
|
||||
"BootImagePackageSafe",
|
||||
"Application",
|
||||
"Patch",
|
||||
"AddOnContent",
|
||||
"" ///< Currently unknown 'DeltaTitle'
|
||||
};
|
||||
|
||||
auto index = static_cast<size_t>(type);
|
||||
// If the index is after the jump in TitleType, subtract it out.
|
||||
if (index >= static_cast<size_t>(TitleType::Application)) {
|
||||
index -= static_cast<size_t>(TitleType::Application) -
|
||||
static_cast<size_t>(TitleType::FirmwarePackageB);
|
||||
}
|
||||
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
|
||||
}
|
||||
|
||||
static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
switch (type) {
|
||||
case NCAContentType::Program:
|
||||
// TODO(DarkLordZach): Differentiate between Program and Patch
|
||||
return ContentRecordType::Program;
|
||||
case NCAContentType::Meta:
|
||||
return ContentRecordType::Meta;
|
||||
case NCAContentType::Control:
|
||||
return ContentRecordType::Control;
|
||||
case NCAContentType::Data:
|
||||
case NCAContentType::Data_Unknown5:
|
||||
return ContentRecordType::Data;
|
||||
case NCAContentType::Manual:
|
||||
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
|
||||
return ContentRecordType::Manual;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type));
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
std::string_view path) const {
|
||||
if (dir->GetFileRelative(path) != nullptr)
|
||||
return dir->GetFileRelative(path);
|
||||
if (dir->GetDirectoryRelative(path) != nullptr) {
|
||||
const auto nca_dir = dir->GetDirectoryRelative(path);
|
||||
VirtualFile file = nullptr;
|
||||
|
||||
const auto files = nca_dir->GetFiles();
|
||||
if (files.size() == 1 && files[0]->GetName() == "00") {
|
||||
file = files[0];
|
||||
} else {
|
||||
std::vector<VirtualFile> concat;
|
||||
// Since the files are a two-digit hex number, max is FF.
|
||||
for (size_t i = 0; i < 0x100; ++i) {
|
||||
auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
|
||||
if (next != nullptr) {
|
||||
concat.push_back(std::move(next));
|
||||
} else {
|
||||
next = nca_dir->GetFile(fmt::format("{:02x}", i));
|
||||
if (next != nullptr)
|
||||
concat.push_back(std::move(next));
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (concat.empty())
|
||||
return nullptr;
|
||||
|
||||
file = FileSys::ConcatenateFiles(concat);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
|
||||
VirtualFile file;
|
||||
// Try all four modes of file storage:
|
||||
// (bit 1 = uppercase/lower, bit 0 = within a two-digit dir)
|
||||
// 00: /000000**/{:032X}.nca
|
||||
// 01: /{:032X}.nca
|
||||
// 10: /000000**/{:032x}.nca
|
||||
// 11: /{:032x}.nca
|
||||
for (u8 i = 0; i < 4; ++i) {
|
||||
const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0);
|
||||
file = OpenFileOrDirectoryConcat(dir, path);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
static boost::optional<NcaID> CheckMapForContentRecord(
|
||||
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
|
||||
if (map.find(title_id) == map.end())
|
||||
return boost::none;
|
||||
|
||||
const auto& cnmt = map.at(title_id);
|
||||
|
||||
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
|
||||
[type](const ContentRecord& rec) { return rec.type == type; });
|
||||
if (iter == cnmt.GetContentRecords().end())
|
||||
return boost::none;
|
||||
|
||||
return boost::make_optional(iter->nca_id);
|
||||
}
|
||||
|
||||
boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
|
||||
ContentRecordType type) const {
|
||||
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
|
||||
return meta_id.at(title_id);
|
||||
|
||||
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
|
||||
if (res1 != boost::none)
|
||||
return res1;
|
||||
return CheckMapForContentRecord(meta, title_id, type);
|
||||
}
|
||||
|
||||
std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
|
||||
std::vector<NcaID> ids;
|
||||
for (const auto& d2_dir : dir->GetSubdirectories()) {
|
||||
if (FollowsNcaIdFormat(d2_dir->GetName())) {
|
||||
ids.push_back(Common::HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!FollowsTwoDigitDirFormat(d2_dir->GetName()))
|
||||
continue;
|
||||
|
||||
for (const auto& nca_dir : d2_dir->GetSubdirectories()) {
|
||||
if (!FollowsNcaIdFormat(nca_dir->GetName()))
|
||||
continue;
|
||||
|
||||
ids.push_back(Common::HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20)));
|
||||
}
|
||||
|
||||
for (const auto& nca_file : d2_dir->GetFiles()) {
|
||||
if (!FollowsNcaIdFormat(nca_file->GetName()))
|
||||
continue;
|
||||
|
||||
ids.push_back(
|
||||
Common::HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20)));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& d2_file : dir->GetFiles()) {
|
||||
if (FollowsNcaIdFormat(d2_file->GetName()))
|
||||
ids.push_back(Common::HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20)));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
|
||||
for (const auto& id : ids) {
|
||||
const auto file = GetFileAtID(id);
|
||||
|
||||
if (file == nullptr)
|
||||
continue;
|
||||
const auto nca = std::make_shared<NCA>(parser(file, id));
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success ||
|
||||
nca->GetType() != NCAContentType::Meta) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& file : section0->GetFiles()) {
|
||||
if (file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
meta.insert_or_assign(nca->GetTitleId(), CNMT(file));
|
||||
meta_id.insert_or_assign(nca->GetTitleId(), id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisteredCache::AccumulateYuzuMeta() {
|
||||
const auto dir = this->dir->GetSubdirectory("yuzu_meta");
|
||||
if (dir == nullptr)
|
||||
return;
|
||||
|
||||
for (const auto& file : dir->GetFiles()) {
|
||||
if (file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
CNMT cnmt(file);
|
||||
yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
|
||||
}
|
||||
}
|
||||
|
||||
void RegisteredCache::Refresh() {
|
||||
if (dir == nullptr)
|
||||
return;
|
||||
const auto ids = AccumulateFiles();
|
||||
ProcessFiles(ids);
|
||||
AccumulateYuzuMeta();
|
||||
}
|
||||
|
||||
RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
|
||||
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type) != nullptr;
|
||||
}
|
||||
|
||||
bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry) != nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
return nullptr;
|
||||
|
||||
return parser(GetFileAtID(id.get()), id.get());
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RegisteredCache::IterateAllMetadata(
|
||||
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
|
||||
std::function<bool(const CNMT&, const ContentRecord&)> filter) const {
|
||||
for (const auto& kv : meta) {
|
||||
const auto& cnmt = kv.second;
|
||||
if (filter(cnmt, EMPTY_META_CONTENT_RECORD))
|
||||
out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD));
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
|
||||
out.push_back(proc(cnmt, rec));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& kv : yuzu_meta) {
|
||||
const auto& cnmt = kv.second;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
|
||||
out.push_back(proc(cnmt, rec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
|
||||
boost::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type != boost::none && title_type.get() != c.GetType())
|
||||
return false;
|
||||
if (record_type != boost::none && record_type.get() != r.type)
|
||||
return false;
|
||||
if (title_id != boost::none && title_id.get() != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
|
||||
const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false));
|
||||
const auto iter =
|
||||
std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(),
|
||||
[&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; });
|
||||
return iter == xci->GetNCAs().end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
|
||||
const VfsCopyFunction& copy) {
|
||||
const auto& ncas = xci->GetNCAs();
|
||||
const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
|
||||
return nca->GetType() == NCAContentType::Meta;
|
||||
});
|
||||
|
||||
if (meta_iter == ncas.end()) {
|
||||
LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
|
||||
"is therefore malformed. Double check your encryption keys.");
|
||||
return InstallResult::ErrorMetaFailed;
|
||||
}
|
||||
|
||||
// Install Metadata File
|
||||
const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
|
||||
const auto meta_id = Common::HexStringToArray<16>(meta_id_raw);
|
||||
|
||||
const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id);
|
||||
if (res != InstallResult::Success)
|
||||
return res;
|
||||
|
||||
// Install all the other NCAs
|
||||
const auto section0 = (*meta_iter)->GetSubdirectories()[0];
|
||||
const auto cnmt_file = section0->GetFiles()[0];
|
||||
const CNMT cnmt(cnmt_file);
|
||||
for (const auto& record : cnmt.GetContentRecords()) {
|
||||
const auto nca = GetNCAFromXCIForID(xci, record.nca_id);
|
||||
if (nca == nullptr)
|
||||
return InstallResult::ErrorCopyFailed;
|
||||
const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
|
||||
if (res2 != InstallResult::Success)
|
||||
return res2;
|
||||
}
|
||||
|
||||
Refresh();
|
||||
return InstallResult::Success;
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
|
||||
bool overwrite_if_exists, const VfsCopyFunction& copy) {
|
||||
CNMTHeader header{
|
||||
nca->GetTitleId(), ///< Title ID
|
||||
0, ///< Ignore/Default title version
|
||||
type, ///< Type
|
||||
{}, ///< Padding
|
||||
0x10, ///< Default table offset
|
||||
1, ///< 1 Content Entry
|
||||
0, ///< No Meta Entries
|
||||
{}, ///< Padding
|
||||
};
|
||||
OptionalHeader opt_header{0, 0};
|
||||
ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}};
|
||||
const auto& data = nca->GetBaseFile()->ReadBytes(0x100000);
|
||||
mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
|
||||
memcpy(&c_rec.nca_id, &c_rec.hash, 16);
|
||||
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
|
||||
if (!RawInstallYuzuMeta(new_cnmt))
|
||||
return InstallResult::ErrorMetaFailed;
|
||||
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
|
||||
bool overwrite_if_exists,
|
||||
boost::optional<NcaID> override_id) {
|
||||
const auto in = nca->GetBaseFile();
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
|
||||
// Calculate NcaID
|
||||
// NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the
|
||||
// game is massive), we're going to cheat and only hash the first MB of the NCA.
|
||||
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
|
||||
NcaID id{};
|
||||
if (override_id == boost::none) {
|
||||
const auto& data = in->ReadBytes(0x100000);
|
||||
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
|
||||
memcpy(id.data(), hash.data(), 16);
|
||||
} else {
|
||||
id = override_id.get();
|
||||
}
|
||||
|
||||
std::string path = GetRelativePathFromNcaID(id, false, true);
|
||||
|
||||
if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
|
||||
LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
|
||||
return InstallResult::ErrorAlreadyExists;
|
||||
}
|
||||
|
||||
if (GetFileAtID(id) != nullptr) {
|
||||
LOG_WARNING(Loader, "Overwriting existing NCA...");
|
||||
VirtualDir c_dir;
|
||||
{ c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
|
||||
c_dir->DeleteFile(FileUtil::GetFilename(path));
|
||||
}
|
||||
|
||||
auto out = dir->CreateFileRelative(path);
|
||||
if (out == nullptr)
|
||||
return InstallResult::ErrorCopyFailed;
|
||||
return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
|
||||
}
|
||||
|
||||
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
// Reasoning behind this method can be found in the comment for InstallEntry, NCA overload.
|
||||
const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta");
|
||||
const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID());
|
||||
if (dir->GetFile(filename) == nullptr) {
|
||||
auto out = dir->CreateFile(filename);
|
||||
const auto buffer = cnmt.Serialize();
|
||||
out->Resize(buffer.size());
|
||||
out->WriteBytes(buffer);
|
||||
} else {
|
||||
auto out = dir->GetFile(filename);
|
||||
CNMT old_cnmt(out);
|
||||
// Returns true on change
|
||||
if (old_cnmt.UnionRecords(cnmt)) {
|
||||
out->Resize(0);
|
||||
const auto buffer = old_cnmt.Serialize();
|
||||
out->Resize(buffer.size());
|
||||
out->WriteBytes(buffer);
|
||||
}
|
||||
}
|
||||
Refresh();
|
||||
return std::find_if(yuzu_meta.begin(), yuzu_meta.end(),
|
||||
[&cnmt](const std::pair<u64, CNMT>& kv) {
|
||||
return kv.second.GetType() == cnmt.GetType() &&
|
||||
kv.second.GetTitleID() == cnmt.GetTitleID();
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
} // namespace FileSys
|
||||
124
src/core/file_sys/registered_cache.h
Normal file
124
src/core/file_sys/registered_cache.h
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
class XCI;
|
||||
class CNMT;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
|
||||
|
||||
enum class InstallResult {
|
||||
Success,
|
||||
ErrorAlreadyExists,
|
||||
ErrorCopyFailed,
|
||||
ErrorMetaFailed,
|
||||
};
|
||||
|
||||
struct RegisteredCacheEntry {
|
||||
u64 title_id;
|
||||
ContentRecordType type;
|
||||
|
||||
std::string DebugInfo() const;
|
||||
};
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
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:
|
||||
*
|
||||
* Root
|
||||
* | 000000XX <- XX is the ____ two digits of the NcaID
|
||||
* | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder)
|
||||
* | 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.)
|
||||
*/
|
||||
class RegisteredCache {
|
||||
public:
|
||||
// Parsing function defines the conversion from raw file to NCA. If there are other steps
|
||||
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
|
||||
// parsing function.
|
||||
explicit RegisteredCache(VirtualDir dir,
|
||||
RegisteredCacheParsingFunction parsing_function =
|
||||
[](const VirtualFile& file, const NcaID& id) { return file; });
|
||||
|
||||
void Refresh();
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type = boost::none,
|
||||
boost::optional<ContentRecordType> record_type = boost::none,
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
|
||||
// Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
|
||||
// is a meta NCA and all of them are accessible.
|
||||
InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
// Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
|
||||
// poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
|
||||
// dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
|
||||
// TODO(DarkLordZach): Author real meta-type NCAs and install those.
|
||||
InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
|
||||
bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void IterateAllMetadata(std::vector<T>& out,
|
||||
std::function<T(const CNMT&, const ContentRecord&)> proc,
|
||||
std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
|
||||
std::vector<NcaID> AccumulateFiles() const;
|
||||
void ProcessFiles(const std::vector<NcaID>& ids);
|
||||
void AccumulateYuzuMeta();
|
||||
boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetFileAtID(NcaID id) const;
|
||||
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
|
||||
InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
|
||||
bool overwrite_if_exists,
|
||||
boost::optional<NcaID> override_id = boost::none);
|
||||
bool RawInstallYuzuMeta(const CNMT& cnmt);
|
||||
|
||||
VirtualDir dir;
|
||||
RegisteredCacheParsingFunction parser;
|
||||
// maps tid -> NcaID of meta
|
||||
boost::container::flat_map<u64, NcaID> meta_id;
|
||||
// maps tid -> meta
|
||||
boost::container::flat_map<u64, CNMT> meta;
|
||||
// maps tid -> meta for CNMT in yuzu_meta
|
||||
boost::container::flat_map<u64, CNMT> yuzu_meta;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t
|
||||
auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
|
||||
|
||||
parent->AddFile(std::make_shared<OffsetVfsFile>(
|
||||
file, entry.first.size, entry.first.offset + data_offset, entry.second, parent));
|
||||
file, entry.first.size, entry.first.offset + data_offset, entry.second));
|
||||
|
||||
if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
|
||||
break;
|
||||
@@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s
|
||||
while (true) {
|
||||
auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
|
||||
auto current = std::make_shared<VectorVfsDirectory>(
|
||||
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second);
|
||||
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
|
||||
|
||||
if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
|
||||
ProcessFile(file, file_offset, data_offset, entry.first.child_file, current);
|
||||
@@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) {
|
||||
const u64 file_offset = header.file_meta.offset;
|
||||
const u64 dir_offset = header.directory_meta.offset + 4;
|
||||
|
||||
const auto root =
|
||||
auto root =
|
||||
std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
|
||||
file->GetContainingDirectory(), file->GetName());
|
||||
file->GetName(), file->GetContainingDirectory());
|
||||
|
||||
ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
|
||||
|
||||
|
||||
@@ -6,20 +6,57 @@
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
|
||||
// Load the RomFS from the app
|
||||
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(file)) {
|
||||
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Service_FS, "Unable to read RomFS!");
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id) {
|
||||
// TODO(DarkLordZach): Use title id.
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
return MakeResult<VirtualFile>(file);
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
switch (storage) {
|
||||
case StorageId::NandSystem: {
|
||||
const auto res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
case StorageId::NandUser: {
|
||||
const auto res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -6,17 +6,33 @@
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Loader {
|
||||
class AppLoader;
|
||||
} // namespace Loader
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
|
||||
enum class StorageId : u8 {
|
||||
None = 0,
|
||||
Host = 1,
|
||||
GameCard = 2,
|
||||
NandSystem = 3,
|
||||
NandUser = 4,
|
||||
SdCard = 5,
|
||||
};
|
||||
|
||||
/// File system interface to the RomFS archive
|
||||
class RomFSFactory {
|
||||
public:
|
||||
explicit RomFSFactory(Loader::AppLoader& app_loader);
|
||||
|
||||
ResultVal<VirtualFile> Open(u64 title_id);
|
||||
ResultVal<VirtualFile> OpenCurrentProcess();
|
||||
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
|
||||
@@ -73,7 +73,7 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
|
||||
u128 user_id, u64 save_id) const {
|
||||
u128 user_id, u64 save_id) {
|
||||
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
|
||||
// be interpreted as the title id of the current process.
|
||||
if (type == SaveDataType::SaveData && title_id == 0)
|
||||
|
||||
@@ -49,11 +49,11 @@ public:
|
||||
|
||||
ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
|
||||
|
||||
static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
|
||||
u128 user_id, u64 save_id);
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id,
|
||||
u64 save_id) const;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -74,15 +75,15 @@ VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view
|
||||
return new_file;
|
||||
}
|
||||
|
||||
VirtualFile VfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
|
||||
const auto old_path = FileUtil::SanitizePath(old_path_);
|
||||
const auto new_path = FileUtil::SanitizePath(new_path_);
|
||||
VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
|
||||
const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
|
||||
const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
|
||||
|
||||
// Again, non-default impls are highly encouraged to provide a more optimized version of this.
|
||||
auto out = CopyFile(old_path_, new_path_);
|
||||
auto out = CopyFile(sanitized_old_path, sanitized_new_path);
|
||||
if (out == nullptr)
|
||||
return nullptr;
|
||||
if (DeleteFile(old_path))
|
||||
if (DeleteFile(sanitized_old_path))
|
||||
return out;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -137,15 +138,15 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
|
||||
return new_dir;
|
||||
}
|
||||
|
||||
VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path_, std::string_view new_path_) {
|
||||
const auto old_path = FileUtil::SanitizePath(old_path_);
|
||||
const auto new_path = FileUtil::SanitizePath(new_path_);
|
||||
VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
|
||||
const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
|
||||
const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
|
||||
|
||||
// Non-default impls are highly encouraged to provide a more optimized version of this.
|
||||
auto out = CopyDirectory(old_path_, new_path_);
|
||||
auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
|
||||
if (out == nullptr)
|
||||
return nullptr;
|
||||
if (DeleteDirectory(old_path))
|
||||
if (DeleteDirectory(sanitized_old_path))
|
||||
return out;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -9,15 +9,16 @@
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "boost/optional.hpp"
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct VfsFilesystem;
|
||||
struct VfsFile;
|
||||
struct VfsDirectory;
|
||||
class VfsDirectory;
|
||||
class VfsFile;
|
||||
class VfsFilesystem;
|
||||
|
||||
enum class Mode : u32;
|
||||
|
||||
// Convenience typedefs to use Vfs* interfaces
|
||||
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
|
||||
@@ -34,8 +35,9 @@ enum class VfsEntryType {
|
||||
// A class representing an abstract filesystem. A default implementation given the root VirtualDir
|
||||
// is provided for convenience, but if the Vfs implementation has any additional state or
|
||||
// functionality, they will need to override.
|
||||
struct VfsFilesystem : NonCopyable {
|
||||
VfsFilesystem(VirtualDir root);
|
||||
class VfsFilesystem : NonCopyable {
|
||||
public:
|
||||
explicit VfsFilesystem(VirtualDir root);
|
||||
virtual ~VfsFilesystem();
|
||||
|
||||
// Gets the friendly name for the filesystem.
|
||||
@@ -81,7 +83,8 @@ protected:
|
||||
};
|
||||
|
||||
// A class representing a file in an abstract filesystem.
|
||||
struct VfsFile : NonCopyable {
|
||||
class VfsFile : NonCopyable {
|
||||
public:
|
||||
virtual ~VfsFile();
|
||||
|
||||
// Retrieves the file name.
|
||||
@@ -179,7 +182,8 @@ struct VfsFile : NonCopyable {
|
||||
};
|
||||
|
||||
// A class representing a directory in an abstract filesystem.
|
||||
struct VfsDirectory : NonCopyable {
|
||||
class VfsDirectory : NonCopyable {
|
||||
public:
|
||||
virtual ~VfsDirectory();
|
||||
|
||||
// Retrives the file located at path as if the current directory was root. Returns nullptr if
|
||||
@@ -295,7 +299,8 @@ protected:
|
||||
|
||||
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
|
||||
// if writable. This is to avoid redundant empty methods everywhere.
|
||||
struct ReadOnlyVfsDirectory : public VfsDirectory {
|
||||
class ReadOnlyVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
|
||||
|
||||
94
src/core/file_sys/vfs_concat.cpp
Normal file
94
src/core/file_sys/vfs_concat.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
|
||||
if (files.empty())
|
||||
return nullptr;
|
||||
if (files.size() == 1)
|
||||
return files[0];
|
||||
|
||||
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
}
|
||||
|
||||
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
|
||||
: name(std::move(name)) {
|
||||
size_t next_offset = 0;
|
||||
for (const auto& file : files_) {
|
||||
files[next_offset] = file;
|
||||
next_offset += file->GetSize();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ConcatenatedVfsFile::GetName() const {
|
||||
if (files.empty())
|
||||
return "";
|
||||
if (!name.empty())
|
||||
return name;
|
||||
return files.begin()->second->GetName();
|
||||
}
|
||||
|
||||
size_t ConcatenatedVfsFile::GetSize() const {
|
||||
if (files.empty())
|
||||
return 0;
|
||||
return files.rbegin()->first + files.rbegin()->second->GetSize();
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::Resize(size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
|
||||
if (files.empty())
|
||||
return nullptr;
|
||||
return files.begin()->second->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const {
|
||||
auto entry = files.end();
|
||||
for (auto iter = files.begin(); iter != files.end(); ++iter) {
|
||||
if (iter->first > offset) {
|
||||
entry = --iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the entry should be the last one. The loop above will make it end().
|
||||
if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
|
||||
--entry;
|
||||
|
||||
if (entry == files.end())
|
||||
return 0;
|
||||
|
||||
const auto remaining = entry->second->GetSize() + offset - entry->first;
|
||||
if (length > remaining) {
|
||||
return entry->second->Read(data, remaining, offset - entry->first) +
|
||||
Read(data + remaining, length - remaining, offset + remaining);
|
||||
}
|
||||
|
||||
return entry->second->Read(data, length, offset - entry->first);
|
||||
}
|
||||
|
||||
size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::Rename(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
41
src/core/file_sys/vfs_concat.h
Normal file
41
src/core/file_sys/vfs_concat.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
|
||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
|
||||
|
||||
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
|
||||
// read-only.
|
||||
class ConcatenatedVfsFile : public VfsFile {
|
||||
friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
|
||||
|
||||
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
|
||||
|
||||
public:
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
private:
|
||||
// Maps starting offset to file -- more efficient.
|
||||
boost::container::flat_map<u64, VirtualFile> files;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -15,7 +15,8 @@ namespace FileSys {
|
||||
// Similar to seeking to an offset.
|
||||
// If the file is writable, operations that would write past the end of the offset file will expand
|
||||
// the size of this wrapper.
|
||||
struct OffsetVfsFile : public VfsFile {
|
||||
class OffsetVfsFile : public VfsFile {
|
||||
public:
|
||||
OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0,
|
||||
std::string new_name = "", VirtualDir new_parent = nullptr);
|
||||
|
||||
|
||||
@@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
||||
|
||||
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path))
|
||||
return nullptr;
|
||||
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
|
||||
if (!FileUtil::Exists(path)) {
|
||||
FileUtil::CreateFullPath(path_fwd);
|
||||
if (!FileUtil::CreateEmptyFile(path))
|
||||
return nullptr;
|
||||
}
|
||||
return OpenFile(path, perms);
|
||||
}
|
||||
|
||||
@@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
|
||||
|
||||
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path))
|
||||
return nullptr;
|
||||
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
|
||||
if (!FileUtil::Exists(path)) {
|
||||
FileUtil::CreateFullPath(path_fwd);
|
||||
if (!FileUtil::CreateDir(path))
|
||||
return nullptr;
|
||||
}
|
||||
// Cannot use make_shared as RealVfsDirectory constructor is private
|
||||
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||
}
|
||||
@@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
if (!FileUtil::Exists(full_path))
|
||||
if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
|
||||
return nullptr;
|
||||
return base.OpenFile(full_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
if (!FileUtil::Exists(full_path))
|
||||
if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
|
||||
return nullptr;
|
||||
return base.OpenDirectory(full_path, perms);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
namespace FileSys {
|
||||
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
|
||||
std::vector<VirtualDir> dirs_, VirtualDir parent_,
|
||||
std::string name_)
|
||||
std::vector<VirtualDir> dirs_, std::string name_,
|
||||
VirtualDir parent_)
|
||||
: files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
|
||||
name(std::move(name_)) {}
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@ namespace FileSys {
|
||||
|
||||
// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
|
||||
// Vector data is supplied upon construction.
|
||||
struct VectorVfsDirectory : public VfsDirectory {
|
||||
class VectorVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
|
||||
std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr,
|
||||
std::string name = "");
|
||||
std::vector<VirtualDir> dirs = {}, std::string name = "",
|
||||
VirtualDir parent = nullptr);
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
|
||||
public std::enable_shared_from_this<TouchState> {
|
||||
public:
|
||||
@@ -108,3 +110,5 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||
void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
|
||||
NotifyFramebufferLayoutChanged(Layout::DefaultFrameLayout(width, height));
|
||||
}
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
/**
|
||||
* Abstraction class used to provide an interface between emulation code and the frontend
|
||||
* (e.g. SDL, QGLWidget, GLFW, etc...).
|
||||
@@ -32,9 +34,9 @@ class EmuWindow {
|
||||
public:
|
||||
/// Data structure to store emuwindow configuration
|
||||
struct WindowConfig {
|
||||
bool fullscreen;
|
||||
int res_width;
|
||||
int res_height;
|
||||
bool fullscreen = false;
|
||||
int res_width = 0;
|
||||
int res_height = 0;
|
||||
std::pair<unsigned, unsigned> min_client_area_size;
|
||||
};
|
||||
|
||||
@@ -166,3 +168,5 @@ private:
|
||||
*/
|
||||
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
unsigned int Object::next_object_id;
|
||||
std::atomic<u32> Object::next_object_id{0};
|
||||
|
||||
/// Initialize the kernel
|
||||
void Init() {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -42,8 +43,8 @@ public:
|
||||
virtual ~Object();
|
||||
|
||||
/// Returns a unique identifier for the object. For debugging purposes only.
|
||||
unsigned int GetObjectId() const {
|
||||
return object_id;
|
||||
u32 GetObjectId() const {
|
||||
return object_id.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
virtual std::string GetTypeName() const {
|
||||
@@ -61,23 +62,23 @@ public:
|
||||
bool IsWaitable() const;
|
||||
|
||||
public:
|
||||
static unsigned int next_object_id;
|
||||
static std::atomic<u32> next_object_id;
|
||||
|
||||
private:
|
||||
friend void intrusive_ptr_add_ref(Object*);
|
||||
friend void intrusive_ptr_release(Object*);
|
||||
|
||||
unsigned int ref_count = 0;
|
||||
unsigned int object_id = next_object_id++;
|
||||
std::atomic<u32> ref_count{0};
|
||||
std::atomic<u32> object_id{next_object_id++};
|
||||
};
|
||||
|
||||
// Special functions used by boost::instrusive_ptr to do automatic ref-counting
|
||||
inline void intrusive_ptr_add_ref(Object* object) {
|
||||
++object->ref_count;
|
||||
object->ref_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
inline void intrusive_ptr_release(Object* object) {
|
||||
if (--object->ref_count == 0) {
|
||||
if (object->ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
delete object;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Scheduler::~Scheduler() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Scheduler::HaveReadyThreads() {
|
||||
bool Scheduler::HaveReadyThreads() const {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
return ready_queue.get_first() != nullptr;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
bool HaveReadyThreads();
|
||||
bool HaveReadyThreads() const;
|
||||
|
||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||
void Reschedule();
|
||||
|
||||
@@ -71,6 +71,14 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
|
||||
const u32 object_id{context.GetDomainMessageHeader()->object_id};
|
||||
switch (domain_message_header->command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > domain_request_handlers.size()) {
|
||||
LOG_CRITICAL(IPC,
|
||||
"object_id {} is too big! This probably means a recent service call "
|
||||
"to {} needed to return a new interface!",
|
||||
object_id, name);
|
||||
UNREACHABLE();
|
||||
return RESULT_SUCCESS; // Ignore error if asserts are off
|
||||
}
|
||||
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
|
||||
|
||||
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
|
||||
@@ -144,7 +152,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||
// Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the
|
||||
// end of the command such that only commands following this one are handled as domains
|
||||
if (convert_to_domain) {
|
||||
ASSERT_MSG(domain_request_handlers.empty(), "already a domain");
|
||||
ASSERT_MSG(IsSession(), "ServerSession is already a domain instance.");
|
||||
domain_request_handlers = {hle_handler};
|
||||
convert_to_domain = false;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,12 @@ public:
|
||||
|
||||
/// Returns true if the session has been converted to a domain, otherwise False
|
||||
bool IsDomain() const {
|
||||
return !domain_request_handlers.empty();
|
||||
return !IsSession();
|
||||
}
|
||||
|
||||
/// Returns true if this session has not been converted to a domain, otherwise false.
|
||||
bool IsSession() const {
|
||||
return domain_request_handlers.empty();
|
||||
}
|
||||
|
||||
/// Converts the session to a domain at the end of the current command
|
||||
|
||||
@@ -250,8 +250,11 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
}
|
||||
|
||||
/// Break program execution
|
||||
static void Break(u64 unk_0, u64 unk_1, u64 unk_2) {
|
||||
LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!");
|
||||
static void Break(u64 reason, u64 info1, u64 info2) {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
@@ -532,7 +535,6 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
CASCADE_RESULT(thread->guest_handle, g_handle_table.Create(thread));
|
||||
*out_handle = thread->guest_handle;
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
@@ -706,8 +708,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
auto owner = g_handle_table.Get<Thread>(owner_handle);
|
||||
ASSERT(owner);
|
||||
ASSERT(thread->status != ThreadStatus::Running);
|
||||
thread->status = ThreadStatus::WaitMutex;
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
thread->wakeup_callback = nullptr;
|
||||
|
||||
owner->AddMutexWaiter(thread);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -104,6 +105,10 @@ void ExitCurrentThread() {
|
||||
*/
|
||||
static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
|
||||
const auto proper_handle = static_cast<Handle>(thread_handle);
|
||||
|
||||
// Lock the global kernel mutex when we enter the kernel HLE.
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>(proper_handle);
|
||||
if (thread == nullptr) {
|
||||
LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
|
||||
@@ -155,12 +160,14 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
if (nanoseconds == -1)
|
||||
return;
|
||||
|
||||
CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType,
|
||||
callback_handle);
|
||||
// This function might be called from any thread so we have to be cautious and use the
|
||||
// thread-safe version of ScheduleEvent.
|
||||
CoreTiming::ScheduleEventThreadsafe(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType,
|
||||
callback_handle);
|
||||
}
|
||||
|
||||
void Thread::CancelWakeupTimer() {
|
||||
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
|
||||
CoreTiming::UnscheduleEventThreadsafe(ThreadWakeupEventType, callback_handle);
|
||||
}
|
||||
|
||||
static boost::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
@@ -419,12 +426,33 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||
}
|
||||
|
||||
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
if (thread->lock_owner == this) {
|
||||
// If the thread is already waiting for this thread to release the mutex, ensure that the
|
||||
// waiters list is consistent and return without doing anything.
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
return;
|
||||
}
|
||||
|
||||
// A thread can't wait on two different mutexes at the same time.
|
||||
ASSERT(thread->lock_owner == nullptr);
|
||||
|
||||
// Ensure that the thread is not already in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr == wait_mutex_threads.end());
|
||||
|
||||
thread->lock_owner = this;
|
||||
wait_mutex_threads.emplace_back(std::move(thread));
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == this);
|
||||
|
||||
// Ensure that the thread is in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
|
||||
boost::remove_erase(wait_mutex_threads, thread);
|
||||
thread->lock_owner = nullptr;
|
||||
UpdatePriority();
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
#include "core/hle/service/acc/acc_aa.h"
|
||||
#include "core/hle/service/acc/acc_su.h"
|
||||
#include "core/hle/service/acc/acc_u0.h"
|
||||
#include "core/hle/service/acc/acc_u1.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
// TODO: RE this structure
|
||||
struct UserData {
|
||||
INSERT_PADDING_WORDS(1);
|
||||
@@ -25,19 +27,10 @@ struct UserData {
|
||||
};
|
||||
static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
|
||||
|
||||
struct ProfileBase {
|
||||
u128 user_id;
|
||||
u64 timestamp;
|
||||
std::array<u8, 0x20> username;
|
||||
};
|
||||
static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size");
|
||||
|
||||
// TODO(ogniK): Generate a real user id based on username, md5(username) maybe?
|
||||
static constexpr u128 DEFAULT_USER_ID{1ull, 0ull};
|
||||
|
||||
class IProfile final : public ServiceFramework<IProfile> {
|
||||
public:
|
||||
explicit IProfile(u128 user_id) : ServiceFramework("IProfile"), user_id(user_id) {
|
||||
explicit IProfile(UUID user_id, ProfileManager& profile_manager)
|
||||
: ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IProfile::Get, "Get"},
|
||||
{1, &IProfile::GetBase, "GetBase"},
|
||||
@@ -49,46 +42,42 @@ public:
|
||||
|
||||
private:
|
||||
void Get(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
|
||||
ProfileBase profile_base{};
|
||||
profile_base.user_id = user_id;
|
||||
if (Settings::values.username.size() > profile_base.username.size()) {
|
||||
std::copy_n(Settings::values.username.begin(), profile_base.username.size(),
|
||||
profile_base.username.begin());
|
||||
std::array<u8, MAX_DATA> data{};
|
||||
if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
|
||||
ctx.WriteBuffer(data);
|
||||
IPC::ResponseBuilder rb{ctx, 16};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(profile_base);
|
||||
} else {
|
||||
std::copy(Settings::values.username.begin(), Settings::values.username.end(),
|
||||
profile_base.username.begin());
|
||||
LOG_ERROR(Service_ACC, "Failed to get profile base and data for user={}",
|
||||
user_id.Format());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 16};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(profile_base);
|
||||
}
|
||||
|
||||
void GetBase(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
|
||||
// TODO(Subv): Retrieve this information from somewhere.
|
||||
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
|
||||
ProfileBase profile_base{};
|
||||
profile_base.user_id = user_id;
|
||||
if (Settings::values.username.size() > profile_base.username.size()) {
|
||||
std::copy_n(Settings::values.username.begin(), profile_base.username.size(),
|
||||
profile_base.username.begin());
|
||||
if (profile_manager.GetProfileBase(user_id, profile_base)) {
|
||||
IPC::ResponseBuilder rb{ctx, 16};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(profile_base);
|
||||
} else {
|
||||
std::copy(Settings::values.username.begin(), Settings::values.username.end(),
|
||||
profile_base.username.begin());
|
||||
LOG_ERROR(Service_ACC, "Failed to get profile base for user={}", user_id.Format());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code
|
||||
}
|
||||
IPC::ResponseBuilder rb{ctx, 16};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(profile_base);
|
||||
}
|
||||
|
||||
void LoadImage(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) 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
|
||||
const u32 jpeg_size = 107;
|
||||
static const std::array<u8, jpeg_size> jpeg{
|
||||
constexpr u32 jpeg_size = 107;
|
||||
static constexpr std::array<u8, jpeg_size> 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,
|
||||
@@ -98,13 +87,14 @@ 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.data(), jpeg_size);
|
||||
ctx.WriteBuffer(jpeg);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(jpeg_size);
|
||||
}
|
||||
|
||||
u128 user_id; ///< The user id this profile refers to.
|
||||
const ProfileManager& profile_manager;
|
||||
UUID user_id; ///< The user id this profile refers to.
|
||||
};
|
||||
|
||||
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
|
||||
@@ -141,44 +131,57 @@ private:
|
||||
};
|
||||
|
||||
void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
LOG_INFO(Service_ACC, "called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(1);
|
||||
rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount()));
|
||||
}
|
||||
|
||||
void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
UUID user_id = rp.PopRaw<UUID>();
|
||||
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(true); // TODO: Check when this is supposed to return true and when not
|
||||
rb.Push(profile_manager->UserExists(user_id));
|
||||
}
|
||||
|
||||
void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
// TODO(Subv): There is only one user for now.
|
||||
const std::vector<u128> user_ids = {DEFAULT_USER_ID};
|
||||
ctx.WriteBuffer(user_ids);
|
||||
LOG_INFO(Service_ACC, "called");
|
||||
ctx.WriteBuffer(profile_manager->GetAllUsers());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
// TODO(Subv): There is only one user for now.
|
||||
const std::vector<u128> user_ids = {DEFAULT_USER_ID};
|
||||
ctx.WriteBuffer(user_ids);
|
||||
LOG_INFO(Service_ACC, "called");
|
||||
ctx.WriteBuffer(profile_manager->GetOpenUsers());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_ACC, "called");
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<UUID>(profile_manager->GetLastOpenedUser());
|
||||
}
|
||||
|
||||
void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u128 user_id = rp.PopRaw<u128>();
|
||||
UUID user_id = rp.PopRaw<UUID>();
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IProfile>(user_id);
|
||||
LOG_DEBUG(Service_ACC, "called user_id=0x{:016X}{:016X}", user_id[1], user_id[0]);
|
||||
rb.PushIpcInterface<IProfile>(user_id, *profile_manager);
|
||||
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
|
||||
}
|
||||
|
||||
void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(profile_manager->CanSystemRegisterUser());
|
||||
}
|
||||
|
||||
void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
|
||||
@@ -194,22 +197,20 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
}
|
||||
|
||||
void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(DEFAULT_USER_ID);
|
||||
}
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager, const char* name)
|
||||
: ServiceFramework(name), module(std::move(module)),
|
||||
profile_manager(std::move(profile_manager)) {}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
|
||||
: ServiceFramework(name), module(std::move(module)) {}
|
||||
Module::Interface::~Interface() = default;
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
auto module = std::make_shared<Module>();
|
||||
std::make_shared<ACC_AA>(module)->InstallAsService(service_manager);
|
||||
std::make_shared<ACC_SU>(module)->InstallAsService(service_manager);
|
||||
std::make_shared<ACC_U0>(module)->InstallAsService(service_manager);
|
||||
std::make_shared<ACC_U1>(module)->InstallAsService(service_manager);
|
||||
auto profile_manager = std::make_shared<ProfileManager>();
|
||||
std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager);
|
||||
std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager);
|
||||
std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager);
|
||||
std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
class ProfileManager;
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
class Interface : public ServiceFramework<Interface> {
|
||||
public:
|
||||
explicit Interface(std::shared_ptr<Module> module, const char* name);
|
||||
explicit Interface(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager, const char* name);
|
||||
~Interface() override;
|
||||
|
||||
void GetUserCount(Kernel::HLERequestContext& ctx);
|
||||
void GetUserExistence(Kernel::HLERequestContext& ctx);
|
||||
@@ -22,9 +26,11 @@ public:
|
||||
void GetProfile(Kernel::HLERequestContext& ctx);
|
||||
void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
|
||||
void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
|
||||
void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Module> module;
|
||||
std::shared_ptr<ProfileManager> profile_manager;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") {
|
||||
ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
|
||||
: Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "EnsureCacheAsync"},
|
||||
{1, nullptr, "LoadCache"},
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Service::Account {
|
||||
|
||||
class ACC_AA final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_AA(std::shared_ptr<Module> module);
|
||||
explicit ACC_AA(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") {
|
||||
ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
|
||||
: Module::Interface(std::move(module), std::move(profile_manager), "acc:su") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_SU::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_SU::GetUserExistence, "GetUserExistence"},
|
||||
@@ -15,7 +16,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
|
||||
{4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_SU::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, nullptr, "IsUserRegistrationRequestPermitted"},
|
||||
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{60, nullptr, "ListOpenContextStoredUsers"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace Account {
|
||||
|
||||
class ACC_SU final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_SU(std::shared_ptr<Module> module);
|
||||
explicit ACC_SU(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
};
|
||||
|
||||
} // namespace Account
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") {
|
||||
ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
|
||||
: Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_U0::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_U0::GetUserExistence, "GetUserExistence"},
|
||||
@@ -15,7 +16,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
|
||||
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_U0::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, nullptr, "IsUserRegistrationRequestPermitted"},
|
||||
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{60, nullptr, "ListOpenContextStoredUsers"},
|
||||
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Service::Account {
|
||||
|
||||
class ACC_U0 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U0(std::shared_ptr<Module> module);
|
||||
explicit ACC_U0(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") {
|
||||
ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
|
||||
: Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_U1::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_U1::GetUserExistence, "GetUserExistence"},
|
||||
@@ -15,7 +16,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
|
||||
{4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_U1::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, nullptr, "IsUserRegistrationRequestPermitted"},
|
||||
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{60, nullptr, "ListOpenContextStoredUsers"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Service::Account {
|
||||
|
||||
class ACC_U1 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U1(std::shared_ptr<Module> module);
|
||||
explicit ACC_U1(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
236
src/core/hle/service/acc/profile_manager.cpp
Normal file
236
src/core/hle/service/acc/profile_manager.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <random>
|
||||
#include <boost/optional.hpp>
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::Account {
|
||||
// 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() {
|
||||
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;
|
||||
}
|
||||
|
||||
ProfileManager::ProfileManager() {
|
||||
// TODO(ogniK): Create the default user we have for now until loading/saving users is added
|
||||
auto user_uuid = UUID{1, 0};
|
||||
CreateNewUser(user_uuid, Settings::values.username);
|
||||
OpenUser(user_uuid);
|
||||
}
|
||||
|
||||
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
|
||||
/// internal management of the users profiles
|
||||
boost::optional<size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) {
|
||||
if (user_count >= MAX_USERS) {
|
||||
return boost::none;
|
||||
}
|
||||
profiles[user_count] = user;
|
||||
return user_count++;
|
||||
}
|
||||
|
||||
/// Deletes a specific profile based on it's profile index
|
||||
bool ProfileManager::RemoveProfileAtIndex(size_t index) {
|
||||
if (index >= MAX_USERS || index >= user_count) {
|
||||
return false;
|
||||
}
|
||||
if (index < user_count - 1) {
|
||||
std::rotate(profiles.begin() + index, profiles.begin() + index + 1, profiles.end());
|
||||
}
|
||||
profiles.back() = {};
|
||||
user_count--;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Helper function to register a user to the system
|
||||
ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
|
||||
if (AddToProfiles(user) == boost::none) {
|
||||
return ERROR_TOO_MANY_USERS;
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Create a new user on the system. If the uuid of the user already exists, the user is not
|
||||
/// created.
|
||||
ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username) {
|
||||
if (user_count == MAX_USERS) {
|
||||
return ERROR_TOO_MANY_USERS;
|
||||
}
|
||||
if (!uuid) {
|
||||
return ERROR_ARGUMENT_IS_NULL;
|
||||
}
|
||||
if (username[0] == 0x0) {
|
||||
return ERROR_ARGUMENT_IS_NULL;
|
||||
}
|
||||
if (std::any_of(profiles.begin(), profiles.end(),
|
||||
[&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) {
|
||||
return ERROR_USER_ALREADY_EXISTS;
|
||||
}
|
||||
ProfileInfo profile;
|
||||
profile.user_uuid = uuid;
|
||||
profile.username = username;
|
||||
profile.data = {};
|
||||
profile.creation_time = 0x0;
|
||||
profile.is_open = false;
|
||||
return AddUser(profile);
|
||||
}
|
||||
|
||||
/// Creates a new user on the system. This function allows a much simpler method of registration
|
||||
/// specifically by allowing an std::string for the username. This is required specifically since
|
||||
/// we're loading a string straight from the config
|
||||
ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) {
|
||||
ProfileUsername username_output;
|
||||
if (username.size() > username_output.size()) {
|
||||
std::copy_n(username.begin(), username_output.size(), username_output.begin());
|
||||
} else {
|
||||
std::copy(username.begin(), username.end(), username_output.begin());
|
||||
}
|
||||
return CreateNewUser(uuid, username_output);
|
||||
}
|
||||
|
||||
/// Returns a users profile index based on their user id.
|
||||
boost::optional<size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
|
||||
if (!uuid) {
|
||||
return boost::none;
|
||||
}
|
||||
auto iter = std::find_if(profiles.begin(), profiles.end(),
|
||||
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
|
||||
if (iter == profiles.end()) {
|
||||
return boost::none;
|
||||
}
|
||||
return static_cast<size_t>(std::distance(profiles.begin(), iter));
|
||||
}
|
||||
|
||||
/// Returns a users profile index based on their profile
|
||||
boost::optional<size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
|
||||
return GetUserIndex(user.user_uuid);
|
||||
}
|
||||
|
||||
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
|
||||
bool ProfileManager::GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const {
|
||||
if (index == boost::none || index >= MAX_USERS) {
|
||||
return false;
|
||||
}
|
||||
const auto& prof_info = profiles[index.get()];
|
||||
profile.user_uuid = prof_info.user_uuid;
|
||||
profile.username = prof_info.username;
|
||||
profile.timestamp = prof_info.creation_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
|
||||
bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
return GetProfileBase(idx, profile);
|
||||
}
|
||||
|
||||
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
|
||||
bool ProfileManager::GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const {
|
||||
return GetProfileBase(user.user_uuid, profile);
|
||||
}
|
||||
|
||||
/// Returns the current user count on the system. We keep a variable which tracks the count so we
|
||||
/// don't have to loop the internal profile array every call.
|
||||
size_t ProfileManager::GetUserCount() const {
|
||||
return user_count;
|
||||
}
|
||||
|
||||
/// Lists the current "opened" users on the system. Users are typically not open until they sign
|
||||
/// into something or pick a profile. As of right now users should all be open until qlaunch is
|
||||
/// booting
|
||||
size_t ProfileManager::GetOpenUserCount() const {
|
||||
return std::count_if(profiles.begin(), profiles.end(),
|
||||
[](const ProfileInfo& p) { return p.is_open; });
|
||||
}
|
||||
|
||||
/// Checks if a user id exists in our profile manager
|
||||
bool ProfileManager::UserExists(UUID uuid) const {
|
||||
return (GetUserIndex(uuid) != boost::none);
|
||||
}
|
||||
|
||||
/// Opens a specific user
|
||||
void ProfileManager::OpenUser(UUID uuid) {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
if (idx == boost::none) {
|
||||
return;
|
||||
}
|
||||
profiles[idx.get()].is_open = true;
|
||||
last_opened_user = uuid;
|
||||
}
|
||||
|
||||
/// Closes a specific user
|
||||
void ProfileManager::CloseUser(UUID uuid) {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
if (idx == boost::none) {
|
||||
return;
|
||||
}
|
||||
profiles[idx.get()].is_open = false;
|
||||
}
|
||||
|
||||
/// Gets all valid user ids on the system
|
||||
UserIDArray ProfileManager::GetAllUsers() const {
|
||||
UserIDArray output;
|
||||
std::transform(profiles.begin(), profiles.end(), output.begin(),
|
||||
[](const ProfileInfo& p) { return p.user_uuid; });
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Get all the open users on the system and zero out the rest of the data. This is specifically
|
||||
/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out
|
||||
UserIDArray ProfileManager::GetOpenUsers() const {
|
||||
UserIDArray output;
|
||||
std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) {
|
||||
if (p.is_open)
|
||||
return p.user_uuid;
|
||||
return UUID{};
|
||||
});
|
||||
std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; });
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Returns the last user which was opened
|
||||
UUID ProfileManager::GetLastOpenedUser() const {
|
||||
return last_opened_user;
|
||||
}
|
||||
|
||||
/// Return the users profile base and the unknown arbitary data.
|
||||
bool ProfileManager::GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile,
|
||||
ProfileData& data) const {
|
||||
if (GetProfileBase(index, profile)) {
|
||||
data = profiles[index.get()].data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Return the users profile base and the unknown arbitary data.
|
||||
bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
|
||||
ProfileData& data) const {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
return GetProfileBaseAndData(idx, profile, data);
|
||||
}
|
||||
|
||||
/// Return the users profile base and the unknown arbitary data.
|
||||
bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
|
||||
ProfileData& data) const {
|
||||
return GetProfileBaseAndData(user.user_uuid, profile, data);
|
||||
}
|
||||
|
||||
/// Returns if the system is allowing user registrations or not
|
||||
bool ProfileManager::CanSystemRegisterUser() const {
|
||||
return false; // TODO(ogniK): Games shouldn't have
|
||||
// access to user registration, when we
|
||||
// emulate qlaunch. Update this to dynamically change.
|
||||
}
|
||||
|
||||
}; // namespace Service::Account
|
||||
117
src/core/hle/service/acc/profile_manager.h
Normal file
117
src/core/hle/service/acc/profile_manager.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "boost/optional.hpp"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::Account {
|
||||
constexpr size_t MAX_USERS = 8;
|
||||
constexpr size_t MAX_DATA = 128;
|
||||
constexpr u128 INVALID_UUID{{0, 0}};
|
||||
|
||||
struct UUID {
|
||||
// UUIDs which are 0 are considered invalid!
|
||||
u128 uuid = INVALID_UUID;
|
||||
UUID() = default;
|
||||
explicit UUID(const u128& id) : uuid{id} {}
|
||||
explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
|
||||
|
||||
explicit operator bool() const {
|
||||
return uuid != INVALID_UUID;
|
||||
}
|
||||
|
||||
bool operator==(const UUID& rhs) const {
|
||||
return uuid == rhs.uuid;
|
||||
}
|
||||
|
||||
bool operator!=(const UUID& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
// TODO(ogniK): Properly generate uuids based on RFC-4122
|
||||
const UUID& Generate();
|
||||
|
||||
// Set the UUID to {0,0} to be considered an invalid user
|
||||
void Invalidate() {
|
||||
uuid = INVALID_UUID;
|
||||
}
|
||||
std::string Format() const {
|
||||
return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
|
||||
|
||||
using ProfileUsername = std::array<u8, 0x20>;
|
||||
using ProfileData = std::array<u8, MAX_DATA>;
|
||||
using UserIDArray = std::array<UUID, MAX_USERS>;
|
||||
|
||||
/// This holds general information about a users profile. This is where we store all the information
|
||||
/// based on a specific user
|
||||
struct ProfileInfo {
|
||||
UUID user_uuid;
|
||||
ProfileUsername username;
|
||||
u64 creation_time;
|
||||
ProfileData data; // TODO(ognik): Work out what this is
|
||||
bool is_open;
|
||||
};
|
||||
|
||||
struct ProfileBase {
|
||||
UUID user_uuid;
|
||||
u64_le timestamp;
|
||||
ProfileUsername username;
|
||||
|
||||
// Zero out all the fields to make the profile slot considered "Empty"
|
||||
void Invalidate() {
|
||||
user_uuid.Invalidate();
|
||||
timestamp = 0;
|
||||
username.fill(0);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size");
|
||||
|
||||
/// The profile manager is used for handling multiple user profiles at once. It keeps track of open
|
||||
/// users, all the accounts registered on the "system" as well as fetching individual "ProfileInfo"
|
||||
/// objects
|
||||
class ProfileManager {
|
||||
public:
|
||||
ProfileManager(); // TODO(ogniK): Load from system save
|
||||
ResultCode AddUser(const ProfileInfo& user);
|
||||
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
|
||||
ResultCode CreateNewUser(UUID uuid, const std::string& username);
|
||||
boost::optional<size_t> GetUserIndex(const UUID& uuid) const;
|
||||
boost::optional<size_t> GetUserIndex(const ProfileInfo& user) const;
|
||||
bool GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const;
|
||||
bool GetProfileBase(UUID uuid, ProfileBase& profile) const;
|
||||
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
|
||||
bool GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile,
|
||||
ProfileData& data) const;
|
||||
bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const;
|
||||
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
|
||||
ProfileData& data) const;
|
||||
size_t GetUserCount() const;
|
||||
size_t GetOpenUserCount() const;
|
||||
bool UserExists(UUID uuid) const;
|
||||
void OpenUser(UUID uuid);
|
||||
void CloseUser(UUID uuid);
|
||||
UserIDArray GetOpenUsers() const;
|
||||
UserIDArray GetAllUsers() const;
|
||||
UUID GetLastOpenedUser() const;
|
||||
|
||||
bool CanSystemRegisterUser() const;
|
||||
|
||||
private:
|
||||
std::array<ProfileInfo, MAX_USERS> profiles{};
|
||||
size_t user_count = 0;
|
||||
boost::optional<size_t> AddToProfiles(const ProfileInfo& profile);
|
||||
bool RemoveProfileAtIndex(size_t index);
|
||||
UUID last_opened_user{INVALID_UUID};
|
||||
};
|
||||
|
||||
}; // namespace Service::Account
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <stack>
|
||||
#include "core/core.h"
|
||||
@@ -145,8 +146,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
|
||||
{51, nullptr, "ApproveToDisplay"},
|
||||
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
|
||||
{61, nullptr, "SetMediaPlaybackState"},
|
||||
{62, nullptr, "SetIdleTimeDetectionExtension"},
|
||||
{63, nullptr, "GetIdleTimeDetectionExtension"},
|
||||
{62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
|
||||
{63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
|
||||
{64, nullptr, "SetInputDetectionSourceSet"},
|
||||
{65, nullptr, "ReportUserIsActive"},
|
||||
{66, nullptr, "GetCurrentIlluminance"},
|
||||
@@ -281,6 +282,23 @@ void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx)
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ISelfController::SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
idle_time_detection_extension = rp.Pop<u32>();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(idle_time_detection_extension);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
|
||||
@@ -306,7 +324,8 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
|
||||
{52, nullptr, "SwitchLcdBacklight"},
|
||||
{55, nullptr, "IsInControllerFirmwareUpdateSection"},
|
||||
{60, nullptr, "GetDefaultDisplayResolution"},
|
||||
{61, nullptr, "GetDefaultDisplayResolutionChangeEvent"},
|
||||
{61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent,
|
||||
"GetDefaultDisplayResolutionChangeEvent"},
|
||||
{62, nullptr, "GetHdcpAuthenticationState"},
|
||||
{63, nullptr, "GetHdcpAuthenticationStateChangeEvent"},
|
||||
};
|
||||
@@ -341,6 +360,16 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx) {
|
||||
event->Signal();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(event);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
|
||||
const bool use_docked_mode{Settings::values.use_docked_mode};
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
@@ -597,16 +626,16 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
|
||||
}
|
||||
|
||||
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
|
||||
constexpr u8 data[0x88] = {
|
||||
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)
|
||||
};
|
||||
}};
|
||||
|
||||
std::vector<u8> buffer(data, data + sizeof(data));
|
||||
std::vector<u8> buffer(data.begin(), data.end());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
|
||||
@@ -87,9 +87,12 @@ private:
|
||||
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
|
||||
void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
|
||||
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
|
||||
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
|
||||
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
Kernel::SharedPtr<Kernel::Event> launchable_event;
|
||||
u32 idle_time_detection_extension = 0;
|
||||
};
|
||||
|
||||
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
|
||||
@@ -110,6 +113,7 @@ private:
|
||||
void GetEventHandle(Kernel::HLERequestContext& ctx);
|
||||
void ReceiveMessage(Kernel::HLERequestContext& ctx);
|
||||
void GetCurrentFocusState(Kernel::HLERequestContext& ctx);
|
||||
void GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx);
|
||||
void GetOperationMode(Kernel::HLERequestContext& ctx);
|
||||
void GetPerformanceMode(Kernel::HLERequestContext& ctx);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ constexpr int DefaultSampleRate{48000};
|
||||
class IAudioOut final : public ServiceFramework<IAudioOut> {
|
||||
public:
|
||||
IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core)
|
||||
: ServiceFramework("IAudioOut"), audio_params(audio_params), audio_core(audio_core) {
|
||||
: ServiceFramework("IAudioOut"), audio_core(audio_core), audio_params(audio_params) {
|
||||
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
|
||||
|
||||
@@ -20,9 +20,9 @@ public:
|
||||
explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params)
|
||||
: ServiceFramework("IAudioRenderer") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetAudioRendererSampleRate"},
|
||||
{1, nullptr, "GetAudioRendererSampleCount"},
|
||||
{2, nullptr, "GetAudioRendererMixBufferCount"},
|
||||
{0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"},
|
||||
{1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"},
|
||||
{2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"},
|
||||
{3, nullptr, "GetAudioRendererState"},
|
||||
{4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
|
||||
{5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
|
||||
@@ -45,6 +45,27 @@ private:
|
||||
system_event->Signal();
|
||||
}
|
||||
|
||||
void GetAudioRendererSampleRate(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(renderer->GetSampleRate());
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
}
|
||||
|
||||
void GetAudioRendererSampleCount(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(renderer->GetSampleCount());
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
}
|
||||
|
||||
void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(renderer->GetMixBufferCount());
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
}
|
||||
|
||||
void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer()));
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -169,7 +190,8 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") {
|
||||
{1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
|
||||
{2, &AudRenU::GetAudioDevice, "GetAudioDevice"},
|
||||
{3, nullptr, "OpenAudioRendererAuto"},
|
||||
{4, nullptr, "GetAudioDeviceServiceWithRevisionInfo"},
|
||||
{4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo,
|
||||
"GetAudioDeviceServiceWithRevisionInfo"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
@@ -189,7 +211,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
|
||||
|
||||
u64 buffer_sz = Common::AlignUp(4 * params.unknown_8, 0x40);
|
||||
u64 buffer_sz = Common::AlignUp(4 * params.mix_buffer_count, 0x40);
|
||||
buffer_sz += params.unknown_c * 1024;
|
||||
buffer_sz += 0x940 * (params.unknown_c + 1);
|
||||
buffer_sz += 0x3F0 * params.voice_count;
|
||||
@@ -197,7 +219,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10);
|
||||
buffer_sz +=
|
||||
Common::AlignUp((0x3C0 * (params.sink_count + params.unknown_c) + 4 * params.sample_count) *
|
||||
(params.unknown_8 + 6),
|
||||
(params.mix_buffer_count + 6),
|
||||
0x40);
|
||||
|
||||
if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
|
||||
@@ -253,6 +275,16 @@ void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
}
|
||||
|
||||
void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<Audio::IAudioDevice>();
|
||||
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called"); // TODO(ogniK): Figure out what is different
|
||||
// based on the current revision
|
||||
}
|
||||
|
||||
bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const {
|
||||
u32_be version_num = (revision - Common::MakeMagic('R', 'E', 'V', '0')); // Byte swap
|
||||
switch (feature) {
|
||||
|
||||
@@ -22,6 +22,7 @@ private:
|
||||
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioDevice(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx);
|
||||
|
||||
enum class AudioFeatures : u32 {
|
||||
Splitter,
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/fsp_ldr.h"
|
||||
#include "core/hle/service/filesystem/fsp_pr.h"
|
||||
@@ -226,6 +228,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
|
||||
static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
|
||||
static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
|
||||
static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
|
||||
static std::unique_ptr<FileSys::BISFactory> bis_factory;
|
||||
|
||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
|
||||
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
|
||||
@@ -248,15 +251,35 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
|
||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
|
||||
ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
|
||||
bis_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registred BIS");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for current process");
|
||||
|
||||
if (romfs_factory == nullptr) {
|
||||
// TODO(bunnei): Find a better error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return romfs_factory->Open(title_id);
|
||||
return romfs_factory->OpenCurrentProcess();
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
|
||||
FileSys::ContentRecordType type) {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
|
||||
title_id, static_cast<u8>(storage_id), static_cast<u8>(type));
|
||||
|
||||
if (romfs_factory == nullptr) {
|
||||
// TODO(bunnei): Find a better error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return romfs_factory->Open(title_id, storage_id, type);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
@@ -281,6 +304,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
|
||||
return sdmc_factory->Open();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
|
||||
return bis_factory->GetSystemNANDContents();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
|
||||
return bis_factory->GetUserNANDContents();
|
||||
}
|
||||
|
||||
void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
|
||||
romfs_factory = nullptr;
|
||||
save_data_factory = nullptr;
|
||||
@@ -291,6 +322,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
|
||||
auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
|
||||
|
||||
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
|
||||
save_data_factory = std::move(savedata);
|
||||
|
||||
|
||||
@@ -7,12 +7,23 @@
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
class BISFactory;
|
||||
class RegisteredCache;
|
||||
class RomFSFactory;
|
||||
class SaveDataFactory;
|
||||
class SDMCFactory;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
enum class Mode : u32;
|
||||
enum class SaveDataSpaceId : u8;
|
||||
enum class StorageId : u8;
|
||||
|
||||
struct SaveDataDescriptor;
|
||||
} // namespace FileSys
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace SM {
|
||||
@@ -24,16 +35,17 @@ namespace FileSystem {
|
||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
|
||||
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
|
||||
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
|
||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
|
||||
|
||||
// TODO(DarkLordZach): BIS Filesystem
|
||||
// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
|
||||
FileSys::ContentRecordType type);
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
FileSys::SaveDataDescriptor save_struct);
|
||||
ResultVal<FileSys::VirtualDir> OpenSDMC();
|
||||
|
||||
// TODO(DarkLordZach): BIS Filesystem
|
||||
// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
|
||||
|
||||
/// Registers all Filesystem services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
@@ -23,15 +26,6 @@
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
enum class StorageId : u8 {
|
||||
None = 0,
|
||||
Host = 1,
|
||||
GameCard = 2,
|
||||
NandSystem = 3,
|
||||
NandUser = 4,
|
||||
SdCard = 5,
|
||||
};
|
||||
|
||||
class IStorage final : public ServiceFramework<IStorage> {
|
||||
public:
|
||||
explicit IStorage(FileSys::VirtualFile backend_)
|
||||
@@ -467,7 +461,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
{110, nullptr, "OpenContentStorageFileSystem"},
|
||||
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
|
||||
{201, nullptr, "OpenDataStorageByProgramId"},
|
||||
{202, nullptr, "OpenDataStorageByDataId"},
|
||||
{202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"},
|
||||
{203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"},
|
||||
{400, nullptr, "OpenDeviceOperator"},
|
||||
{500, nullptr, "OpenSdCardDetectionEventNotifier"},
|
||||
@@ -580,7 +574,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
auto romfs = OpenRomFS(Core::System::GetInstance().CurrentProcess()->program_id);
|
||||
auto romfs = OpenRomFSCurrentProcess();
|
||||
if (romfs.Failed()) {
|
||||
// TODO (bunnei): Find the right error code to use here
|
||||
LOG_CRITICAL(Service_FS, "no file system interface available!");
|
||||
@@ -596,10 +590,37 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
rb.PushIpcInterface<IStorage>(std::move(storage));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto storage_id = rp.PopRaw<FileSys::StorageId>();
|
||||
const auto unknown = rp.PopRaw<u32>();
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
|
||||
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,
|
||||
"could not open data storage with title_id={:016X}, storage_id={:02X}", title_id,
|
||||
static_cast<u8>(storage_id));
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
IStorage storage(std::move(data.Unwrap()));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IStorage>(std::move(storage));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto storage_id = rp.PopRaw<StorageId>();
|
||||
auto storage_id = rp.PopRaw<FileSys::StorageId>();
|
||||
auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user