Compare commits
419 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcc5155601 | ||
|
|
e67630b51e | ||
|
|
bd14653417 | ||
|
|
2e89719d3e | ||
|
|
baaafbd5ea | ||
|
|
3476f5b4d3 | ||
|
|
bdf17fe0cc | ||
|
|
54ef9302a2 | ||
|
|
e56a444da9 | ||
|
|
8fe118bcaa | ||
|
|
c56a0e3c34 | ||
|
|
fecffeb0dd | ||
|
|
9608f51cde | ||
|
|
e4ed5bc836 | ||
|
|
de5d431eec | ||
|
|
8da753ab81 | ||
|
|
d923766042 | ||
|
|
a9877c8f65 | ||
|
|
2e7802ad7d | ||
|
|
3a338d9286 | ||
|
|
84b542c386 | ||
|
|
0135b328ed | ||
|
|
a970709d5d | ||
|
|
534abf9d97 | ||
|
|
5224cc49c4 | ||
|
|
b82b093108 | ||
|
|
cf0a7cd1c1 | ||
|
|
424e90f0f5 | ||
|
|
e12a07079e | ||
|
|
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 | ||
|
|
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 | ||
|
|
4f0818144e | ||
|
|
20c2928c2b | ||
|
|
f380496728 | ||
|
|
2e80e7480d | ||
|
|
8eb97706b8 | ||
|
|
aaf671a309 | ||
|
|
be53097577 | ||
|
|
0a003efde4 | ||
|
|
2e8620c877 | ||
|
|
6b0bc48a42 | ||
|
|
a5b65df9cf | ||
|
|
57626fda7b | ||
|
|
6313d54cef | ||
|
|
7e6a73963e | ||
|
|
2156cb3cbe | ||
|
|
ef41983c84 | ||
|
|
8069fbd37f | ||
|
|
ec3bef7b4c | ||
|
|
0e1510ac29 | ||
|
|
b8c43b6080 | ||
|
|
3a67876252 | ||
|
|
6828c25498 | ||
|
|
5191c20b71 | ||
|
|
a91bb7080d | ||
|
|
69cd213fac | ||
|
|
05c33d89a1 | ||
|
|
96ef22d3d0 | ||
|
|
e8c52d4c89 | ||
|
|
37e1ed3744 | ||
|
|
75e12a33ae | ||
|
|
e58f7ff843 | ||
|
|
6ef027b958 | ||
|
|
59ea37daa7 | ||
|
|
0bfe974281 | ||
|
|
88b18b9ba4 | ||
|
|
b125137493 | ||
|
|
f765a6b902 | ||
|
|
5cb6eceecf | ||
|
|
b46a5c42ff | ||
|
|
ff5024ee2a | ||
|
|
5c908c0373 | ||
|
|
c333bfc193 | ||
|
|
e831b80d69 | ||
|
|
434f352eb3 | ||
|
|
5b1b758326 | ||
|
|
dfc3eed0cb | ||
|
|
06d0b96ca9 | ||
|
|
4283019aa0 | ||
|
|
efe6b473c5 | ||
|
|
557c466994 | ||
|
|
25ba4d1b68 | ||
|
|
668458525e | ||
|
|
2f4f4f147f | ||
|
|
94cf327e77 | ||
|
|
2b6128fe0b | ||
|
|
dad2ae1ee0 | ||
|
|
656e97df16 | ||
|
|
52a2e42cb9 | ||
|
|
4b471f0554 | ||
|
|
aaa8fdea52 | ||
|
|
2de2ec25d6 | ||
|
|
3f82dad1e4 | ||
|
|
3bf488ce52 | ||
|
|
b36dee364e | ||
|
|
ddec200290 | ||
|
|
cf917a5e93 | ||
|
|
9ceceb212f | ||
|
|
9f48454ea9 | ||
|
|
7353cfc781 | ||
|
|
4afb05d0cc | ||
|
|
cc2526dd51 | ||
|
|
096b04f1a4 | ||
|
|
756e1e6f9b | ||
|
|
6e90f0bf6a | ||
|
|
599c011f40 | ||
|
|
5a9c00ea04 | ||
|
|
76197a4be9 | ||
|
|
cc9d7bbf01 | ||
|
|
d224eb7c39 | ||
|
|
fd9da4232b | ||
|
|
507e6ae100 | ||
|
|
6a2f415298 | ||
|
|
448264e719 | ||
|
|
c0d44d3b2a | ||
|
|
f156a45c01 | ||
|
|
6a5d032809 | ||
|
|
4941e3d412 | ||
|
|
7bf422d58c | ||
|
|
9fde7d739a | ||
|
|
3c498189b6 | ||
|
|
7f0d0a93f7 | ||
|
|
57982df105 | ||
|
|
8c6338b6f9 | ||
|
|
c120ed7d18 | ||
|
|
0f834e2284 | ||
|
|
aaf8d9ac2f | ||
|
|
e542356d0c | ||
|
|
b7fb9f2071 | ||
|
|
934a2b9604 | ||
|
|
2bc296801a | ||
|
|
80cfd88e4e | ||
|
|
d378d98e26 | ||
|
|
766c1a2d50 | ||
|
|
5c323d96e0 | ||
|
|
4e3bc37791 | ||
|
|
a7d6efc520 | ||
|
|
cd1a96f389 | ||
|
|
62b0b83fd8 | ||
|
|
825e8cb925 | ||
|
|
1fbb7aff1e | ||
|
|
4fa3511a63 | ||
|
|
7139f05fc5 | ||
|
|
e530f82dd3 | ||
|
|
1abfd4166e | ||
|
|
b9829a05be | ||
|
|
7ed8565978 | ||
|
|
6576bc8927 | ||
|
|
2d57cbaec1 | ||
|
|
3242e7c164 | ||
|
|
c707240685 | ||
|
|
573a66c23d | ||
|
|
97c6f984dc | ||
|
|
07595987ac | ||
|
|
b09c4f45c7 | ||
|
|
0735a0c8a1 | ||
|
|
bc6d91a103 | ||
|
|
c392650e21 | ||
|
|
8f73f41824 | ||
|
|
c4397ec77e | ||
|
|
438d9aa407 | ||
|
|
3e81c09094 | ||
|
|
12ab5a0547 | ||
|
|
d3f64785d1 | ||
|
|
300ab211e8 | ||
|
|
fa8017295b | ||
|
|
e40b0cf437 | ||
|
|
7e49881b7f | ||
|
|
0f5c4615ae | ||
|
|
890e543304 | ||
|
|
20c976ff2a | ||
|
|
45bc449ff9 | ||
|
|
c8f6754417 | ||
|
|
0db0e4c8f3 | ||
|
|
91cfe70301 | ||
|
|
e4422b09b6 | ||
|
|
5927cf0e17 | ||
|
|
9e88f03e75 | ||
|
|
da2f00ab7d | ||
|
|
826b1394e8 | ||
|
|
0c3c91e41c | ||
|
|
e2b74f6354 | ||
|
|
e218d79cc2 | ||
|
|
75df8a3969 | ||
|
|
645d35ac32 | ||
|
|
168958f8e2 | ||
|
|
e3321c2e00 | ||
|
|
f179e87864 | ||
|
|
cf82358ee6 | ||
|
|
83ef37ca37 | ||
|
|
bf51bbffcb | ||
|
|
96b6ad11c1 | ||
|
|
10d693b9c2 | ||
|
|
a5ac53dd4c | ||
|
|
251e92513a | ||
|
|
cf983888cc | ||
|
|
2b2dc00bfd | ||
|
|
d33f641912 | ||
|
|
9764b4ec0e | ||
|
|
7846295a8f | ||
|
|
00a68c5eea | ||
|
|
2feb1a8ba6 | ||
|
|
1ac45342dd | ||
|
|
37adf04dcd | ||
|
|
2ea0f0fd16 | ||
|
|
904d7eaa94 | ||
|
|
57eb936200 | ||
|
|
0b80bbeb95 | ||
|
|
f1b93d63d1 | ||
|
|
03b7ebbc08 | ||
|
|
bb21c2198a | ||
|
|
6c56754322 | ||
|
|
a0c3a46aa9 | ||
|
|
2a7a2b739b | ||
|
|
4aa31b0618 | ||
|
|
2fc5c783ed | ||
|
|
6edd828101 | ||
|
|
d9815b523b | ||
|
|
7f9430f7ae | ||
|
|
c8e5c74092 | ||
|
|
00f7e584ce | ||
|
|
89c076b4b1 | ||
|
|
c0af42d6eb | ||
|
|
7a77d0a71e | ||
|
|
ca96f8db4e | ||
|
|
e06953626c | ||
|
|
e816745b19 | ||
|
|
fd715e54a1 | ||
|
|
ce46fb27ca | ||
|
|
b46df98e93 | ||
|
|
b96010bfa9 | ||
|
|
6d1e30e041 | ||
|
|
a0a605df06 | ||
|
|
cd96c04339 | ||
|
|
3f4fcd582e | ||
|
|
1dee8ceda1 | ||
|
|
f1cb3903ac | ||
|
|
de72956181 | ||
|
|
df51207ed2 | ||
|
|
0d04ee97dc | ||
|
|
64c8212ae1 | ||
|
|
b25468b498 | ||
|
|
8da651ac4d | ||
|
|
c1f76abfaf | ||
|
|
02fccc0940 | ||
|
|
2cc962e171 | ||
|
|
34b3f83498 | ||
|
|
9f846d3aa4 | ||
|
|
2b06301dbf | ||
|
|
13d6593753 | ||
|
|
3b678b9e8e | ||
|
|
dde5dce736 | ||
|
|
2665457f4a | ||
|
|
6030c5ce41 | ||
|
|
762fcaf5de | ||
|
|
b0129489ea | ||
|
|
206f2e3436 | ||
|
|
d43dad001e | ||
|
|
e93fa7f2cc | ||
|
|
29f31356d8 | ||
|
|
e649db8c6b | ||
|
|
2beda7c2b3 | ||
|
|
59b04c0df6 | ||
|
|
40e63ede6d | ||
|
|
64806a8397 | ||
|
|
b4e050e6c4 | ||
|
|
b45e5c2399 | ||
|
|
555d76d065 | ||
|
|
26de4bb521 | ||
|
|
c1d54f4aea | ||
|
|
b6d61abd78 | ||
|
|
40e78b9a89 | ||
|
|
ef9433411d | ||
|
|
00ba704a7f | ||
|
|
4c3c608d59 | ||
|
|
4b84d5bcec | ||
|
|
9e48ca23b2 | ||
|
|
49d817134a | ||
|
|
61ed68f3d0 | ||
|
|
291ccf7257 | ||
|
|
52da0ce399 | ||
|
|
db340f6402 | ||
|
|
684fc2c320 | ||
|
|
29b6afb82f | ||
|
|
4de18e054b | ||
|
|
bae1822aed | ||
|
|
e79e967151 | ||
|
|
fe64e1d38e | ||
|
|
d0bd01146e | ||
|
|
d94a173877 | ||
|
|
c4e0c3d76c | ||
|
|
ce5ad45278 | ||
|
|
6058c84b79 | ||
|
|
fac0e42b2f | ||
|
|
9a50a4f2cc | ||
|
|
42a4c6b79e | ||
|
|
cec9e9b811 | ||
|
|
6b32e24161 | ||
|
|
d92e8ab062 | ||
|
|
f2a03468b1 | ||
|
|
c6db1c390b | ||
|
|
7469e26e5e | ||
|
|
a03c644aed | ||
|
|
c1c397d37c | ||
|
|
2bc4ab3958 | ||
|
|
89ebef6571 | ||
|
|
bf45092c61 | ||
|
|
42c5171322 | ||
|
|
5233040ab4 | ||
|
|
0f2ac928f2 | ||
|
|
746d7d4d28 | ||
|
|
9bb8720289 | ||
|
|
16b2fd9fc8 | ||
|
|
200c95db8a | ||
|
|
48acb764a4 | ||
|
|
f77cfab516 | ||
|
|
208a457909 | ||
|
|
e39294c267 | ||
|
|
d109279543 | ||
|
|
99a1d7440d | ||
|
|
0d8fcab136 | ||
|
|
703663d761 | ||
|
|
13cdf1f159 | ||
|
|
0497bb5528 | ||
|
|
187d8e215f | ||
|
|
9d59b96ef9 | ||
|
|
a9c921a41d | ||
|
|
03149d3e4a | ||
|
|
150527ec19 | ||
|
|
cc8234fa89 | ||
|
|
239a3113e4 | ||
|
|
22342487e8 | ||
|
|
83c3ae8be8 | ||
|
|
c54a10cb4f | ||
|
|
df5b75694f | ||
|
|
d2ad279a32 | ||
|
|
49e198b20d | ||
|
|
ff2c1b0a94 | ||
|
|
98af269415 | ||
|
|
ca84b530a3 | ||
|
|
f5efac3442 | ||
|
|
bba63b33a1 | ||
|
|
da07faebfe | ||
|
|
0a2b219a31 | ||
|
|
3575c076cb | ||
|
|
fd020ad52a | ||
|
|
3a2581cc7d | ||
|
|
369f6e58aa | ||
|
|
1ced7bbea5 | ||
|
|
a2304fad16 | ||
|
|
b79362b9da | ||
|
|
86491da0d6 | ||
|
|
7da8f15461 | ||
|
|
f08c0520a4 | ||
|
|
9d6aa7bff7 | ||
|
|
444a01afa6 | ||
|
|
e373027a73 | ||
|
|
8191273a3d | ||
|
|
e119e17d18 | ||
|
|
2482aca7c3 | ||
|
|
6e5f83ee24 |
7
.gitmodules
vendored
7
.gitmodules
vendored
@@ -25,6 +25,9 @@
|
||||
[submodule "unicorn"]
|
||||
path = externals/unicorn
|
||||
url = https://github.com/yuzu-emu/unicorn
|
||||
[submodule "mbedtls"]
|
||||
path = externals/mbedtls
|
||||
url = https://github.com/DarkLordZach/mbedtls
|
||||
[submodule "opus"]
|
||||
path = externals/opus
|
||||
url = https://github.com/ogniK5377/opus.git
|
||||
path = externals/opus
|
||||
url = https://github.com/ogniK5377/opus.git
|
||||
|
||||
@@ -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)
|
||||
@@ -187,8 +187,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 +220,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 +279,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 +303,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)
|
||||
|
||||
@@ -4,8 +4,10 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin")
|
||||
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
|
||||
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
|
||||
set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
|
||||
set(PLATFORMS ${DLL_DEST}platforms/)
|
||||
set(STYLES ${DLL_DEST}styles/)
|
||||
set(IMAGEFORMATS ${DLL_DEST}imageformats/)
|
||||
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
|
||||
icudt*.dll
|
||||
icuin*.dll
|
||||
@@ -17,4 +19,5 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
)
|
||||
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)
|
||||
endfunction(copy_yuzu_Qt5_deps)
|
||||
|
||||
@@ -117,6 +117,7 @@ after_build:
|
||||
mkdir $RELEASE_DIST
|
||||
mkdir $RELEASE_DIST/platforms
|
||||
mkdir $RELEASE_DIST/styles
|
||||
mkdir $RELEASE_DIST/imageformats
|
||||
|
||||
# copy the compiled binaries and other release files to the release folder
|
||||
Get-ChildItem "$CMAKE_BINARY_DIR" -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
@@ -140,6 +141,9 @@ after_build:
|
||||
# copy the qt windows vista style dll to platforms
|
||||
Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/styles/qwindowsvistastyle.dll" -force -destination "$RELEASE_DIST/styles"
|
||||
|
||||
# copy the qt jpeg imageformat dll to platforms
|
||||
Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/imageformats/qjpeg.dll" -force -destination "$RELEASE_DIST/imageformats"
|
||||
|
||||
7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\*
|
||||
7z a $MINGW_SEVENZIP $RELEASE_DIST
|
||||
}
|
||||
|
||||
8
externals/CMakeLists.txt
vendored
8
externals/CMakeLists.txt
vendored
@@ -32,9 +32,13 @@ add_subdirectory(inih)
|
||||
|
||||
# lz4
|
||||
set(LZ4_BUNDLED_MODE ON)
|
||||
add_subdirectory(lz4/contrib/cmake_unofficial)
|
||||
add_subdirectory(lz4/contrib/cmake_unofficial EXCLUDE_FROM_ALL)
|
||||
target_include_directories(lz4_static INTERFACE ./lz4/lib)
|
||||
|
||||
# mbedtls
|
||||
add_subdirectory(mbedtls EXCLUDE_FROM_ALL)
|
||||
target_include_directories(mbedtls PUBLIC ./mbedtls/include)
|
||||
|
||||
# MicroProfile
|
||||
add_library(microprofile INTERFACE)
|
||||
target_include_directories(microprofile INTERFACE ./microprofile)
|
||||
@@ -58,5 +62,5 @@ target_include_directories(opus INTERFACE ./opus/include)
|
||||
# Cubeb
|
||||
if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(cubeb)
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
2
externals/catch
vendored
2
externals/catch
vendored
Submodule externals/catch updated: d2a130f243...15cf3caace
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 73d3efc3e0...0118ee04f9
23
externals/glad/include/glad/glad.h
vendored
23
externals/glad/include/glad/glad.h
vendored
File diff suppressed because one or more lines are too long
6385
externals/glad/src/glad.c
vendored
6385
externals/glad/src/glad.c
vendored
File diff suppressed because one or more lines are too long
1
externals/mbedtls
vendored
Submodule
1
externals/mbedtls
vendored
Submodule
Submodule externals/mbedtls added at d409b75a4c
@@ -1,9 +1,11 @@
|
||||
add_library(audio_core STATIC
|
||||
audio_out.cpp
|
||||
audio_out.h
|
||||
audio_renderer.cpp
|
||||
audio_renderer.h
|
||||
buffer.h
|
||||
cubeb_sink.cpp
|
||||
cubeb_sink.h
|
||||
codec.cpp
|
||||
codec.h
|
||||
null_sink.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
@@ -11,6 +13,8 @@ add_library(audio_core STATIC
|
||||
sink_details.cpp
|
||||
sink_details.h
|
||||
sink_stream.h
|
||||
|
||||
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
|
||||
)
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "audio_core/sink_details.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
@@ -26,19 +27,19 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
|
||||
return {};
|
||||
}
|
||||
|
||||
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels,
|
||||
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
|
||||
Stream::ReleaseCallback&& release_callback) {
|
||||
if (!sink) {
|
||||
const SinkDetails& sink_details = GetSinkDetails("auto");
|
||||
sink = sink_details.factory("");
|
||||
const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id);
|
||||
sink = sink_details.factory(Settings::values.audio_device_id);
|
||||
}
|
||||
|
||||
return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
|
||||
std::move(release_callback),
|
||||
sink->AcquireSinkStream(sample_rate, num_channels));
|
||||
return std::make_shared<Stream>(
|
||||
sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback),
|
||||
sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name));
|
||||
}
|
||||
|
||||
std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
|
||||
std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
|
||||
return stream->GetTagsAndReleaseBuffers(max_count);
|
||||
}
|
||||
|
||||
@@ -50,7 +51,7 @@ void AudioOut::StopStream(StreamPtr stream) {
|
||||
stream->Stop();
|
||||
}
|
||||
|
||||
bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data) {
|
||||
bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) {
|
||||
return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data)));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/buffer.h"
|
||||
@@ -20,11 +21,11 @@ namespace AudioCore {
|
||||
class AudioOut {
|
||||
public:
|
||||
/// Opens a new audio stream
|
||||
StreamPtr OpenStream(u32 sample_rate, u32 num_channels,
|
||||
StreamPtr OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
|
||||
Stream::ReleaseCallback&& release_callback);
|
||||
|
||||
/// Returns a vector of recently released buffers specified by tag for the specified stream
|
||||
std::vector<u64> GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count);
|
||||
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count);
|
||||
|
||||
/// Starts an audio stream for playback
|
||||
void StartStream(StreamPtr stream);
|
||||
@@ -33,7 +34,7 @@ public:
|
||||
void StopStream(StreamPtr stream);
|
||||
|
||||
/// Queues a buffer into the specified audio stream, returns true on success
|
||||
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
|
||||
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data);
|
||||
|
||||
private:
|
||||
SinkPtr sink;
|
||||
|
||||
246
src/audio_core/audio_renderer.cpp
Normal file
246
src/audio_core/audio_renderer.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr u32 STREAM_SAMPLE_RATE{48000};
|
||||
constexpr u32 STREAM_NUM_CHANNELS{2};
|
||||
|
||||
AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
|
||||
|
||||
audio_core = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_core->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_core->StartStream(stream);
|
||||
|
||||
QueueMixedBuffer(0);
|
||||
QueueMixedBuffer(1);
|
||||
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{};
|
||||
std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
|
||||
u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
|
||||
|
||||
// Copy MemoryPoolInfo structs
|
||||
std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
|
||||
std::memcpy(mem_pool_info.data(),
|
||||
input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
|
||||
memory_pool_count * sizeof(MemoryPoolInfo));
|
||||
|
||||
// Copy VoiceInfo structs
|
||||
size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size +
|
||||
config.voice_resource_size};
|
||||
for (auto& voice : voices) {
|
||||
std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo));
|
||||
offset += sizeof(VoiceInfo);
|
||||
}
|
||||
|
||||
// Update voices
|
||||
for (auto& voice : voices) {
|
||||
voice.UpdateState();
|
||||
if (!voice.GetInfo().is_in_use) {
|
||||
continue;
|
||||
}
|
||||
if (voice.GetInfo().is_new) {
|
||||
voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
|
||||
}
|
||||
}
|
||||
|
||||
// Update memory pool state
|
||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||
for (size_t index = 0; index < memory_pool.size(); ++index) {
|
||||
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||
}
|
||||
}
|
||||
|
||||
// Release previous buffers and queue next ones for playback
|
||||
ReleaseAndQueueBuffers();
|
||||
|
||||
// Copy output header
|
||||
UpdateDataHeader response_data{worker_params};
|
||||
std::vector<u8> output_params(response_data.total_size);
|
||||
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
|
||||
|
||||
// Copy output memory pool entries
|
||||
std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
|
||||
response_data.memory_pools_size);
|
||||
|
||||
// Copy output voice status
|
||||
size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
|
||||
for (const auto& voice : voices) {
|
||||
std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
|
||||
sizeof(VoiceOutStatus));
|
||||
voice_out_status_offset += sizeof(VoiceOutStatus);
|
||||
}
|
||||
|
||||
return output_params;
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::SetWaveIndex(size_t index) {
|
||||
wave_index = index & 3;
|
||||
is_refresh_pending = true;
|
||||
}
|
||||
|
||||
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(size_t sample_count) {
|
||||
if (!IsPlaying()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is_refresh_pending) {
|
||||
RefreshBuffer();
|
||||
}
|
||||
|
||||
const size_t max_size{samples.size() - offset};
|
||||
const size_t dequeue_offset{offset};
|
||||
size_t size{sample_count * STREAM_NUM_CHANNELS};
|
||||
if (size > max_size) {
|
||||
size = max_size;
|
||||
}
|
||||
|
||||
out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
|
||||
offset += size;
|
||||
|
||||
const auto& wave_buffer{info.wave_buffer[wave_index]};
|
||||
if (offset == samples.size()) {
|
||||
offset = 0;
|
||||
|
||||
if (!wave_buffer.is_looping) {
|
||||
SetWaveIndex(wave_index + 1);
|
||||
}
|
||||
|
||||
out_status.wave_buffer_consumed++;
|
||||
|
||||
if (wave_buffer.end_of_stream) {
|
||||
info.play_state = PlayState::Paused;
|
||||
}
|
||||
}
|
||||
|
||||
return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::UpdateState() {
|
||||
if (is_in_use && !info.is_in_use) {
|
||||
// No longer in use, reset state
|
||||
is_refresh_pending = true;
|
||||
wave_index = 0;
|
||||
offset = 0;
|
||||
out_status = {};
|
||||
}
|
||||
is_in_use = info.is_in_use;
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::RefreshBuffer() {
|
||||
std::vector<s16> new_samples(info.wave_buffer[wave_index].buffer_sz / sizeof(s16));
|
||||
Memory::ReadBlock(info.wave_buffer[wave_index].buffer_addr, new_samples.data(),
|
||||
info.wave_buffer[wave_index].buffer_sz);
|
||||
|
||||
switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
|
||||
case Codec::PcmFormat::Int16: {
|
||||
// PCM16 is played as-is
|
||||
break;
|
||||
}
|
||||
case Codec::PcmFormat::Adpcm: {
|
||||
// Decode ADPCM to PCM16
|
||||
Codec::ADPCM_Coeff coeffs;
|
||||
Memory::ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
|
||||
new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
|
||||
new_samples.size() * sizeof(s16), coeffs, adpcm_state);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(Audio, "Unimplemented sample_format={}", info.sample_format);
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
switch (info.channel_count) {
|
||||
case 1:
|
||||
// 1 channel is upsampled to 2 channel
|
||||
samples.resize(new_samples.size() * 2);
|
||||
for (size_t index = 0; index < new_samples.size(); ++index) {
|
||||
samples[index * 2] = new_samples[index];
|
||||
samples[index * 2 + 1] = new_samples[index];
|
||||
}
|
||||
break;
|
||||
case 2: {
|
||||
// 2 channel is played as is
|
||||
samples = std::move(new_samples);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(Audio, "Unimplemented channel_count={}", info.channel_count);
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
is_refresh_pending = false;
|
||||
}
|
||||
|
||||
static constexpr s16 ClampToS16(s32 value) {
|
||||
return static_cast<s16>(std::clamp(value, -32768, 32767));
|
||||
}
|
||||
|
||||
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
constexpr size_t BUFFER_SIZE{512};
|
||||
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
|
||||
|
||||
for (auto& voice : voices) {
|
||||
if (!voice.IsPlaying()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t offset{};
|
||||
s64 samples_remaining{BUFFER_SIZE};
|
||||
while (samples_remaining > 0) {
|
||||
const std::vector<s16> samples{voice.DequeueSamples(samples_remaining)};
|
||||
|
||||
if (samples.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
samples_remaining -= samples.size();
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
const s32 buffer_sample{buffer[offset]};
|
||||
buffer[offset++] =
|
||||
ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
|
||||
}
|
||||
}
|
||||
}
|
||||
audio_core->QueueBuffer(stream, tag, std::move(buffer));
|
||||
}
|
||||
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||
const auto released_buffers{audio_core->GetTagsAndReleaseBuffers(stream, 2)};
|
||||
for (const auto& tag : released_buffers) {
|
||||
QueueMixedBuffer(tag);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
209
src/audio_core/audio_renderer.h
Normal file
209
src/audio_core/audio_renderer.h
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "audio_core/codec.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
enum class PlayState : u8 {
|
||||
Started = 0,
|
||||
Stopped = 1,
|
||||
Paused = 2,
|
||||
};
|
||||
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le unknown_c;
|
||||
u32_le voice_count;
|
||||
u32_le sink_count;
|
||||
u32_le effect_count;
|
||||
u32_le unknown_1c;
|
||||
u8 unknown_20;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
u32_le splitter_count;
|
||||
u32_le unknown_2c;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32_le revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||
|
||||
enum class MemoryPoolStates : u32 { // Should be LE
|
||||
Invalid = 0x0,
|
||||
Unknown = 0x1,
|
||||
RequestDetach = 0x2,
|
||||
Detached = 0x3,
|
||||
RequestAttach = 0x4,
|
||||
Attached = 0x5,
|
||||
Released = 0x6,
|
||||
};
|
||||
|
||||
struct MemoryPoolEntry {
|
||||
MemoryPoolStates state;
|
||||
u32_le unknown_4;
|
||||
u32_le unknown_8;
|
||||
u32_le unknown_c;
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
|
||||
|
||||
struct MemoryPoolInfo {
|
||||
u64_le pool_address;
|
||||
u64_le pool_size;
|
||||
MemoryPoolStates pool_state;
|
||||
INSERT_PADDING_WORDS(3); // Unknown
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
|
||||
struct BiquadFilter {
|
||||
u8 enable;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
std::array<s16_le, 3> numerator;
|
||||
std::array<s16_le, 2> denominator;
|
||||
};
|
||||
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
|
||||
|
||||
struct WaveBuffer {
|
||||
u64_le buffer_addr;
|
||||
u64_le buffer_sz;
|
||||
s32_le start_sample_offset;
|
||||
s32_le end_sample_offset;
|
||||
u8 is_looping;
|
||||
u8 end_of_stream;
|
||||
u8 sent_to_server;
|
||||
INSERT_PADDING_BYTES(5);
|
||||
u64 context_addr;
|
||||
u64 context_sz;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
};
|
||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
|
||||
|
||||
struct VoiceInfo {
|
||||
u32_le id;
|
||||
u32_le node_id;
|
||||
u8 is_new;
|
||||
u8 is_in_use;
|
||||
PlayState play_state;
|
||||
u8 sample_format;
|
||||
u32_le sample_rate;
|
||||
u32_le priority;
|
||||
u32_le sorting_order;
|
||||
u32_le channel_count;
|
||||
float_le pitch;
|
||||
float_le volume;
|
||||
std::array<BiquadFilter, 2> biquad_filter;
|
||||
u32_le wave_buffer_count;
|
||||
u32_le wave_buffer_head;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u64_le additional_params_addr;
|
||||
u64_le additional_params_sz;
|
||||
u32_le mix_id;
|
||||
u32_le splitter_info_id;
|
||||
std::array<WaveBuffer, 4> wave_buffer;
|
||||
std::array<u32_le, 6> voice_channel_resource_ids;
|
||||
INSERT_PADDING_BYTES(24);
|
||||
};
|
||||
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
|
||||
|
||||
struct VoiceOutStatus {
|
||||
u64_le played_sample_count;
|
||||
u32_le wave_buffer_consumed;
|
||||
u32_le voice_drops_count;
|
||||
};
|
||||
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
UpdateDataHeader() {}
|
||||
|
||||
explicit UpdateDataHeader(const AudioRendererParameter& config) {
|
||||
revision = Common::MakeMagic('R', 'E', 'V', '4'); // 5.1.0 Revision
|
||||
behavior_size = 0xb0;
|
||||
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
|
||||
voices_size = config.voice_count * 0x10;
|
||||
voice_resource_size = 0x0;
|
||||
effects_size = config.effect_count * 0x10;
|
||||
mixes_size = 0x0;
|
||||
sinks_size = config.sink_count * 0x20;
|
||||
performance_manager_size = 0x10;
|
||||
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
|
||||
effects_size + sinks_size + performance_manager_size;
|
||||
}
|
||||
|
||||
u32_le revision;
|
||||
u32_le behavior_size;
|
||||
u32_le memory_pools_size;
|
||||
u32_le voices_size;
|
||||
u32_le voice_resource_size;
|
||||
u32_le effects_size;
|
||||
u32_le mixes_size;
|
||||
u32_le sinks_size;
|
||||
u32_le performance_manager_size;
|
||||
INSERT_PADDING_WORDS(6);
|
||||
u32_le total_size;
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
|
||||
|
||||
class AudioRenderer {
|
||||
public:
|
||||
AudioRenderer(AudioRendererParameter params, Kernel::SharedPtr<Kernel::Event> buffer_event);
|
||||
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 {
|
||||
public:
|
||||
bool IsPlaying() const {
|
||||
return is_in_use && info.play_state == PlayState::Started;
|
||||
}
|
||||
|
||||
const VoiceOutStatus& GetOutStatus() const {
|
||||
return out_status;
|
||||
}
|
||||
|
||||
const VoiceInfo& GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
VoiceInfo& Info() {
|
||||
return info;
|
||||
}
|
||||
|
||||
void SetWaveIndex(size_t index);
|
||||
std::vector<s16> DequeueSamples(size_t sample_count);
|
||||
void UpdateState();
|
||||
void RefreshBuffer();
|
||||
|
||||
private:
|
||||
bool is_in_use{};
|
||||
bool is_refresh_pending{};
|
||||
size_t wave_index{};
|
||||
size_t offset{};
|
||||
Codec::ADPCMState adpcm_state{};
|
||||
std::vector<s16> samples;
|
||||
VoiceOutStatus out_status{};
|
||||
VoiceInfo info{};
|
||||
};
|
||||
|
||||
AudioRendererParameter worker_params;
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||
std::vector<VoiceState> voices;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_core;
|
||||
AudioCore::StreamPtr stream;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -18,11 +18,16 @@ class Buffer {
|
||||
public:
|
||||
using Tag = u64;
|
||||
|
||||
Buffer(Tag tag, std::vector<u8>&& data) : tag{tag}, data{std::move(data)} {}
|
||||
Buffer(Tag tag, std::vector<s16>&& samples) : tag{tag}, samples{std::move(samples)} {}
|
||||
|
||||
/// Returns the raw audio data for the buffer
|
||||
const std::vector<u8>& GetData() const {
|
||||
return data;
|
||||
std::vector<s16>& Samples() {
|
||||
return samples;
|
||||
}
|
||||
|
||||
/// Returns the raw audio data for the buffer
|
||||
const std::vector<s16>& GetSamples() const {
|
||||
return samples;
|
||||
}
|
||||
|
||||
/// Returns the buffer tag, this is provided by the game to the audout service
|
||||
@@ -32,7 +37,7 @@ public:
|
||||
|
||||
private:
|
||||
Tag tag;
|
||||
std::vector<u8> data;
|
||||
std::vector<s16> samples;
|
||||
};
|
||||
|
||||
using BufferPtr = std::shared_ptr<Buffer>;
|
||||
|
||||
77
src/audio_core/codec.cpp
Normal file
77
src/audio_core/codec.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "audio_core/codec.h"
|
||||
|
||||
namespace AudioCore::Codec {
|
||||
|
||||
std::vector<s16> DecodeADPCM(const u8* const data, size_t size, const ADPCM_Coeff& coeff,
|
||||
ADPCMState& state) {
|
||||
// GC-ADPCM with scale factor and variable coefficients.
|
||||
// Frames are 8 bytes long containing 14 samples each.
|
||||
// Samples are 4 bits (one nibble) long.
|
||||
|
||||
constexpr size_t FRAME_LEN = 8;
|
||||
constexpr size_t SAMPLES_PER_FRAME = 14;
|
||||
constexpr std::array<int, 16> SIGNED_NIBBLES = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
|
||||
|
||||
const size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME;
|
||||
const size_t ret_size =
|
||||
sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
|
||||
std::vector<s16> ret(ret_size);
|
||||
|
||||
int yn1 = state.yn1, yn2 = state.yn2;
|
||||
|
||||
const size_t NUM_FRAMES =
|
||||
(sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
|
||||
for (size_t framei = 0; framei < NUM_FRAMES; framei++) {
|
||||
const int frame_header = data[framei * FRAME_LEN];
|
||||
const int scale = 1 << (frame_header & 0xF);
|
||||
const int idx = (frame_header >> 4) & 0x7;
|
||||
|
||||
// Coefficients are fixed point with 11 bits fractional part.
|
||||
const int coef1 = coeff[idx * 2 + 0];
|
||||
const int coef2 = coeff[idx * 2 + 1];
|
||||
|
||||
// Decodes an audio sample. One nibble produces one sample.
|
||||
const auto decode_sample = [&](const int nibble) -> s16 {
|
||||
const int xn = nibble * scale;
|
||||
// We first transform everything into 11 bit fixed point, perform the second order
|
||||
// digital filter, then transform back.
|
||||
// 0x400 == 0.5 in 11 bit fixed point.
|
||||
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
|
||||
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
|
||||
// Clamp to output range.
|
||||
val = std::clamp<s32>(val, -32768, 32767);
|
||||
// Advance output feedback.
|
||||
yn2 = yn1;
|
||||
yn1 = val;
|
||||
return static_cast<s16>(val);
|
||||
};
|
||||
|
||||
size_t outputi = framei * SAMPLES_PER_FRAME;
|
||||
size_t datai = framei * FRAME_LEN + 1;
|
||||
for (size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) {
|
||||
const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]);
|
||||
ret[outputi] = sample1;
|
||||
outputi++;
|
||||
|
||||
const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]);
|
||||
ret[outputi] = sample2;
|
||||
outputi++;
|
||||
|
||||
datai++;
|
||||
}
|
||||
}
|
||||
|
||||
state.yn1 = yn1;
|
||||
state.yn2 = yn2;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Codec
|
||||
44
src/audio_core/codec.h
Normal file
44
src/audio_core/codec.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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::Codec {
|
||||
|
||||
enum class PcmFormat : u32 {
|
||||
Invalid = 0,
|
||||
Int8 = 1,
|
||||
Int16 = 2,
|
||||
Int24 = 3,
|
||||
Int32 = 4,
|
||||
PcmFloat = 5,
|
||||
Adpcm = 6,
|
||||
};
|
||||
|
||||
/// See: Codec::DecodeADPCM
|
||||
struct ADPCMState {
|
||||
// Two historical samples from previous processed buffer,
|
||||
// required for ADPCM decoding
|
||||
s16 yn1; ///< y[n-1]
|
||||
s16 yn2; ///< y[n-2]
|
||||
};
|
||||
|
||||
using ADPCM_Coeff = std::array<s16, 16>;
|
||||
|
||||
/**
|
||||
* @param data Pointer to buffer that contains ADPCM data to decode
|
||||
* @param size Size of buffer in bytes
|
||||
* @param coeff ADPCM coefficients
|
||||
* @param state ADPCM state, this is updated with new state
|
||||
* @return Decoded stereo signed PCM16 data, sample_count in length
|
||||
*/
|
||||
std::vector<s16> DecodeADPCM(const u8* const data, size_t size, const ADPCM_Coeff& coeff,
|
||||
ADPCMState& state);
|
||||
|
||||
}; // namespace AudioCore::Codec
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#include "audio_core/stream.h"
|
||||
@@ -13,20 +14,30 @@ namespace AudioCore {
|
||||
|
||||
class SinkStreamImpl final : public SinkStream {
|
||||
public:
|
||||
SinkStreamImpl(cubeb* ctx, cubeb_devid output_device) : ctx{ctx} {
|
||||
cubeb_stream_params params;
|
||||
params.rate = 48000;
|
||||
params.channels = GetNumChannels();
|
||||
params.format = CUBEB_SAMPLE_S16NE;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
SinkStreamImpl(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
|
||||
const std::string& name)
|
||||
: ctx{ctx}, num_channels{num_channels_} {
|
||||
|
||||
u32 minimum_latency = 0;
|
||||
if (num_channels == 6) {
|
||||
// 6-channel audio does not seem to work with cubeb + SDL, so we downsample this to 2
|
||||
// channel for now
|
||||
is_6_channel = true;
|
||||
num_channels = 2;
|
||||
}
|
||||
|
||||
cubeb_stream_params params{};
|
||||
params.rate = sample_rate;
|
||||
params.channels = num_channels;
|
||||
params.format = CUBEB_SAMPLE_S16NE;
|
||||
params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
|
||||
|
||||
u32 minimum_latency{};
|
||||
if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
|
||||
}
|
||||
|
||||
if (cubeb_stream_init(ctx, &stream_backend, "yuzu Audio Output", nullptr, nullptr,
|
||||
output_device, ¶ms, std::max(512u, minimum_latency),
|
||||
if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device,
|
||||
¶ms, std::max(512u, minimum_latency),
|
||||
&SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback,
|
||||
this) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
|
||||
@@ -51,33 +62,31 @@ public:
|
||||
cubeb_stream_destroy(stream_backend);
|
||||
}
|
||||
|
||||
void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) override {
|
||||
void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) override {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
queue.reserve(queue.size() + sample_count * GetNumChannels());
|
||||
std::lock_guard lock{queue_mutex};
|
||||
|
||||
if (num_channels == 2) {
|
||||
// Copy as-is
|
||||
std::copy(samples, samples + sample_count * GetNumChannels(),
|
||||
std::back_inserter(queue));
|
||||
} else if (num_channels == 6) {
|
||||
queue.reserve(queue.size() + samples.size() * GetNumChannels());
|
||||
|
||||
if (is_6_channel) {
|
||||
// Downsample 6 channels to 2
|
||||
const size_t sample_count_copy_size = sample_count * num_channels * 2;
|
||||
const size_t sample_count_copy_size = samples.size() * 2;
|
||||
queue.reserve(sample_count_copy_size);
|
||||
for (size_t i = 0; i < sample_count * num_channels; i += num_channels) {
|
||||
for (size_t i = 0; i < samples.size(); i += num_channels) {
|
||||
queue.push_back(samples[i]);
|
||||
queue.push_back(samples[i + 1]);
|
||||
}
|
||||
} else {
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
// Copy as-is
|
||||
std::copy(samples.begin(), samples.end(), std::back_inserter(queue));
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetNumChannels() const {
|
||||
// Only support 2-channel stereo output for now
|
||||
return 2;
|
||||
return num_channels;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -85,7 +94,10 @@ private:
|
||||
|
||||
cubeb* ctx{};
|
||||
cubeb_stream* stream_backend{};
|
||||
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,
|
||||
@@ -129,8 +141,10 @@ CubebSink::~CubebSink() {
|
||||
cubeb_destroy(ctx);
|
||||
}
|
||||
|
||||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels) {
|
||||
sink_streams.push_back(std::make_unique<SinkStreamImpl>(ctx, output_device));
|
||||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
|
||||
const std::string& name) {
|
||||
sink_streams.push_back(
|
||||
std::make_unique<SinkStreamImpl>(ctx, sample_rate, num_channels, output_device, name));
|
||||
return *sink_streams.back();
|
||||
}
|
||||
|
||||
@@ -143,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))};
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ public:
|
||||
explicit CubebSink(std::string device_id);
|
||||
~CubebSink() override;
|
||||
|
||||
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override;
|
||||
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
|
||||
const std::string& name) override;
|
||||
|
||||
private:
|
||||
cubeb* ctx{};
|
||||
|
||||
@@ -13,14 +13,14 @@ public:
|
||||
explicit NullSink(std::string){};
|
||||
~NullSink() override = default;
|
||||
|
||||
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/) override {
|
||||
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,
|
||||
const std::string& /*name*/) override {
|
||||
return null_sink_stream;
|
||||
}
|
||||
|
||||
private:
|
||||
struct NullSinkStreamImpl final : SinkStream {
|
||||
void EnqueueSamples(u32 /*num_channels*/, const s16* /*samples*/,
|
||||
size_t /*sample_count*/) override {}
|
||||
void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {}
|
||||
} null_sink_stream;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/sink_stream.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -21,7 +22,8 @@ constexpr char auto_device_name[] = "auto";
|
||||
class Sink {
|
||||
public:
|
||||
virtual ~Sink() = default;
|
||||
virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) = 0;
|
||||
virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
|
||||
const std::string& name) = 0;
|
||||
};
|
||||
|
||||
using SinkPtr = std::unique_ptr<Sink>;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace AudioCore {
|
||||
@@ -13,16 +14,18 @@ namespace AudioCore {
|
||||
class Sink;
|
||||
|
||||
struct SinkDetails {
|
||||
SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>(std::string)> factory_,
|
||||
std::function<std::vector<std::string>()> list_devices_)
|
||||
: id(id_), factory(factory_), list_devices(list_devices_) {}
|
||||
using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>;
|
||||
using ListDevicesFn = std::function<std::vector<std::string>()>;
|
||||
|
||||
SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_)
|
||||
: id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {}
|
||||
|
||||
/// Name for this sink.
|
||||
const char* id;
|
||||
/// A method to call to construct an instance of this type of sink.
|
||||
std::function<std::unique_ptr<Sink>(std::string device_id)> factory;
|
||||
FactoryFn factory;
|
||||
/// A method to call to list available devices.
|
||||
std::function<std::vector<std::string>()> list_devices;
|
||||
ListDevicesFn list_devices;
|
||||
};
|
||||
|
||||
extern const std::vector<SinkDetails> g_sink_details;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -22,9 +23,8 @@ public:
|
||||
* Feed stereo samples to sink.
|
||||
* @param num_channels Number of channels used.
|
||||
* @param samples Samples in interleaved stereo PCM16 format.
|
||||
* @param sample_count Number of samples.
|
||||
*/
|
||||
virtual void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) = 0;
|
||||
virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0;
|
||||
};
|
||||
|
||||
using SinkStreamPtr = std::unique_ptr<SinkStream>;
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
@@ -29,17 +32,13 @@ u32 Stream::GetNumChannels() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
u32 Stream::GetSampleSize() const {
|
||||
return GetNumChannels() * 2;
|
||||
}
|
||||
|
||||
Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream)
|
||||
SinkStream& sink_stream, std::string&& name_)
|
||||
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
|
||||
sink_stream{sink_stream} {
|
||||
sink_stream{sink_stream}, name{std::move(name_)} {
|
||||
|
||||
release_event = CoreTiming::RegisterEvent(
|
||||
"Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
|
||||
name, [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
|
||||
}
|
||||
|
||||
void Stream::Play() {
|
||||
@@ -52,10 +51,24 @@ void Stream::Stop() {
|
||||
}
|
||||
|
||||
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
|
||||
const size_t num_samples{buffer.GetData().size() / GetSampleSize()};
|
||||
const size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
|
||||
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
}
|
||||
|
||||
static void VolumeAdjustSamples(std::vector<s16>& samples) {
|
||||
const float volume{std::clamp(Settings::values.volume, 0.0f, 1.0f)};
|
||||
|
||||
if (volume == 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Implementation of a volume slider with a dynamic range of 60 dB
|
||||
const float volume_scale_factor{std::exp(6.90775f * volume) * 0.001f};
|
||||
for (auto& sample : samples) {
|
||||
sample = static_cast<s16>(sample * volume_scale_factor);
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::PlayNextBuffer() {
|
||||
if (!IsPlaying()) {
|
||||
// Ensure we are in playing state before playing the next buffer
|
||||
@@ -75,14 +88,14 @@ void Stream::PlayNextBuffer() {
|
||||
active_buffer = queued_buffers.front();
|
||||
queued_buffers.pop();
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(),
|
||||
reinterpret_cast<const s16*>(active_buffer->GetData().data()),
|
||||
active_buffer->GetData().size() / GetSampleSize());
|
||||
VolumeAdjustSamples(active_buffer->Samples());
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
|
||||
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
}
|
||||
|
||||
void Stream::ReleaseActiveBuffer() {
|
||||
ASSERT(active_buffer);
|
||||
released_buffers.push(std::move(active_buffer));
|
||||
release_callback();
|
||||
PlayNextBuffer();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
@@ -33,7 +34,7 @@ public:
|
||||
using ReleaseCallback = std::function<void()>;
|
||||
|
||||
Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream);
|
||||
SinkStream& sink_stream, std::string&& name_);
|
||||
|
||||
/// Plays the audio stream
|
||||
void Play();
|
||||
@@ -68,9 +69,6 @@ public:
|
||||
/// Gets the number of channels
|
||||
u32 GetNumChannels() const;
|
||||
|
||||
/// Gets the sample size in bytes
|
||||
u32 GetSampleSize() const;
|
||||
|
||||
private:
|
||||
/// Current state of the stream
|
||||
enum class State {
|
||||
@@ -96,6 +94,7 @@ private:
|
||||
std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
|
||||
std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
|
||||
SinkStream& sink_stream; ///< Output sink for the stream
|
||||
std::string name; ///< Name of the stream, must be unique
|
||||
};
|
||||
|
||||
using StreamPtr = std::shared_ptr<Stream>;
|
||||
|
||||
@@ -9,13 +9,13 @@ namespace Common {
|
||||
|
||||
template <typename T>
|
||||
constexpr T AlignUp(T value, size_t size) {
|
||||
static_assert(std::is_unsigned<T>::value, "T must be an unsigned value.");
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return static_cast<T>(value + (size - value % size) % size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T AlignDown(T value, size_t size) {
|
||||
static_assert(std::is_unsigned<T>::value, "T must be an unsigned value.");
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return static_cast<T>(value - value % size);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ static inline int LeastSignificantSetBit(u64 val) {
|
||||
|
||||
template <typename IntTy>
|
||||
class BitSet {
|
||||
static_assert(!std::is_signed<IntTy>::value, "BitSet should not be used with signed types");
|
||||
static_assert(!std::is_signed_v<IntTy>, "BitSet should not be used with signed types");
|
||||
|
||||
public:
|
||||
// A reference to a particular bit, returned from operator[].
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "common/vector_math.h"
|
||||
@@ -55,7 +57,7 @@ constexpr u8 Convert8To6(u8 value) {
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
*/
|
||||
inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||
inline Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||
return {bytes[3], bytes[2], bytes[1], bytes[0]};
|
||||
}
|
||||
|
||||
@@ -64,7 +66,7 @@ inline const Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
*/
|
||||
inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||
inline Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||
return {bytes[2], bytes[1], bytes[0], 255};
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ inline const Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
*/
|
||||
inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||
inline Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||
return {bytes[1], bytes[0], 0, 255};
|
||||
}
|
||||
|
||||
@@ -82,8 +84,9 @@ inline const Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
*/
|
||||
inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
||||
inline Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
|
||||
Convert5To8(pixel & 0x1F), 255};
|
||||
}
|
||||
@@ -93,8 +96,9 @@ inline const Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
*/
|
||||
inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
||||
inline Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
|
||||
Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1)};
|
||||
}
|
||||
@@ -104,8 +108,9 @@ inline const Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
*/
|
||||
inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||
const u16_le pixel = *reinterpret_cast<const u16_le*>(bytes);
|
||||
inline Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
|
||||
Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)};
|
||||
}
|
||||
@@ -116,7 +121,9 @@ inline const Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||
* @return Depth value as an u32
|
||||
*/
|
||||
inline u32 DecodeD16(const u8* bytes) {
|
||||
return *reinterpret_cast<const u16_le*>(bytes);
|
||||
u16_le data;
|
||||
std::memcpy(&data, bytes, sizeof(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +140,7 @@ inline u32 DecodeD24(const u8* bytes) {
|
||||
* @param bytes Pointer to encoded source values
|
||||
* @return Resulting values stored as a Math::Vec2
|
||||
*/
|
||||
inline const Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
inline Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
|
||||
}
|
||||
|
||||
@@ -175,8 +182,10 @@ inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
*reinterpret_cast<u16_le*>(bytes) =
|
||||
const u16_le data =
|
||||
(Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b());
|
||||
|
||||
std::memcpy(bytes, &data, sizeof(data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,9 +194,10 @@ inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
*reinterpret_cast<u16_le*>(bytes) = (Convert8To5(color.r()) << 11) |
|
||||
(Convert8To5(color.g()) << 6) |
|
||||
(Convert8To5(color.b()) << 1) | Convert8To1(color.a());
|
||||
const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To5(color.g()) << 6) |
|
||||
(Convert8To5(color.b()) << 1) | Convert8To1(color.a());
|
||||
|
||||
std::memcpy(bytes, &data, sizeof(data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,9 +206,10 @@ inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
*reinterpret_cast<u16_le*>(bytes) = (Convert8To4(color.r()) << 12) |
|
||||
(Convert8To4(color.g()) << 8) |
|
||||
(Convert8To4(color.b()) << 4) | Convert8To4(color.a());
|
||||
const u16 data = (Convert8To4(color.r()) << 12) | (Convert8To4(color.g()) << 8) |
|
||||
(Convert8To4(color.b()) << 4) | Convert8To4(color.a());
|
||||
|
||||
std::memcpy(bytes, &data, sizeof(data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,7 +218,8 @@ inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param bytes Pointer where to store the encoded value
|
||||
*/
|
||||
inline void EncodeD16(u32 value, u8* bytes) {
|
||||
*reinterpret_cast<u16_le*>(bytes) = value & 0xFFFF;
|
||||
const u16_le data = static_cast<u16>(value);
|
||||
std::memcpy(bytes, &data, sizeof(data));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#define SDMC_DIR "sdmc"
|
||||
#define NAND_DIR "nand"
|
||||
#define SYSDATA_DIR "sysdata"
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOG_DIR "log"
|
||||
|
||||
// Filenames
|
||||
|
||||
@@ -706,6 +706,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
|
||||
paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
|
||||
// TODO: Put the logs in a better location for each OS
|
||||
paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP);
|
||||
}
|
||||
@@ -736,6 +737,19 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
|
||||
return paths[path];
|
||||
}
|
||||
|
||||
std::string GetHactoolConfigurationPath() {
|
||||
#ifdef _WIN32
|
||||
PWSTR pw_local_path = nullptr;
|
||||
if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK)
|
||||
return "";
|
||||
std::string local_path = Common::UTF16ToUTF8(pw_local_path);
|
||||
CoTaskMemFree(pw_local_path);
|
||||
return local_path + "\\.switch";
|
||||
#else
|
||||
return GetHomeDirectory() + "/.switch";
|
||||
#endif
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
@@ -870,11 +884,21 @@ std::string_view RemoveTrailingSlash(std::string_view path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string SanitizePath(std::string_view path_) {
|
||||
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
|
||||
std::string path(path_);
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
|
||||
char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
|
||||
|
||||
if (directory_separator == DirectorySeparator::PlatformDefault) {
|
||||
#ifdef _WIN32
|
||||
type1 = '/';
|
||||
type2 = '\\';
|
||||
#endif
|
||||
}
|
||||
|
||||
std::replace(path.begin(), path.end(), type1, type2);
|
||||
path.erase(std::unique(path.begin(), path.end(),
|
||||
[](char c1, char c2) { return c1 == '/' && c2 == '/'; }),
|
||||
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
|
||||
path.end());
|
||||
return std::string(RemoveTrailingSlash(path));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
@@ -23,6 +24,7 @@ namespace FileUtil {
|
||||
enum class UserPath {
|
||||
CacheDir,
|
||||
ConfigDir,
|
||||
KeysDir,
|
||||
LogDir,
|
||||
NANDDir,
|
||||
RootDir,
|
||||
@@ -125,6 +127,8 @@ bool SetCurrentDir(const std::string& directory);
|
||||
// directory. To be used in "multi-user" mode (that is, installed).
|
||||
const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
|
||||
|
||||
std::string GetHactoolConfigurationPath();
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
std::string GetSysDirectory();
|
||||
|
||||
@@ -178,8 +182,12 @@ std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t la
|
||||
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
|
||||
}
|
||||
|
||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'.
|
||||
std::string SanitizePath(std::string_view path);
|
||||
enum class DirectorySeparator { ForwardSlash, BackwardSlash, PlatformDefault };
|
||||
|
||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
||||
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
||||
std::string SanitizePath(std::string_view path,
|
||||
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
||||
|
||||
// simple wrapper for cstdlib file functions to
|
||||
// hopefully will make error checking easier
|
||||
@@ -204,39 +212,42 @@ public:
|
||||
|
||||
template <typename T>
|
||||
size_t ReadArray(T* data, size_t length) const {
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
|
||||
if (!IsOpen())
|
||||
return -1;
|
||||
if (!IsOpen()) {
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
return std::fread(data, sizeof(T), length, m_file);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t WriteArray(const T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
if (!IsOpen())
|
||||
return -1;
|
||||
if (!IsOpen()) {
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
return std::fwrite(data, sizeof(T), length, m_file);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t ReadBytes(T* data, size_t length) const {
|
||||
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t WriteBytes(const T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
return WriteArray(reinterpret_cast<const char*>(data), length);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t WriteObject(const T& object) {
|
||||
static_assert(!std::is_pointer<T>::value, "Given object is a pointer");
|
||||
static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer");
|
||||
return WriteArray(&object, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ static inline u64 ComputeHash64(const void* data, size_t len) {
|
||||
*/
|
||||
template <typename T>
|
||||
static inline u64 ComputeStructHash64(const T& data) {
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"Type passed to ComputeStructHash64 must be trivially copyable");
|
||||
return ComputeHash64(&data, sizeof(data));
|
||||
}
|
||||
@@ -38,7 +38,7 @@ template <typename T>
|
||||
struct HashableStruct {
|
||||
// In addition to being trivially copyable, T must also have a trivial default constructor,
|
||||
// because any member initialization would be overridden by memset
|
||||
static_assert(std::is_trivial<T>(), "Type passed to HashableStruct must be trivial");
|
||||
static_assert(std::is_trivial_v<T>, "Type passed to HashableStruct must be trivial");
|
||||
/*
|
||||
* We use a union because "implicitly-defined copy/move constructor for a union X copies the
|
||||
* object representation of X." and "implicitly-defined copy assignment operator for a union X
|
||||
|
||||
@@ -168,31 +168,47 @@ void FileBackend::Write(const Entry& entry) {
|
||||
SUB(Service, AM) \
|
||||
SUB(Service, AOC) \
|
||||
SUB(Service, APM) \
|
||||
SUB(Service, ARP) \
|
||||
SUB(Service, BCAT) \
|
||||
SUB(Service, BPC) \
|
||||
SUB(Service, BTDRV) \
|
||||
SUB(Service, BTM) \
|
||||
SUB(Service, Capture) \
|
||||
SUB(Service, ERPT) \
|
||||
SUB(Service, ETicket) \
|
||||
SUB(Service, EUPLD) \
|
||||
SUB(Service, Fatal) \
|
||||
SUB(Service, FGM) \
|
||||
SUB(Service, Friend) \
|
||||
SUB(Service, FS) \
|
||||
SUB(Service, GRC) \
|
||||
SUB(Service, HID) \
|
||||
SUB(Service, LBL) \
|
||||
SUB(Service, LDN) \
|
||||
SUB(Service, LDR) \
|
||||
SUB(Service, LM) \
|
||||
SUB(Service, Migration) \
|
||||
SUB(Service, Mii) \
|
||||
SUB(Service, MM) \
|
||||
SUB(Service, NCM) \
|
||||
SUB(Service, NFC) \
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NIM) \
|
||||
SUB(Service, NS) \
|
||||
SUB(Service, NVDRV) \
|
||||
SUB(Service, PCIE) \
|
||||
SUB(Service, PCTL) \
|
||||
SUB(Service, PCV) \
|
||||
SUB(Service, PM) \
|
||||
SUB(Service, PREPO) \
|
||||
SUB(Service, PSC) \
|
||||
SUB(Service, SET) \
|
||||
SUB(Service, SM) \
|
||||
SUB(Service, SPL) \
|
||||
SUB(Service, SSL) \
|
||||
SUB(Service, Time) \
|
||||
SUB(Service, USB) \
|
||||
SUB(Service, VI) \
|
||||
SUB(Service, WLAN) \
|
||||
CLS(HW) \
|
||||
@@ -211,6 +227,7 @@ void FileBackend::Write(const Entry& entry) {
|
||||
CLS(Input) \
|
||||
CLS(Network) \
|
||||
CLS(Loader) \
|
||||
CLS(Crypto) \
|
||||
CLS(WebService)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
|
||||
@@ -54,32 +54,48 @@ enum class Class : ClassType {
|
||||
Service_AM, ///< The AM (Applet manager) service
|
||||
Service_AOC, ///< The AOC (AddOn Content) service
|
||||
Service_APM, ///< The APM (Performance) service
|
||||
Service_ARP, ///< The ARP service
|
||||
Service_Audio, ///< The Audio (Audio control) service
|
||||
Service_BCAT, ///< The BCAT service
|
||||
Service_BPC, ///< The BPC service
|
||||
Service_BTDRV, ///< The Bluetooth driver service
|
||||
Service_BTM, ///< The BTM service
|
||||
Service_Capture, ///< The capture service
|
||||
Service_ERPT, ///< The error reporting service
|
||||
Service_ETicket, ///< The ETicket service
|
||||
Service_EUPLD, ///< The error upload service
|
||||
Service_Fatal, ///< The Fatal service
|
||||
Service_FGM, ///< The FGM service
|
||||
Service_Friend, ///< The friend service
|
||||
Service_FS, ///< The FS (Filesystem) service
|
||||
Service_GRC, ///< The game recording service
|
||||
Service_HID, ///< The HID (Human interface device) service
|
||||
Service_LBL, ///< The LBL (LCD backlight) service
|
||||
Service_LDN, ///< The LDN (Local domain network) service
|
||||
Service_LDR, ///< The loader service
|
||||
Service_LM, ///< The LM (Logger) service
|
||||
Service_Migration, ///< The migration service
|
||||
Service_Mii, ///< The Mii service
|
||||
Service_MM, ///< The MM (Multimedia) service
|
||||
Service_NCM, ///< The NCM service
|
||||
Service_NFC, ///< The NFC (Near-field communication) service
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NIM, ///< The NIM service
|
||||
Service_NS, ///< The NS services
|
||||
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
||||
Service_PCIE, ///< The PCIe service
|
||||
Service_PCTL, ///< The PCTL (Parental control) service
|
||||
Service_PCV, ///< The PCV service
|
||||
Service_PM, ///< The PM service
|
||||
Service_PREPO, ///< The PREPO (Play report) service
|
||||
Service_PSC, ///< The PSC service
|
||||
Service_SET, ///< The SET (Settings) service
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
Service_SSL, ///< The SSL service
|
||||
Service_Time, ///< The time service
|
||||
Service_USB, ///< The USB (Universal Serial Bus) service
|
||||
Service_VI, ///< The VI (Video interface) service
|
||||
Service_WLAN, ///< The WLAN (Wireless local area network) service
|
||||
HW, ///< Low-level hardware emulation
|
||||
@@ -96,6 +112,7 @@ enum class Class : ClassType {
|
||||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
Loader, ///< ROM loader
|
||||
Crypto, ///< Cryptographic engine/functions
|
||||
Input, ///< Input emulation
|
||||
Network, ///< Network emulation
|
||||
WebService, ///< Interface to yuzu Web Services
|
||||
|
||||
@@ -19,12 +19,12 @@ inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start
|
||||
|
||||
template <class T>
|
||||
struct Rectangle {
|
||||
T left;
|
||||
T top;
|
||||
T right;
|
||||
T bottom;
|
||||
T left{};
|
||||
T top{};
|
||||
T right{};
|
||||
T bottom{};
|
||||
|
||||
Rectangle() {}
|
||||
Rectangle() = default;
|
||||
|
||||
Rectangle(T left, T top, T right, T bottom)
|
||||
: left(left), top(top), right(right), bottom(bottom) {}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -33,9 +33,11 @@ public:
|
||||
bool Empty() const {
|
||||
return !read_ptr->next.load();
|
||||
}
|
||||
|
||||
T& Front() const {
|
||||
return read_ptr->current;
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
void Push(Arg&& t) {
|
||||
// create the element, add it to the queue
|
||||
@@ -108,15 +110,41 @@ private:
|
||||
// single reader, multiple writer queue
|
||||
|
||||
template <typename T, bool NeedSize = true>
|
||||
class MPSCQueue : public SPSCQueue<T, NeedSize> {
|
||||
class MPSCQueue {
|
||||
public:
|
||||
u32 Size() const {
|
||||
return spsc_queue.Size();
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return spsc_queue.Empty();
|
||||
}
|
||||
|
||||
T& Front() const {
|
||||
return spsc_queue.Front();
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
void Push(Arg&& t) {
|
||||
std::lock_guard<std::mutex> lock(write_lock);
|
||||
SPSCQueue<T, NeedSize>::Push(t);
|
||||
spsc_queue.Push(t);
|
||||
}
|
||||
|
||||
void Pop() {
|
||||
return spsc_queue.Pop();
|
||||
}
|
||||
|
||||
bool Pop(T& t) {
|
||||
return spsc_queue.Pop(t);
|
||||
}
|
||||
|
||||
// not thread-safe
|
||||
void Clear() {
|
||||
spsc_queue.Clear();
|
||||
}
|
||||
|
||||
private:
|
||||
SPSCQueue<T, NeedSize> spsc_queue;
|
||||
std::mutex write_lock;
|
||||
};
|
||||
} // namespace Common
|
||||
|
||||
@@ -42,140 +42,136 @@ class Vec3;
|
||||
template <typename T>
|
||||
class Vec4;
|
||||
|
||||
template <typename T>
|
||||
static inline Vec2<T> MakeVec(const T& x, const T& y);
|
||||
template <typename T>
|
||||
static inline Vec3<T> MakeVec(const T& x, const T& y, const T& z);
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w);
|
||||
|
||||
template <typename T>
|
||||
class Vec2 {
|
||||
public:
|
||||
T x{};
|
||||
T y{};
|
||||
|
||||
Vec2() = default;
|
||||
Vec2(const T& _x, const T& _y) : x(_x), y(_y) {}
|
||||
constexpr Vec2() = default;
|
||||
constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {}
|
||||
|
||||
template <typename T2>
|
||||
Vec2<T2> Cast() const {
|
||||
return Vec2<T2>((T2)x, (T2)y);
|
||||
constexpr Vec2<T2> Cast() const {
|
||||
return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y));
|
||||
}
|
||||
|
||||
static Vec2 AssignToAll(const T& f) {
|
||||
return Vec2<T>(f, f);
|
||||
static constexpr Vec2 AssignToAll(const T& f) {
|
||||
return Vec2{f, f};
|
||||
}
|
||||
|
||||
Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
|
||||
return MakeVec(x + other.x, y + other.y);
|
||||
constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
|
||||
return {x + other.x, y + other.y};
|
||||
}
|
||||
void operator+=(const Vec2& other) {
|
||||
constexpr Vec2& operator+=(const Vec2& other) {
|
||||
x += other.x;
|
||||
y += other.y;
|
||||
return *this;
|
||||
}
|
||||
Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
|
||||
return MakeVec(x - other.x, y - other.y);
|
||||
constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
|
||||
return {x - other.x, y - other.y};
|
||||
}
|
||||
void operator-=(const Vec2& other) {
|
||||
constexpr Vec2& operator-=(const Vec2& other) {
|
||||
x -= other.x;
|
||||
y -= other.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
Vec2<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
|
||||
return MakeVec(-x, -y);
|
||||
constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
|
||||
return {-x, -y};
|
||||
}
|
||||
Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
|
||||
return MakeVec(x * other.x, y * other.y);
|
||||
}
|
||||
template <typename V>
|
||||
Vec2<decltype(T{} * V{})> operator*(const V& f) const {
|
||||
return MakeVec(x * f, y * f);
|
||||
}
|
||||
template <typename V>
|
||||
void operator*=(const V& f) {
|
||||
*this = *this * f;
|
||||
}
|
||||
template <typename V>
|
||||
Vec2<decltype(T{} / V{})> operator/(const V& f) const {
|
||||
return MakeVec(x / f, y / f);
|
||||
}
|
||||
template <typename V>
|
||||
void operator/=(const V& f) {
|
||||
*this = *this / f;
|
||||
constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
|
||||
return {x * other.x, y * other.y};
|
||||
}
|
||||
|
||||
T Length2() const {
|
||||
template <typename V>
|
||||
constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
|
||||
return {x * f, y * f};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec2& operator*=(const V& f) {
|
||||
*this = *this * f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
|
||||
return {x / f, y / f};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec2& operator/=(const V& f) {
|
||||
*this = *this / f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr T Length2() const {
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
||||
// Only implemented for T=float
|
||||
float Length() const;
|
||||
void SetLength(const float l);
|
||||
Vec2 WithLength(const float l) const;
|
||||
float Distance2To(Vec2& other);
|
||||
Vec2 Normalized() const;
|
||||
float Normalize(); // returns the previous length, which is often useful
|
||||
|
||||
T& operator[](int i) // allow vector[1] = 3 (vector.y=3)
|
||||
{
|
||||
constexpr T& operator[](std::size_t i) {
|
||||
return *((&x) + i);
|
||||
}
|
||||
T operator[](const int i) const {
|
||||
constexpr const T& operator[](std::size_t i) const {
|
||||
return *((&x) + i);
|
||||
}
|
||||
|
||||
void SetZero() {
|
||||
constexpr void SetZero() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
// Common aliases: UV (texel coordinates), ST (texture coordinates)
|
||||
T& u() {
|
||||
constexpr T& u() {
|
||||
return x;
|
||||
}
|
||||
T& v() {
|
||||
constexpr T& v() {
|
||||
return y;
|
||||
}
|
||||
T& s() {
|
||||
constexpr T& s() {
|
||||
return x;
|
||||
}
|
||||
T& t() {
|
||||
constexpr T& t() {
|
||||
return y;
|
||||
}
|
||||
|
||||
const T& u() const {
|
||||
constexpr const T& u() const {
|
||||
return x;
|
||||
}
|
||||
const T& v() const {
|
||||
constexpr const T& v() const {
|
||||
return y;
|
||||
}
|
||||
const T& s() const {
|
||||
constexpr const T& s() const {
|
||||
return x;
|
||||
}
|
||||
const T& t() const {
|
||||
constexpr const T& t() const {
|
||||
return y;
|
||||
}
|
||||
|
||||
// swizzlers - create a subvector of specific components
|
||||
const Vec2 yx() const {
|
||||
constexpr Vec2 yx() const {
|
||||
return Vec2(y, x);
|
||||
}
|
||||
const Vec2 vu() const {
|
||||
constexpr Vec2 vu() const {
|
||||
return Vec2(y, x);
|
||||
}
|
||||
const Vec2 ts() const {
|
||||
constexpr Vec2 ts() const {
|
||||
return Vec2(y, x);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename V>
|
||||
Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
|
||||
constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
|
||||
return Vec2<T>(f * vec.x, f * vec.y);
|
||||
}
|
||||
|
||||
typedef Vec2<float> Vec2f;
|
||||
using Vec2f = Vec2<float>;
|
||||
|
||||
template <>
|
||||
inline float Vec2<float>::Length() const {
|
||||
@@ -196,147 +192,151 @@ public:
|
||||
T y{};
|
||||
T z{};
|
||||
|
||||
Vec3() = default;
|
||||
Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {}
|
||||
constexpr Vec3() = default;
|
||||
constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {}
|
||||
|
||||
template <typename T2>
|
||||
Vec3<T2> Cast() const {
|
||||
return MakeVec<T2>((T2)x, (T2)y, (T2)z);
|
||||
constexpr Vec3<T2> Cast() const {
|
||||
return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z));
|
||||
}
|
||||
|
||||
// Only implemented for T=int and T=float
|
||||
static Vec3 FromRGB(unsigned int rgb);
|
||||
unsigned int ToRGB() const; // alpha bits set to zero
|
||||
|
||||
static Vec3 AssignToAll(const T& f) {
|
||||
return MakeVec(f, f, f);
|
||||
static constexpr Vec3 AssignToAll(const T& f) {
|
||||
return Vec3(f, f, f);
|
||||
}
|
||||
|
||||
Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
|
||||
return MakeVec(x + other.x, y + other.y, z + other.z);
|
||||
constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
|
||||
return {x + other.x, y + other.y, z + other.z};
|
||||
}
|
||||
void operator+=(const Vec3& other) {
|
||||
|
||||
constexpr Vec3& operator+=(const Vec3& other) {
|
||||
x += other.x;
|
||||
y += other.y;
|
||||
z += other.z;
|
||||
return *this;
|
||||
}
|
||||
Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
|
||||
return MakeVec(x - other.x, y - other.y, z - other.z);
|
||||
|
||||
constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
|
||||
return {x - other.x, y - other.y, z - other.z};
|
||||
}
|
||||
void operator-=(const Vec3& other) {
|
||||
|
||||
constexpr Vec3& operator-=(const Vec3& other) {
|
||||
x -= other.x;
|
||||
y -= other.y;
|
||||
z -= other.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
Vec3<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
|
||||
return MakeVec(-x, -y, -z);
|
||||
}
|
||||
Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
|
||||
return MakeVec(x * other.x, y * other.y, z * other.z);
|
||||
}
|
||||
template <typename V>
|
||||
Vec3<decltype(T{} * V{})> operator*(const V& f) const {
|
||||
return MakeVec(x * f, y * f, z * f);
|
||||
}
|
||||
template <typename V>
|
||||
void operator*=(const V& f) {
|
||||
*this = *this * f;
|
||||
}
|
||||
template <typename V>
|
||||
Vec3<decltype(T{} / V{})> operator/(const V& f) const {
|
||||
return MakeVec(x / f, y / f, z / f);
|
||||
}
|
||||
template <typename V>
|
||||
void operator/=(const V& f) {
|
||||
*this = *this / f;
|
||||
constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
|
||||
return {-x, -y, -z};
|
||||
}
|
||||
|
||||
T Length2() const {
|
||||
constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
|
||||
return {x * other.x, y * other.y, z * other.z};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
|
||||
return {x * f, y * f, z * f};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec3& operator*=(const V& f) {
|
||||
*this = *this * f;
|
||||
return *this;
|
||||
}
|
||||
template <typename V>
|
||||
constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
|
||||
return {x / f, y / f, z / f};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec3& operator/=(const V& f) {
|
||||
*this = *this / f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr T Length2() const {
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
// Only implemented for T=float
|
||||
float Length() const;
|
||||
void SetLength(const float l);
|
||||
Vec3 WithLength(const float l) const;
|
||||
float Distance2To(Vec3& other);
|
||||
Vec3 Normalized() const;
|
||||
float Normalize(); // returns the previous length, which is often useful
|
||||
|
||||
T& operator[](int i) // allow vector[2] = 3 (vector.z=3)
|
||||
{
|
||||
return *((&x) + i);
|
||||
}
|
||||
T operator[](const int i) const {
|
||||
constexpr T& operator[](std::size_t i) {
|
||||
return *((&x) + i);
|
||||
}
|
||||
|
||||
void SetZero() {
|
||||
constexpr const T& operator[](std::size_t i) const {
|
||||
return *((&x) + i);
|
||||
}
|
||||
|
||||
constexpr void SetZero() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
}
|
||||
|
||||
// Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates)
|
||||
T& u() {
|
||||
constexpr T& u() {
|
||||
return x;
|
||||
}
|
||||
T& v() {
|
||||
constexpr T& v() {
|
||||
return y;
|
||||
}
|
||||
T& w() {
|
||||
constexpr T& w() {
|
||||
return z;
|
||||
}
|
||||
|
||||
T& r() {
|
||||
constexpr T& r() {
|
||||
return x;
|
||||
}
|
||||
T& g() {
|
||||
constexpr T& g() {
|
||||
return y;
|
||||
}
|
||||
T& b() {
|
||||
constexpr T& b() {
|
||||
return z;
|
||||
}
|
||||
|
||||
T& s() {
|
||||
constexpr T& s() {
|
||||
return x;
|
||||
}
|
||||
T& t() {
|
||||
constexpr T& t() {
|
||||
return y;
|
||||
}
|
||||
T& q() {
|
||||
constexpr T& q() {
|
||||
return z;
|
||||
}
|
||||
|
||||
const T& u() const {
|
||||
constexpr const T& u() const {
|
||||
return x;
|
||||
}
|
||||
const T& v() const {
|
||||
constexpr const T& v() const {
|
||||
return y;
|
||||
}
|
||||
const T& w() const {
|
||||
constexpr const T& w() const {
|
||||
return z;
|
||||
}
|
||||
|
||||
const T& r() const {
|
||||
constexpr const T& r() const {
|
||||
return x;
|
||||
}
|
||||
const T& g() const {
|
||||
constexpr const T& g() const {
|
||||
return y;
|
||||
}
|
||||
const T& b() const {
|
||||
constexpr const T& b() const {
|
||||
return z;
|
||||
}
|
||||
|
||||
const T& s() const {
|
||||
constexpr const T& s() const {
|
||||
return x;
|
||||
}
|
||||
const T& t() const {
|
||||
constexpr const T& t() const {
|
||||
return y;
|
||||
}
|
||||
const T& q() const {
|
||||
constexpr const T& q() const {
|
||||
return z;
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ public:
|
||||
// _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all
|
||||
// component names (x<->r) and permutations (xy<->yx)
|
||||
#define _DEFINE_SWIZZLER2(a, b, name) \
|
||||
const Vec2<T> name() const { \
|
||||
constexpr Vec2<T> name() const { \
|
||||
return Vec2<T>(a, b); \
|
||||
}
|
||||
#define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \
|
||||
@@ -366,7 +366,7 @@ public:
|
||||
};
|
||||
|
||||
template <typename T, typename V>
|
||||
Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
|
||||
constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
|
||||
return Vec3<T>(f * vec.x, f * vec.y, f * vec.z);
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ inline float Vec3<float>::Normalize() {
|
||||
return length;
|
||||
}
|
||||
|
||||
typedef Vec3<float> Vec3f;
|
||||
using Vec3f = Vec3<float>;
|
||||
|
||||
template <typename T>
|
||||
class Vec4 {
|
||||
@@ -397,86 +397,88 @@ public:
|
||||
T z{};
|
||||
T w{};
|
||||
|
||||
Vec4() = default;
|
||||
Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {}
|
||||
constexpr Vec4() = default;
|
||||
constexpr Vec4(const T& x_, const T& y_, const T& z_, const T& w_)
|
||||
: x(x_), y(y_), z(z_), w(w_) {}
|
||||
|
||||
template <typename T2>
|
||||
Vec4<T2> Cast() const {
|
||||
return Vec4<T2>((T2)x, (T2)y, (T2)z, (T2)w);
|
||||
constexpr Vec4<T2> Cast() const {
|
||||
return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z),
|
||||
static_cast<T2>(w));
|
||||
}
|
||||
|
||||
// Only implemented for T=int and T=float
|
||||
static Vec4 FromRGBA(unsigned int rgba);
|
||||
unsigned int ToRGBA() const;
|
||||
|
||||
static Vec4 AssignToAll(const T& f) {
|
||||
return Vec4<T>(f, f, f, f);
|
||||
static constexpr Vec4 AssignToAll(const T& f) {
|
||||
return Vec4(f, f, f, f);
|
||||
}
|
||||
|
||||
Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
|
||||
return MakeVec(x + other.x, y + other.y, z + other.z, w + other.w);
|
||||
constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
|
||||
return {x + other.x, y + other.y, z + other.z, w + other.w};
|
||||
}
|
||||
void operator+=(const Vec4& other) {
|
||||
|
||||
constexpr Vec4& operator+=(const Vec4& other) {
|
||||
x += other.x;
|
||||
y += other.y;
|
||||
z += other.z;
|
||||
w += other.w;
|
||||
return *this;
|
||||
}
|
||||
Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
|
||||
return MakeVec(x - other.x, y - other.y, z - other.z, w - other.w);
|
||||
|
||||
constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
|
||||
return {x - other.x, y - other.y, z - other.z, w - other.w};
|
||||
}
|
||||
void operator-=(const Vec4& other) {
|
||||
|
||||
constexpr Vec4& operator-=(const Vec4& other) {
|
||||
x -= other.x;
|
||||
y -= other.y;
|
||||
z -= other.z;
|
||||
w -= other.w;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
Vec4<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
|
||||
return MakeVec(-x, -y, -z, -w);
|
||||
}
|
||||
Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
|
||||
return MakeVec(x * other.x, y * other.y, z * other.z, w * other.w);
|
||||
}
|
||||
template <typename V>
|
||||
Vec4<decltype(T{} * V{})> operator*(const V& f) const {
|
||||
return MakeVec(x * f, y * f, z * f, w * f);
|
||||
}
|
||||
template <typename V>
|
||||
void operator*=(const V& f) {
|
||||
*this = *this * f;
|
||||
}
|
||||
template <typename V>
|
||||
Vec4<decltype(T{} / V{})> operator/(const V& f) const {
|
||||
return MakeVec(x / f, y / f, z / f, w / f);
|
||||
}
|
||||
template <typename V>
|
||||
void operator/=(const V& f) {
|
||||
*this = *this / f;
|
||||
constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
|
||||
return {-x, -y, -z, -w};
|
||||
}
|
||||
|
||||
T Length2() const {
|
||||
constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
|
||||
return {x * other.x, y * other.y, z * other.z, w * other.w};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
|
||||
return {x * f, y * f, z * f, w * f};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec4& operator*=(const V& f) {
|
||||
*this = *this * f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
|
||||
return {x / f, y / f, z / f, w / f};
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr Vec4& operator/=(const V& f) {
|
||||
*this = *this / f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr T Length2() const {
|
||||
return x * x + y * y + z * z + w * w;
|
||||
}
|
||||
|
||||
// Only implemented for T=float
|
||||
float Length() const;
|
||||
void SetLength(const float l);
|
||||
Vec4 WithLength(const float l) const;
|
||||
float Distance2To(Vec4& other);
|
||||
Vec4 Normalized() const;
|
||||
float Normalize(); // returns the previous length, which is often useful
|
||||
|
||||
T& operator[](int i) // allow vector[2] = 3 (vector.z=3)
|
||||
{
|
||||
return *((&x) + i);
|
||||
}
|
||||
T operator[](const int i) const {
|
||||
constexpr T& operator[](std::size_t i) {
|
||||
return *((&x) + i);
|
||||
}
|
||||
|
||||
void SetZero() {
|
||||
constexpr const T& operator[](std::size_t i) const {
|
||||
return *((&x) + i);
|
||||
}
|
||||
|
||||
constexpr void SetZero() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
@@ -484,29 +486,29 @@ public:
|
||||
}
|
||||
|
||||
// Common alias: RGBA (colors)
|
||||
T& r() {
|
||||
constexpr T& r() {
|
||||
return x;
|
||||
}
|
||||
T& g() {
|
||||
constexpr T& g() {
|
||||
return y;
|
||||
}
|
||||
T& b() {
|
||||
constexpr T& b() {
|
||||
return z;
|
||||
}
|
||||
T& a() {
|
||||
constexpr T& a() {
|
||||
return w;
|
||||
}
|
||||
|
||||
const T& r() const {
|
||||
constexpr const T& r() const {
|
||||
return x;
|
||||
}
|
||||
const T& g() const {
|
||||
constexpr const T& g() const {
|
||||
return y;
|
||||
}
|
||||
const T& b() const {
|
||||
constexpr const T& b() const {
|
||||
return z;
|
||||
}
|
||||
const T& a() const {
|
||||
constexpr const T& a() const {
|
||||
return w;
|
||||
}
|
||||
|
||||
@@ -518,7 +520,7 @@ public:
|
||||
// DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and
|
||||
// permutations (xy<->yx)
|
||||
#define _DEFINE_SWIZZLER2(a, b, name) \
|
||||
const Vec2<T> name() const { \
|
||||
constexpr Vec2<T> name() const { \
|
||||
return Vec2<T>(a, b); \
|
||||
}
|
||||
#define DEFINE_SWIZZLER2_COMP1(a, a2) \
|
||||
@@ -545,7 +547,7 @@ public:
|
||||
#undef _DEFINE_SWIZZLER2
|
||||
|
||||
#define _DEFINE_SWIZZLER3(a, b, c, name) \
|
||||
const Vec3<T> name() const { \
|
||||
constexpr Vec3<T> name() const { \
|
||||
return Vec3<T>(a, b, c); \
|
||||
}
|
||||
#define DEFINE_SWIZZLER3_COMP1(a, a2) \
|
||||
@@ -579,51 +581,51 @@ public:
|
||||
};
|
||||
|
||||
template <typename T, typename V>
|
||||
Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
|
||||
return MakeVec(f * vec.x, f * vec.y, f * vec.z, f * vec.w);
|
||||
constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
|
||||
return {f * vec.x, f * vec.y, f * vec.z, f * vec.w};
|
||||
}
|
||||
|
||||
typedef Vec4<float> Vec4f;
|
||||
using Vec4f = Vec4<float>;
|
||||
|
||||
template <typename T>
|
||||
static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) {
|
||||
constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
|
||||
constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
|
||||
constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) {
|
||||
return MakeVec(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
|
||||
constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) {
|
||||
return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
|
||||
}
|
||||
|
||||
// linear interpolation via float: 0.0=begin, 1.0=end
|
||||
template <typename X>
|
||||
static inline decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
|
||||
const float t) {
|
||||
constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
|
||||
const float t) {
|
||||
return begin * (1.f - t) + end * t;
|
||||
}
|
||||
|
||||
// linear interpolation via int: 0=begin, base=end
|
||||
template <typename X, int base>
|
||||
static inline decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end,
|
||||
const int t) {
|
||||
constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end,
|
||||
const int t) {
|
||||
return (begin * (base - t) + end * t) / base;
|
||||
}
|
||||
|
||||
// bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second
|
||||
// interpolation.
|
||||
template <typename X>
|
||||
inline auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s,
|
||||
const float t) {
|
||||
constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s,
|
||||
const float t) {
|
||||
auto y0 = Lerp(x00, x01, s);
|
||||
auto y1 = Lerp(x10, x11, s);
|
||||
return Lerp(y0, y1, t);
|
||||
@@ -631,42 +633,42 @@ inline auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x1
|
||||
|
||||
// Utility vector factories
|
||||
template <typename T>
|
||||
static inline Vec2<T> MakeVec(const T& x, const T& y) {
|
||||
constexpr Vec2<T> MakeVec(const T& x, const T& y) {
|
||||
return Vec2<T>{x, y};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
|
||||
constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
|
||||
return Vec3<T>{x, y, z};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
|
||||
constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
|
||||
return MakeVec(x, y, zw[0], zw[1]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
|
||||
constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
|
||||
return MakeVec(xy[0], xy[1], z);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
|
||||
constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
|
||||
return MakeVec(x, yz[0], yz[1]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
|
||||
constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
|
||||
return Vec4<T>{x, y, z, w};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
|
||||
constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
|
||||
return MakeVec(xy[0], xy[1], z, w);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
|
||||
constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
|
||||
return MakeVec(x, yz[0], yz[1], w);
|
||||
}
|
||||
|
||||
@@ -674,17 +676,17 @@ static inline Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
|
||||
// Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error
|
||||
// out soon enough due to misuse of the returned structure.
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
|
||||
constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
|
||||
return MakeVec(xy[0], xy[1], zw[0], zw[1]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
|
||||
constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
|
||||
return MakeVec(xyz[0], xyz[1], xyz[2], w);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
|
||||
constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
|
||||
return MakeVec(x, yzw[0], yzw[1], yzw[2]);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) {
|
||||
|
||||
template <typename T>
|
||||
inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
|
||||
static_assert(std::is_pointer<T>(), "Argument must be a (function) pointer.");
|
||||
static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
|
||||
size_t addr = reinterpret_cast<size_t>(f);
|
||||
if (IsWithin2G(code, addr)) {
|
||||
code.call(f);
|
||||
|
||||
@@ -12,6 +12,16 @@ add_library(core STATIC
|
||||
core_timing.h
|
||||
core_timing_util.cpp
|
||||
core_timing_util.h
|
||||
crypto/aes_util.cpp
|
||||
crypto/aes_util.h
|
||||
crypto/encryption_layer.cpp
|
||||
crypto/encryption_layer.h
|
||||
crypto/key_manager.cpp
|
||||
crypto/key_manager.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
crypto/ctr_encryption_layer.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
@@ -63,12 +73,10 @@ add_library(core STATIC
|
||||
hle/kernel/hle_ipc.h
|
||||
hle/kernel/kernel.cpp
|
||||
hle/kernel/kernel.h
|
||||
hle/kernel/memory.cpp
|
||||
hle/kernel/memory.h
|
||||
hle/kernel/mutex.cpp
|
||||
hle/kernel/mutex.h
|
||||
hle/kernel/object_address_table.cpp
|
||||
hle/kernel/object_address_table.h
|
||||
hle/kernel/object.cpp
|
||||
hle/kernel/object.h
|
||||
hle/kernel/process.cpp
|
||||
hle/kernel/process.h
|
||||
hle/kernel/resource_limit.cpp
|
||||
@@ -96,8 +104,6 @@ add_library(core STATIC
|
||||
hle/lock.cpp
|
||||
hle/lock.h
|
||||
hle/result.h
|
||||
hle/romfs.cpp
|
||||
hle/romfs.h
|
||||
hle/service/acc/acc.cpp
|
||||
hle/service/acc/acc.h
|
||||
hle/service/acc/acc_aa.cpp
|
||||
@@ -114,23 +120,41 @@ add_library(core STATIC
|
||||
hle/service/am/applet_ae.h
|
||||
hle/service/am/applet_oe.cpp
|
||||
hle/service/am/applet_oe.h
|
||||
hle/service/am/idle.cpp
|
||||
hle/service/am/idle.h
|
||||
hle/service/am/omm.cpp
|
||||
hle/service/am/omm.h
|
||||
hle/service/am/spsm.cpp
|
||||
hle/service/am/spsm.h
|
||||
hle/service/aoc/aoc_u.cpp
|
||||
hle/service/aoc/aoc_u.h
|
||||
hle/service/apm/apm.cpp
|
||||
hle/service/apm/apm.h
|
||||
hle/service/apm/interface.cpp
|
||||
hle/service/apm/interface.h
|
||||
hle/service/arp/arp.cpp
|
||||
hle/service/arp/arp.h
|
||||
hle/service/audio/audctl.cpp
|
||||
hle/service/audio/audctl.h
|
||||
hle/service/audio/auddbg.cpp
|
||||
hle/service/audio/auddbg.h
|
||||
hle/service/audio/audin_a.cpp
|
||||
hle/service/audio/audin_a.h
|
||||
hle/service/audio/audin_u.cpp
|
||||
hle/service/audio/audin_u.h
|
||||
hle/service/audio/audio.cpp
|
||||
hle/service/audio/audio.h
|
||||
hle/service/audio/audout_a.cpp
|
||||
hle/service/audio/audout_a.h
|
||||
hle/service/audio/audout_u.cpp
|
||||
hle/service/audio/audout_u.h
|
||||
hle/service/audio/audrec_a.cpp
|
||||
hle/service/audio/audrec_a.h
|
||||
hle/service/audio/audrec_u.cpp
|
||||
hle/service/audio/audrec_u.h
|
||||
hle/service/audio/audren_a.cpp
|
||||
hle/service/audio/audren_a.h
|
||||
hle/service/audio/audren_u.cpp
|
||||
hle/service/audio/audren_u.cpp
|
||||
hle/service/audio/audren_u.h
|
||||
hle/service/audio/audren_u.h
|
||||
hle/service/audio/codecctl.cpp
|
||||
hle/service/audio/codecctl.h
|
||||
@@ -140,10 +164,14 @@ add_library(core STATIC
|
||||
hle/service/bcat/bcat.h
|
||||
hle/service/bcat/module.cpp
|
||||
hle/service/bcat/module.h
|
||||
hle/service/bpc/bpc.cpp
|
||||
hle/service/bpc/bpc.h
|
||||
hle/service/btdrv/btdrv.cpp
|
||||
hle/service/btdrv/btdrv.h
|
||||
hle/service/btm/btm.cpp
|
||||
hle/service/btm/btm.h
|
||||
hle/service/caps/caps.cpp
|
||||
hle/service/caps/caps.h
|
||||
hle/service/erpt/erpt.cpp
|
||||
hle/service/erpt/erpt.h
|
||||
hle/service/es/es.cpp
|
||||
@@ -158,6 +186,10 @@ add_library(core STATIC
|
||||
hle/service/fatal/fatal_u.h
|
||||
hle/service/filesystem/filesystem.cpp
|
||||
hle/service/filesystem/filesystem.h
|
||||
hle/service/filesystem/fsp_ldr.cpp
|
||||
hle/service/filesystem/fsp_ldr.h
|
||||
hle/service/filesystem/fsp_pr.cpp
|
||||
hle/service/filesystem/fsp_pr.h
|
||||
hle/service/filesystem/fsp_srv.cpp
|
||||
hle/service/filesystem/fsp_srv.h
|
||||
hle/service/fgm/fgm.cpp
|
||||
@@ -182,6 +214,8 @@ add_library(core STATIC
|
||||
hle/service/ldr/ldr.h
|
||||
hle/service/lm/lm.cpp
|
||||
hle/service/lm/lm.h
|
||||
hle/service/mig/mig.cpp
|
||||
hle/service/mig/mig.h
|
||||
hle/service/mii/mii.cpp
|
||||
hle/service/mii/mii.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
@@ -227,14 +261,20 @@ add_library(core STATIC
|
||||
hle/service/nvflinger/buffer_queue.h
|
||||
hle/service/nvflinger/nvflinger.cpp
|
||||
hle/service/nvflinger/nvflinger.h
|
||||
hle/service/pcie/pcie.cpp
|
||||
hle/service/pcie/pcie.h
|
||||
hle/service/pctl/module.cpp
|
||||
hle/service/pctl/module.h
|
||||
hle/service/pctl/pctl.cpp
|
||||
hle/service/pctl/pctl.h
|
||||
hle/service/pcv/pcv.cpp
|
||||
hle/service/pcv/pcv.h
|
||||
hle/service/pm/pm.cpp
|
||||
hle/service/pm/pm.h
|
||||
hle/service/prepo/prepo.cpp
|
||||
hle/service/prepo/prepo.h
|
||||
hle/service/psc/psc.cpp
|
||||
hle/service/psc/psc.h
|
||||
hle/service/service.cpp
|
||||
hle/service/service.h
|
||||
hle/service/set/set.cpp
|
||||
@@ -273,6 +313,8 @@ add_library(core STATIC
|
||||
hle/service/time/interface.h
|
||||
hle/service/time/time.cpp
|
||||
hle/service/time/time.h
|
||||
hle/service/usb/usb.cpp
|
||||
hle/service/usb/usb.h
|
||||
hle/service/vi/vi.cpp
|
||||
hle/service/vi/vi.h
|
||||
hle/service/vi/vi_m.cpp
|
||||
@@ -283,10 +325,6 @@ add_library(core STATIC
|
||||
hle/service/vi/vi_u.h
|
||||
hle/service/wlan/wlan.cpp
|
||||
hle/service/wlan/wlan.h
|
||||
hw/hw.cpp
|
||||
hw/hw.h
|
||||
hw/lcd.cpp
|
||||
hw/lcd.h
|
||||
loader/deconstructed_rom_directory.cpp
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/elf.cpp
|
||||
@@ -301,6 +339,8 @@ add_library(core STATIC
|
||||
loader/nro.h
|
||||
loader/nso.cpp
|
||||
loader/nso.h
|
||||
loader/xci.cpp
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_hook.cpp
|
||||
@@ -320,7 +360,7 @@ add_library(core STATIC
|
||||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static opus unicorn)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn)
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_sources(core PRIVATE
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -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 {
|
||||
@@ -139,14 +148,12 @@ void ARM_Dynarmic::Step() {
|
||||
}
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, size_t core_index)
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)),
|
||||
jit(MakeJit()), exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(
|
||||
exclusive_monitor)},
|
||||
core_index{core_index} {
|
||||
ARM_Interface::ThreadContext ctx;
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
|
||||
exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
|
||||
ThreadContext ctx;
|
||||
inner_unicorn.SaveContext(ctx);
|
||||
LoadContext(ctx);
|
||||
PageTableChanged();
|
||||
LoadContext(ctx);
|
||||
}
|
||||
|
||||
ARM_Dynarmic::~ARM_Dynarmic() = default;
|
||||
@@ -205,7 +212,7 @@ u64 ARM_Dynarmic::GetTlsAddress() const {
|
||||
return cb->tpidrro_el0;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetTlsAddress(u64 address) {
|
||||
void ARM_Dynarmic::SetTlsAddress(VAddr address) {
|
||||
cb->tpidrro_el0 = address;
|
||||
}
|
||||
|
||||
@@ -217,7 +224,7 @@ void ARM_Dynarmic::SetTPIDR_EL0(u64 value) {
|
||||
cb->tpidr_el0 = value;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
void ARM_Dynarmic::SaveContext(ThreadContext& ctx) {
|
||||
ctx.cpu_registers = jit->GetRegisters();
|
||||
ctx.sp = jit->GetSP();
|
||||
ctx.pc = jit->GetPC();
|
||||
@@ -226,7 +233,7 @@ void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
ctx.fpscr = jit->GetFpcr();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) {
|
||||
void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) {
|
||||
jit->SetRegisters(ctx.cpu_registers);
|
||||
jit->SetSP(ctx.sp);
|
||||
jit->SetPC(ctx.pc);
|
||||
@@ -236,9 +243,7 @@ void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) {
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::PrepareReschedule() {
|
||||
if (jit->IsExecuting()) {
|
||||
jit->HaltExecution();
|
||||
}
|
||||
jit->HaltExecution();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::ClearInstructionCache() {
|
||||
|
||||
@@ -203,7 +203,7 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
}
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
if (last_bkpt_hit || (num_instructions == 1)) {
|
||||
if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "core/settings.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -63,7 +62,6 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
// execute. Otherwise, get out of the loop function.
|
||||
if (GDBStub::GetCpuHaltFlag()) {
|
||||
if (GDBStub::GetCpuStepFlag()) {
|
||||
GDBStub::SetCpuStepFlag(false);
|
||||
tight_loop = false;
|
||||
} else {
|
||||
return ResultStatus::Success;
|
||||
@@ -79,6 +77,10 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
}
|
||||
}
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::SetCpuStepFlag(false);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -86,8 +88,8 @@ System::ResultStatus System::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(filepath));
|
||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read));
|
||||
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
@@ -100,19 +102,11 @@ 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::ErrorEncrypted:
|
||||
return ResultStatus::ErrorLoader_ErrorEncrypted;
|
||||
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, system_mode.first.get())};
|
||||
ResultStatus init_result{Init(emu_window)};
|
||||
if (init_result != ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||
static_cast<int>(init_result));
|
||||
@@ -125,15 +119,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::ErrorEncrypted:
|
||||
return ResultStatus::ErrorLoader_ErrorEncrypted;
|
||||
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;
|
||||
@@ -163,11 +151,15 @@ Cpu& System::CpuCore(size_t core_index) {
|
||||
return *cpu_cores[core_index];
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
CoreTiming::Init();
|
||||
|
||||
// Create a default fs if one doesn't already exist.
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
|
||||
current_process = Kernel::Process::Create("main");
|
||||
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
@@ -176,19 +168,20 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||
cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index);
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>();
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
HW::Init();
|
||||
Kernel::Init(system_mode);
|
||||
Service::Init(service_manager);
|
||||
Kernel::Init();
|
||||
Service::Init(service_manager, virtual_filesystem);
|
||||
GDBStub::Init();
|
||||
|
||||
if (!VideoCore::Init(emu_window)) {
|
||||
renderer = VideoCore::CreateRenderer(emu_window);
|
||||
if (!renderer->Init()) {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>(renderer->Rasterizer());
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
// CPU core 0 is run on the main thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
@@ -220,11 +213,10 @@ void System::Shutdown() {
|
||||
perf_results.frametime * 1000.0);
|
||||
|
||||
// Shutdown emulation session
|
||||
VideoCore::Shutdown();
|
||||
renderer.reset();
|
||||
GDBStub::Shutdown();
|
||||
Service::Shutdown();
|
||||
Kernel::Shutdown();
|
||||
HW::Shutdown();
|
||||
service_manager.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
@@ -11,22 +11,31 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "hle/service/filesystem/filesystem.h"
|
||||
#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;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class RendererBase;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System {
|
||||
@@ -43,19 +52,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_ErrorEncrypted, ///< 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)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -76,16 +81,28 @@ public:
|
||||
*/
|
||||
ResultStatus SingleStep();
|
||||
|
||||
/**
|
||||
* Invalidate the CPU instruction caches
|
||||
* This function should only be used by GDB Stub to support breakpoints, memory updates and
|
||||
* step/continue commands.
|
||||
*/
|
||||
void InvalidateCpuInstructionCaches() {
|
||||
for (auto& cpu : cpu_cores) {
|
||||
cpu->ArmInterface().ClearInstructionCache();
|
||||
}
|
||||
}
|
||||
|
||||
/// Shutdown the emulated system.
|
||||
void Shutdown();
|
||||
|
||||
/**
|
||||
* Load an executable application.
|
||||
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
* input.
|
||||
* @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
|
||||
@@ -126,11 +143,26 @@ public:
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
Cpu& CpuCore(size_t core_index);
|
||||
|
||||
/// Gets the GPU interface
|
||||
/// Gets a mutable reference to the GPU interface
|
||||
Tegra::GPU& GPU() {
|
||||
return *gpu_core;
|
||||
}
|
||||
|
||||
/// Gets an immutable reference to the GPU interface.
|
||||
const Tegra::GPU& GPU() const {
|
||||
return *gpu_core;
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the renderer.
|
||||
VideoCore::RendererBase& Renderer() {
|
||||
return *renderer;
|
||||
}
|
||||
|
||||
/// Gets an immutable reference to the renderer.
|
||||
const VideoCore::RendererBase& Renderer() const {
|
||||
return *renderer;
|
||||
}
|
||||
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
Kernel::Scheduler& CurrentScheduler() {
|
||||
return *CurrentCpuCore().Scheduler();
|
||||
@@ -178,6 +210,14 @@ public:
|
||||
return debug_context;
|
||||
}
|
||||
|
||||
void SetFilesystem(FileSys::VirtualFilesystem vfs) {
|
||||
virtual_filesystem = std::move(vfs);
|
||||
}
|
||||
|
||||
FileSys::VirtualFilesystem GetFilesystem() const {
|
||||
return virtual_filesystem;
|
||||
}
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
@@ -186,14 +226,17 @@ private:
|
||||
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
|
||||
* @param system_mode The system mode.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
* input.
|
||||
* @return ResultStatus code, indicating if the operation succeeded.
|
||||
*/
|
||||
ResultStatus Init(EmuWindow* emu_window, u32 system_mode);
|
||||
ResultStatus Init(Frontend::EmuWindow& emu_window);
|
||||
|
||||
/// RealVfsFilesystem instance
|
||||
FileSys::VirtualFilesystem virtual_filesystem;
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -91,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();
|
||||
}
|
||||
@@ -126,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -135,13 +135,11 @@ 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<Event>());
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
|
||||
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
|
||||
@@ -156,7 +154,7 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != event_queue.end()) {
|
||||
event_queue.erase(itr, event_queue.end());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<Event>());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +165,7 @@ void RemoveEvent(const EventType* event_type) {
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != event_queue.end()) {
|
||||
event_queue.erase(itr, event_queue.end());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<Event>());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +188,7 @@ void MoveEvents() {
|
||||
for (Event ev; ts_queue.Pop(ev);) {
|
||||
ev.fifo_order = event_fifo_id++;
|
||||
event_queue.emplace_back(std::move(ev));
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<Event>());
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +203,7 @@ void Advance() {
|
||||
|
||||
while (!event_queue.empty() && event_queue.front().time <= global_timer) {
|
||||
Event evt = std::move(event_queue.front());
|
||||
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<Event>());
|
||||
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
event_queue.pop_back();
|
||||
evt.type->callback(evt.userdata, static_cast<int>(global_timer - evt.time));
|
||||
}
|
||||
@@ -226,8 +224,8 @@ void Idle() {
|
||||
downcount = 0;
|
||||
}
|
||||
|
||||
u64 GetGlobalTimeUs() {
|
||||
return GetTicks() * 1000000 / BASE_CLOCK_RATE;
|
||||
std::chrono::microseconds GetGlobalTimeUs() {
|
||||
return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE};
|
||||
}
|
||||
|
||||
int GetDowncount() {
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
* ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
|
||||
struct EventType;
|
||||
|
||||
using TimedCallback = std::function<void(u64 userdata, int cycles_late)>;
|
||||
|
||||
/**
|
||||
* CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
|
||||
* required to end slice -1 and start slice 0 before the first cycle of code is executed.
|
||||
@@ -30,8 +35,6 @@ namespace CoreTiming {
|
||||
void Init();
|
||||
void Shutdown();
|
||||
|
||||
typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback;
|
||||
|
||||
/**
|
||||
* This should only be called from the emu thread, if you are calling it any other thread, you are
|
||||
* doing something evil
|
||||
@@ -40,8 +43,6 @@ u64 GetTicks();
|
||||
u64 GetIdleTicks();
|
||||
void AddTicks(u64 ticks);
|
||||
|
||||
struct EventType;
|
||||
|
||||
/**
|
||||
* Returns the event_type identifier. if name is not unique, it will assert.
|
||||
*/
|
||||
@@ -86,7 +87,7 @@ void ClearPendingEvents();
|
||||
|
||||
void ForceExceptionCheck(s64 cycles);
|
||||
|
||||
u64 GetGlobalTimeUs();
|
||||
std::chrono::microseconds GetGlobalTimeUs();
|
||||
|
||||
int GetDowncount();
|
||||
|
||||
|
||||
115
src/core/crypto/aes_util.cpp
Normal file
115
src/core/crypto/aes_util.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <mbedtls/cipher.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
namespace {
|
||||
std::vector<u8> CalculateNintendoTweak(size_t sector_id) {
|
||||
std::vector<u8> out(0x10);
|
||||
for (size_t i = 0xF; i <= 0xF; --i) {
|
||||
out[i] = sector_id & 0xFF;
|
||||
sector_id >>= 8;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR),
|
||||
"CTR has incorrect value.");
|
||||
static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB),
|
||||
"ECB has incorrect value.");
|
||||
static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS),
|
||||
"XTS has incorrect value.");
|
||||
|
||||
// Structure to hide mbedtls types from header file
|
||||
struct CipherContext {
|
||||
mbedtls_cipher_context_t encryption_context;
|
||||
mbedtls_cipher_context_t decryption_context;
|
||||
};
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
|
||||
: ctx(std::make_unique<CipherContext>()) {
|
||||
mbedtls_cipher_init(&ctx->encryption_context);
|
||||
mbedtls_cipher_init(&ctx->decryption_context);
|
||||
|
||||
ASSERT_MSG((mbedtls_cipher_setup(
|
||||
&ctx->encryption_context,
|
||||
mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
|
||||
mbedtls_cipher_setup(
|
||||
&ctx->decryption_context,
|
||||
mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
|
||||
"Failed to initialize mbedtls ciphers.");
|
||||
|
||||
ASSERT(
|
||||
!mbedtls_cipher_setkey(&ctx->encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
|
||||
ASSERT(
|
||||
!mbedtls_cipher_setkey(&ctx->decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT));
|
||||
//"Failed to set key on mbedtls ciphers.");
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
AESCipher<Key, KeySize>::~AESCipher() {
|
||||
mbedtls_cipher_free(&ctx->encryption_context);
|
||||
mbedtls_cipher_free(&ctx->decryption_context);
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) {
|
||||
ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) ||
|
||||
mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0,
|
||||
"Failed to set IV on mbedtls ciphers.");
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op) const {
|
||||
auto* const context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context;
|
||||
|
||||
mbedtls_cipher_reset(context);
|
||||
|
||||
size_t written = 0;
|
||||
if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
|
||||
mbedtls_cipher_update(context, src, size, dest, &written);
|
||||
if (written != size) {
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
size, written);
|
||||
}
|
||||
} else {
|
||||
const auto block_size = mbedtls_cipher_get_block_size(context);
|
||||
|
||||
for (size_t offset = 0; offset < size; offset += block_size) {
|
||||
auto length = std::min<size_t>(block_size, size - offset);
|
||||
mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
|
||||
if (written != length) {
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
length, written);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mbedtls_cipher_finish(context, nullptr, nullptr);
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id,
|
||||
size_t sector_size, Op op) {
|
||||
if (size % sector_size > 0) {
|
||||
LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; i += sector_size) {
|
||||
SetIV(CalculateNintendoTweak(sector_id++));
|
||||
Transcode<u8, u8>(src + i, sector_size, dest + i, op);
|
||||
}
|
||||
}
|
||||
|
||||
template class AESCipher<Key128>;
|
||||
template class AESCipher<Key256>;
|
||||
} // namespace Core::Crypto
|
||||
64
src/core/crypto/aes_util.h
Normal file
64
src/core/crypto/aes_util.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
struct CipherContext;
|
||||
|
||||
enum class Mode {
|
||||
CTR = 11,
|
||||
ECB = 2,
|
||||
XTS = 70,
|
||||
};
|
||||
|
||||
enum class Op {
|
||||
Encrypt,
|
||||
Decrypt,
|
||||
};
|
||||
|
||||
template <typename Key, size_t KeySize = sizeof(Key)>
|
||||
class AESCipher {
|
||||
static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8.");
|
||||
static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256.");
|
||||
|
||||
public:
|
||||
AESCipher(Key key, Mode mode);
|
||||
|
||||
~AESCipher();
|
||||
|
||||
void SetIV(std::vector<u8> iv);
|
||||
|
||||
template <typename Source, typename Dest>
|
||||
void Transcode(const Source* src, size_t size, Dest* dest, Op op) const {
|
||||
static_assert(std::is_trivially_copyable_v<Source> && std::is_trivially_copyable_v<Dest>,
|
||||
"Transcode source and destination types must be trivially copyable.");
|
||||
Transcode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), op);
|
||||
}
|
||||
|
||||
void Transcode(const u8* src, size_t size, u8* dest, Op op) const;
|
||||
|
||||
template <typename Source, typename Dest>
|
||||
void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id,
|
||||
size_t sector_size, Op op) {
|
||||
static_assert(std::is_trivially_copyable_v<Source> && std::is_trivially_copyable_v<Dest>,
|
||||
"XTSTranscode source and destination types must be trivially copyable.");
|
||||
XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id,
|
||||
sector_size, op);
|
||||
}
|
||||
|
||||
void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size,
|
||||
Op op);
|
||||
|
||||
private:
|
||||
std::unique_ptr<CipherContext> ctx;
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
56
src/core/crypto/ctr_encryption_layer.cpp
Normal file
56
src/core/crypto/ctr_encryption_layer.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset)
|
||||
: EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR),
|
||||
iv(16, 0) {}
|
||||
|
||||
size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
|
||||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
const auto sector_offset = offset & 0xF;
|
||||
if (sector_offset == 0) {
|
||||
UpdateIV(base_offset + offset);
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
if (raw.size() != length)
|
||||
return Read(data, raw.size(), offset);
|
||||
cipher.Transcode(raw.data(), length, data, Op::Decrypt);
|
||||
return length;
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x10)
|
||||
std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset);
|
||||
UpdateIV(base_offset + offset - sector_offset);
|
||||
cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt);
|
||||
size_t read = 0x10 - sector_offset;
|
||||
|
||||
if (length + sector_offset < 0x10) {
|
||||
std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
|
||||
return read;
|
||||
}
|
||||
std::memcpy(data, block.data() + sector_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
}
|
||||
|
||||
void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) {
|
||||
const auto length = std::min(iv_.size(), iv.size());
|
||||
iv.assign(iv_.cbegin(), iv_.cbegin() + length);
|
||||
}
|
||||
|
||||
void CTREncryptionLayer::UpdateIV(size_t offset) const {
|
||||
offset >>= 4;
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
iv[16 - i - 1] = offset & 0xFF;
|
||||
offset >>= 8;
|
||||
}
|
||||
cipher.SetIV(iv);
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
33
src/core/crypto/ctr_encryption_layer.h
Normal file
33
src/core/crypto/ctr_encryption_layer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
// Sits on top of a VirtualFile and provides CTR-mode AES decription.
|
||||
class CTREncryptionLayer : public EncryptionLayer {
|
||||
public:
|
||||
CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset);
|
||||
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
|
||||
void SetIV(const std::vector<u8>& iv);
|
||||
|
||||
private:
|
||||
size_t base_offset;
|
||||
|
||||
// Must be mutable as operations modify cipher contexts.
|
||||
mutable AESCipher<Key128> cipher;
|
||||
mutable std::vector<u8> iv;
|
||||
|
||||
void UpdateIV(size_t offset) const;
|
||||
};
|
||||
|
||||
} // namespace Core::Crypto
|
||||
42
src/core/crypto/encryption_layer.cpp
Normal file
42
src/core/crypto/encryption_layer.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {}
|
||||
|
||||
std::string EncryptionLayer::GetName() const {
|
||||
return base->GetName();
|
||||
}
|
||||
|
||||
size_t EncryptionLayer::GetSize() const {
|
||||
return base->GetSize();
|
||||
}
|
||||
|
||||
bool EncryptionLayer::Resize(size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::VfsDirectory> EncryptionLayer::GetContainingDirectory() const {
|
||||
return base->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool EncryptionLayer::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EncryptionLayer::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool EncryptionLayer::Rename(std::string_view name) {
|
||||
return base->Rename(name);
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
33
src/core/crypto/encryption_layer.h
Normal file
33
src/core/crypto/encryption_layer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
// Basically non-functional class that implements all of the methods that are irrelevant to an
|
||||
// EncryptionLayer. Reduces duplicate code.
|
||||
class EncryptionLayer : public FileSys::VfsFile {
|
||||
public:
|
||||
explicit EncryptionLayer(FileSys::VirtualFile base);
|
||||
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override = 0;
|
||||
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
std::shared_ptr<FileSys::VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
protected:
|
||||
FileSys::VirtualFile base;
|
||||
};
|
||||
|
||||
} // namespace Core::Crypto
|
||||
208
src/core/crypto/key_manager.cpp
Normal file
208
src/core/crypto/key_manager.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.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();
|
||||
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
|
||||
if (Settings::values.use_dev_keys) {
|
||||
dev_mode = true;
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
|
||||
} else {
|
||||
dev_mode = false;
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false);
|
||||
}
|
||||
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
std::vector<std::string> out;
|
||||
std::stringstream stream(line);
|
||||
std::string item;
|
||||
while (std::getline(stream, item, '='))
|
||||
out.push_back(std::move(item));
|
||||
|
||||
if (out.size() != 2)
|
||||
continue;
|
||||
|
||||
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
|
||||
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]);
|
||||
u128 rights_id{};
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
|
||||
Key128 key = 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]);
|
||||
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]);
|
||||
SetKey(index.type, key, index.field1, index.field2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
|
||||
const std::string& filename, bool title) {
|
||||
if (FileUtil::Exists(dir1 + DIR_SEP + filename))
|
||||
LoadFromFile(dir1 + DIR_SEP + filename, title);
|
||||
else if (FileUtil::Exists(dir2 + DIR_SEP + filename))
|
||||
LoadFromFile(dir2 + DIR_SEP + filename, title);
|
||||
}
|
||||
|
||||
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
|
||||
return s128_keys.find({id, field1, field2}) != s128_keys.end();
|
||||
}
|
||||
|
||||
bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const {
|
||||
return s256_keys.find({id, field1, field2}) != s256_keys.end();
|
||||
}
|
||||
|
||||
Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const {
|
||||
if (!HasKey(id, field1, field2))
|
||||
return {};
|
||||
return s128_keys.at({id, field1, field2});
|
||||
}
|
||||
|
||||
Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
|
||||
if (!HasKey(id, field1, field2))
|
||||
return {};
|
||||
return s256_keys.at({id, field1, field2});
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
s128_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||
s256_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
bool KeyManager::KeyFileExists(bool title) {
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
|
||||
if (title) {
|
||||
return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
|
||||
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
|
||||
}
|
||||
|
||||
if (Settings::values.use_dev_keys) {
|
||||
return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
|
||||
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
|
||||
}
|
||||
|
||||
return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
|
||||
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
|
||||
{"master_key_00", {S128KeyType::Master, 0, 0}},
|
||||
{"master_key_01", {S128KeyType::Master, 1, 0}},
|
||||
{"master_key_02", {S128KeyType::Master, 2, 0}},
|
||||
{"master_key_03", {S128KeyType::Master, 3, 0}},
|
||||
{"master_key_04", {S128KeyType::Master, 4, 0}},
|
||||
{"package1_key_00", {S128KeyType::Package1, 0, 0}},
|
||||
{"package1_key_01", {S128KeyType::Package1, 1, 0}},
|
||||
{"package1_key_02", {S128KeyType::Package1, 2, 0}},
|
||||
{"package1_key_03", {S128KeyType::Package1, 3, 0}},
|
||||
{"package1_key_04", {S128KeyType::Package1, 4, 0}},
|
||||
{"package2_key_00", {S128KeyType::Package2, 0, 0}},
|
||||
{"package2_key_01", {S128KeyType::Package2, 1, 0}},
|
||||
{"package2_key_02", {S128KeyType::Package2, 2, 0}},
|
||||
{"package2_key_03", {S128KeyType::Package2, 3, 0}},
|
||||
{"package2_key_04", {S128KeyType::Package2, 4, 0}},
|
||||
{"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
|
||||
{"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
|
||||
{"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
|
||||
{"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
|
||||
{"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
|
||||
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
|
||||
{"key_area_key_application_00",
|
||||
{S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_01",
|
||||
{S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_02",
|
||||
{S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_03",
|
||||
{S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_04",
|
||||
{S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
|
||||
{"header_key", {S256KeyType::Header, 0, 0}},
|
||||
{"sd_card_save_key", {S256KeyType::SDSave, 0, 0}},
|
||||
{"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}},
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
120
src/core/crypto/key_manager.h
Normal file
120
src/core/crypto/key_manager.h
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
using Key128 = std::array<u8, 0x10>;
|
||||
using Key256 = std::array<u8, 0x20>;
|
||||
using SHA256Hash = std::array<u8, 0x20>;
|
||||
|
||||
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
|
||||
|
||||
enum class S256KeyType : u64 {
|
||||
Header, //
|
||||
SDSave, //
|
||||
SDNCA, //
|
||||
};
|
||||
|
||||
enum class S128KeyType : u64 {
|
||||
Master, // f1=crypto revision
|
||||
Package1, // f1=crypto revision
|
||||
Package2, // f1=crypto revision
|
||||
Titlekek, // f1=crypto revision
|
||||
ETicketRSAKek, //
|
||||
KeyArea, // f1=crypto revision f2=type {app, ocean, system}
|
||||
SDSeed, //
|
||||
Titlekey, // f1=rights id LSB f2=rights id MSB
|
||||
};
|
||||
|
||||
enum class KeyAreaKeyType : u8 {
|
||||
Application,
|
||||
Ocean,
|
||||
System,
|
||||
};
|
||||
|
||||
template <typename KeyType>
|
||||
struct KeyIndex {
|
||||
KeyType type;
|
||||
u64 field1;
|
||||
u64 field2;
|
||||
|
||||
std::string DebugInfo() const {
|
||||
u8 key_size = 16;
|
||||
if constexpr (std::is_same_v<KeyType, S256KeyType>)
|
||||
key_size = 32;
|
||||
return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size,
|
||||
static_cast<u8>(type), field1, field2);
|
||||
}
|
||||
};
|
||||
|
||||
// The following two (== and hash) are so KeyIndex can be a key in unordered_map
|
||||
|
||||
template <typename KeyType>
|
||||
bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2);
|
||||
}
|
||||
|
||||
template <typename KeyType>
|
||||
bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
namespace std {
|
||||
template <typename KeyType>
|
||||
struct hash<Core::Crypto::KeyIndex<KeyType>> {
|
||||
size_t operator()(const Core::Crypto::KeyIndex<KeyType>& k) const {
|
||||
using std::hash;
|
||||
|
||||
return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^
|
||||
(hash<u64>()(k.field2) << 1);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
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();
|
||||
|
||||
bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
|
||||
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
|
||||
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
|
||||
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
|
||||
|
||||
static bool KeyFileExists(bool title);
|
||||
|
||||
private:
|
||||
std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
|
||||
bool dev_mode;
|
||||
void LoadFromFile(const std::string& filename, bool is_title_keys);
|
||||
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
|
||||
const std::string& filename, bool title);
|
||||
|
||||
static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
|
||||
static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
5
src/core/crypto/sha_util.cpp
Normal file
5
src/core/crypto/sha_util.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
namespace Crypto {} // namespace Crypto
|
||||
20
src/core/crypto/sha_util.h
Normal file
20
src/core/crypto/sha_util.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "key_manager.h"
|
||||
#include "mbedtls/cipher.h"
|
||||
|
||||
namespace Crypto {
|
||||
typedef std::array<u8, 0x20> SHA256Hash;
|
||||
|
||||
inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
|
||||
if (len != 0x40)
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Crypto
|
||||
155
src/core/file_sys/card_image.cpp
Normal file
155
src/core/file_sys/card_image.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <core/loader/loader.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"
|
||||
|
||||
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::ErrorBadXCIHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
|
||||
status = Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionFilesystem main_hfs(
|
||||
std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset));
|
||||
|
||||
if (main_hfs.GetStatus() != Loader::ResultStatus::Success) {
|
||||
status = main_hfs.GetStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
for (XCIPartition partition :
|
||||
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
|
||||
auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]);
|
||||
if (raw != nullptr)
|
||||
partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
|
||||
}
|
||||
|
||||
auto result = AddNCAFromPartition(XCIPartition::Secure);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
|
||||
result = AddNCAFromPartition(XCIPartition::Update);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
|
||||
result = AddNCAFromPartition(XCIPartition::Normal);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetFormatVersion() >= 0x2) {
|
||||
result = AddNCAFromPartition(XCIPartition::Logo);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetPartition(XCIPartition partition) const {
|
||||
return partitions[static_cast<size_t>(partition)];
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetSecurePartition() const {
|
||||
return GetPartition(XCIPartition::Secure);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetNormalPartition() const {
|
||||
return GetPartition(XCIPartition::Normal);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetUpdatePartition() const {
|
||||
return GetPartition(XCIPartition::Update);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetLogoPartition() const {
|
||||
return GetPartition(XCIPartition::Logo);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
|
||||
const auto iter =
|
||||
std::find_if(ncas.begin(), ncas.end(),
|
||||
[type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; });
|
||||
return iter == ncas.end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
|
||||
auto nca = GetNCAByType(type);
|
||||
if (nca != nullptr)
|
||||
return nca->GetBaseFile();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<VirtualFile> XCI::GetFiles() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> XCI::GetSubdirectories() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string XCI::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
if (partitions[static_cast<size_t>(part)] == nullptr) {
|
||||
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) {
|
||||
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,
|
||||
Loader::GetMessageForResultStatus(nca->GetStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
u8 XCI::GetFormatVersion() const {
|
||||
return GetLogoPartition() == nullptr ? 0x1 : 0x2;
|
||||
}
|
||||
} // namespace FileSys
|
||||
96
src/core/file_sys/card_image.h
Normal file
96
src/core/file_sys/card_image.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// 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"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class GamecardSize : u8 {
|
||||
S_1GB = 0xFA,
|
||||
S_2GB = 0xF8,
|
||||
S_4GB = 0xF0,
|
||||
S_8GB = 0xE0,
|
||||
S_16GB = 0xE1,
|
||||
S_32GB = 0xE2,
|
||||
};
|
||||
|
||||
struct GamecardInfo {
|
||||
std::array<u8, 0x70> data;
|
||||
};
|
||||
static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");
|
||||
|
||||
struct GamecardHeader {
|
||||
std::array<u8, 0x100> signature;
|
||||
u32_le magic;
|
||||
u32_le secure_area_start;
|
||||
u32_le backup_area_start;
|
||||
u8 kek_index;
|
||||
GamecardSize size;
|
||||
u8 header_version;
|
||||
u8 flags;
|
||||
u64_le package_id;
|
||||
u64_le valid_data_end;
|
||||
u128 info_iv;
|
||||
u64_le hfs_offset;
|
||||
u64_le hfs_size;
|
||||
std::array<u8, 0x20> hfs_header_hash;
|
||||
std::array<u8, 0x20> initial_data_hash;
|
||||
u32_le secure_mode_flag;
|
||||
u32_le title_key_flag;
|
||||
u32_le key_flag;
|
||||
u32_le normal_area_end;
|
||||
GamecardInfo info;
|
||||
};
|
||||
static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size.");
|
||||
|
||||
enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
|
||||
|
||||
class XCI : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit XCI(VirtualFile file);
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
u8 GetFormatVersion() const;
|
||||
|
||||
VirtualDir GetPartition(XCIPartition partition) const;
|
||||
VirtualDir GetSecurePartition() const;
|
||||
VirtualDir GetNormalPartition() const;
|
||||
VirtualDir GetUpdatePartition() const;
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
|
||||
VirtualFile GetNCAFileByType(NCAContentType type) const;
|
||||
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
|
||||
|
||||
VirtualFile file;
|
||||
GamecardHeader header{};
|
||||
|
||||
Loader::ResultStatus status;
|
||||
|
||||
std::vector<VirtualDir> partitions;
|
||||
std::vector<std::shared_ptr<NCA>> ncas;
|
||||
};
|
||||
} // namespace FileSys
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "romfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -29,11 +31,19 @@ enum class NCASectionFilesystemType : u8 {
|
||||
struct NCASectionHeaderBlock {
|
||||
INSERT_PADDING_BYTES(3);
|
||||
NCASectionFilesystemType filesystem_type;
|
||||
u8 crypto_type;
|
||||
NCASectionCryptoType crypto_type;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
|
||||
|
||||
struct NCASectionRaw {
|
||||
NCASectionHeaderBlock header;
|
||||
std::array<u8, 0x138> block_data;
|
||||
std::array<u8, 0x8> section_ctr;
|
||||
INSERT_PADDING_BYTES(0xB8);
|
||||
};
|
||||
static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
|
||||
|
||||
struct PFS0Superblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
std::array<u8, 0x20> hash;
|
||||
@@ -43,67 +53,258 @@ struct PFS0Superblock {
|
||||
u64_le hash_table_size;
|
||||
u64_le pfs0_header_offset;
|
||||
u64_le pfs0_size;
|
||||
INSERT_PADDING_BYTES(432);
|
||||
INSERT_PADDING_BYTES(0x1B0);
|
||||
};
|
||||
static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
|
||||
|
||||
struct RomFSSuperblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
IVFCHeader ivfc;
|
||||
INSERT_PADDING_BYTES(0x118);
|
||||
};
|
||||
static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size.");
|
||||
static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
|
||||
|
||||
union NCASectionHeader {
|
||||
NCASectionRaw raw;
|
||||
PFS0Superblock pfs0;
|
||||
RomFSSuperblock romfs;
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
|
||||
|
||||
bool IsValidNCA(const NCAHeader& header) {
|
||||
// TODO(DarkLordZach): Add NCA2/NCA0 support.
|
||||
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
||||
}
|
||||
|
||||
u8 NCA::GetCryptoRevision() const {
|
||||
u8 master_key_id = header.crypto_type;
|
||||
if (header.crypto_type_2 > master_key_id)
|
||||
master_key_id = header.crypto_type_2;
|
||||
if (master_key_id > 0)
|
||||
--master_key_id;
|
||||
return master_key_id;
|
||||
}
|
||||
|
||||
boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
|
||||
const auto master_key_id = GetCryptoRevision();
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
|
||||
return boost::none;
|
||||
|
||||
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
|
||||
keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
|
||||
Core::Crypto::Mode::ECB);
|
||||
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
|
||||
|
||||
Core::Crypto::Key128 out;
|
||||
if (type == NCASectionCryptoType::XTS)
|
||||
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
|
||||
else if (type == NCASectionCryptoType::CTR)
|
||||
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
|
||||
else
|
||||
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
|
||||
static_cast<u8>(type));
|
||||
u128 out_128{};
|
||||
memcpy(out_128.data(), out.data(), 16);
|
||||
LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
|
||||
master_key_id, header.key_index, out_128[1], out_128[0]);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
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{}) {
|
||||
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{}) {
|
||||
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);
|
||||
|
||||
return titlekey;
|
||||
}
|
||||
|
||||
VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) {
|
||||
if (!encrypted)
|
||||
return in;
|
||||
|
||||
switch (s_header.raw.header.crypto_type) {
|
||||
case NCASectionCryptoType::NONE:
|
||||
LOG_DEBUG(Crypto, "called with mode=NONE");
|
||||
return in;
|
||||
case NCASectionCryptoType::CTR:
|
||||
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
|
||||
{
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
|
||||
std::move(in), key.value(), starting_offset);
|
||||
std::vector<u8> iv(16);
|
||||
for (u8 i = 0; i < 8; ++i)
|
||||
iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
|
||||
out->SetIV(iv);
|
||||
return std::static_pointer_cast<VfsFile>(out);
|
||||
}
|
||||
case NCASectionCryptoType::XTS:
|
||||
// TODO(DarkLordZach): Implement XTSEncryptionLayer.
|
||||
default:
|
||||
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
|
||||
static_cast<u8>(s_header.raw.header.crypto_type));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
status = Loader::ResultStatus::Success;
|
||||
|
||||
if (!IsValidNCA(header)) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
|
||||
std::ptrdiff_t number_sections =
|
||||
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);
|
||||
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
if (IsValidNCA(dec_header)) {
|
||||
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::ErrorMissingHeaderKey;
|
||||
else
|
||||
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; });
|
||||
|
||||
std::vector<NCASectionHeader> sections(number_sections);
|
||||
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
|
||||
|
||||
if (encrypted) {
|
||||
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
} else {
|
||||
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
|
||||
}
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
// Seek to beginning of this section.
|
||||
NCASectionHeaderBlock block{};
|
||||
if (sizeof(NCASectionHeaderBlock) !=
|
||||
file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
if (block.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
RomFSSuperblock sb{};
|
||||
if (sizeof(RomFSSuperblock) !=
|
||||
file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
auto section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
const size_t romfs_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
|
||||
sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset));
|
||||
romfs = files.back();
|
||||
} else if (block.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
PFS0Superblock sb{};
|
||||
// Seek back to beginning of this section.
|
||||
if (sizeof(PFS0Superblock) !=
|
||||
file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
|
||||
romfs_offset);
|
||||
if (dec != nullptr) {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
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) {
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
sb.pfs0_header_offset;
|
||||
section.pfs0.pfs0_header_offset;
|
||||
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(
|
||||
std::make_shared<OffsetVfsFile>(file, size, offset));
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
|
||||
if (dec != nullptr) {
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
|
||||
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.emplace_back(npfs);
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
}
|
||||
} else {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +354,10 @@ VirtualDir NCA::GetExeFS() const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetBaseFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -8,14 +8,19 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "control_metadata.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
union NCASectionHeader;
|
||||
|
||||
enum class NCAContentType : u8 {
|
||||
Program = 0,
|
||||
Meta = 1,
|
||||
@@ -24,6 +29,13 @@ enum class NCAContentType : u8 {
|
||||
Data = 4,
|
||||
};
|
||||
|
||||
enum class NCASectionCryptoType : u8 {
|
||||
NONE = 1,
|
||||
XTS = 2,
|
||||
CTR = 3,
|
||||
BKTR = 4,
|
||||
};
|
||||
|
||||
struct NCASectionTableEntry {
|
||||
u32_le media_offset;
|
||||
u32_le media_end_offset;
|
||||
@@ -48,7 +60,7 @@ struct NCAHeader {
|
||||
std::array<u8, 0x10> rights_id;
|
||||
std::array<NCASectionTableEntry, 0x4> section_tables;
|
||||
std::array<std::array<u8, 0x20>, 0x4> hash_tables;
|
||||
std::array<std::array<u8, 0x10>, 0x4> key_area;
|
||||
std::array<u8, 0x40> key_area;
|
||||
INSERT_PADDING_BYTES(0xC0);
|
||||
};
|
||||
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
|
||||
@@ -58,10 +70,7 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
|
||||
inline bool IsValidNCA(const NCAHeader& header) {
|
||||
return header.magic == Common::MakeMagic('N', 'C', 'A', '2') ||
|
||||
header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
||||
}
|
||||
bool IsValidNCA(const NCAHeader& header);
|
||||
|
||||
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
@@ -81,10 +90,17 @@ public:
|
||||
VirtualFile GetRomFS() const;
|
||||
VirtualDir GetExeFS() const;
|
||||
|
||||
VirtualFile GetBaseFile() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
u8 GetCryptoRevision() const;
|
||||
boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
|
||||
boost::optional<Core::Crypto::Key128> GetTitlekey();
|
||||
VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
|
||||
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::vector<VirtualFile> files;
|
||||
|
||||
@@ -93,8 +109,13 @@ private:
|
||||
VirtualFile file;
|
||||
|
||||
NCAHeader header{};
|
||||
bool has_rights_id{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
bool encrypted;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -62,6 +62,13 @@ enum class Language : u8 {
|
||||
Chinese = 14,
|
||||
};
|
||||
|
||||
static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
|
||||
"AmericanEnglish", "BritishEnglish", "Japanese",
|
||||
"French", "German", "LatinAmericanSpanish",
|
||||
"Spanish", "Italian", "Dutch",
|
||||
"CanadianFrench", "Portugese", "Russian",
|
||||
"Korean", "Taiwanese", "Chinese"};
|
||||
|
||||
// A class representing the format used by NX metadata files, typically named Control.nacp.
|
||||
// These store application name, dev name, title id, and other miscellaneous data.
|
||||
class NACP {
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <string_view>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -21,9 +22,14 @@ enum EntryType : u8 {
|
||||
|
||||
// Structure of a directory entry, from
|
||||
// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
|
||||
const size_t FILENAME_LENGTH = 0x300;
|
||||
struct Entry {
|
||||
char filename[FILENAME_LENGTH];
|
||||
Entry(std::string_view view, EntryType entry_type, u64 entry_size)
|
||||
: type{entry_type}, file_size{entry_size} {
|
||||
const size_t copy_size = view.copy(filename, std::size(filename) - 1);
|
||||
filename[copy_size] = '\0';
|
||||
}
|
||||
|
||||
char filename[0x300];
|
||||
INSERT_PADDING_BYTES(4);
|
||||
EntryType type;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -4,12 +4,160 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {}
|
||||
|
||||
VfsFilesystem::~VfsFilesystem() = default;
|
||||
|
||||
std::string VfsFilesystem::GetName() const {
|
||||
return root->GetName();
|
||||
}
|
||||
|
||||
bool VfsFilesystem::IsReadable() const {
|
||||
return root->IsReadable();
|
||||
}
|
||||
|
||||
bool VfsFilesystem::IsWritable() const {
|
||||
return root->IsWritable();
|
||||
}
|
||||
|
||||
VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
|
||||
const auto path = FileUtil::SanitizePath(path_);
|
||||
if (root->GetFileRelative(path) != nullptr)
|
||||
return VfsEntryType::File;
|
||||
if (root->GetDirectoryRelative(path) != nullptr)
|
||||
return VfsEntryType::Directory;
|
||||
|
||||
return VfsEntryType::None;
|
||||
}
|
||||
|
||||
VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_);
|
||||
return root->GetFileRelative(path);
|
||||
}
|
||||
|
||||
VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_);
|
||||
return root->CreateFileRelative(path);
|
||||
}
|
||||
|
||||
VirtualFile VfsFilesystem::CopyFile(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_);
|
||||
|
||||
// VfsDirectory impls are only required to implement copy across the current directory.
|
||||
if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) {
|
||||
if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path)))
|
||||
return nullptr;
|
||||
return OpenFile(new_path, Mode::ReadWrite);
|
||||
}
|
||||
|
||||
// Do it using RawCopy. Non-default impls are encouraged to optimize this.
|
||||
const auto old_file = OpenFile(old_path, Mode::Read);
|
||||
if (old_file == nullptr)
|
||||
return nullptr;
|
||||
auto new_file = OpenFile(new_path, Mode::Read);
|
||||
if (new_file != nullptr)
|
||||
return nullptr;
|
||||
new_file = CreateFile(new_path, Mode::Write);
|
||||
if (new_file == nullptr)
|
||||
return nullptr;
|
||||
if (!VfsRawCopy(old_file, new_file))
|
||||
return nullptr;
|
||||
return new_file;
|
||||
}
|
||||
|
||||
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(sanitized_old_path, sanitized_new_path);
|
||||
if (out == nullptr)
|
||||
return nullptr;
|
||||
if (DeleteFile(sanitized_old_path))
|
||||
return out;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool VfsFilesystem::DeleteFile(std::string_view path_) {
|
||||
const auto path = FileUtil::SanitizePath(path_);
|
||||
auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
|
||||
if (parent == nullptr)
|
||||
return false;
|
||||
return parent->DeleteFile(FileUtil::GetFilename(path));
|
||||
}
|
||||
|
||||
VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_);
|
||||
return root->GetDirectoryRelative(path);
|
||||
}
|
||||
|
||||
VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_);
|
||||
return root->CreateDirectoryRelative(path);
|
||||
}
|
||||
|
||||
VirtualDir VfsFilesystem::CopyDirectory(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_);
|
||||
|
||||
// Non-default impls are highly encouraged to provide a more optimized version of this.
|
||||
auto old_dir = OpenDirectory(old_path, Mode::Read);
|
||||
if (old_dir == nullptr)
|
||||
return nullptr;
|
||||
auto new_dir = OpenDirectory(new_path, Mode::Read);
|
||||
if (new_dir != nullptr)
|
||||
return nullptr;
|
||||
new_dir = CreateDirectory(new_path, Mode::Write);
|
||||
if (new_dir == nullptr)
|
||||
return nullptr;
|
||||
|
||||
for (const auto& file : old_dir->GetFiles()) {
|
||||
const auto x =
|
||||
CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName());
|
||||
if (x == nullptr)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& dir : old_dir->GetSubdirectories()) {
|
||||
const auto x =
|
||||
CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName());
|
||||
if (x == nullptr)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new_dir;
|
||||
}
|
||||
|
||||
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(sanitized_old_path, sanitized_new_path);
|
||||
if (out == nullptr)
|
||||
return nullptr;
|
||||
if (DeleteDirectory(sanitized_old_path))
|
||||
return out;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
|
||||
const auto path = FileUtil::SanitizePath(path_);
|
||||
auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
|
||||
if (parent == nullptr)
|
||||
return false;
|
||||
return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path));
|
||||
}
|
||||
|
||||
VfsFile::~VfsFile() = default;
|
||||
|
||||
std::string VfsFile::GetExtension() const {
|
||||
@@ -285,6 +433,26 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) {
|
||||
if (file1->GetSize() != file2->GetSize())
|
||||
return false;
|
||||
|
||||
std::vector<u8> f1_v(block_size);
|
||||
std::vector<u8> f2_v(block_size);
|
||||
for (size_t i = 0; i < file1->GetSize(); i += block_size) {
|
||||
auto f1_vs = file1->Read(f1_v.data(), block_size, i);
|
||||
auto f2_vs = file2->Read(f2_v.data(), block_size, i);
|
||||
|
||||
if (f1_vs != f2_vs)
|
||||
return false;
|
||||
auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end());
|
||||
if (iters.first != f1_v.end() && iters.second != f2_v.end())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
|
||||
if (src == nullptr || dest == nullptr)
|
||||
return false;
|
||||
|
||||
@@ -11,17 +11,79 @@
|
||||
#include <vector>
|
||||
#include "boost/optional.hpp"
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
|
||||
namespace FileSys {
|
||||
struct VfsFile;
|
||||
struct VfsDirectory;
|
||||
|
||||
// Convenience typedefs to use VfsDirectory and VfsFile
|
||||
using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>;
|
||||
using VirtualFile = std::shared_ptr<FileSys::VfsFile>;
|
||||
class VfsDirectory;
|
||||
class VfsFile;
|
||||
class VfsFilesystem;
|
||||
|
||||
// Convenience typedefs to use Vfs* interfaces
|
||||
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
|
||||
using VirtualDir = std::shared_ptr<VfsDirectory>;
|
||||
using VirtualFile = std::shared_ptr<VfsFile>;
|
||||
|
||||
// An enumeration representing what can be at the end of a path in a VfsFilesystem
|
||||
enum class VfsEntryType {
|
||||
None,
|
||||
File,
|
||||
Directory,
|
||||
};
|
||||
|
||||
// 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.
|
||||
class VfsFilesystem : NonCopyable {
|
||||
public:
|
||||
explicit VfsFilesystem(VirtualDir root);
|
||||
virtual ~VfsFilesystem();
|
||||
|
||||
// Gets the friendly name for the filesystem.
|
||||
virtual std::string GetName() const;
|
||||
|
||||
// Return whether or not the user has read permissions on this filesystem.
|
||||
virtual bool IsReadable() const;
|
||||
// Return whether or not the user has write permission on this filesystem.
|
||||
virtual bool IsWritable() const;
|
||||
|
||||
// Determine if the entry at path is non-existant, a file, or a directory.
|
||||
virtual VfsEntryType GetEntryType(std::string_view path) const;
|
||||
|
||||
// Opens the file with path relative to root. If it doesn't exist, returns nullptr.
|
||||
virtual VirtualFile OpenFile(std::string_view path, Mode perms);
|
||||
// Creates a new, empty file at path
|
||||
virtual VirtualFile CreateFile(std::string_view path, Mode perms);
|
||||
// Copies the file from old_path to new_path, returning the new file on success and nullptr on
|
||||
// failure.
|
||||
virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path);
|
||||
// Moves the file from old_path to new_path, returning the moved file on success and nullptr on
|
||||
// failure.
|
||||
virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path);
|
||||
// Deletes the file with path relative to root, returing true on success.
|
||||
virtual bool DeleteFile(std::string_view path);
|
||||
|
||||
// Opens the directory with path relative to root. If it doesn't exist, returns nullptr.
|
||||
virtual VirtualDir OpenDirectory(std::string_view path, Mode perms);
|
||||
// Creates a new, empty directory at path
|
||||
virtual VirtualDir CreateDirectory(std::string_view path, Mode perms);
|
||||
// Copies the directory from old_path to new_path, returning the new directory on success and
|
||||
// nullptr on failure.
|
||||
virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path);
|
||||
// Moves the directory from old_path to new_path, returning the moved directory on success and
|
||||
// nullptr on failure.
|
||||
virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path);
|
||||
// Deletes the directory with path relative to root, returing true on success.
|
||||
virtual bool DeleteDirectory(std::string_view path);
|
||||
|
||||
protected:
|
||||
// Root directory in default implementation.
|
||||
VirtualDir root;
|
||||
};
|
||||
|
||||
// A class representing a file in an abstract filesystem.
|
||||
struct VfsFile : NonCopyable {
|
||||
class VfsFile : NonCopyable {
|
||||
public:
|
||||
virtual ~VfsFile();
|
||||
|
||||
// Retrieves the file name.
|
||||
@@ -119,7 +181,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
|
||||
@@ -235,7 +298,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;
|
||||
@@ -245,6 +309,9 @@ struct ReadOnlyVfsDirectory : public VfsDirectory {
|
||||
bool Rename(std::string_view name) override;
|
||||
};
|
||||
|
||||
// Compare the two files, byte-for-byte, in increments specificed by block_size
|
||||
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200);
|
||||
|
||||
// A method that copies the raw data between two different implementations of VirtualFile. If you
|
||||
// are using the same implementation, it is probably better to use the Copy method in the parent
|
||||
// directory of src/dest.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
@@ -29,6 +29,8 @@ static std::string ModeFlagsToString(Mode mode) {
|
||||
mode_str = "a";
|
||||
else if (mode & Mode::Write)
|
||||
mode_str = "w";
|
||||
else
|
||||
UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode));
|
||||
}
|
||||
|
||||
mode_str += "b";
|
||||
@@ -36,8 +38,174 @@ static std::string ModeFlagsToString(Mode mode) {
|
||||
return mode_str;
|
||||
}
|
||||
|
||||
RealVfsFile::RealVfsFile(const std::string& path_, Mode perms_)
|
||||
: backing(path_, ModeFlagsToString(perms_).c_str()), path(path_),
|
||||
RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
|
||||
|
||||
std::string RealVfsFilesystem::GetName() const {
|
||||
return "Real";
|
||||
}
|
||||
|
||||
bool RealVfsFilesystem::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RealVfsFilesystem::IsWritable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (!FileUtil::Exists(path))
|
||||
return VfsEntryType::None;
|
||||
if (FileUtil::IsDirectory(path))
|
||||
return VfsEntryType::Directory;
|
||||
|
||||
return VfsEntryType::File;
|
||||
}
|
||||
|
||||
VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (cache.find(path) != cache.end()) {
|
||||
auto weak = cache[path];
|
||||
if (!weak.expired()) {
|
||||
return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms));
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileUtil::Exists(path) && (perms & Mode::WriteAppend) != 0)
|
||||
FileUtil::CreateEmptyFile(path);
|
||||
|
||||
auto backing = std::make_shared<FileUtil::IOFile>(path, ModeFlagsToString(perms).c_str());
|
||||
cache[path] = backing;
|
||||
|
||||
// Cannot use make_shared as RealVfsFile constructor is private
|
||||
return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, 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;
|
||||
return OpenFile(path, perms);
|
||||
}
|
||||
|
||||
VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
|
||||
const auto old_path =
|
||||
FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
const auto new_path =
|
||||
FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
|
||||
if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
|
||||
FileUtil::IsDirectory(old_path) || !FileUtil::Copy(old_path, new_path))
|
||||
return nullptr;
|
||||
return OpenFile(new_path, Mode::ReadWrite);
|
||||
}
|
||||
|
||||
VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
|
||||
const auto old_path =
|
||||
FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
const auto new_path =
|
||||
FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
|
||||
if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
|
||||
FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
|
||||
return nullptr;
|
||||
|
||||
if (cache.find(old_path) != cache.end()) {
|
||||
auto cached = cache[old_path];
|
||||
if (!cached.expired()) {
|
||||
auto file = cached.lock();
|
||||
file->Open(new_path, "r+b");
|
||||
cache.erase(old_path);
|
||||
cache[new_path] = file;
|
||||
}
|
||||
}
|
||||
return OpenFile(new_path, Mode::ReadWrite);
|
||||
}
|
||||
|
||||
bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (cache.find(path) != cache.end()) {
|
||||
if (!cache[path].expired())
|
||||
cache[path].lock()->Close();
|
||||
cache.erase(path);
|
||||
}
|
||||
return FileUtil::Delete(path);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
// Cannot use make_shared as RealVfsDirectory constructor is private
|
||||
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, 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;
|
||||
// Cannot use make_shared as RealVfsDirectory constructor is private
|
||||
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
|
||||
std::string_view new_path_) {
|
||||
const auto old_path =
|
||||
FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
const auto new_path =
|
||||
FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
|
||||
!FileUtil::IsDirectory(old_path))
|
||||
return nullptr;
|
||||
FileUtil::CopyDir(old_path, new_path);
|
||||
return OpenDirectory(new_path, Mode::ReadWrite);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
|
||||
std::string_view new_path_) {
|
||||
const auto old_path =
|
||||
FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
const auto new_path =
|
||||
FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
|
||||
FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
|
||||
return nullptr;
|
||||
|
||||
for (auto& kv : cache) {
|
||||
// Path in cache starts with old_path
|
||||
if (kv.first.rfind(old_path, 0) == 0) {
|
||||
const auto file_old_path =
|
||||
FileUtil::SanitizePath(kv.first, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
const auto file_new_path =
|
||||
FileUtil::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
auto cached = cache[file_old_path];
|
||||
if (!cached.expired()) {
|
||||
auto file = cached.lock();
|
||||
file->Open(file_new_path, "r+b");
|
||||
cache.erase(file_old_path);
|
||||
cache[file_new_path] = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OpenDirectory(new_path, Mode::ReadWrite);
|
||||
}
|
||||
|
||||
bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
for (auto& kv : cache) {
|
||||
// Path in cache starts with old_path
|
||||
if (kv.first.rfind(path, 0) == 0) {
|
||||
if (!cache[kv.first].expired())
|
||||
cache[kv.first].lock()->Close();
|
||||
cache.erase(kv.first);
|
||||
}
|
||||
}
|
||||
return FileUtil::DeleteDirRecursively(path);
|
||||
}
|
||||
|
||||
RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOFile> backing_,
|
||||
const std::string& path_, Mode perms_)
|
||||
: base(base_), backing(std::move(backing_)), path(path_),
|
||||
parent_path(FileUtil::GetParentPath(path_)),
|
||||
path_components(FileUtil::SplitPathComponents(path_)),
|
||||
parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
|
||||
@@ -48,15 +216,15 @@ std::string RealVfsFile::GetName() const {
|
||||
}
|
||||
|
||||
size_t RealVfsFile::GetSize() const {
|
||||
return backing.GetSize();
|
||||
return backing->GetSize();
|
||||
}
|
||||
|
||||
bool RealVfsFile::Resize(size_t new_size) {
|
||||
return backing.Resize(new_size);
|
||||
return backing->Resize(new_size);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
|
||||
return std::make_shared<RealVfsDirectory>(parent_path, perms);
|
||||
return base.OpenDirectory(parent_path, perms);
|
||||
}
|
||||
|
||||
bool RealVfsFile::IsWritable() const {
|
||||
@@ -68,62 +236,118 @@ bool RealVfsFile::IsReadable() const {
|
||||
}
|
||||
|
||||
size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const {
|
||||
if (!backing.Seek(offset, SEEK_SET))
|
||||
if (!backing->Seek(offset, SEEK_SET))
|
||||
return 0;
|
||||
return backing.ReadBytes(data, length);
|
||||
return backing->ReadBytes(data, length);
|
||||
}
|
||||
|
||||
size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) {
|
||||
if (!backing.Seek(offset, SEEK_SET))
|
||||
if (!backing->Seek(offset, SEEK_SET))
|
||||
return 0;
|
||||
return backing.WriteBytes(data, length);
|
||||
return backing->WriteBytes(data, length);
|
||||
}
|
||||
|
||||
bool RealVfsFile::Rename(std::string_view name) {
|
||||
std::string name_str(name.begin(), name.end());
|
||||
const auto out = FileUtil::Rename(GetName(), name_str);
|
||||
return base.MoveFile(path, parent_path + DIR_SEP + std::string(name)) != nullptr;
|
||||
}
|
||||
|
||||
path = (parent_path + DIR_SEP).append(name);
|
||||
path_components = parent_components;
|
||||
path_components.push_back(std::move(name_str));
|
||||
backing = FileUtil::IOFile(path, ModeFlagsToString(perms).c_str());
|
||||
bool RealVfsFile::Close() {
|
||||
return backing->Close();
|
||||
}
|
||||
|
||||
// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
|
||||
// constexpr' because there is a compile error in the branch not used.
|
||||
|
||||
template <>
|
||||
std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
|
||||
if (perms == Mode::Append)
|
||||
return {};
|
||||
|
||||
std::vector<VirtualFile> out;
|
||||
FileUtil::ForeachDirectoryEntry(
|
||||
nullptr, path,
|
||||
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
|
||||
const std::string full_path = directory + DIR_SEP + filename;
|
||||
if (!FileUtil::IsDirectory(full_path))
|
||||
out.emplace_back(base.OpenFile(full_path, perms));
|
||||
return true;
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool RealVfsFile::Close() {
|
||||
return backing.Close();
|
||||
template <>
|
||||
std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
|
||||
if (perms == Mode::Append)
|
||||
return {};
|
||||
|
||||
std::vector<VirtualDir> out;
|
||||
FileUtil::ForeachDirectoryEntry(
|
||||
nullptr, path,
|
||||
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
|
||||
const std::string full_path = directory + DIR_SEP + filename;
|
||||
if (FileUtil::IsDirectory(full_path))
|
||||
out.emplace_back(base.OpenDirectory(full_path, perms));
|
||||
return true;
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
RealVfsDirectory::RealVfsDirectory(const std::string& path_, Mode perms_)
|
||||
: path(FileUtil::RemoveTrailingSlash(path_)), parent_path(FileUtil::GetParentPath(path)),
|
||||
RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
|
||||
: base(base_), path(FileUtil::RemoveTrailingSlash(path_)),
|
||||
parent_path(FileUtil::GetParentPath(path)),
|
||||
path_components(FileUtil::SplitPathComponents(path)),
|
||||
parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
|
||||
perms(perms_) {
|
||||
if (!FileUtil::Exists(path) && perms & Mode::WriteAppend)
|
||||
FileUtil::CreateDir(path);
|
||||
}
|
||||
|
||||
if (perms == Mode::Append)
|
||||
return;
|
||||
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))
|
||||
return nullptr;
|
||||
return base.OpenFile(full_path, perms);
|
||||
}
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(
|
||||
nullptr, path,
|
||||
[this](u64* entries_out, const std::string& directory, const std::string& filename) {
|
||||
std::string full_path = directory + DIR_SEP + filename;
|
||||
if (FileUtil::IsDirectory(full_path))
|
||||
subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(full_path, perms));
|
||||
else
|
||||
files.emplace_back(std::make_shared<RealVfsFile>(full_path, perms));
|
||||
return true;
|
||||
});
|
||||
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))
|
||||
return nullptr;
|
||||
return base.OpenDirectory(full_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::GetFile(std::string_view name) const {
|
||||
return GetFileRelative(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
return GetDirectoryRelative(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
return base.CreateFile(full_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
auto parent = std::string(FileUtil::GetParentPath(full_path));
|
||||
return base.CreateDirectory(full_path, perms);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
|
||||
auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(name));
|
||||
return base.DeleteDirectory(full_path);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
|
||||
return files;
|
||||
return IterateEntries<RealVfsFile, VfsFile>();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
|
||||
return subdirectories;
|
||||
return IterateEntries<RealVfsDirectory, VfsDirectory>();
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::IsWritable() const {
|
||||
@@ -142,57 +366,32 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
|
||||
if (path_components.size() <= 1)
|
||||
return nullptr;
|
||||
|
||||
return std::make_shared<RealVfsDirectory>(parent_path, perms);
|
||||
return base.OpenDirectory(parent_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
const std::string subdir_path = (path + DIR_SEP).append(name);
|
||||
|
||||
if (!FileUtil::CreateDir(subdir_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(subdir_path, perms));
|
||||
return subdirectories.back();
|
||||
return base.CreateDirectory(subdir_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(std::string_view name) {
|
||||
const std::string file_path = (path + DIR_SEP).append(name);
|
||||
|
||||
if (!FileUtil::CreateEmptyFile(file_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
files.emplace_back(std::make_shared<RealVfsFile>(file_path, perms));
|
||||
return files.back();
|
||||
return base.CreateFile(file_path, perms);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
|
||||
const std::string subdir_path = (path + DIR_SEP).append(name);
|
||||
|
||||
return FileUtil::DeleteDirRecursively(subdir_path);
|
||||
return base.DeleteDirectory(subdir_path);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::DeleteFile(std::string_view name) {
|
||||
const auto file = GetFile(name);
|
||||
|
||||
if (file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
files.erase(std::find(files.begin(), files.end(), file));
|
||||
|
||||
auto real_file = std::static_pointer_cast<RealVfsFile>(file);
|
||||
real_file->Close();
|
||||
|
||||
const std::string file_path = (path + DIR_SEP).append(name);
|
||||
return FileUtil::Delete(file_path);
|
||||
return base.DeleteFile(file_path);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::Rename(std::string_view name) {
|
||||
const std::string new_name = (parent_path + DIR_SEP).append(name);
|
||||
|
||||
return FileUtil::Rename(path, new_name);
|
||||
return base.MoveFile(path, new_name) != nullptr;
|
||||
}
|
||||
|
||||
std::string RealVfsDirectory::GetFullPath() const {
|
||||
@@ -202,16 +401,6 @@ std::string RealVfsDirectory::GetFullPath() const {
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
const auto iter = std::find(files.begin(), files.end(), file);
|
||||
if (iter == files.end())
|
||||
return false;
|
||||
|
||||
const std::ptrdiff_t offset = std::distance(files.begin(), iter);
|
||||
files[offset] = files.back();
|
||||
files.pop_back();
|
||||
|
||||
subdirectories.emplace_back(std::move(dir));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -6,18 +6,45 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class RealVfsFilesystem : public VfsFilesystem {
|
||||
public:
|
||||
RealVfsFilesystem();
|
||||
|
||||
std::string GetName() const override;
|
||||
bool IsReadable() const override;
|
||||
bool IsWritable() const override;
|
||||
VfsEntryType GetEntryType(std::string_view path) const override;
|
||||
VirtualFile OpenFile(std::string_view path, Mode perms = Mode::Read) override;
|
||||
VirtualFile CreateFile(std::string_view path, Mode perms = Mode::ReadWrite) override;
|
||||
VirtualFile CopyFile(std::string_view old_path, std::string_view new_path) override;
|
||||
VirtualFile MoveFile(std::string_view old_path, std::string_view new_path) override;
|
||||
bool DeleteFile(std::string_view path) override;
|
||||
VirtualDir OpenDirectory(std::string_view path, Mode perms = Mode::Read) override;
|
||||
VirtualDir CreateDirectory(std::string_view path, Mode perms = Mode::ReadWrite) override;
|
||||
VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path) override;
|
||||
VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path) override;
|
||||
bool DeleteDirectory(std::string_view path) override;
|
||||
|
||||
private:
|
||||
boost::container::flat_map<std::string, std::weak_ptr<FileUtil::IOFile>> cache;
|
||||
};
|
||||
|
||||
// An implmentation of VfsFile that represents a file on the user's computer.
|
||||
struct RealVfsFile : public VfsFile {
|
||||
friend struct RealVfsDirectory;
|
||||
class RealVfsFile : public VfsFile {
|
||||
friend class RealVfsDirectory;
|
||||
friend class RealVfsFilesystem;
|
||||
|
||||
RealVfsFile(const std::string& name, Mode perms = Mode::Read);
|
||||
RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing,
|
||||
const std::string& path, Mode perms = Mode::Read);
|
||||
|
||||
public:
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
@@ -31,7 +58,8 @@ struct RealVfsFile : public VfsFile {
|
||||
private:
|
||||
bool Close();
|
||||
|
||||
FileUtil::IOFile backing;
|
||||
RealVfsFilesystem& base;
|
||||
std::shared_ptr<FileUtil::IOFile> backing;
|
||||
std::string path;
|
||||
std::string parent_path;
|
||||
std::vector<std::string> path_components;
|
||||
@@ -40,9 +68,19 @@ private:
|
||||
};
|
||||
|
||||
// An implementation of VfsDirectory that represents a directory on the user's computer.
|
||||
struct RealVfsDirectory : public VfsDirectory {
|
||||
RealVfsDirectory(const std::string& path, Mode perms = Mode::Read);
|
||||
class RealVfsDirectory : public VfsDirectory {
|
||||
friend class RealVfsFilesystem;
|
||||
|
||||
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
|
||||
|
||||
public:
|
||||
std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
|
||||
std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
|
||||
std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
|
||||
std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
|
||||
bool DeleteSubdirectoryRecursive(std::string_view name) override;
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
bool IsWritable() const override;
|
||||
@@ -60,13 +98,15 @@ protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
template <typename T, typename R>
|
||||
std::vector<std::shared_ptr<R>> IterateEntries() const;
|
||||
|
||||
RealVfsFilesystem& base;
|
||||
std::string path;
|
||||
std::string parent_path;
|
||||
std::vector<std::string> path_components;
|
||||
std::vector<std::string> parent_components;
|
||||
Mode perms;
|
||||
std::vector<std::shared_ptr<VfsFile>> files;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> subdirectories;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -31,16 +32,18 @@ bool VectorVfsDirectory::IsReadable() const {
|
||||
std::string VectorVfsDirectory::GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VectorVfsDirectory::GetParentDirectory() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool FindAndRemoveVectorElement(std::vector<T>& vec, std::string_view name) {
|
||||
auto iter = std::find_if(vec.begin(), vec.end(), [name](T e) { return e->GetName() == name; });
|
||||
const auto iter =
|
||||
std::find_if(vec.begin(), vec.end(), [name](const T& e) { return e->GetName() == name; });
|
||||
if (iter == vec.end())
|
||||
return false;
|
||||
auto old_size = vec.size();
|
||||
|
||||
vec.erase(iter);
|
||||
return true;
|
||||
}
|
||||
@@ -77,7 +80,7 @@ void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
|
||||
bool VectorVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
if (!DeleteFile(file->GetName()))
|
||||
return false;
|
||||
dirs.emplace_back(dir);
|
||||
dirs.emplace_back(std::move(dir));
|
||||
return true;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -10,7 +10,8 @@ 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 = "");
|
||||
|
||||
@@ -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...).
|
||||
@@ -166,3 +168,5 @@ private:
|
||||
*/
|
||||
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -37,45 +37,46 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
const int GDB_BUFFER_SIZE = 10000;
|
||||
namespace GDBStub {
|
||||
namespace {
|
||||
constexpr int GDB_BUFFER_SIZE = 10000;
|
||||
|
||||
const char GDB_STUB_START = '$';
|
||||
const char GDB_STUB_END = '#';
|
||||
const char GDB_STUB_ACK = '+';
|
||||
const char GDB_STUB_NACK = '-';
|
||||
constexpr char GDB_STUB_START = '$';
|
||||
constexpr char GDB_STUB_END = '#';
|
||||
constexpr char GDB_STUB_ACK = '+';
|
||||
constexpr char GDB_STUB_NACK = '-';
|
||||
|
||||
#ifndef SIGTRAP
|
||||
const u32 SIGTRAP = 5;
|
||||
constexpr u32 SIGTRAP = 5;
|
||||
#endif
|
||||
|
||||
#ifndef SIGTERM
|
||||
const u32 SIGTERM = 15;
|
||||
constexpr u32 SIGTERM = 15;
|
||||
#endif
|
||||
|
||||
#ifndef MSG_WAITALL
|
||||
const u32 MSG_WAITALL = 8;
|
||||
constexpr u32 MSG_WAITALL = 8;
|
||||
#endif
|
||||
|
||||
const u32 LR_REGISTER = 30;
|
||||
const u32 SP_REGISTER = 31;
|
||||
const u32 PC_REGISTER = 32;
|
||||
const u32 CPSR_REGISTER = 33;
|
||||
const u32 UC_ARM64_REG_Q0 = 34;
|
||||
const u32 FPSCR_REGISTER = 66;
|
||||
constexpr u32 LR_REGISTER = 30;
|
||||
constexpr u32 SP_REGISTER = 31;
|
||||
constexpr u32 PC_REGISTER = 32;
|
||||
constexpr u32 CPSR_REGISTER = 33;
|
||||
constexpr u32 UC_ARM64_REG_Q0 = 34;
|
||||
constexpr u32 FPSCR_REGISTER = 66;
|
||||
|
||||
// TODO/WiP - Used while working on support for FPU
|
||||
const u32 TODO_DUMMY_REG_997 = 997;
|
||||
const u32 TODO_DUMMY_REG_998 = 998;
|
||||
constexpr u32 TODO_DUMMY_REG_997 = 997;
|
||||
constexpr u32 TODO_DUMMY_REG_998 = 998;
|
||||
|
||||
// For sample XML files see the GDB source /gdb/features
|
||||
// GDB also wants the l character at the start
|
||||
// This XML defines what the registers are for this specific ARM device
|
||||
static const char* target_xml =
|
||||
constexpr char target_xml[] =
|
||||
R"(l<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target version="1.0">
|
||||
@@ -141,30 +142,28 @@ static const char* target_xml =
|
||||
</target>
|
||||
)";
|
||||
|
||||
namespace GDBStub {
|
||||
int gdbserver_socket = -1;
|
||||
|
||||
static int gdbserver_socket = -1;
|
||||
u8 command_buffer[GDB_BUFFER_SIZE];
|
||||
u32 command_length;
|
||||
|
||||
static u8 command_buffer[GDB_BUFFER_SIZE];
|
||||
static u32 command_length;
|
||||
u32 latest_signal = 0;
|
||||
bool memory_break = false;
|
||||
|
||||
static u32 latest_signal = 0;
|
||||
static bool memory_break = false;
|
||||
|
||||
static Kernel::Thread* current_thread = nullptr;
|
||||
static u32 current_core = 0;
|
||||
Kernel::Thread* current_thread = nullptr;
|
||||
u32 current_core = 0;
|
||||
|
||||
// Binding to a port within the reserved ports range (0-1023) requires root permissions,
|
||||
// so default to a port outside of that range.
|
||||
static u16 gdbstub_port = 24689;
|
||||
u16 gdbstub_port = 24689;
|
||||
|
||||
static bool halt_loop = true;
|
||||
static bool step_loop = false;
|
||||
static bool send_trap = false;
|
||||
bool halt_loop = true;
|
||||
bool step_loop = false;
|
||||
bool send_trap = false;
|
||||
|
||||
// If set to false, the server will never be started and no
|
||||
// gdbstub-related functions will be executed.
|
||||
static std::atomic<bool> server_enabled(false);
|
||||
std::atomic<bool> server_enabled(false);
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA InitData;
|
||||
@@ -172,23 +171,26 @@ WSADATA InitData;
|
||||
|
||||
struct Breakpoint {
|
||||
bool active;
|
||||
PAddr addr;
|
||||
VAddr addr;
|
||||
u64 len;
|
||||
std::array<u8, 4> inst;
|
||||
};
|
||||
|
||||
static std::map<u64, Breakpoint> breakpoints_execute;
|
||||
static std::map<u64, Breakpoint> breakpoints_read;
|
||||
static std::map<u64, Breakpoint> breakpoints_write;
|
||||
using BreakpointMap = std::map<VAddr, Breakpoint>;
|
||||
BreakpointMap breakpoints_execute;
|
||||
BreakpointMap breakpoints_read;
|
||||
BreakpointMap breakpoints_write;
|
||||
|
||||
struct Module {
|
||||
std::string name;
|
||||
PAddr beg;
|
||||
PAddr end;
|
||||
VAddr beg;
|
||||
VAddr end;
|
||||
};
|
||||
|
||||
static std::vector<Module> modules;
|
||||
std::vector<Module> modules;
|
||||
} // Anonymous namespace
|
||||
|
||||
void RegisterModule(std::string name, PAddr beg, PAddr end, bool add_elf_ext) {
|
||||
void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
|
||||
Module module;
|
||||
if (add_elf_ext) {
|
||||
Common::SplitPath(name, nullptr, &module.name, nullptr);
|
||||
@@ -419,11 +421,11 @@ static u8 CalculateChecksum(const u8* buffer, size_t length) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of breakpoints for a given breakpoint type.
|
||||
* Get the map of breakpoints for a given breakpoint type.
|
||||
*
|
||||
* @param type Type of breakpoint list.
|
||||
* @param type Type of breakpoint map.
|
||||
*/
|
||||
static std::map<u64, Breakpoint>& GetBreakpointList(BreakpointType type) {
|
||||
static BreakpointMap& GetBreakpointMap(BreakpointType type) {
|
||||
switch (type) {
|
||||
case BreakpointType::Execute:
|
||||
return breakpoints_execute;
|
||||
@@ -442,20 +444,24 @@ static std::map<u64, Breakpoint>& GetBreakpointList(BreakpointType type) {
|
||||
* @param type Type of breakpoint.
|
||||
* @param addr Address of breakpoint.
|
||||
*/
|
||||
static void RemoveBreakpoint(BreakpointType type, PAddr addr) {
|
||||
std::map<u64, Breakpoint>& p = GetBreakpointList(type);
|
||||
static void RemoveBreakpoint(BreakpointType type, VAddr addr) {
|
||||
BreakpointMap& p = GetBreakpointMap(type);
|
||||
|
||||
auto bp = p.find(static_cast<u64>(addr));
|
||||
if (bp != p.end()) {
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}",
|
||||
bp->second.len, bp->second.addr, static_cast<int>(type));
|
||||
p.erase(static_cast<u64>(addr));
|
||||
const auto bp = p.find(addr);
|
||||
if (bp == p.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}",
|
||||
bp->second.len, bp->second.addr, static_cast<int>(type));
|
||||
Memory::WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size());
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
p.erase(addr);
|
||||
}
|
||||
|
||||
BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, BreakpointType type) {
|
||||
std::map<u64, Breakpoint>& p = GetBreakpointList(type);
|
||||
auto next_breakpoint = p.lower_bound(static_cast<u64>(addr));
|
||||
BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, BreakpointType type) {
|
||||
const BreakpointMap& p = GetBreakpointMap(type);
|
||||
const auto next_breakpoint = p.lower_bound(addr);
|
||||
BreakpointAddress breakpoint;
|
||||
|
||||
if (next_breakpoint != p.end()) {
|
||||
@@ -469,36 +475,38 @@ BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, BreakpointType type)
|
||||
return breakpoint;
|
||||
}
|
||||
|
||||
bool CheckBreakpoint(PAddr addr, BreakpointType type) {
|
||||
bool CheckBreakpoint(VAddr addr, BreakpointType type) {
|
||||
if (!IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<u64, Breakpoint>& p = GetBreakpointList(type);
|
||||
const BreakpointMap& p = GetBreakpointMap(type);
|
||||
const auto bp = p.find(addr);
|
||||
|
||||
auto bp = p.find(static_cast<u64>(addr));
|
||||
if (bp != p.end()) {
|
||||
u64 len = bp->second.len;
|
||||
if (bp == p.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints
|
||||
// no matter if it's a 4-byte or 2-byte instruction. When you execute a
|
||||
// Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on
|
||||
// two instructions instead of the single instruction you placed the breakpoint
|
||||
// on. So, as a way to make sure that execution breakpoints are only breaking
|
||||
// on the instruction that was specified, set the length of an execution
|
||||
// breakpoint to 1. This should be fine since the CPU should never begin executing
|
||||
// an instruction anywhere except the beginning of the instruction.
|
||||
if (type == BreakpointType::Execute) {
|
||||
len = 1;
|
||||
}
|
||||
u64 len = bp->second.len;
|
||||
|
||||
if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
|
||||
LOG_DEBUG(Debug_GDBStub,
|
||||
"Found breakpoint type {} @ {:016X}, range: {:016X}"
|
||||
" - {:016X} ({:X} bytes)",
|
||||
static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len);
|
||||
return true;
|
||||
}
|
||||
// IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints
|
||||
// no matter if it's a 4-byte or 2-byte instruction. When you execute a
|
||||
// Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on
|
||||
// two instructions instead of the single instruction you placed the breakpoint
|
||||
// on. So, as a way to make sure that execution breakpoints are only breaking
|
||||
// on the instruction that was specified, set the length of an execution
|
||||
// breakpoint to 1. This should be fine since the CPU should never begin executing
|
||||
// an instruction anywhere except the beginning of the instruction.
|
||||
if (type == BreakpointType::Execute) {
|
||||
len = 1;
|
||||
}
|
||||
|
||||
if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
|
||||
LOG_DEBUG(Debug_GDBStub,
|
||||
"Found breakpoint type {} @ {:016X}, range: {:016X}"
|
||||
" - {:016X} ({:X} bytes)",
|
||||
static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -932,6 +940,7 @@ static void WriteMemory() {
|
||||
|
||||
GdbHexToMem(data.data(), len_pos + 1, len);
|
||||
Memory::WriteBlock(addr, data.data(), len);
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
SendReply("OK");
|
||||
}
|
||||
|
||||
@@ -951,6 +960,7 @@ static void Step() {
|
||||
step_loop = true;
|
||||
halt_loop = true;
|
||||
send_trap = true;
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
}
|
||||
|
||||
/// Tell the CPU if we hit a memory breakpoint.
|
||||
@@ -967,6 +977,7 @@ static void Continue() {
|
||||
memory_break = false;
|
||||
step_loop = false;
|
||||
halt_loop = false;
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -976,13 +987,17 @@ static void Continue() {
|
||||
* @param addr Address of breakpoint.
|
||||
* @param len Length of breakpoint.
|
||||
*/
|
||||
static bool CommitBreakpoint(BreakpointType type, PAddr addr, u64 len) {
|
||||
std::map<u64, Breakpoint>& p = GetBreakpointList(type);
|
||||
static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) {
|
||||
BreakpointMap& p = GetBreakpointMap(type);
|
||||
|
||||
Breakpoint breakpoint;
|
||||
breakpoint.active = true;
|
||||
breakpoint.addr = addr;
|
||||
breakpoint.len = len;
|
||||
Memory::ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size());
|
||||
static constexpr std::array<u8, 4> btrap{{0xd4, 0x20, 0x7d, 0x0}};
|
||||
Memory::WriteBlock(addr, btrap.data(), btrap.size());
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
p.insert({addr, breakpoint});
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}",
|
||||
@@ -1016,7 +1031,7 @@ static void AddBreakpoint() {
|
||||
|
||||
auto start_offset = command_buffer + 3;
|
||||
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
|
||||
PAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
|
||||
VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
|
||||
|
||||
start_offset = addr_pos + 1;
|
||||
u64 len =
|
||||
@@ -1065,7 +1080,7 @@ static void RemoveBreakpoint() {
|
||||
|
||||
auto start_offset = command_buffer + 3;
|
||||
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
|
||||
PAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
|
||||
VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
|
||||
|
||||
if (type == BreakpointType::Access) {
|
||||
// Access is made up of Read and Write types, so add both breakpoints
|
||||
|
||||
@@ -22,7 +22,7 @@ enum class BreakpointType {
|
||||
};
|
||||
|
||||
struct BreakpointAddress {
|
||||
PAddr address;
|
||||
VAddr address;
|
||||
BreakpointType type;
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ bool IsServerEnabled();
|
||||
bool IsConnected();
|
||||
|
||||
/// Register module.
|
||||
void RegisterModule(std::string name, PAddr beg, PAddr end, bool add_elf_ext = true);
|
||||
void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext = true);
|
||||
|
||||
/**
|
||||
* Signal to the gdbstub server that it should halt CPU execution.
|
||||
@@ -74,7 +74,7 @@ void HandlePacket();
|
||||
* @param addr Address to search from.
|
||||
* @param type Type of breakpoint.
|
||||
*/
|
||||
BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, GDBStub::BreakpointType type);
|
||||
BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, GDBStub::BreakpointType type);
|
||||
|
||||
/**
|
||||
* Check if a breakpoint of the specified type exists at the given address.
|
||||
@@ -82,7 +82,7 @@ BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, GDBStub::BreakpointTy
|
||||
* @param addr Address of breakpoint.
|
||||
* @param type Type of breakpoint.
|
||||
*/
|
||||
bool CheckBreakpoint(PAddr addr, GDBStub::BreakpointType type);
|
||||
bool CheckBreakpoint(VAddr addr, GDBStub::BreakpointType type);
|
||||
|
||||
/// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
||||
bool GetCpuHaltFlag();
|
||||
|
||||
@@ -5,15 +5,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_port.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#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"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -30,9 +32,8 @@ static ResultCode WaitForAddress(VAddr address, s64 timeout) {
|
||||
}
|
||||
|
||||
// Gets the threads waiting on an address.
|
||||
static void GetThreadsWaitingOnAddress(std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr address) {
|
||||
auto RetrieveWaitingThreads =
|
||||
static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) {
|
||||
const auto RetrieveWaitingThreads =
|
||||
[](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr arb_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
auto& thread_list = scheduler->GetThreadList();
|
||||
@@ -43,16 +44,20 @@ static void GetThreadsWaitingOnAddress(std::vector<SharedPtr<Thread>>& waiting_t
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve a list of all threads that are waiting for this address.
|
||||
RetrieveWaitingThreads(0, waiting_threads, address);
|
||||
RetrieveWaitingThreads(1, waiting_threads, address);
|
||||
RetrieveWaitingThreads(2, waiting_threads, address);
|
||||
RetrieveWaitingThreads(3, waiting_threads, address);
|
||||
// Retrieve all threads that are waiting for this address.
|
||||
std::vector<SharedPtr<Thread>> threads;
|
||||
RetrieveWaitingThreads(0, threads, address);
|
||||
RetrieveWaitingThreads(1, threads, address);
|
||||
RetrieveWaitingThreads(2, threads, address);
|
||||
RetrieveWaitingThreads(3, threads, address);
|
||||
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(waiting_threads.begin(), waiting_threads.end(),
|
||||
std::sort(threads.begin(), threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
});
|
||||
|
||||
return threads;
|
||||
}
|
||||
|
||||
// Wake up num_to_wake (or all) threads in a vector.
|
||||
@@ -74,9 +79,7 @@ static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num
|
||||
|
||||
// Signals an address being waited on.
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake) {
|
||||
// Get threads waiting on the address.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads;
|
||||
GetThreadsWaitingOnAddress(waiting_threads, address);
|
||||
std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
@@ -108,12 +111,11 @@ ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 valu
|
||||
}
|
||||
|
||||
// Get threads waiting on the address.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads;
|
||||
GetThreadsWaitingOnAddress(waiting_threads, address);
|
||||
std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
// Determine the modified value depending on the waiting count.
|
||||
s32 updated_value;
|
||||
if (waiting_threads.size() == 0) {
|
||||
if (waiting_threads.empty()) {
|
||||
updated_value = value - 1;
|
||||
} else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
|
||||
updated_value = value + 1;
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include <tuple>
|
||||
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_port.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
ClientPort::ClientPort() {}
|
||||
ClientPort::~ClientPort() {}
|
||||
ClientPort::ClientPort() = default;
|
||||
ClientPort::~ClientPort() = default;
|
||||
|
||||
ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
|
||||
// Note: Threads do not wait for the server endpoint to call
|
||||
@@ -39,4 +40,12 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
|
||||
return MakeResult(std::get<SharedPtr<ClientSession>>(sessions));
|
||||
}
|
||||
|
||||
void ClientPort::ConnectionClosed() {
|
||||
if (active_sessions == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
--active_sessions;
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -37,14 +37,20 @@ public:
|
||||
*/
|
||||
ResultVal<SharedPtr<ClientSession>> Connect();
|
||||
|
||||
SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port.
|
||||
u32 max_sessions; ///< Maximum number of simultaneous sessions the port can have
|
||||
u32 active_sessions; ///< Number of currently open sessions to this port
|
||||
std::string name; ///< Name of client port (optional)
|
||||
/**
|
||||
* Signifies that a previously active connection has been closed,
|
||||
* decreasing the total number of active connections to this port.
|
||||
*/
|
||||
void ConnectionClosed();
|
||||
|
||||
private:
|
||||
ClientPort();
|
||||
~ClientPort() override;
|
||||
|
||||
SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port.
|
||||
u32 max_sessions = 0; ///< Maximum number of simultaneous sessions the port can have
|
||||
u32 active_sessions = 0; ///< Number of currently open sessions to this port
|
||||
std::string name; ///< Name of client port (optional)
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -31,10 +31,9 @@ public:
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
ResetType reset_type; ///< Current ResetType
|
||||
|
||||
bool signaled; ///< Whether the event has already been signaled
|
||||
std::string name; ///< Name of event (optional)
|
||||
ResetType GetResetType() const {
|
||||
return reset_type;
|
||||
}
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
@@ -47,6 +46,11 @@ public:
|
||||
private:
|
||||
Event();
|
||||
~Event() override;
|
||||
|
||||
ResetType reset_type; ///< Current ResetType
|
||||
|
||||
bool signaled; ///< Whether the event has already been signaled
|
||||
std::string name; ///< Name of event (optional)
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -2,17 +2,22 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/memory.h"
|
||||
@@ -196,7 +201,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
|
||||
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
|
||||
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
|
||||
Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
dst_cmdbuf.size() * sizeof(u32));
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -14,7 +13,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
@@ -133,7 +132,7 @@ public:
|
||||
ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process,
|
||||
HandleTable& src_table);
|
||||
/// Writes data from this context back to the requesting process/thread.
|
||||
ResultCode WriteToOutgoingCommandBuffer(Thread& thread);
|
||||
ResultCode WriteToOutgoingCommandBuffer(const Thread& thread);
|
||||
|
||||
u32_le GetCommand() const {
|
||||
return command;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/object_address_table.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
@@ -13,12 +11,10 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
unsigned int Object::next_object_id;
|
||||
std::atomic<u32> Object::next_object_id{0};
|
||||
|
||||
/// Initialize the kernel
|
||||
void Init(u32 system_mode) {
|
||||
Kernel::MemoryInit(system_mode);
|
||||
|
||||
void Init() {
|
||||
Kernel::ResourceLimitsInit();
|
||||
Kernel::ThreadingInit();
|
||||
Kernel::TimersInit();
|
||||
@@ -33,13 +29,11 @@ void Init(u32 system_mode) {
|
||||
void Shutdown() {
|
||||
// Free all kernel objects
|
||||
g_handle_table.Clear();
|
||||
g_object_address_table.Clear();
|
||||
|
||||
Kernel::ThreadingShutdown();
|
||||
|
||||
Kernel::TimersShutdown();
|
||||
Kernel::ResourceLimitsShutdown();
|
||||
Kernel::MemoryShutdown();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -4,122 +4,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
using Handle = u32;
|
||||
|
||||
enum class HandleType : u32 {
|
||||
Unknown,
|
||||
Event,
|
||||
SharedMemory,
|
||||
Thread,
|
||||
Process,
|
||||
AddressArbiter,
|
||||
Timer,
|
||||
ResourceLimit,
|
||||
CodeSet,
|
||||
ClientPort,
|
||||
ServerPort,
|
||||
ClientSession,
|
||||
ServerSession,
|
||||
};
|
||||
|
||||
enum class ResetType {
|
||||
OneShot,
|
||||
Sticky,
|
||||
Pulse,
|
||||
};
|
||||
|
||||
class Object : NonCopyable {
|
||||
public:
|
||||
virtual ~Object() {}
|
||||
|
||||
/// Returns a unique identifier for the object. For debugging purposes only.
|
||||
unsigned int GetObjectId() const {
|
||||
return object_id;
|
||||
}
|
||||
|
||||
virtual std::string GetTypeName() const {
|
||||
return "[BAD KERNEL OBJECT TYPE]";
|
||||
}
|
||||
virtual std::string GetName() const {
|
||||
return "[UNKNOWN KERNEL OBJECT]";
|
||||
}
|
||||
virtual Kernel::HandleType GetHandleType() const = 0;
|
||||
|
||||
/**
|
||||
* Check if a thread can wait on the object
|
||||
* @return True if a thread can wait on the object, otherwise false
|
||||
*/
|
||||
bool IsWaitable() const {
|
||||
switch (GetHandleType()) {
|
||||
case HandleType::Event:
|
||||
case HandleType::Thread:
|
||||
case HandleType::Timer:
|
||||
case HandleType::ServerPort:
|
||||
case HandleType::ServerSession:
|
||||
return true;
|
||||
|
||||
case HandleType::Unknown:
|
||||
case HandleType::SharedMemory:
|
||||
case HandleType::Process:
|
||||
case HandleType::AddressArbiter:
|
||||
case HandleType::ResourceLimit:
|
||||
case HandleType::CodeSet:
|
||||
case HandleType::ClientPort:
|
||||
case HandleType::ClientSession:
|
||||
return false;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
public:
|
||||
static unsigned int 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++;
|
||||
};
|
||||
|
||||
// Special functions used by boost::instrusive_ptr to do automatic ref-counting
|
||||
inline void intrusive_ptr_add_ref(Object* object) {
|
||||
++object->ref_count;
|
||||
}
|
||||
|
||||
inline void intrusive_ptr_release(Object* object) {
|
||||
if (--object->ref_count == 0) {
|
||||
delete object;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using SharedPtr = boost::intrusive_ptr<T>;
|
||||
|
||||
/**
|
||||
* Attempts to downcast the given Object pointer to a pointer to T.
|
||||
* @return Derived pointer to the object, or `nullptr` if `object` isn't of type T.
|
||||
*/
|
||||
template <typename T>
|
||||
inline SharedPtr<T> DynamicObjectCast(SharedPtr<Object> object) {
|
||||
if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) {
|
||||
return boost::static_pointer_cast<T>(std::move(object));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Initialize the kernel with the specified system mode.
|
||||
void Init(u32 system_mode);
|
||||
void Init();
|
||||
|
||||
/// Shutdown the kernel
|
||||
void Shutdown();
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_setup.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
MemoryRegionInfo memory_regions[3];
|
||||
|
||||
/// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each system
|
||||
/// memory configuration type.
|
||||
static const u32 memory_region_sizes[8][3] = {
|
||||
// Old 3DS layouts
|
||||
{0x04000000, 0x02C00000, 0x01400000}, // 0
|
||||
{/* This appears to be unused. */}, // 1
|
||||
{0x06000000, 0x00C00000, 0x01400000}, // 2
|
||||
{0x05000000, 0x01C00000, 0x01400000}, // 3
|
||||
{0x04800000, 0x02400000, 0x01400000}, // 4
|
||||
{0x02000000, 0x04C00000, 0x01400000}, // 5
|
||||
|
||||
// New 3DS layouts
|
||||
{0x07C00000, 0x06400000, 0x02000000}, // 6
|
||||
{0x0B200000, 0x02E00000, 0x02000000}, // 7
|
||||
};
|
||||
|
||||
void MemoryInit(u32 mem_type) {
|
||||
// TODO(yuriks): On the n3DS, all o3DS configurations (<=5) are forced to 6 instead.
|
||||
ASSERT_MSG(mem_type <= 5, "New 3DS memory configuration aren't supported yet!");
|
||||
ASSERT(mem_type != 1);
|
||||
|
||||
// The kernel allocation regions (APPLICATION, SYSTEM and BASE) are laid out in sequence, with
|
||||
// the sizes specified in the memory_region_sizes table.
|
||||
VAddr base = 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
memory_regions[i].base = base;
|
||||
memory_regions[i].size = memory_region_sizes[mem_type][i];
|
||||
memory_regions[i].used = 0;
|
||||
memory_regions[i].linear_heap_memory = std::make_shared<std::vector<u8>>();
|
||||
// Reserve enough space for this region of FCRAM.
|
||||
// We do not want this block of memory to be relocated when allocating from it.
|
||||
memory_regions[i].linear_heap_memory->reserve(memory_regions[i].size);
|
||||
|
||||
base += memory_regions[i].size;
|
||||
}
|
||||
|
||||
// We must've allocated the entire FCRAM by the end
|
||||
ASSERT(base == Memory::FCRAM_SIZE);
|
||||
}
|
||||
|
||||
void MemoryShutdown() {
|
||||
for (auto& region : memory_regions) {
|
||||
region.base = 0;
|
||||
region.size = 0;
|
||||
region.used = 0;
|
||||
region.linear_heap_memory = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) {
|
||||
switch (region) {
|
||||
case MemoryRegion::APPLICATION:
|
||||
return &memory_regions[0];
|
||||
case MemoryRegion::SYSTEM:
|
||||
return &memory_regions[1];
|
||||
case MemoryRegion::BASE:
|
||||
return &memory_regions[2];
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) {}
|
||||
|
||||
void MapSharedPages(VMManager& address_space) {}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class VMManager;
|
||||
|
||||
struct MemoryRegionInfo {
|
||||
u64 base; // Not an address, but offset from start of FCRAM
|
||||
u64 size;
|
||||
u64 used;
|
||||
|
||||
std::shared_ptr<std::vector<u8>> linear_heap_memory;
|
||||
};
|
||||
|
||||
void MemoryInit(u32 mem_type);
|
||||
void MemoryShutdown();
|
||||
MemoryRegionInfo* GetMemoryRegion(MemoryRegion region);
|
||||
|
||||
void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);
|
||||
void MapSharedPages(VMManager& address_space);
|
||||
|
||||
extern MemoryRegionInfo memory_regions[3];
|
||||
} // namespace Kernel
|
||||
@@ -3,16 +3,19 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/object_address_table.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user