Compare commits
549 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
218ae888f3 | ||
|
|
89b8801a97 | ||
|
|
00207cc965 | ||
|
|
e86b26cd2b | ||
|
|
41890a84be | ||
|
|
23d3cd7604 | ||
|
|
d6cddffb78 | ||
|
|
520e4e5d4b | ||
|
|
b8fbd125e6 | ||
|
|
cb2209d06a | ||
|
|
854ac468b9 | ||
|
|
150a3c0890 | ||
|
|
d9ee5b874c | ||
|
|
8a1bcc3d30 | ||
|
|
00e7190e29 | ||
|
|
1efdb4897e | ||
|
|
c13fbe6a41 | ||
|
|
76ef6e5c2b | ||
|
|
34510bcda8 | ||
|
|
085b388a7a | ||
|
|
9dec087fca | ||
|
|
c0e320ad0d | ||
|
|
845607481c | ||
|
|
bbeb859122 | ||
|
|
9d296f8a35 | ||
|
|
30ce9b2b5c | ||
|
|
5dfcf7cf26 | ||
|
|
37b23efece | ||
|
|
a1868286b0 | ||
|
|
28e36de56f | ||
|
|
c05c8a7a06 | ||
|
|
d0ed3ff4b7 | ||
|
|
d9ee58a3b5 | ||
|
|
d6b7195192 | ||
|
|
66be5150d6 | ||
|
|
7c1af3aa10 | ||
|
|
e6a9459b04 | ||
|
|
f7d6e08688 | ||
|
|
9959c95966 | ||
|
|
8502cda17a | ||
|
|
09789c3ffc | ||
|
|
15e0c4c4ec | ||
|
|
52746ed8dc | ||
|
|
88a3c05b7b | ||
|
|
7f506be2ee | ||
|
|
5b0a9f8ba8 | ||
|
|
3fd5998d84 | ||
|
|
e8f3d85ea5 | ||
|
|
e5bb07a973 | ||
|
|
5ba5f82082 | ||
|
|
3f8c9b25d8 | ||
|
|
872d480c60 | ||
|
|
ba4e1adda1 | ||
|
|
7c31661869 | ||
|
|
0e2f617abc | ||
|
|
acde8d3f68 | ||
|
|
a56c4ac91b | ||
|
|
d6374b2522 | ||
|
|
d7438d067f | ||
|
|
a655b59cef | ||
|
|
a973271b8c | ||
|
|
c39c8e6982 | ||
|
|
ea8f633dc0 | ||
|
|
140cd5e209 | ||
|
|
06c1f75f21 | ||
|
|
a6d5ff05dc | ||
|
|
908f24eb88 | ||
|
|
7931a68d4e | ||
|
|
580e3564c9 | ||
|
|
74a4a50470 | ||
|
|
774fa0b828 | ||
|
|
65ae1ac4e5 | ||
|
|
a504bad3fb | ||
|
|
7ccb0b16cd | ||
|
|
6b629f4816 | ||
|
|
e796351a0d | ||
|
|
57279e1981 | ||
|
|
28719ee3b4 | ||
|
|
cb2bce8006 | ||
|
|
628153cccd | ||
|
|
592a24ae53 | ||
|
|
29df6bbbd3 | ||
|
|
4555b63750 | ||
|
|
4366241739 | ||
|
|
20cc0b8d3c | ||
|
|
2d70c30fb2 | ||
|
|
26d0381161 | ||
|
|
d09e98f566 | ||
|
|
62860dc0b0 | ||
|
|
ffc72c8f15 | ||
|
|
e0eee250bb | ||
|
|
781ab8407b | ||
|
|
d9b7bc4474 | ||
|
|
a89266bc5e | ||
|
|
1960164055 | ||
|
|
e199d1e14f | ||
|
|
ba3d95e550 | ||
|
|
f911d1f685 | ||
|
|
824b8e4086 | ||
|
|
cb805f45ae | ||
|
|
4b33a346ed | ||
|
|
11505d3d9f | ||
|
|
cc737e5832 | ||
|
|
108be41316 | ||
|
|
c6147a439d | ||
|
|
394095438a | ||
|
|
4fad477aeb | ||
|
|
c791192d64 | ||
|
|
6a1a2d4aa5 | ||
|
|
74cee1b65d | ||
|
|
798d76f4c7 | ||
|
|
ff7e6a42c1 | ||
|
|
746dab407e | ||
|
|
3a846aa80f | ||
|
|
2289e895aa | ||
|
|
f770c17d01 | ||
|
|
b404fcdf14 | ||
|
|
5d4ab5ec2f | ||
|
|
427f1e3e3d | ||
|
|
2aca7b9e1e | ||
|
|
16dc3a1dd5 | ||
|
|
76f024865d | ||
|
|
a09d8cc8a2 | ||
|
|
c1ba3e3d4a | ||
|
|
1650593927 | ||
|
|
7d88fc83bf | ||
|
|
db42bcb306 | ||
|
|
f35e09fe0d | ||
|
|
dde0814837 | ||
|
|
9dbba9240b | ||
|
|
3bc815a5dc | ||
|
|
522957f9f3 | ||
|
|
d68716efdc | ||
|
|
947d364dba | ||
|
|
e2131f7310 | ||
|
|
a5fa4b311e | ||
|
|
bbe700359d | ||
|
|
e36f1a5ba9 | ||
|
|
758d84db9a | ||
|
|
96d518a59f | ||
|
|
47f2405ab1 | ||
|
|
595511876e | ||
|
|
8a24a804c5 | ||
|
|
b93a8a368f | ||
|
|
b26481c94b | ||
|
|
c5d41fd812 | ||
|
|
bd7ec1a749 | ||
|
|
7c4bc7b883 | ||
|
|
1e92ba2785 | ||
|
|
99a163478b | ||
|
|
abdb81ccaf | ||
|
|
9f63acac0f | ||
|
|
52980df1aa | ||
|
|
586cab6172 | ||
|
|
3f74518e19 | ||
|
|
1665b70cc6 | ||
|
|
f08db7295a | ||
|
|
6af322a347 | ||
|
|
a5dbda3f76 | ||
|
|
2bcebcff2a | ||
|
|
c0a01f3adc | ||
|
|
819dd93257 | ||
|
|
e5893db3e6 | ||
|
|
a7157fe27d | ||
|
|
f3297d8cd1 | ||
|
|
733cf179b8 | ||
|
|
540235bb05 | ||
|
|
611f4666fd | ||
|
|
1cf90f4570 | ||
|
|
90e27ea003 | ||
|
|
ee49e1fcb6 | ||
|
|
7b6d516faa | ||
|
|
b78e7b3454 | ||
|
|
d0dddb3e9d | ||
|
|
4d95adcac5 | ||
|
|
e703772c83 | ||
|
|
639f0c524d | ||
|
|
76f27d1f44 | ||
|
|
18918f5f2f | ||
|
|
56c80a2a21 | ||
|
|
c221308a66 | ||
|
|
839c0f829b | ||
|
|
109b78a6d6 | ||
|
|
3e930304fe | ||
|
|
52f36ea1c7 | ||
|
|
b72664abfd | ||
|
|
2117edd0f8 | ||
|
|
5a5fccaa23 | ||
|
|
f2c41ba256 | ||
|
|
b0d70096a1 | ||
|
|
e76f442a0e | ||
|
|
72837e4b3d | ||
|
|
3ae0de9b53 | ||
|
|
19330f45d3 | ||
|
|
197dcf0b5e | ||
|
|
21eb4cfa7f | ||
|
|
22d3dfbcd4 | ||
|
|
241563d15c | ||
|
|
43b83d6b6a | ||
|
|
1b6adb5308 | ||
|
|
872a7bee72 | ||
|
|
e8ff8a66b0 | ||
|
|
723ad4512f | ||
|
|
c1409602da | ||
|
|
8f454a5c68 | ||
|
|
1b6bd9d6df | ||
|
|
9d11303a36 | ||
|
|
adf07cbe17 | ||
|
|
8a320a6ee2 | ||
|
|
9c7319a4d4 | ||
|
|
1342c53e27 | ||
|
|
ab00552118 | ||
|
|
acbdfdae64 | ||
|
|
774f139e65 | ||
|
|
ec74a4fd4a | ||
|
|
b8c7072206 | ||
|
|
746167f11a | ||
|
|
eb335f51ca | ||
|
|
874826b6dd | ||
|
|
8dc2f01eae | ||
|
|
e05136f70b | ||
|
|
9f092554c2 | ||
|
|
26b809549b | ||
|
|
c07ebeac19 | ||
|
|
ecd3afdc8e | ||
|
|
30f228a8c9 | ||
|
|
c57d8eb66c | ||
|
|
51f609fee7 | ||
|
|
57ca1e3e69 | ||
|
|
e6612d6d8d | ||
|
|
114060fd87 | ||
|
|
d74aa13bd3 | ||
|
|
834d3fe336 | ||
|
|
13bc74e957 | ||
|
|
41566c615b | ||
|
|
9ad3b01d30 | ||
|
|
fd0533ef4c | ||
|
|
ed7a1e1443 | ||
|
|
93da8e0abf | ||
|
|
032e4c4ca3 | ||
|
|
2392e146b0 | ||
|
|
bf41132aa9 | ||
|
|
059465d496 | ||
|
|
64444ff481 | ||
|
|
f71c598907 | ||
|
|
6abc56672c | ||
|
|
99f982dce2 | ||
|
|
29c242721a | ||
|
|
bdf2da4ee8 | ||
|
|
10118c71e0 | ||
|
|
574e89d924 | ||
|
|
47b622825c | ||
|
|
51d7f6bffc | ||
|
|
c892cf01fa | ||
|
|
db47d7e471 | ||
|
|
e0d1f11968 | ||
|
|
39483b92b7 | ||
|
|
0b78cfcc53 | ||
|
|
06ac6460d3 | ||
|
|
e5b004e903 | ||
|
|
59f16f2e02 | ||
|
|
2eaf6c41a4 | ||
|
|
84d3cdf7d7 | ||
|
|
6788ebffc8 | ||
|
|
d71cad6ed0 | ||
|
|
555cd26ec2 | ||
|
|
2d9546848e | ||
|
|
8bd17aa044 | ||
|
|
4e6c667586 | ||
|
|
ffe2e50458 | ||
|
|
6ed6129b4f | ||
|
|
9030a8259f | ||
|
|
fdf76a25ab | ||
|
|
e7850a7f11 | ||
|
|
c1ea6a39a0 | ||
|
|
0a923b4ab3 | ||
|
|
e8a21f5276 | ||
|
|
71c4e876ef | ||
|
|
5379063108 | ||
|
|
567134f874 | ||
|
|
cb198d7985 | ||
|
|
a63295a872 | ||
|
|
a3734d7e31 | ||
|
|
aa59d77c3b | ||
|
|
6eddb60db0 | ||
|
|
3bfd199497 | ||
|
|
0f6dd7b64e | ||
|
|
8ebeb9ade2 | ||
|
|
2ad44a453f | ||
|
|
3350c0a779 | ||
|
|
1070c020db | ||
|
|
cd2921a047 | ||
|
|
aa44eb639b | ||
|
|
7ad3d4e49c | ||
|
|
a6c048920e | ||
|
|
debc7442f2 | ||
|
|
73f2ee5484 | ||
|
|
597c00698d | ||
|
|
0c28ab92e6 | ||
|
|
3f602dde0f | ||
|
|
f7ec0bcfc2 | ||
|
|
d870cc5ad7 | ||
|
|
8603f0f9b1 | ||
|
|
0aa824b12f | ||
|
|
037d9bdde3 | ||
|
|
633ce92908 | ||
|
|
4a84921b31 | ||
|
|
add8b1df68 | ||
|
|
ed82bb968a | ||
|
|
0ea2771889 | ||
|
|
9ae680c639 | ||
|
|
46fdf8c819 | ||
|
|
4a4e87e971 | ||
|
|
e6f652ae12 | ||
|
|
6ce8de4b5f | ||
|
|
a0be7b3b92 | ||
|
|
45ef421b6b | ||
|
|
fedef7bda3 | ||
|
|
6ee0ba64c8 | ||
|
|
9909d40530 | ||
|
|
160fc63c72 | ||
|
|
78c803b4f3 | ||
|
|
1143923cdd | ||
|
|
d10dffed44 | ||
|
|
e7ac5a6adf | ||
|
|
fbb82e61e3 | ||
|
|
69749a88cd | ||
|
|
8e510d5afa | ||
|
|
b7f331afa3 | ||
|
|
0209de123b | ||
|
|
d26ee6e01e | ||
|
|
e99a148628 | ||
|
|
c2d4c8b95e | ||
|
|
48a461a629 | ||
|
|
24e2e601d5 | ||
|
|
3b63a46ca4 | ||
|
|
c63a0e88b7 | ||
|
|
1a4d733ec7 | ||
|
|
39e895c5ff | ||
|
|
d9e9e71aec | ||
|
|
4f352833a5 | ||
|
|
d03ae881fd | ||
|
|
960057cba0 | ||
|
|
d41d85766f | ||
|
|
3293877456 | ||
|
|
64e7524f36 | ||
|
|
076c76f4e4 | ||
|
|
ed0bdcc638 | ||
|
|
84ad81ee67 | ||
|
|
63aa08acbe | ||
|
|
3f1b4fb23a | ||
|
|
aaa373585c | ||
|
|
7b574f406b | ||
|
|
65651078e5 | ||
|
|
d2ff93c319 | ||
|
|
ac51d048a9 | ||
|
|
4483089d70 | ||
|
|
d6015ee211 | ||
|
|
81e086b5ac | ||
|
|
75b417489a | ||
|
|
e9b05e86b9 | ||
|
|
8ee78521fa | ||
|
|
c8d6f0cb82 | ||
|
|
9ac176d5a3 | ||
|
|
234f00bdd4 | ||
|
|
5a57b1a09b | ||
|
|
22f105c06d | ||
|
|
10f08ab9ec | ||
|
|
196cc82913 | ||
|
|
f9ee0dc7ee | ||
|
|
221613d4ea | ||
|
|
7526b6fce3 | ||
|
|
ad9dbeb44b | ||
|
|
c161389a0f | ||
|
|
9d9676f620 | ||
|
|
ec6664f6d6 | ||
|
|
42085ff110 | ||
|
|
52ac6419da | ||
|
|
79f970e6de | ||
|
|
02bc9e9de1 | ||
|
|
cc92c054ec | ||
|
|
7053546687 | ||
|
|
769b346682 | ||
|
|
c100a4b8d4 | ||
|
|
b952a30555 | ||
|
|
4495bf5706 | ||
|
|
c5091bfe00 | ||
|
|
9d1ab766a0 | ||
|
|
4130b07f88 | ||
|
|
40de7f6fe8 | ||
|
|
6c42a23550 | ||
|
|
fad20213e6 | ||
|
|
f59040d752 | ||
|
|
0be8fffc99 | ||
|
|
07e13d6728 | ||
|
|
6ad66acce2 | ||
|
|
b114928459 | ||
|
|
319365fdf0 | ||
|
|
697a4669e1 | ||
|
|
b5f0dc95db | ||
|
|
90febaf717 | ||
|
|
1f6571b3de | ||
|
|
be6bf37224 | ||
|
|
aa30fd75cd | ||
|
|
169d19f7b9 | ||
|
|
f8f1ff0b4f | ||
|
|
43c1092031 | ||
|
|
a461e266ea | ||
|
|
71817afbe9 | ||
|
|
8b98f60e3c | ||
|
|
09ac66388c | ||
|
|
c8554d218b | ||
|
|
2e2f6aa71a | ||
|
|
71c30a0a89 | ||
|
|
90fd257b47 | ||
|
|
5159f4eee8 | ||
|
|
3c39b39bbc | ||
|
|
8e84e81e74 | ||
|
|
e22670fbc3 | ||
|
|
ab70c2583d | ||
|
|
35c105a108 | ||
|
|
1da8a0c2a8 | ||
|
|
12e74fe801 | ||
|
|
115fc6120c | ||
|
|
84aff56644 | ||
|
|
42dc73157c | ||
|
|
e85066dac7 | ||
|
|
bb3ab7d66c | ||
|
|
49c6d21b31 | ||
|
|
1b13859af8 | ||
|
|
1f5d6a8fed | ||
|
|
66f4fd4c81 | ||
|
|
7ea097e5c2 | ||
|
|
5a9204dbd7 | ||
|
|
d6b9b51606 | ||
|
|
e64fa4d2ea | ||
|
|
3558c88442 | ||
|
|
e9d84ef22c | ||
|
|
5bc82d124c | ||
|
|
8932001610 | ||
|
|
44ea2810e4 | ||
|
|
d583fc1e97 | ||
|
|
45b6d2d349 | ||
|
|
f15e2dd881 | ||
|
|
ef84c70d22 | ||
|
|
532dda0499 | ||
|
|
1068c1b06f | ||
|
|
27ddbeb01c | ||
|
|
6335bf136f | ||
|
|
456c7043bd | ||
|
|
42f7c11021 | ||
|
|
14430f7df9 | ||
|
|
eb5a3dd1c7 | ||
|
|
be1a1584fc | ||
|
|
66e023fba2 | ||
|
|
b27e6ad912 | ||
|
|
16ea93c11e | ||
|
|
a6a783b3dc | ||
|
|
e7eff72e83 | ||
|
|
46b3209abb | ||
|
|
0e1b5acc6a | ||
|
|
b9238edd0d | ||
|
|
e56f32a071 | ||
|
|
1b855efd5e | ||
|
|
a1574aabd5 | ||
|
|
92ea1c32d6 | ||
|
|
254b1e3df7 | ||
|
|
0ad3c031f4 | ||
|
|
0ccd490fcd | ||
|
|
1b2872eebc | ||
|
|
10d1d58390 | ||
|
|
d91e35a50a | ||
|
|
5219edd715 | ||
|
|
730eb1dad7 | ||
|
|
5ca63d0675 | ||
|
|
48e6f77c03 | ||
|
|
c3471bf618 | ||
|
|
da1b45de34 | ||
|
|
1cffd3848b | ||
|
|
93c1630570 | ||
|
|
d29f9e9709 | ||
|
|
5167d1577d | ||
|
|
4f8cd74061 | ||
|
|
0220862ba5 | ||
|
|
04d7b7e09d | ||
|
|
8250f9bb1c | ||
|
|
7cdeec20ec | ||
|
|
a12f4efa2f | ||
|
|
c1b2e35625 | ||
|
|
be7dad5e7e | ||
|
|
c07987dfab | ||
|
|
c4243c07cc | ||
|
|
c6170565b5 | ||
|
|
57985fb16a | ||
|
|
517933adcb | ||
|
|
030814b1cb | ||
|
|
90c780e6f3 | ||
|
|
33a0597603 | ||
|
|
281a8bf259 | ||
|
|
f7090bacc5 | ||
|
|
d062991643 | ||
|
|
4ab978d670 | ||
|
|
92050c4d86 | ||
|
|
abef11a540 | ||
|
|
f546fb35ed | ||
|
|
94b27bb8a5 | ||
|
|
90528f1326 | ||
|
|
fd15730767 | ||
|
|
fa4dc2cf42 | ||
|
|
9539c4203b | ||
|
|
ae437320c8 | ||
|
|
3273f93cd5 | ||
|
|
ef559f5741 | ||
|
|
8d5d369b54 | ||
|
|
6dd40976d0 | ||
|
|
717394c980 | ||
|
|
b675c97cdd | ||
|
|
4bce08d497 | ||
|
|
2bb02a0b78 | ||
|
|
a8d4927e29 | ||
|
|
e869c5ef1a | ||
|
|
4699fdca8f | ||
|
|
cd7e1183e2 | ||
|
|
b009bda67a | ||
|
|
0113c36300 | ||
|
|
ecccfe0337 | ||
|
|
3ea48e8ebe | ||
|
|
5b7ec71fb7 | ||
|
|
2195f10d15 | ||
|
|
4154936568 | ||
|
|
9a56b99fa4 | ||
|
|
8dfc81239f | ||
|
|
ae6c052ed9 | ||
|
|
a2b6de7e9f | ||
|
|
0ffdd0a683 | ||
|
|
aa0b6babda | ||
|
|
25c2fe1c6b | ||
|
|
33a4cebc22 | ||
|
|
10682ad7e0 | ||
|
|
bb41683394 | ||
|
|
efd83570bd | ||
|
|
f27c65eb91 | ||
|
|
996ddb202b | ||
|
|
9d411699d8 | ||
|
|
89abef3518 | ||
|
|
6b81ceb060 | ||
|
|
71530781f3 | ||
|
|
6734c64976 | ||
|
|
94bc48dd78 |
@@ -24,7 +24,7 @@ matrix:
|
||||
- os: osx
|
||||
env: NAME="macos build"
|
||||
sudo: false
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.1
|
||||
install: "./.travis/macos/deps.sh"
|
||||
script: "./.travis/macos/build.sh"
|
||||
after_success: "./.travis/macos/upload.sh"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -o pipefail
|
||||
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.13
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.14
|
||||
export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export UNICORNDIR=$(pwd)/externals/unicorn
|
||||
export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
brew update
|
||||
brew install dylibbundler p7zip qt5 sdl2 ccache
|
||||
brew install p7zip qt5 sdl2 ccache
|
||||
brew outdated cmake || brew upgrade cmake
|
||||
pip3 install macpack
|
||||
|
||||
@@ -11,92 +11,19 @@ mkdir "$REV_NAME"
|
||||
cp build/bin/yuzu-cmd "$REV_NAME"
|
||||
cp -r build/bin/yuzu.app "$REV_NAME"
|
||||
|
||||
# move qt libs into app bundle for deployment
|
||||
$(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/yuzu.app"
|
||||
# move libs into folder for deployment
|
||||
macpack "${REV_NAME}/yuzu.app/Contents/MacOS/yuzu" -d "../Frameworks"
|
||||
# move qt frameworks into app bundle for deployment
|
||||
$(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/yuzu.app" -executable="${REV_NAME}/yuzu.app/Contents/MacOS/yuzu"
|
||||
|
||||
# move SDL2 libs into folder for deployment
|
||||
dylibbundler -b -x "${REV_NAME}/yuzu-cmd" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/"
|
||||
|
||||
# Make the changes to make the yuzu app standalone (i.e. not dependent on the current brew installation).
|
||||
# To do this, the absolute references to each and every QT framework must be re-written to point to the local frameworks
|
||||
# (in the Contents/Frameworks folder).
|
||||
# The "install_name_tool" is used to do so.
|
||||
|
||||
# Coreutils is a hack to coerce Homebrew to point to the absolute Cellar path (symlink dereferenced). i.e:
|
||||
# ls -l /usr/local/opt/qt5:: /usr/local/opt/qt5 -> ../Cellar/qt5/5.6.1-1
|
||||
# grealpath ../Cellar/qt5/5.6.1-1:: /usr/local/Cellar/qt5/5.6.1-1
|
||||
brew install coreutils || brew upgrade coreutils || true
|
||||
|
||||
REV_NAME_ALT=$REV_NAME/
|
||||
# grealpath is located in coreutils, there is no "realpath" for OS X :(
|
||||
QT_BREWS_PATH=$(grealpath "$(brew --prefix qt5)")
|
||||
BREW_PATH=$(brew --prefix)
|
||||
QT_VERSION_NUM=5
|
||||
|
||||
$BREW_PATH/opt/qt5/bin/macdeployqt "${REV_NAME_ALT}yuzu.app" \
|
||||
-executable="${REV_NAME_ALT}yuzu.app/Contents/MacOS/yuzu"
|
||||
|
||||
# These are the files that macdeployqt packed into Contents/Frameworks/ - we don't want those, so we replace them.
|
||||
declare -a macos_libs=("QtCore" "QtWidgets" "QtGui" "QtOpenGL" "QtPrintSupport")
|
||||
|
||||
for macos_lib in "${macos_libs[@]}"
|
||||
do
|
||||
SC_FRAMEWORK_PART=$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib
|
||||
# Replace macdeployqt versions of the Frameworks with our own (from /usr/local/opt/qt5/lib/)
|
||||
cp "$BREW_PATH/opt/qt5/lib/$SC_FRAMEWORK_PART" "${REV_NAME_ALT}yuzu.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
|
||||
|
||||
# Replace references within the embedded Framework files with "internal" versions.
|
||||
for macos_lib2 in "${macos_libs[@]}"
|
||||
do
|
||||
# Since brew references both the non-symlinked and symlink paths of QT5, it needs to be duplicated.
|
||||
# /usr/local/Cellar/qt5/5.6.1-1/lib and /usr/local/opt/qt5/lib both resolve to the same files.
|
||||
# So the two lines below are effectively duplicates when resolved as a path, but as strings, they aren't.
|
||||
RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
|
||||
install_name_tool -change \
|
||||
$QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
|
||||
@executable_path/../Frameworks/$RM_FRAMEWORK_PART \
|
||||
"${REV_NAME_ALT}yuzu.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
|
||||
install_name_tool -change \
|
||||
"$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
|
||||
@executable_path/../Frameworks/$RM_FRAMEWORK_PART \
|
||||
"${REV_NAME_ALT}yuzu.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
|
||||
done
|
||||
done
|
||||
|
||||
# Handles `This application failed to start because it could not find or load the Qt platform plugin "cocoa"`
|
||||
# Which manifests itself as:
|
||||
# "Exception Type: EXC_CRASH (SIGABRT) | Exception Codes: 0x0000000000000000, 0x0000000000000000 | Exception Note: EXC_CORPSE_NOTIFY"
|
||||
# There may be more dylibs needed to be fixed...
|
||||
declare -a macos_plugins=("Plugins/platforms/libqcocoa.dylib")
|
||||
|
||||
for macos_lib in "${macos_plugins[@]}"
|
||||
do
|
||||
install_name_tool -id @executable_path/../$macos_lib "${REV_NAME_ALT}yuzu.app/Contents/$macos_lib"
|
||||
for macos_lib2 in "${macos_libs[@]}"
|
||||
do
|
||||
RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
|
||||
install_name_tool -change \
|
||||
$QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
|
||||
@executable_path/../Frameworks/$RM_FRAMEWORK_PART \
|
||||
"${REV_NAME_ALT}yuzu.app/Contents/$macos_lib"
|
||||
install_name_tool -change \
|
||||
"$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
|
||||
@executable_path/../Frameworks/$RM_FRAMEWORK_PART \
|
||||
"${REV_NAME_ALT}yuzu.app/Contents/$macos_lib"
|
||||
done
|
||||
done
|
||||
|
||||
for macos_lib in "${macos_libs[@]}"
|
||||
do
|
||||
# Debugging info for Travis-CI
|
||||
otool -L "${REV_NAME_ALT}yuzu.app/Contents/Frameworks/$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib"
|
||||
done
|
||||
# move libs into folder for deployment
|
||||
macpack "${REV_NAME}/yuzu-cmd" -d "libs"
|
||||
|
||||
# Make the yuzu.app application launch a debugging terminal.
|
||||
# Store away the actual binary
|
||||
mv ${REV_NAME_ALT}yuzu.app/Contents/MacOS/yuzu ${REV_NAME_ALT}yuzu.app/Contents/MacOS/yuzu-bin
|
||||
mv ${REV_NAME}/yuzu.app/Contents/MacOS/yuzu ${REV_NAME}/yuzu.app/Contents/MacOS/yuzu-bin
|
||||
|
||||
cat > ${REV_NAME_ALT}yuzu.app/Contents/MacOS/yuzu <<EOL
|
||||
cat > ${REV_NAME}/yuzu.app/Contents/MacOS/yuzu <<EOL
|
||||
#!/usr/bin/env bash
|
||||
cd "\`dirname "\$0"\`"
|
||||
chmod +x yuzu-bin
|
||||
@@ -105,6 +32,9 @@ EOL
|
||||
# Content that will serve as the launching script for yuzu (within the .app folder)
|
||||
|
||||
# Make the launching script executable
|
||||
chmod +x ${REV_NAME_ALT}yuzu.app/Contents/MacOS/yuzu
|
||||
chmod +x ${REV_NAME}/yuzu.app/Contents/MacOS/yuzu
|
||||
|
||||
# Verify loader instructions
|
||||
find "$REV_NAME" -exec otool -L {} \;
|
||||
|
||||
. .travis/common/post-upload.sh
|
||||
|
||||
@@ -104,90 +104,18 @@ endif()
|
||||
message(STATUS "Target architecture: ${ARCHITECTURE}")
|
||||
|
||||
|
||||
# Configure compilation flags
|
||||
# Configure C++ standard
|
||||
# ===========================
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
|
||||
|
||||
if (MINGW)
|
||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||
|
||||
if (MINGW_STATIC_BUILD)
|
||||
add_definitions(-DQT_STATICPLUGIN)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
# Silence "deprecation" warnings
|
||||
add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS)
|
||||
# Avoid windows.h junk
|
||||
add_definitions(/DNOMINMAX)
|
||||
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
|
||||
add_definitions(/DWIN32_LEAN_AND_MEAN)
|
||||
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
# Tweak optimization settings
|
||||
# As far as I can tell, there's no way to override the CMake defaults while leaving user
|
||||
# changes intact, so we'll just clobber everything and say sorry.
|
||||
message(STATUS "Cache compiler flags ignored, please edit CMakeLists.txt to change the flags.")
|
||||
|
||||
# /W3 - Level 3 warnings
|
||||
# /MP - Multi-threaded compilation
|
||||
# /Zi - Output debugging information
|
||||
# /Zo - enhanced debug info for optimized builds
|
||||
# /permissive- - enables stricter C++ standards conformance checks
|
||||
set(CMAKE_C_FLAGS "/W3 /MP /Zi /Zo /permissive-" CACHE STRING "" FORCE)
|
||||
# /EHsc - C++-only exception handling semantics
|
||||
# /Zc:throwingNew - let codegen assume `operator new` will never return null
|
||||
# /Zc:inline - let codegen omit inline functions in object files
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} /EHsc /std:c++latest /Zc:throwingNew,inline" CACHE STRING "" FORCE)
|
||||
|
||||
# /MDd - Multi-threaded Debug Runtime DLL
|
||||
set(CMAKE_C_FLAGS_DEBUG "/Od /MDd" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}" CACHE STRING "" FORCE)
|
||||
|
||||
# /O2 - Optimization level 2
|
||||
# /GS- - No stack buffer overflow checks
|
||||
# /MD - Multi-threaded runtime DLL
|
||||
set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# Fix GCC C++17 and Boost.ICL incompatibility (needed to build dynarmic)
|
||||
# See https://bugzilla.redhat.com/show_bug.cgi?id=1485641#c1
|
||||
if (CMAKE_COMPILER_IS_GNUCC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-new-ttp-matching")
|
||||
endif()
|
||||
|
||||
# Set file offset size to 64 bits.
|
||||
#
|
||||
# On modern Unixes, this is typically already the case. The lone exception is
|
||||
# glibc, which may default to 32 bits. glibc allows this to be configured
|
||||
# by setting _FILE_OFFSET_BITS.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
|
||||
add_definitions(-D_FILE_OFFSET_BITS=64)
|
||||
endif()
|
||||
|
||||
# CMake seems to only define _DEBUG on Windows
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
||||
find_package(Boost 1.63.0 QUIET)
|
||||
find_package(Boost 1.66.0 QUIET)
|
||||
if (NOT Boost_FOUND)
|
||||
message(STATUS "Boost 1.63.0 or newer not found, falling back to externals")
|
||||
message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
|
||||
|
||||
set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
|
||||
set(Boost_NO_SYSTEM_PATHS OFF)
|
||||
@@ -332,25 +260,21 @@ endif()
|
||||
# Platform-specific library requirements
|
||||
# ======================================
|
||||
|
||||
IF (APPLE)
|
||||
find_library(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
|
||||
if (APPLE)
|
||||
# Umbrella framework for everything GUI-related
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
|
||||
endif()
|
||||
ELSEIF (WIN32)
|
||||
elseif (WIN32)
|
||||
# WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
|
||||
add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)
|
||||
set(PLATFORM_LIBRARIES winmm ws2_32)
|
||||
IF (MINGW)
|
||||
if (MINGW)
|
||||
# PSAPI is the Process Status API
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
|
||||
ENDIF (MINGW)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
endif()
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
set(PLATFORM_LIBRARIES rt)
|
||||
ENDIF (APPLE)
|
||||
endif()
|
||||
|
||||
# Setup a custom clang-format target (if clang-format can be found) that will run
|
||||
# against all the src files. This should be used before making a pull request.
|
||||
|
||||
@@ -73,6 +73,7 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/texture.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
|
||||
@@ -7,7 +7,7 @@ yuzu is an experimental open-source emulator for the Nintendo Switch from the cr
|
||||
|
||||
It is written in C++ with portability in mind, with builds actively maintained for Windows, Linux and macOS. The emulator is currently only useful for homebrew development and research purposes.
|
||||
|
||||
yuzu only emulates a subset of Switch hardware and therefore is generally only useful for running/debugging homebrew applications. At this time, yuzu cannot play any commercial games without major problems. yuzu can boot some games, to varying degrees of success, but does not implement any of the necessary GPU features to render 3D graphics.
|
||||
yuzu only emulates a subset of Switch hardware and therefore is generally only useful for running/debugging homebrew applications. At this time, yuzu cannot play any commercial games without major problems. yuzu can boot some games, to varying degrees of success.
|
||||
|
||||
yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included.
|
||||
|
||||
|
||||
2
externals/Vulkan-Headers
vendored
2
externals/Vulkan-Headers
vendored
Submodule externals/Vulkan-Headers updated: 7f02d9bb81...15e5c4db75
2
externals/cubeb
vendored
2
externals/cubeb
vendored
Submodule externals/cubeb updated: 12b78c0edf...6f2420de8f
2
externals/opus
vendored
2
externals/opus
vendored
Submodule externals/opus updated: b2871922a1...562f8ba555
@@ -1,18 +1,79 @@
|
||||
# Enable modules to include each other's files
|
||||
include_directories(.)
|
||||
|
||||
# CMake seems to only define _DEBUG on Windows
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
||||
# Set compilation flags
|
||||
if (MSVC)
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
# Silence "deprecation" warnings
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
||||
|
||||
# Avoid windows.h junk
|
||||
add_definitions(-DNOMINMAX)
|
||||
|
||||
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||
|
||||
# /W3 - Level 3 warnings
|
||||
# /MP - Multi-threaded compilation
|
||||
# /Zi - Output debugging information
|
||||
# /Zo - enhanced debug info for optimized builds
|
||||
# /permissive- - enables stricter C++ standards conformance checks
|
||||
# /EHsc - C++-only exception handling semantics
|
||||
# /Zc:throwingNew - let codegen assume `operator new` will never return null
|
||||
# /Zc:inline - let codegen omit inline functions in object files
|
||||
add_compile_options(/W3 /MP /Zi /Zo /permissive- /EHsc /std:c++latest /Zc:throwingNew,inline)
|
||||
|
||||
# /GS- - No stack buffer overflow checks
|
||||
add_compile_options("$<$<CONFIG:Release>:/GS->")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
|
||||
else()
|
||||
add_compile_options("-Wno-attributes")
|
||||
|
||||
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
add_compile_options("-stdlib=libc++")
|
||||
endif()
|
||||
|
||||
# Set file offset size to 64 bits.
|
||||
#
|
||||
# On modern Unixes, this is typically already the case. The lone exception is
|
||||
# glibc, which may default to 32 bits. glibc allows this to be configured
|
||||
# by setting _FILE_OFFSET_BITS.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
|
||||
add_definitions(-D_FILE_OFFSET_BITS=64)
|
||||
endif()
|
||||
|
||||
if (MINGW)
|
||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||
|
||||
if (MINGW_STATIC_BUILD)
|
||||
add_definitions(-DQT_STATICPLUGIN)
|
||||
add_compile_options("-static")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(audio_core)
|
||||
add_subdirectory(video_core)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(tests)
|
||||
|
||||
if (ENABLE_SDL2)
|
||||
add_subdirectory(yuzu_cmd)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(yuzu)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
add_subdirectory(web_service)
|
||||
endif()
|
||||
|
||||
@@ -46,16 +46,18 @@ struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le unknown_c;
|
||||
u32_le submix_count;
|
||||
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 performance_frame_count;
|
||||
u8 is_voice_drop_enabled;
|
||||
u8 unknown_21;
|
||||
u8 unknown_22;
|
||||
u8 execution_mode;
|
||||
u32_le splitter_count;
|
||||
u32_le unknown_2c;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32_le num_splitter_send_channels;
|
||||
u32_le unknown_30;
|
||||
u32_le revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
Buffer(Tag tag, std::vector<s16>&& samples) : tag{tag}, samples{std::move(samples)} {}
|
||||
|
||||
/// Returns the raw audio data for the buffer
|
||||
std::vector<s16>& Samples() {
|
||||
std::vector<s16>& GetSamples() {
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM
|
||||
}
|
||||
}
|
||||
|
||||
state.yn1 = yn1;
|
||||
state.yn2 = yn2;
|
||||
state.yn1 = static_cast<s16>(yn1);
|
||||
state.yn2 = static_cast<s16>(yn2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "common/ring_buffer.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <objbase.h>
|
||||
#endif
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class CubebSinkStream final : public SinkStream {
|
||||
@@ -46,7 +50,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
~CubebSinkStream() {
|
||||
~CubebSinkStream() override {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
@@ -75,11 +79,11 @@ public:
|
||||
queue.Push(samples);
|
||||
}
|
||||
|
||||
std::size_t SamplesInQueue(u32 num_channels) const override {
|
||||
std::size_t SamplesInQueue(u32 channel_count) const override {
|
||||
if (!ctx)
|
||||
return 0;
|
||||
|
||||
return queue.Size() / num_channels;
|
||||
return queue.Size() / channel_count;
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
@@ -98,7 +102,7 @@ private:
|
||||
u32 num_channels{};
|
||||
|
||||
Common::RingBuffer<s16, 0x10000> queue;
|
||||
std::array<s16, 2> last_frame;
|
||||
std::array<s16, 2> last_frame{};
|
||||
std::atomic<bool> should_flush{};
|
||||
TimeStretcher time_stretch;
|
||||
|
||||
@@ -108,6 +112,11 @@ private:
|
||||
};
|
||||
|
||||
CubebSink::CubebSink(std::string_view target_device_name) {
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
#ifdef _WIN32
|
||||
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return;
|
||||
@@ -142,6 +151,12 @@ CubebSink::~CubebSink() {
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
|
||||
|
||||
@@ -25,6 +25,10 @@ private:
|
||||
cubeb* ctx{};
|
||||
cubeb_devid output_device{};
|
||||
std::vector<SinkStreamPtr> sink_streams;
|
||||
|
||||
#ifdef _WIN32
|
||||
u32 com_init_result = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
std::vector<std::string> ListCubebSinkDevices();
|
||||
|
||||
@@ -38,7 +38,7 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo
|
||||
sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
|
||||
|
||||
release_event = core_timing.RegisterEvent(
|
||||
name, [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
|
||||
name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(); });
|
||||
}
|
||||
|
||||
void Stream::Play() {
|
||||
@@ -95,7 +95,7 @@ void Stream::PlayNextBuffer() {
|
||||
active_buffer = queued_buffers.front();
|
||||
queued_buffers.pop();
|
||||
|
||||
VolumeAdjustSamples(active_buffer->Samples());
|
||||
VolumeAdjustSamples(active_buffer->GetSamples());
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/texture.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
@@ -90,11 +91,18 @@ add_library(common STATIC
|
||||
logging/log.h
|
||||
logging/text_formatter.cpp
|
||||
logging/text_formatter.h
|
||||
lz4_compression.cpp
|
||||
lz4_compression.h
|
||||
math_util.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
microprofile.cpp
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
misc.cpp
|
||||
multi_level_queue.h
|
||||
page_table.cpp
|
||||
page_table.h
|
||||
param_package.cpp
|
||||
param_package.h
|
||||
quaternion.h
|
||||
@@ -113,6 +121,8 @@ add_library(common STATIC
|
||||
threadsafe_queue.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
uint128.cpp
|
||||
uint128.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
)
|
||||
@@ -128,3 +138,4 @@ endif()
|
||||
create_target_directory_groups(common)
|
||||
|
||||
target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
|
||||
target_link_libraries(common PRIVATE lz4_static)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
/*
|
||||
* Abstract bitfield class
|
||||
@@ -108,15 +109,9 @@
|
||||
* symptoms.
|
||||
*/
|
||||
#pragma pack(1)
|
||||
template <std::size_t Position, std::size_t Bits, typename T>
|
||||
template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
|
||||
struct BitField {
|
||||
private:
|
||||
// We hide the copy assigment operator here, because the default copy
|
||||
// assignment would copy the full storage value, rather than just the bits
|
||||
// relevant to this particular bit field.
|
||||
// We don't delete it because we want BitField to be trivially copyable.
|
||||
constexpr BitField& operator=(const BitField&) = default;
|
||||
|
||||
// UnderlyingType is T for non-enum types and the underlying type of T if
|
||||
// T is an enumeration. Note that T is wrapped within an enable_if in the
|
||||
// former case to workaround compile errors which arise when using
|
||||
@@ -127,6 +122,8 @@ private:
|
||||
// We store the value as the unsigned type to avoid undefined behaviour on value shifting
|
||||
using StorageType = std::make_unsigned_t<UnderlyingType>;
|
||||
|
||||
using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::type;
|
||||
|
||||
public:
|
||||
/// Constants to allow limited introspection of fields if needed
|
||||
static constexpr std::size_t position = Position;
|
||||
@@ -163,16 +160,20 @@ public:
|
||||
BitField(T val) = delete;
|
||||
BitField& operator=(T val) = delete;
|
||||
|
||||
// Force default constructor to be created
|
||||
// so that we can use this within unions
|
||||
constexpr BitField() = default;
|
||||
constexpr BitField() noexcept = default;
|
||||
|
||||
constexpr BitField(const BitField&) noexcept = default;
|
||||
constexpr BitField& operator=(const BitField&) noexcept = default;
|
||||
|
||||
constexpr BitField(BitField&&) noexcept = default;
|
||||
constexpr BitField& operator=(BitField&&) noexcept = default;
|
||||
|
||||
constexpr FORCE_INLINE operator T() const {
|
||||
return Value();
|
||||
}
|
||||
|
||||
constexpr FORCE_INLINE void Assign(const T& value) {
|
||||
storage = (storage & ~mask) | FormatValue(value);
|
||||
storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
|
||||
}
|
||||
|
||||
constexpr T Value() const {
|
||||
@@ -184,7 +185,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
StorageType storage;
|
||||
StorageTypeWithEndian storage;
|
||||
|
||||
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
|
||||
|
||||
@@ -195,3 +196,6 @@ private:
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
template <std::size_t Position, std::size_t Bits, typename T>
|
||||
using BitFieldBE = BitField<Position, Bits, T, BETag>;
|
||||
|
||||
@@ -58,4 +58,43 @@ inline u64 CountLeadingZeroes64(u64 value) {
|
||||
return __builtin_clzll(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
inline u32 CountTrailingZeroes32(u32 value) {
|
||||
unsigned long trailing_zero = 0;
|
||||
|
||||
if (_BitScanForward(&trailing_zero, value) != 0) {
|
||||
return trailing_zero;
|
||||
}
|
||||
|
||||
return 32;
|
||||
}
|
||||
|
||||
inline u64 CountTrailingZeroes64(u64 value) {
|
||||
unsigned long trailing_zero = 0;
|
||||
|
||||
if (_BitScanForward64(&trailing_zero, value) != 0) {
|
||||
return trailing_zero;
|
||||
}
|
||||
|
||||
return 64;
|
||||
}
|
||||
#else
|
||||
inline u32 CountTrailingZeroes32(u32 value) {
|
||||
if (value == 0) {
|
||||
return 32;
|
||||
}
|
||||
|
||||
return __builtin_ctz(value);
|
||||
}
|
||||
|
||||
inline u64 CountTrailingZeroes64(u64 value) {
|
||||
if (value == 0) {
|
||||
return 64;
|
||||
}
|
||||
|
||||
return __builtin_ctzll(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -55,36 +55,36 @@ constexpr u8 Convert8To6(u8 value) {
|
||||
/**
|
||||
* Decode a color stored in RGBA8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||
return {bytes[3], bytes[2], bytes[1], bytes[0]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in RGB8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||
return {bytes[2], bytes[1], bytes[0], 255};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in RG8 (aka HILO8) format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||
return {bytes[1], bytes[0], 0, 255};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in RGB565 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
|
||||
@@ -94,9 +94,9 @@ inline Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
/**
|
||||
* Decode a color stored in RGB5A1 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
|
||||
@@ -106,9 +106,9 @@ inline Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
/**
|
||||
* Decode a color stored in RGBA4 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
|
||||
@@ -138,9 +138,9 @@ inline u32 DecodeD24(const u8* bytes) {
|
||||
/**
|
||||
* Decode a depth value and a stencil value stored in D24S8 format
|
||||
* @param bytes Pointer to encoded source values
|
||||
* @return Resulting values stored as a Math::Vec2
|
||||
* @return Resulting values stored as a Common::Vec2
|
||||
*/
|
||||
inline Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ inline Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGBA8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGBA8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[3] = color.r();
|
||||
bytes[2] = color.g();
|
||||
bytes[1] = color.b();
|
||||
@@ -161,7 +161,7 @@ inline void EncodeRGBA8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGB8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[2] = color.r();
|
||||
bytes[1] = color.g();
|
||||
bytes[0] = color.b();
|
||||
@@ -172,7 +172,7 @@ inline void EncodeRGB8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRG8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[1] = color.r();
|
||||
bytes[0] = color.g();
|
||||
}
|
||||
@@ -181,7 +181,7 @@ inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGB565(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
const u16_le data =
|
||||
(Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b());
|
||||
|
||||
@@ -193,7 +193,7 @@ inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGB5A1(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To5(color.g()) << 6) |
|
||||
(Convert8To5(color.b()) << 1) | Convert8To1(color.a());
|
||||
|
||||
@@ -205,7 +205,7 @@ inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGBA4(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
const u16 data = (Convert8To4(color.r()) << 12) | (Convert8To4(color.g()) << 8) |
|
||||
(Convert8To4(color.b()) << 4) | Convert8To4(color.a());
|
||||
|
||||
|
||||
@@ -40,10 +40,9 @@ using s64 = std::int64_t; ///< 64-bit signed int
|
||||
using f32 = float; ///< 32-bit floating point
|
||||
using f64 = double; ///< 64-bit floating point
|
||||
|
||||
// TODO: It would be nice to eventually replace these with strong types that prevent accidental
|
||||
// conversion between each other.
|
||||
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
|
||||
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
|
||||
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
|
||||
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
|
||||
using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space.
|
||||
|
||||
using u128 = std::array<std::uint64_t, 2>;
|
||||
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
|
||||
|
||||
@@ -16,22 +16,22 @@ DetachedTasks::DetachedTasks() {
|
||||
}
|
||||
|
||||
void DetachedTasks::WaitForAllTasks() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock lock{mutex};
|
||||
cv.wait(lock, [this]() { return count == 0; });
|
||||
}
|
||||
|
||||
DetachedTasks::~DetachedTasks() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock lock{mutex};
|
||||
ASSERT(count == 0);
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
void DetachedTasks::AddTask(std::function<void()> task) {
|
||||
std::unique_lock<std::mutex> lock(instance->mutex);
|
||||
std::unique_lock lock{instance->mutex};
|
||||
++instance->count;
|
||||
std::thread([task{std::move(task)}]() {
|
||||
task();
|
||||
std::unique_lock<std::mutex> lock(instance->mutex);
|
||||
std::unique_lock lock{instance->mutex};
|
||||
--instance->count;
|
||||
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
|
||||
})
|
||||
|
||||
@@ -39,19 +39,19 @@ public:
|
||||
Impl(Impl const&) = delete;
|
||||
const Impl& operator=(Impl const&) = delete;
|
||||
|
||||
void PushEntry(Entry e) {
|
||||
std::lock_guard<std::mutex> lock(message_mutex);
|
||||
message_queue.Push(std::move(e));
|
||||
message_cv.notify_one();
|
||||
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, std::string message) {
|
||||
message_queue.Push(
|
||||
CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
|
||||
}
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
std::lock_guard lock{writing_mutex};
|
||||
backends.push_back(std::move(backend));
|
||||
}
|
||||
|
||||
void RemoveBackend(std::string_view backend_name) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
std::lock_guard lock{writing_mutex};
|
||||
const auto it =
|
||||
std::remove_if(backends.begin(), backends.end(),
|
||||
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
|
||||
@@ -80,21 +80,19 @@ private:
|
||||
backend_thread = std::thread([&] {
|
||||
Entry entry;
|
||||
auto write_logs = [&](Entry& e) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
std::lock_guard lock{writing_mutex};
|
||||
for (const auto& backend : backends) {
|
||||
backend->Write(e);
|
||||
}
|
||||
};
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(message_mutex);
|
||||
message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
|
||||
}
|
||||
if (!running) {
|
||||
entry = message_queue.PopWait();
|
||||
if (entry.final_entry) {
|
||||
break;
|
||||
}
|
||||
write_logs(entry);
|
||||
}
|
||||
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
|
||||
// where a system is repeatedly spamming logs even on close.
|
||||
const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
|
||||
@@ -106,18 +104,36 @@ private:
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
running = false;
|
||||
message_cv.notify_one();
|
||||
Entry entry;
|
||||
entry.final_entry = true;
|
||||
message_queue.Push(entry);
|
||||
backend_thread.join();
|
||||
}
|
||||
|
||||
std::atomic_bool running{true};
|
||||
std::mutex message_mutex, writing_mutex;
|
||||
std::condition_variable message_cv;
|
||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
||||
const char* function, std::string message) const {
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::steady_clock;
|
||||
|
||||
Entry entry;
|
||||
entry.timestamp =
|
||||
duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
|
||||
entry.log_class = log_class;
|
||||
entry.log_level = log_level;
|
||||
entry.filename = Common::TrimSourcePath(filename);
|
||||
entry.line_num = line_nr;
|
||||
entry.function = function;
|
||||
entry.message = std::move(message);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
std::mutex writing_mutex;
|
||||
std::thread backend_thread;
|
||||
std::vector<std::unique_ptr<Backend>> backends;
|
||||
Common::MPSCQueue<Log::Entry> message_queue;
|
||||
Filter filter;
|
||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||
};
|
||||
|
||||
void ConsoleBackend::Write(const Entry& entry) {
|
||||
@@ -276,25 +292,6 @@ const char* GetLevelName(Level log_level) {
|
||||
#undef LVL
|
||||
}
|
||||
|
||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
||||
const char* function, std::string message) {
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::steady_clock;
|
||||
|
||||
static steady_clock::time_point time_origin = steady_clock::now();
|
||||
|
||||
Entry entry;
|
||||
entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
|
||||
entry.log_class = log_class;
|
||||
entry.log_level = log_level;
|
||||
entry.filename = Common::TrimSourcePath(filename);
|
||||
entry.line_num = line_nr;
|
||||
entry.function = function;
|
||||
entry.message = std::move(message);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void SetGlobalFilter(const Filter& filter) {
|
||||
Impl::Instance().SetGlobalFilter(filter);
|
||||
}
|
||||
@@ -319,9 +316,7 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
if (!filter.CheckMessage(log_class, log_level))
|
||||
return;
|
||||
|
||||
Entry entry =
|
||||
CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
|
||||
|
||||
instance.PushEntry(std::move(entry));
|
||||
instance.PushEntry(log_class, log_level, filename, line_num, function,
|
||||
fmt::vformat(format, args));
|
||||
}
|
||||
} // namespace Log
|
||||
|
||||
@@ -27,6 +27,7 @@ struct Entry {
|
||||
unsigned int line_num;
|
||||
std::string function;
|
||||
std::string message;
|
||||
bool final_entry = false;
|
||||
|
||||
Entry() = default;
|
||||
Entry(Entry&& o) = default;
|
||||
@@ -134,10 +135,6 @@ const char* GetLogClassName(Class log_class);
|
||||
*/
|
||||
const char* GetLevelName(Level log_level);
|
||||
|
||||
/// Creates a log entry by formatting the given source location, and message.
|
||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
||||
const char* function, std::string message);
|
||||
|
||||
/**
|
||||
* The global filter will prevent any messages from even being processed if they are filtered. Each
|
||||
* backend can have a filter, but if the level is lower than the global filter, the backend will
|
||||
|
||||
76
src/common/lz4_compression.cpp
Normal file
76
src/common/lz4_compression.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <lz4hc.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/lz4_compression.h"
|
||||
|
||||
namespace Common::Compression {
|
||||
|
||||
std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size) {
|
||||
ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
|
||||
|
||||
const auto source_size_int = static_cast<int>(source_size);
|
||||
const int max_compressed_size = LZ4_compressBound(source_size_int);
|
||||
std::vector<u8> compressed(max_compressed_size);
|
||||
|
||||
const int compressed_size = LZ4_compress_default(reinterpret_cast<const char*>(source),
|
||||
reinterpret_cast<char*>(compressed.data()),
|
||||
source_size_int, max_compressed_size);
|
||||
|
||||
if (compressed_size <= 0) {
|
||||
// Compression failed
|
||||
return {};
|
||||
}
|
||||
|
||||
compressed.resize(compressed_size);
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size,
|
||||
s32 compression_level) {
|
||||
ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
|
||||
|
||||
compression_level = std::clamp(compression_level, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
|
||||
|
||||
const auto source_size_int = static_cast<int>(source_size);
|
||||
const int max_compressed_size = LZ4_compressBound(source_size_int);
|
||||
std::vector<u8> compressed(max_compressed_size);
|
||||
|
||||
const int compressed_size = LZ4_compress_HC(
|
||||
reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()),
|
||||
source_size_int, max_compressed_size, compression_level);
|
||||
|
||||
if (compressed_size <= 0) {
|
||||
// Compression failed
|
||||
return {};
|
||||
}
|
||||
|
||||
compressed.resize(compressed_size);
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size) {
|
||||
return CompressDataLZ4HC(source, source_size, LZ4HC_CLEVEL_MAX);
|
||||
}
|
||||
|
||||
std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed,
|
||||
std::size_t uncompressed_size) {
|
||||
std::vector<u8> uncompressed(uncompressed_size);
|
||||
const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()),
|
||||
reinterpret_cast<char*>(uncompressed.data()),
|
||||
static_cast<int>(compressed.size()),
|
||||
static_cast<int>(uncompressed.size()));
|
||||
if (static_cast<int>(uncompressed_size) != size_check) {
|
||||
// Decompression failed
|
||||
return {};
|
||||
}
|
||||
return uncompressed;
|
||||
}
|
||||
|
||||
} // namespace Common::Compression
|
||||
55
src/common/lz4_compression.h
Normal file
55
src/common/lz4_compression.h
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common::Compression {
|
||||
|
||||
/**
|
||||
* Compresses a source memory region with LZ4 and returns the compressed data in a vector.
|
||||
*
|
||||
* @param source the uncompressed source memory region.
|
||||
* @param source_size the size in bytes of the uncompressed source memory region.
|
||||
*
|
||||
* @return the compressed data.
|
||||
*/
|
||||
std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
|
||||
|
||||
/**
|
||||
* Utilizes the LZ4 subalgorithm LZ4HC with the specified compression level. Higher compression
|
||||
* levels result in a smaller compressed size, but require more CPU time for compression. The
|
||||
* compression level has almost no impact on decompression speed. Data compressed with LZ4HC can
|
||||
* also be decompressed with the default LZ4 decompression.
|
||||
*
|
||||
* @param source the uncompressed source memory region.
|
||||
* @param source_size the size in bytes of the uncompressed source memory region.
|
||||
* @param compression_level the used compression level. Should be between 3 and 12.
|
||||
*
|
||||
* @return the compressed data.
|
||||
*/
|
||||
std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, s32 compression_level);
|
||||
|
||||
/**
|
||||
* Utilizes the LZ4 subalgorithm LZ4HC with the highest possible compression level.
|
||||
*
|
||||
* @param source the uncompressed source memory region.
|
||||
* @param source_size the size in bytes of the uncompressed source memory region.
|
||||
*
|
||||
* @return the compressed data.
|
||||
*/
|
||||
std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
|
||||
|
||||
/**
|
||||
* Decompresses a source memory region with LZ4 and returns the uncompressed data in a vector.
|
||||
*
|
||||
* @param compressed the compressed source memory region.
|
||||
* @param uncompressed_size the size in bytes of the uncompressed data.
|
||||
*
|
||||
* @return the decompressed data.
|
||||
*/
|
||||
std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, std::size_t uncompressed_size);
|
||||
|
||||
} // namespace Common::Compression
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <cstdlib>
|
||||
#include <type_traits>
|
||||
|
||||
namespace MathUtil {
|
||||
namespace Common {
|
||||
|
||||
constexpr float PI = 3.14159265f;
|
||||
|
||||
@@ -41,4 +41,4 @@ struct Rectangle {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace MathUtil
|
||||
} // namespace Common
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/memory_hook.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
MemoryHook::~MemoryHook() = default;
|
||||
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* Memory hooks have two purposes:
|
||||
@@ -44,4 +44,4 @@ public:
|
||||
};
|
||||
|
||||
using MemoryHookPointer = std::shared_ptr<MemoryHook>;
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
337
src/common/multi_level_queue.h
Normal file
337
src/common/multi_level_queue.h
Normal file
@@ -0,0 +1,337 @@
|
||||
// Copyright 2019 TuxSH
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* A MultiLevelQueue is a type of priority queue which has the following characteristics:
|
||||
* - iteratable through each of its elements.
|
||||
* - back can be obtained.
|
||||
* - O(1) add, lookup (both front and back)
|
||||
* - discrete priorities and a max of 64 priorities (limited domain)
|
||||
* This type of priority queue is normaly used for managing threads within an scheduler
|
||||
*/
|
||||
template <typename T, std::size_t Depth>
|
||||
class MultiLevelQueue {
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
|
||||
using difference_type = typename std::pointer_traits<pointer>::difference_type;
|
||||
using size_type = std::size_t;
|
||||
|
||||
template <bool is_constant>
|
||||
class iterator_impl {
|
||||
public:
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = T;
|
||||
using pointer = std::conditional_t<is_constant, T*, const T*>;
|
||||
using reference = std::conditional_t<is_constant, const T&, T&>;
|
||||
using difference_type = typename std::pointer_traits<pointer>::difference_type;
|
||||
|
||||
friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) {
|
||||
if (lhs.IsEnd() && rhs.IsEnd())
|
||||
return true;
|
||||
return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it);
|
||||
}
|
||||
|
||||
friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
reference operator*() const {
|
||||
return *it;
|
||||
}
|
||||
|
||||
pointer operator->() const {
|
||||
return it.operator->();
|
||||
}
|
||||
|
||||
iterator_impl& operator++() {
|
||||
if (IsEnd()) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
++it;
|
||||
|
||||
if (it == GetEndItForPrio()) {
|
||||
u64 prios = mlq.used_priorities;
|
||||
prios &= ~((1ULL << (current_priority + 1)) - 1);
|
||||
if (prios == 0) {
|
||||
current_priority = mlq.depth();
|
||||
} else {
|
||||
current_priority = CountTrailingZeroes64(prios);
|
||||
it = GetBeginItForPrio();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator_impl& operator--() {
|
||||
if (IsEnd()) {
|
||||
if (mlq.used_priorities != 0) {
|
||||
current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities);
|
||||
it = GetEndItForPrio();
|
||||
--it;
|
||||
}
|
||||
} else if (it == GetBeginItForPrio()) {
|
||||
u64 prios = mlq.used_priorities;
|
||||
prios &= (1ULL << current_priority) - 1;
|
||||
if (prios != 0) {
|
||||
current_priority = CountTrailingZeroes64(prios);
|
||||
it = GetEndItForPrio();
|
||||
--it;
|
||||
}
|
||||
} else {
|
||||
--it;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator_impl operator++(int) {
|
||||
const iterator_impl v{*this};
|
||||
++(*this);
|
||||
return v;
|
||||
}
|
||||
|
||||
iterator_impl operator--(int) {
|
||||
const iterator_impl v{*this};
|
||||
--(*this);
|
||||
return v;
|
||||
}
|
||||
|
||||
// allow implicit const->non-const
|
||||
iterator_impl(const iterator_impl<false>& other)
|
||||
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
|
||||
|
||||
iterator_impl(const iterator_impl<true>& other)
|
||||
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
|
||||
|
||||
iterator_impl& operator=(const iterator_impl<false>& other) {
|
||||
mlq = other.mlq;
|
||||
it = other.it;
|
||||
current_priority = other.current_priority;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend class iterator_impl<true>;
|
||||
iterator_impl() = default;
|
||||
|
||||
private:
|
||||
friend class MultiLevelQueue;
|
||||
using container_ref =
|
||||
std::conditional_t<is_constant, const MultiLevelQueue&, MultiLevelQueue&>;
|
||||
using list_iterator = std::conditional_t<is_constant, typename std::list<T>::const_iterator,
|
||||
typename std::list<T>::iterator>;
|
||||
|
||||
explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority)
|
||||
: mlq(mlq), it(it), current_priority(current_priority) {}
|
||||
explicit iterator_impl(container_ref mlq, u32 current_priority)
|
||||
: mlq(mlq), it(), current_priority(current_priority) {}
|
||||
|
||||
bool IsEnd() const {
|
||||
return current_priority == mlq.depth();
|
||||
}
|
||||
|
||||
list_iterator GetBeginItForPrio() const {
|
||||
return mlq.levels[current_priority].begin();
|
||||
}
|
||||
|
||||
list_iterator GetEndItForPrio() const {
|
||||
return mlq.levels[current_priority].end();
|
||||
}
|
||||
|
||||
container_ref mlq;
|
||||
list_iterator it;
|
||||
u32 current_priority;
|
||||
};
|
||||
|
||||
using iterator = iterator_impl<false>;
|
||||
using const_iterator = iterator_impl<true>;
|
||||
|
||||
void add(const T& element, u32 priority, bool send_back = true) {
|
||||
if (send_back)
|
||||
levels[priority].push_back(element);
|
||||
else
|
||||
levels[priority].push_front(element);
|
||||
used_priorities |= 1ULL << priority;
|
||||
}
|
||||
|
||||
void remove(const T& element, u32 priority) {
|
||||
auto it = ListIterateTo(levels[priority], element);
|
||||
if (it == levels[priority].end())
|
||||
return;
|
||||
levels[priority].erase(it);
|
||||
if (levels[priority].empty()) {
|
||||
used_priorities &= ~(1ULL << priority);
|
||||
}
|
||||
}
|
||||
|
||||
void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) {
|
||||
remove(element, old_priority);
|
||||
add(element, new_priority, !adjust_front);
|
||||
}
|
||||
void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) {
|
||||
adjust(*it, old_priority, new_priority, adjust_front);
|
||||
}
|
||||
|
||||
void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) {
|
||||
ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority],
|
||||
ListIterateTo(levels[priority], element));
|
||||
|
||||
other.used_priorities |= 1ULL << priority;
|
||||
|
||||
if (levels[priority].empty()) {
|
||||
used_priorities &= ~(1ULL << priority);
|
||||
}
|
||||
}
|
||||
|
||||
void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) {
|
||||
transfer_to_front(*it, priority, other);
|
||||
}
|
||||
|
||||
void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) {
|
||||
ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority],
|
||||
ListIterateTo(levels[priority], element));
|
||||
|
||||
other.used_priorities |= 1ULL << priority;
|
||||
|
||||
if (levels[priority].empty()) {
|
||||
used_priorities &= ~(1ULL << priority);
|
||||
}
|
||||
}
|
||||
|
||||
void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) {
|
||||
transfer_to_back(*it, priority, other);
|
||||
}
|
||||
|
||||
void yield(u32 priority, std::size_t n = 1) {
|
||||
ListShiftForward(levels[priority], n);
|
||||
}
|
||||
|
||||
std::size_t depth() const {
|
||||
return Depth;
|
||||
}
|
||||
|
||||
std::size_t size(u32 priority) const {
|
||||
return levels[priority].size();
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
u64 priorities = used_priorities;
|
||||
std::size_t size = 0;
|
||||
while (priorities != 0) {
|
||||
const u64 current_priority = CountTrailingZeroes64(priorities);
|
||||
size += levels[current_priority].size();
|
||||
priorities &= ~(1ULL << current_priority);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return used_priorities == 0;
|
||||
}
|
||||
|
||||
bool empty(u32 priority) const {
|
||||
return (used_priorities & (1ULL << priority)) == 0;
|
||||
}
|
||||
|
||||
u32 highest_priority_set(u32 max_priority = 0) const {
|
||||
const u64 priorities =
|
||||
max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
|
||||
return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
|
||||
}
|
||||
|
||||
u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
|
||||
const u64 priorities = min_priority >= Depth - 1
|
||||
? used_priorities
|
||||
: (used_priorities & ((1ULL << (min_priority + 1)) - 1));
|
||||
return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
|
||||
}
|
||||
|
||||
const_iterator cbegin(u32 max_prio = 0) const {
|
||||
const u32 priority = highest_priority_set(max_prio);
|
||||
return priority == Depth ? cend()
|
||||
: const_iterator{*this, levels[priority].cbegin(), priority};
|
||||
}
|
||||
const_iterator begin(u32 max_prio = 0) const {
|
||||
return cbegin(max_prio);
|
||||
}
|
||||
iterator begin(u32 max_prio = 0) {
|
||||
const u32 priority = highest_priority_set(max_prio);
|
||||
return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
|
||||
}
|
||||
|
||||
const_iterator cend(u32 min_prio = Depth - 1) const {
|
||||
return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
|
||||
}
|
||||
const_iterator end(u32 min_prio = Depth - 1) const {
|
||||
return cend(min_prio);
|
||||
}
|
||||
iterator end(u32 min_prio = Depth - 1) {
|
||||
return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
|
||||
}
|
||||
|
||||
T& front(u32 max_priority = 0) {
|
||||
const u32 priority = highest_priority_set(max_priority);
|
||||
return levels[priority == Depth ? 0 : priority].front();
|
||||
}
|
||||
const T& front(u32 max_priority = 0) const {
|
||||
const u32 priority = highest_priority_set(max_priority);
|
||||
return levels[priority == Depth ? 0 : priority].front();
|
||||
}
|
||||
|
||||
T back(u32 min_priority = Depth - 1) {
|
||||
const u32 priority = lowest_priority_set(min_priority); // intended
|
||||
return levels[priority == Depth ? 63 : priority].back();
|
||||
}
|
||||
const T& back(u32 min_priority = Depth - 1) const {
|
||||
const u32 priority = lowest_priority_set(min_priority); // intended
|
||||
return levels[priority == Depth ? 63 : priority].back();
|
||||
}
|
||||
|
||||
private:
|
||||
using const_list_iterator = typename std::list<T>::const_iterator;
|
||||
|
||||
static void ListShiftForward(std::list<T>& list, const std::size_t shift = 1) {
|
||||
if (shift >= list.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto begin_range = list.begin();
|
||||
const auto end_range = std::next(begin_range, shift);
|
||||
list.splice(list.end(), list, begin_range, end_range);
|
||||
}
|
||||
|
||||
static void ListSplice(std::list<T>& in_list, const_list_iterator position,
|
||||
std::list<T>& out_list, const_list_iterator element) {
|
||||
in_list.splice(position, out_list, element);
|
||||
}
|
||||
|
||||
static const_list_iterator ListIterateTo(const std::list<T>& list, const T& element) {
|
||||
auto it = list.cbegin();
|
||||
while (it != list.cend() && *it != element) {
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
std::array<std::list<T>, Depth> levels;
|
||||
u64 used_priorities = 0;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
31
src/common/page_table.cpp
Normal file
31
src/common/page_table.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/page_table.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {}
|
||||
|
||||
PageTable::~PageTable() = default;
|
||||
|
||||
void PageTable::Resize(std::size_t address_space_width_in_bits) {
|
||||
const std::size_t num_page_table_entries = 1ULL
|
||||
<< (address_space_width_in_bits - page_size_in_bits);
|
||||
|
||||
pointers.resize(num_page_table_entries);
|
||||
attributes.resize(num_page_table_entries);
|
||||
backing_addr.resize(num_page_table_entries);
|
||||
|
||||
// The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
|
||||
// vector size is subsequently decreased (via resize), the vector might not automatically
|
||||
// actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
|
||||
// 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
|
||||
|
||||
pointers.shrink_to_fit();
|
||||
attributes.shrink_to_fit();
|
||||
backing_addr.shrink_to_fit();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
84
src/common/page_table.h
Normal file
84
src/common/page_table.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum class PageType : u8 {
|
||||
/// Page is unmapped and should cause an access error.
|
||||
Unmapped,
|
||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||
Memory,
|
||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||
/// invalidation
|
||||
RasterizerCachedMemory,
|
||||
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
|
||||
Special,
|
||||
/// Page is allocated for use.
|
||||
Allocated,
|
||||
};
|
||||
|
||||
struct SpecialRegion {
|
||||
enum class Type {
|
||||
DebugHook,
|
||||
IODevice,
|
||||
} type;
|
||||
|
||||
MemoryHookPointer handler;
|
||||
|
||||
bool operator<(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) < std::tie(other.type, other.handler);
|
||||
}
|
||||
|
||||
bool operator==(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) == std::tie(other.type, other.handler);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
|
||||
* mimics the way a real CPU page table works.
|
||||
*/
|
||||
struct PageTable {
|
||||
explicit PageTable(std::size_t page_size_in_bits);
|
||||
~PageTable();
|
||||
|
||||
/**
|
||||
* Resizes the page table to be able to accomodate enough pages within
|
||||
* a given address space.
|
||||
*
|
||||
* @param address_space_width_in_bits The address size width in bits.
|
||||
*/
|
||||
void Resize(std::size_t address_space_width_in_bits);
|
||||
|
||||
/**
|
||||
* Vector of memory pointers backing each page. An entry can only be non-null if the
|
||||
* corresponding entry in the `attributes` vector is of type `Memory`.
|
||||
*/
|
||||
std::vector<u8*> pointers;
|
||||
|
||||
/**
|
||||
* Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
|
||||
* of type `Special`.
|
||||
*/
|
||||
boost::icl::interval_map<u64, std::set<SpecialRegion>> special_regions;
|
||||
|
||||
/**
|
||||
* Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
|
||||
* the corresponding entry in `pointers` MUST be set to null.
|
||||
*/
|
||||
std::vector<PageType> attributes;
|
||||
|
||||
std::vector<u64> backing_addr;
|
||||
|
||||
const std::size_t page_size_in_bits{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
#include "common/vector_math.h"
|
||||
|
||||
namespace Math {
|
||||
namespace Common {
|
||||
|
||||
template <typename T>
|
||||
class Quaternion {
|
||||
public:
|
||||
Math::Vec3<T> xyz;
|
||||
Vec3<T> xyz;
|
||||
T w{};
|
||||
|
||||
Quaternion<decltype(-T{})> Inverse() const {
|
||||
@@ -38,12 +38,12 @@ public:
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
auto QuaternionRotate(const Quaternion<T>& q, const Math::Vec3<T>& v) {
|
||||
auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
|
||||
return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
|
||||
}
|
||||
|
||||
inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float angle) {
|
||||
inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
|
||||
return {axis * std::sin(angle / 2), std::cos(angle / 2)};
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
} // namespace Common
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <cstdlib>
|
||||
#elif defined(__linux__)
|
||||
@@ -28,8 +30,8 @@
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
|
||||
// GCC 4.6+
|
||||
#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
|
||||
// GCC
|
||||
#ifdef __GNUC__
|
||||
|
||||
#if __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) && !defined(COMMON_LITTLE_ENDIAN)
|
||||
#define COMMON_LITTLE_ENDIAN 1
|
||||
@@ -38,7 +40,7 @@
|
||||
#endif
|
||||
|
||||
// LLVM/clang
|
||||
#elif __clang__
|
||||
#elif defined(__clang__)
|
||||
|
||||
#if __LITTLE_ENDIAN__ && !defined(COMMON_LITTLE_ENDIAN)
|
||||
#define COMMON_LITTLE_ENDIAN 1
|
||||
@@ -170,7 +172,7 @@ struct swap_struct_t {
|
||||
using swapped_t = swap_struct_t;
|
||||
|
||||
protected:
|
||||
T value = T();
|
||||
T value;
|
||||
|
||||
static T swap(T v) {
|
||||
return F::swap(v);
|
||||
@@ -605,52 +607,154 @@ struct swap_double_t {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct swap_enum_t {
|
||||
static_assert(std::is_enum_v<T>);
|
||||
using base = std::underlying_type_t<T>;
|
||||
|
||||
public:
|
||||
swap_enum_t() = default;
|
||||
swap_enum_t(const T& v) : value(swap(v)) {}
|
||||
|
||||
swap_enum_t& operator=(const T& v) {
|
||||
value = swap(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
return swap(value);
|
||||
}
|
||||
|
||||
explicit operator base() const {
|
||||
return static_cast<base>(swap(value));
|
||||
}
|
||||
|
||||
protected:
|
||||
T value{};
|
||||
// clang-format off
|
||||
using swap_t = std::conditional_t<
|
||||
std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
|
||||
std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
|
||||
std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t<
|
||||
std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
|
||||
std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
|
||||
std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
|
||||
// clang-format on
|
||||
static T swap(T x) {
|
||||
return static_cast<T>(swap_t::swap(static_cast<base>(x)));
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapTag {}; // Use the different endianness from the system
|
||||
struct KeepTag {}; // Use the same endianness as the system
|
||||
|
||||
template <typename T, typename Tag>
|
||||
struct AddEndian;
|
||||
|
||||
// KeepTag specializations
|
||||
|
||||
template <typename T>
|
||||
struct AddEndian<T, KeepTag> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
// SwapTag specializations
|
||||
|
||||
template <>
|
||||
struct AddEndian<u8, SwapTag> {
|
||||
using type = u8;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u16, SwapTag> {
|
||||
using type = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u32, SwapTag> {
|
||||
using type = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u64, SwapTag> {
|
||||
using type = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s8, SwapTag> {
|
||||
using type = s8;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s16, SwapTag> {
|
||||
using type = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s32, SwapTag> {
|
||||
using type = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s64, SwapTag> {
|
||||
using type = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<float, SwapTag> {
|
||||
using type = swap_struct_t<float, swap_float_t<float>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<double, SwapTag> {
|
||||
using type = swap_struct_t<double, swap_double_t<double>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct AddEndian<T, SwapTag> {
|
||||
static_assert(std::is_enum_v<T>);
|
||||
using type = swap_enum_t<T>;
|
||||
};
|
||||
|
||||
// Alias LETag/BETag as KeepTag/SwapTag depending on the system
|
||||
#if COMMON_LITTLE_ENDIAN
|
||||
using u16_le = u16;
|
||||
using u32_le = u32;
|
||||
using u64_le = u64;
|
||||
|
||||
using s16_le = s16;
|
||||
using s32_le = s32;
|
||||
using s64_le = s64;
|
||||
using LETag = KeepTag;
|
||||
using BETag = SwapTag;
|
||||
|
||||
using float_le = float;
|
||||
using double_le = double;
|
||||
|
||||
using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_be = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_be = swap_struct_t<double, swap_double_t<double>>;
|
||||
#else
|
||||
|
||||
using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_le = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_le = swap_struct_t<double, swap_double_t<double>>;
|
||||
|
||||
using u16_be = u16;
|
||||
using u32_be = u32;
|
||||
using u64_be = u64;
|
||||
|
||||
using s16_be = s16;
|
||||
using s32_be = s32;
|
||||
using s64_be = s64;
|
||||
|
||||
using float_be = float;
|
||||
using double_be = double;
|
||||
using BETag = KeepTag;
|
||||
using LETag = SwapTag;
|
||||
|
||||
#endif
|
||||
|
||||
// Aliases for LE types
|
||||
using u16_le = AddEndian<u16, LETag>::type;
|
||||
using u32_le = AddEndian<u32, LETag>::type;
|
||||
using u64_le = AddEndian<u64, LETag>::type;
|
||||
|
||||
using s16_le = AddEndian<s16, LETag>::type;
|
||||
using s32_le = AddEndian<s32, LETag>::type;
|
||||
using s64_le = AddEndian<s64, LETag>::type;
|
||||
|
||||
template <typename T>
|
||||
using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>;
|
||||
|
||||
using float_le = AddEndian<float, LETag>::type;
|
||||
using double_le = AddEndian<double, LETag>::type;
|
||||
|
||||
// Aliases for BE types
|
||||
using u16_be = AddEndian<u16, BETag>::type;
|
||||
using u32_be = AddEndian<u32, BETag>::type;
|
||||
using u64_be = AddEndian<u64, BETag>::type;
|
||||
|
||||
using s16_be = AddEndian<s16, BETag>::type;
|
||||
using s32_be = AddEndian<s32, BETag>::type;
|
||||
using s64_be = AddEndian<s64, BETag>::type;
|
||||
|
||||
template <typename T>
|
||||
using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>;
|
||||
|
||||
using float_be = AddEndian<float, BETag>::type;
|
||||
using double_be = AddEndian<double, BETag>::type;
|
||||
|
||||
@@ -27,18 +27,6 @@ namespace Common {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) {
|
||||
SetThreadAffinityMask(thread, mask);
|
||||
}
|
||||
|
||||
void SetCurrentThreadAffinity(u32 mask) {
|
||||
SetThreadAffinityMask(GetCurrentThread(), mask);
|
||||
}
|
||||
|
||||
void SwitchCurrentThread() {
|
||||
SwitchToThread();
|
||||
}
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
// Uses undocumented (actually, it is now documented) trick.
|
||||
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp
|
||||
@@ -70,31 +58,6 @@ void SetCurrentThreadName(const char* name) {
|
||||
|
||||
#else // !MSVC_VER, so must be POSIX threads
|
||||
|
||||
void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) {
|
||||
#ifdef __APPLE__
|
||||
thread_policy_set(pthread_mach_thread_np(thread), THREAD_AFFINITY_POLICY, (integer_t*)&mask, 1);
|
||||
#elif (defined __linux__ || defined __FreeBSD__) && !(defined ANDROID)
|
||||
cpu_set_t cpu_set;
|
||||
CPU_ZERO(&cpu_set);
|
||||
|
||||
for (int i = 0; i != sizeof(mask) * 8; ++i)
|
||||
if ((mask >> i) & 1)
|
||||
CPU_SET(i, &cpu_set);
|
||||
|
||||
pthread_setaffinity_np(thread, sizeof(cpu_set), &cpu_set);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadAffinity(u32 mask) {
|
||||
SetThreadAffinity(pthread_self(), mask);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
void SwitchCurrentThread() {
|
||||
usleep(1000 * 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// MinGW with the POSIX threading model does not support pthread_setname_np
|
||||
#if !defined(_WIN32) || defined(_MSC_VER)
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
|
||||
@@ -9,14 +9,13 @@
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class Event {
|
||||
public:
|
||||
void Set() {
|
||||
std::lock_guard<std::mutex> lk(mutex);
|
||||
std::lock_guard lk{mutex};
|
||||
if (!is_set) {
|
||||
is_set = true;
|
||||
condvar.notify_one();
|
||||
@@ -24,14 +23,14 @@ public:
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
std::unique_lock lk{mutex};
|
||||
condvar.wait(lk, [&] { return is_set; });
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
template <class Clock, class Duration>
|
||||
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
std::unique_lock lk{mutex};
|
||||
if (!condvar.wait_until(lk, time, [this] { return is_set; }))
|
||||
return false;
|
||||
is_set = false;
|
||||
@@ -39,7 +38,7 @@ public:
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
std::unique_lock lk{mutex};
|
||||
// no other action required, since wait loops on the predicate and any lingering signal will
|
||||
// get cleared on the first iteration
|
||||
is_set = false;
|
||||
@@ -57,7 +56,7 @@ public:
|
||||
|
||||
/// Blocks until all "count" threads have called Sync()
|
||||
void Sync() {
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
std::unique_lock lk{mutex};
|
||||
const std::size_t current_generation = generation;
|
||||
|
||||
if (++waiting == count) {
|
||||
@@ -78,9 +77,6 @@ private:
|
||||
std::size_t generation = 0; // Incremented once each time the barrier is used
|
||||
};
|
||||
|
||||
void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask);
|
||||
void SetCurrentThreadAffinity(u32 mask);
|
||||
void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms
|
||||
void SetCurrentThreadName(const char* name);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -111,8 +110,9 @@ struct ThreadQueueList {
|
||||
}
|
||||
|
||||
void remove(Priority priority, const T& thread_id) {
|
||||
Queue* cur = &queues[priority];
|
||||
boost::remove_erase(cur->data, thread_id);
|
||||
Queue* const cur = &queues[priority];
|
||||
const auto iter = std::remove(cur->data.begin(), cur->data.end(), thread_id);
|
||||
cur->data.erase(iter, cur->data.end());
|
||||
}
|
||||
|
||||
void rotate(Priority priority) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// single reader, single writer queue
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
ElementPtr* new_ptr = new ElementPtr();
|
||||
write_ptr->next.store(new_ptr, std::memory_order_release);
|
||||
write_ptr = new_ptr;
|
||||
cv.notify_one();
|
||||
|
||||
++size;
|
||||
}
|
||||
@@ -74,6 +76,16 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
T PopWait() {
|
||||
if (Empty()) {
|
||||
std::unique_lock lock{cv_mutex};
|
||||
cv.wait(lock, [this]() { return !Empty(); });
|
||||
}
|
||||
T t;
|
||||
Pop(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
// not thread-safe
|
||||
void Clear() {
|
||||
size.store(0);
|
||||
@@ -101,6 +113,8 @@ private:
|
||||
ElementPtr* write_ptr;
|
||||
ElementPtr* read_ptr;
|
||||
std::atomic_size_t size{0};
|
||||
std::mutex cv_mutex;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
// a simple thread-safe,
|
||||
@@ -123,7 +137,7 @@ public:
|
||||
|
||||
template <typename Arg>
|
||||
void Push(Arg&& t) {
|
||||
std::lock_guard<std::mutex> lock(write_lock);
|
||||
std::lock_guard lock{write_lock};
|
||||
spsc_queue.Push(t);
|
||||
}
|
||||
|
||||
@@ -135,6 +149,10 @@ public:
|
||||
return spsc_queue.Pop(t);
|
||||
}
|
||||
|
||||
T PopWait() {
|
||||
return spsc_queue.PopWait();
|
||||
}
|
||||
|
||||
// not thread-safe
|
||||
void Clear() {
|
||||
spsc_queue.Clear();
|
||||
|
||||
45
src/common/uint128.cpp
Normal file
45
src/common/uint128.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
|
||||
#pragma intrinsic(_umul128)
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
u128 Multiply64Into128(u64 a, u64 b) {
|
||||
u128 result;
|
||||
#ifdef _MSC_VER
|
||||
result[0] = _umul128(a, b, &result[1]);
|
||||
#else
|
||||
unsigned __int128 tmp = a;
|
||||
tmp *= b;
|
||||
std::memcpy(&result, &tmp, sizeof(u128));
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor) {
|
||||
u64 remainder = dividend[0] % divisor;
|
||||
u64 accum = dividend[0] / divisor;
|
||||
if (dividend[1] == 0)
|
||||
return {accum, remainder};
|
||||
// We ignore dividend[1] / divisor as that overflows
|
||||
const u64 first_segment = (dividend[1] % divisor) << 32;
|
||||
accum += (first_segment / divisor) << 32;
|
||||
const u64 second_segment = (first_segment % divisor) << 32;
|
||||
accum += (second_segment / divisor);
|
||||
remainder += second_segment % divisor;
|
||||
if (remainder >= divisor) {
|
||||
accum++;
|
||||
remainder -= divisor;
|
||||
}
|
||||
return {accum, remainder};
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
19
src/common/uint128.h
Normal file
19
src/common/uint128.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
// This function multiplies 2 u64 values and produces a u128 value;
|
||||
u128 Multiply64Into128(u64 a, u64 b);
|
||||
|
||||
// This function divides a u128 by a u32 value and produces two u64 values:
|
||||
// the result of division and the remainder
|
||||
std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
|
||||
|
||||
} // namespace Common
|
||||
@@ -33,7 +33,7 @@
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Math {
|
||||
namespace Common {
|
||||
|
||||
template <typename T>
|
||||
class Vec2;
|
||||
@@ -690,4 +690,4 @@ constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
|
||||
return MakeVec(x, yzw[0], yzw[1], yzw[2]);
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
} // namespace Common
|
||||
|
||||
@@ -31,6 +31,8 @@ add_library(core STATIC
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/cheat_engine.cpp
|
||||
file_sys/cheat_engine.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
@@ -68,6 +70,8 @@ add_library(core STATIC
|
||||
file_sys/system_archive/ng_word.h
|
||||
file_sys/system_archive/system_archive.cpp
|
||||
file_sys/system_archive/system_archive.h
|
||||
file_sys/system_archive/system_version.cpp
|
||||
file_sys/system_archive/system_version.h
|
||||
file_sys/vfs.cpp
|
||||
file_sys/vfs.h
|
||||
file_sys/vfs_concat.cpp
|
||||
@@ -107,6 +111,8 @@ add_library(core STATIC
|
||||
hle/kernel/client_port.h
|
||||
hle/kernel/client_session.cpp
|
||||
hle/kernel/client_session.h
|
||||
hle/kernel/code_set.cpp
|
||||
hle/kernel/code_set.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/handle_table.cpp
|
||||
hle/kernel/handle_table.h
|
||||
@@ -140,6 +146,8 @@ add_library(core STATIC
|
||||
hle/kernel/svc_wrap.h
|
||||
hle/kernel/thread.cpp
|
||||
hle/kernel/thread.h
|
||||
hle/kernel/transfer_memory.cpp
|
||||
hle/kernel/transfer_memory.h
|
||||
hle/kernel/vm_manager.cpp
|
||||
hle/kernel/vm_manager.h
|
||||
hle/kernel/wait_object.cpp
|
||||
@@ -217,6 +225,7 @@ add_library(core STATIC
|
||||
hle/service/audio/audren_u.h
|
||||
hle/service/audio/codecctl.cpp
|
||||
hle/service/audio/codecctl.h
|
||||
hle/service/audio/errors.h
|
||||
hle/service/audio/hwopus.cpp
|
||||
hle/service/audio/hwopus.h
|
||||
hle/service/bcat/bcat.cpp
|
||||
@@ -400,6 +409,10 @@ add_library(core STATIC
|
||||
hle/service/time/time.h
|
||||
hle/service/usb/usb.cpp
|
||||
hle/service/usb/usb.h
|
||||
hle/service/vi/display/vi_display.cpp
|
||||
hle/service/vi/display/vi_display.h
|
||||
hle/service/vi/layer/vi_layer.cpp
|
||||
hle/service/vi/layer/vi_layer.h
|
||||
hle/service/vi/vi.cpp
|
||||
hle/service/vi/vi.h
|
||||
hle/service/vi/vi_m.cpp
|
||||
@@ -414,8 +427,6 @@ add_library(core STATIC
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/elf.cpp
|
||||
loader/elf.h
|
||||
loader/linker.cpp
|
||||
loader/linker.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/nax.cpp
|
||||
@@ -432,8 +443,6 @@ add_library(core STATIC
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
memory_setup.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
@@ -449,7 +458,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 mbedtls opus unicorn open_source_archives)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(core PRIVATE web_service)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -25,7 +26,6 @@ using Vector = Dynarmic::A64::Vector;
|
||||
class ARM_Dynarmic_Callbacks : public Dynarmic::A64::UserCallbacks {
|
||||
public:
|
||||
explicit ARM_Dynarmic_Callbacks(ARM_Dynarmic& parent) : parent(parent) {}
|
||||
~ARM_Dynarmic_Callbacks() = default;
|
||||
|
||||
u8 MemoryRead8(u64 vaddr) override {
|
||||
return Memory::Read8(vaddr);
|
||||
@@ -119,7 +119,7 @@ public:
|
||||
return std::max(parent.core_timing.GetDowncount(), 0);
|
||||
}
|
||||
u64 GetCNTPCT() override {
|
||||
return parent.core_timing.GetTicks();
|
||||
return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks());
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
@@ -151,7 +151,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
config.tpidr_el0 = &cb->tpidr_el0;
|
||||
config.dczid_el0 = 4;
|
||||
config.ctr_el0 = 0x8444c004;
|
||||
config.cntfrq_el0 = 19200000; // Value from fusee.
|
||||
config.cntfrq_el0 = Timing::CNTFREQ;
|
||||
|
||||
// Unpredictable instructions
|
||||
config.define_unpredictable_behaviour = true;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
std::size_t core_index);
|
||||
~ARM_Dynarmic();
|
||||
~ARM_Dynarmic() override;
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
Kernel::VMAPermission perms) override;
|
||||
@@ -70,13 +70,13 @@ private:
|
||||
Timing::CoreTiming& core_timing;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
Common::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
public:
|
||||
explicit DynarmicExclusiveMonitor(std::size_t core_count);
|
||||
~DynarmicExclusiveMonitor();
|
||||
~DynarmicExclusiveMonitor() override;
|
||||
|
||||
void SetExclusive(std::size_t core_index, VAddr addr) override;
|
||||
void ClearExclusive() override;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Core {
|
||||
class ARM_Unicorn final : public ARM_Interface {
|
||||
public:
|
||||
explicit ARM_Unicorn(Timing::CoreTiming& core_timing);
|
||||
~ARM_Unicorn();
|
||||
~ARM_Unicorn() override;
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
Kernel::VMAPermission perms) override;
|
||||
|
||||
@@ -32,11 +32,13 @@
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "file_sys/cheat_engine.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
#include "frontend/applets/software_keyboard.h"
|
||||
#include "frontend/applets/web_browser.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/gpu_asynch.h"
|
||||
#include "video_core/gpu_synch.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@@ -78,6 +80,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
return vfs->OpenFile(path, FileSys::Mode::Read);
|
||||
}
|
||||
struct System::Impl {
|
||||
explicit Impl(System& system) : kernel{system} {}
|
||||
|
||||
Cpu& CurrentCpuCore() {
|
||||
return cpu_core_manager.GetCurrentCore();
|
||||
@@ -95,7 +98,7 @@ struct System::Impl {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
core_timing.Initialize();
|
||||
kernel.Initialize(core_timing);
|
||||
kernel.Initialize();
|
||||
|
||||
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
@@ -114,7 +117,7 @@ struct System::Impl {
|
||||
if (web_browser == nullptr)
|
||||
web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
auto main_process = Kernel::Process::Create(system, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
@@ -128,10 +131,16 @@ struct System::Impl {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>(system, renderer->Rasterizer());
|
||||
is_powered_on = true;
|
||||
|
||||
if (Settings::values.use_asynchronous_gpu_emulation) {
|
||||
gpu_core = std::make_unique<VideoCommon::GPUAsynch>(system, *renderer);
|
||||
} else {
|
||||
gpu_core = std::make_unique<VideoCommon::GPUSynch>(system, *renderer);
|
||||
}
|
||||
|
||||
cpu_core_manager.Initialize(system);
|
||||
is_powered_on = true;
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
@@ -182,13 +191,13 @@ struct System::Impl {
|
||||
|
||||
void Shutdown() {
|
||||
// Log last frame performance stats
|
||||
auto perf_results = GetAndResetPerfStats();
|
||||
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
|
||||
perf_results.emulation_speed * 100.0);
|
||||
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
|
||||
perf_results.game_fps);
|
||||
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
const auto perf_results = GetAndResetPerfStats();
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
|
||||
perf_results.emulation_speed * 100.0);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
|
||||
perf_results.game_fps);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
|
||||
is_powered_on = false;
|
||||
|
||||
@@ -197,6 +206,7 @@ struct System::Impl {
|
||||
GDBStub::Shutdown();
|
||||
Service::Shutdown();
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
@@ -247,6 +257,8 @@ struct System::Impl {
|
||||
CpuCoreManager cpu_core_manager;
|
||||
bool is_powered_on = false;
|
||||
|
||||
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
|
||||
|
||||
/// Frontend applets
|
||||
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
|
||||
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
|
||||
@@ -265,7 +277,7 @@ struct System::Impl {
|
||||
Core::FrameLimiter frame_limiter;
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>()} {}
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
System::~System() = default;
|
||||
|
||||
Cpu& System::CurrentCpuCore() {
|
||||
@@ -445,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
|
||||
return impl->debug_context.get();
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end) {
|
||||
impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
|
||||
code_region_start, code_region_end);
|
||||
}
|
||||
|
||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
|
||||
impl->virtual_filesystem = std::move(vfs);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class WebBrowserApplet;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
class CheatList;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -253,6 +254,9 @@ public:
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
|
||||
void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
|
||||
|
||||
const Frontend::ProfileSelectApplet& GetProfileSelector() const;
|
||||
@@ -293,10 +297,6 @@ inline ARM_Interface& CurrentArmInterface() {
|
||||
return System::GetInstance().CurrentArmInterface();
|
||||
}
|
||||
|
||||
inline TelemetrySession& Telemetry() {
|
||||
return System::GetInstance().TelemetrySession();
|
||||
}
|
||||
|
||||
inline Kernel::Process* CurrentProcess() {
|
||||
return System::GetInstance().CurrentProcess();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#endif
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
@@ -21,7 +22,7 @@
|
||||
namespace Core {
|
||||
|
||||
void CpuBarrier::NotifyEnd() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock lock{mutex};
|
||||
end = true;
|
||||
condition.notify_all();
|
||||
}
|
||||
@@ -33,7 +34,7 @@ bool CpuBarrier::Rendezvous() {
|
||||
}
|
||||
|
||||
if (!end) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock lock{mutex};
|
||||
|
||||
--cores_waiting;
|
||||
if (!cores_waiting) {
|
||||
@@ -49,9 +50,9 @@ bool CpuBarrier::Rendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{core_timing}, core_index{core_index} {
|
||||
Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(core_timing, exclusive_monitor, core_index);
|
||||
@@ -63,7 +64,7 @@ Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
arm_interface = std::make_unique<ARM_Unicorn>(core_timing);
|
||||
}
|
||||
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface);
|
||||
}
|
||||
|
||||
Cpu::~Cpu() = default;
|
||||
@@ -130,7 +131,7 @@ 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);
|
||||
std::lock_guard lock{HLE::g_hle_lock};
|
||||
scheduler->Reschedule();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Kernel {
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
@@ -45,8 +49,8 @@ private:
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index);
|
||||
Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index);
|
||||
~Cpu();
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
|
||||
@@ -186,7 +186,7 @@ void CoreTiming::Advance() {
|
||||
Event evt = std::move(event_queue.front());
|
||||
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));
|
||||
evt.type->callback(evt.userdata, global_timer - evt.time);
|
||||
}
|
||||
|
||||
is_global_timer_sane = false;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace Core::Timing {
|
||||
|
||||
/// A callback that may be scheduled for a particular core timing event.
|
||||
using TimedCallback = std::function<void(u64 userdata, int cycles_late)>;
|
||||
using TimedCallback = std::function<void(u64 userdata, s64 cycles_late)>;
|
||||
|
||||
/// Contains the characteristics of a particular event.
|
||||
struct EventType {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
@@ -60,4 +61,9 @@ s64 nsToCycles(u64 ns) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ);
|
||||
return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first;
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Core::Timing {
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
constexpr u64 CNTFREQ = 19200000; // Value from fusee.
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
@@ -61,4 +62,6 @@ inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks);
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -27,8 +27,7 @@ void CpuCoreManager::Initialize(System& system) {
|
||||
exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size());
|
||||
|
||||
for (std::size_t index = 0; index < cores.size(); ++index) {
|
||||
cores[index] =
|
||||
std::make_unique<Cpu>(system.CoreTiming(), *exclusive_monitor, *barrier, index);
|
||||
cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
|
||||
}
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
|
||||
@@ -398,7 +398,8 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
std::ifstream file(filename);
|
||||
std::ifstream file;
|
||||
OpenFStream(file, filename, std::ios_base::in);
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
|
||||
492
src/core/file_sys/cheat_engine.cpp
Normal file
492
src/core/file_sys/cheat_engine.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
|
||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
|
||||
|
||||
u64 Cheat::Address() const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u64));
|
||||
return Common::swap64(out) & 0xFFFFFFFFFF;
|
||||
}
|
||||
|
||||
u64 Cheat::ValueWidth(u64 offset) const {
|
||||
return Value(offset, width);
|
||||
}
|
||||
|
||||
u64 Cheat::Value(u64 offset, u64 width) const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data() + offset, sizeof(u64));
|
||||
out = Common::swap64(out);
|
||||
if (width == 8)
|
||||
return out;
|
||||
return out & ((1ull << (width * CHAR_BIT)) - 1);
|
||||
}
|
||||
|
||||
u32 Cheat::KeypadValue() const {
|
||||
u32 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u32));
|
||||
return Common::swap32(out) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
|
||||
VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
|
||||
this->main_region_begin = main_begin;
|
||||
this->main_region_end = main_end;
|
||||
this->heap_region_begin = heap_begin;
|
||||
this->heap_region_end = heap_end;
|
||||
this->writer = writer;
|
||||
this->reader = reader;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
|
||||
|
||||
void CheatList::Execute() {
|
||||
MICROPROFILE_SCOPE(Cheat_Engine);
|
||||
|
||||
std::fill(scratch.begin(), scratch.end(), 0);
|
||||
in_standard = false;
|
||||
for (std::size_t i = 0; i < master_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(master_list[i].second);
|
||||
}
|
||||
|
||||
in_standard = true;
|
||||
for (std::size_t i = 0; i < standard_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(standard_list[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
|
||||
: master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
|
||||
|
||||
bool CheatList::EvaluateConditional(const Cheat& cheat) const {
|
||||
using ComparisonFunction = bool (*)(u64, u64);
|
||||
constexpr std::array<ComparisonFunction, 6> comparison_functions{
|
||||
[](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
|
||||
[](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
|
||||
[](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
|
||||
};
|
||||
|
||||
if (cheat.type == CodeType::ConditionalInput) {
|
||||
const auto applet_resource =
|
||||
system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
|
||||
if (applet_resource == nullptr) {
|
||||
LOG_WARNING(
|
||||
Common_Filesystem,
|
||||
"Attempted to evaluate input conditional, but applet resource is not initialized!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto press_state =
|
||||
applet_resource
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
|
||||
.GetAndResetPressState();
|
||||
return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
|
||||
}
|
||||
|
||||
ASSERT(cheat.type == CodeType::Conditional);
|
||||
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
|
||||
auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
|
||||
const auto addr = cheat.Address() + offset;
|
||||
|
||||
return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::ProcessBlockPairs(const Block& block) {
|
||||
block_pairs.clear();
|
||||
|
||||
u64 scope = 0;
|
||||
std::map<u64, u64> pairs;
|
||||
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
const auto& cheat = block[i];
|
||||
|
||||
switch (cheat.type) {
|
||||
case CodeType::Conditional:
|
||||
case CodeType::ConditionalInput:
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
break;
|
||||
case CodeType::EndConditional: {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
break;
|
||||
}
|
||||
case CodeType::Loop: {
|
||||
if (cheat.end_of_loop) {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
} else {
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheatList::WriteImmediate(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = cheat.Address() + offset + register_3;
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
|
||||
cheat.Value(8, cheat.width));
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditional(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
void CheatList::EndConditional(const Cheat& cheat) {
|
||||
LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
|
||||
}
|
||||
|
||||
void CheatList::Loop(const Cheat& cheat) {
|
||||
if (cheat.end_of_loop.Value())
|
||||
ASSERT(!cheat.end_of_loop.Value());
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
ASSERT(iter->first < iter->second);
|
||||
|
||||
const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
|
||||
for (s32 i = initial_value; i >= 0; --i) {
|
||||
register_3 = static_cast<u64>(i);
|
||||
for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
|
||||
current_index = c;
|
||||
ExecuteSingleCheat(
|
||||
(in_standard ? standard_list : master_list)[current_block].second[c]);
|
||||
}
|
||||
}
|
||||
|
||||
current_index = iter->second;
|
||||
}
|
||||
|
||||
void CheatList::LoadImmediate(const Cheat& cheat) {
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
|
||||
cheat.Value(4, 8));
|
||||
register_3 = cheat.Value(4, 8);
|
||||
}
|
||||
|
||||
void CheatList::LoadIndexed(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
|
||||
LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
|
||||
cheat.register_3, addr);
|
||||
register_3 = reader(cheat.width, SanitizeAddress(addr));
|
||||
}
|
||||
|
||||
void CheatList::StoreIndexed(const Cheat& cheat) {
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr =
|
||||
register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
|
||||
cheat.Value(4, cheat.width), addr);
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::RegisterArithmetic(const Cheat& cheat) {
|
||||
using ArithmeticFunction = u64 (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
|
||||
[](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
|
||||
[](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
|
||||
[](u64 a, u64 b) { return a >> b; },
|
||||
};
|
||||
|
||||
using ArithmeticOverflowCheck = bool (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
|
||||
};
|
||||
|
||||
static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
|
||||
"Missing or have extra arithmetic overflow checks compared to functions!");
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
|
||||
auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
auto* overflow_function =
|
||||
arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
|
||||
cheat.register_3, cheat.ValueWidth(4));
|
||||
|
||||
if (overflow_function(register_3, cheat.ValueWidth(4))) {
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"overflow will occur when performing arithmetic operation={:02X} with operands "
|
||||
"a={:016X}, b={:016X}!",
|
||||
static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
register_3 = function(register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditionalInput(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat))
|
||||
return;
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
VAddr CheatList::SanitizeAddress(VAddr in) const {
|
||||
if ((in < main_region_begin || in >= main_region_end) &&
|
||||
(in < heap_region_begin || in >= heap_region_end)) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return 0; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
|
||||
using CheatOperationFunction = void (CheatList::*)(const Cheat&);
|
||||
constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
|
||||
&CheatList::WriteImmediate, &CheatList::BeginConditional,
|
||||
&CheatList::EndConditional, &CheatList::Loop,
|
||||
&CheatList::LoadImmediate, &CheatList::LoadIndexed,
|
||||
&CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
|
||||
&CheatList::BeginConditionalInput,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u8>(cheat.type.Value());
|
||||
ASSERT(index < sizeof(cheat_operation_functions));
|
||||
const auto op = cheat_operation_functions[index];
|
||||
(this->*op)(cheat);
|
||||
}
|
||||
|
||||
void CheatList::ExecuteBlock(const Block& block) {
|
||||
encountered_loops.clear();
|
||||
|
||||
ProcessBlockPairs(block);
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
current_index = i;
|
||||
ExecuteSingleCheat(block[i]);
|
||||
i = current_index;
|
||||
}
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const {
|
||||
return {system, std::move(master), std::move(standard)};
|
||||
}
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
|
||||
std::stringstream ss;
|
||||
ss.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(ss, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
CheatList::ProgramSegment master_list;
|
||||
CheatList::ProgramSegment standard_list;
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
const auto master = line[0] == '{';
|
||||
const auto begin = master ? line.find('{') : line.find('[');
|
||||
const auto end = master ? line.rfind('}') : line.rfind(']');
|
||||
|
||||
ASSERT(begin != std::string::npos && end != std::string::npos);
|
||||
|
||||
const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
|
||||
CheatList::Block block{};
|
||||
|
||||
while (i < lines.size() - 1) {
|
||||
line = lines[++i];
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.size() < 8)
|
||||
continue;
|
||||
|
||||
Cheat out{};
|
||||
out.raw = ParseSingleLineCheat(line);
|
||||
block.push_back(out);
|
||||
}
|
||||
|
||||
(master ? master_list : standard_list).emplace_back(patch_name, block);
|
||||
}
|
||||
}
|
||||
|
||||
return MakeCheatList(system, master_list, standard_list);
|
||||
}
|
||||
|
||||
std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
|
||||
std::array<u8, 16> out{};
|
||||
|
||||
if (line.size() < 8)
|
||||
return out;
|
||||
|
||||
const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
|
||||
std::memcpy(out.data(), word1.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 17 || line[8] != ' ')
|
||||
return out;
|
||||
|
||||
const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
|
||||
std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 26 || line[17] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
|
||||
type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
|
||||
std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
|
||||
std::memset(out.data() + 4, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
|
||||
std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 35 || line[26] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
|
||||
std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
|
||||
std::memset(out.data() + 8, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
|
||||
std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
u64 MemoryReadImpl(u32 width, VAddr addr) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return Memory::Read8(addr);
|
||||
case 2:
|
||||
return Memory::Read16(addr);
|
||||
case 4:
|
||||
return Memory::Read32(addr);
|
||||
case 8:
|
||||
return Memory::Read64(addr);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
Memory::Write8(addr, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
Memory::Write16(addr, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
Memory::Write32(addr, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
Memory::Write64(addr, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end)
|
||||
: cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
|
||||
event = core_timing.RegisterEvent(
|
||||
"CheatEngine::FrameCallback::" + build_id,
|
||||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
|
||||
|
||||
const auto& vm_manager = system.CurrentProcess()->VMManager();
|
||||
for (auto& list : this->cheats) {
|
||||
list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
|
||||
code_region_end, vm_manager.GetHeapRegionEndAddress(),
|
||||
&MemoryWriteImpl, &MemoryReadImpl);
|
||||
}
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
core_timing.UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
|
||||
for (auto& list : cheats) {
|
||||
list.Execute();
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
234
src/core/file_sys/cheat_engine.h
Normal file
234
src/core/file_sys/cheat_engine.h
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class CodeType : u32 {
|
||||
// 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Writes a T sized value Y to the address A added to the value of register R in memory domain M
|
||||
WriteImmediate = 0,
|
||||
|
||||
// 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Compares the T sized value Y to the value at address A in memory domain M using the
|
||||
// conditional function C. If success, continues execution. If failure, jumps to the matching
|
||||
// EndConditional statement.
|
||||
Conditional = 1,
|
||||
|
||||
// 20000000
|
||||
// Terminates a Conditional or ConditionalInput block.
|
||||
EndConditional = 2,
|
||||
|
||||
// 300R0000 VVVVVVVV
|
||||
// Starts looping V times, storing the current count in register R.
|
||||
// Loop block is terminated with a matching 310R0000.
|
||||
Loop = 3,
|
||||
|
||||
// 400R0000 VVVVVVVV VVVVVVVV
|
||||
// Sets the value of register R to the value V.
|
||||
LoadImmediate = 4,
|
||||
|
||||
// 5TMRI0AA AAAAAAAA
|
||||
// Sets the value of register R to the value of width T at address A in memory domain M, with
|
||||
// the current value of R added to the address if I == 1.
|
||||
LoadIndexed = 5,
|
||||
|
||||
// 6T0RIFG0 VVVVVVVV VVVVVVVV
|
||||
// Writes the value V of width T to the memory address stored in register R. Adds the value of
|
||||
// register G to the final calculation if F is nonzero. Increments the value of register R by T
|
||||
// after operation if I is nonzero.
|
||||
StoreIndexed = 6,
|
||||
|
||||
// 7T0RA000 VVVVVVVV
|
||||
// Performs the arithmetic operation A on the value in register R and the value V of width T,
|
||||
// storing the result in register R.
|
||||
RegisterArithmetic = 7,
|
||||
|
||||
// 8KKKKKKK
|
||||
// Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
|
||||
// execution continues. If none are, execution skips to the next EndConditional command.
|
||||
ConditionalInput = 8,
|
||||
};
|
||||
|
||||
enum class MemoryType : u32 {
|
||||
// Addressed relative to start of main NSO
|
||||
MainNSO = 0,
|
||||
|
||||
// Addressed relative to start of heap
|
||||
Heap = 1,
|
||||
};
|
||||
|
||||
enum class ArithmeticOp : u32 {
|
||||
Add = 0,
|
||||
Sub = 1,
|
||||
Mult = 2,
|
||||
LShift = 3,
|
||||
RShift = 4,
|
||||
};
|
||||
|
||||
enum class ComparisonOp : u32 {
|
||||
GreaterThan = 1,
|
||||
GreaterThanEqual = 2,
|
||||
LessThan = 3,
|
||||
LessThanEqual = 4,
|
||||
Equal = 5,
|
||||
Inequal = 6,
|
||||
};
|
||||
|
||||
union Cheat {
|
||||
std::array<u8, 16> raw;
|
||||
|
||||
BitField<4, 4, CodeType> type;
|
||||
BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
|
||||
BitField<0, 4, u32> end_of_loop;
|
||||
BitField<12, 4, MemoryType> memory_type;
|
||||
BitField<8, 4, u32> register_3;
|
||||
BitField<8, 4, ComparisonOp> comparison_op;
|
||||
BitField<20, 4, u32> load_from_register;
|
||||
BitField<20, 4, u32> increment_register;
|
||||
BitField<20, 4, ArithmeticOp> arithmetic_op;
|
||||
BitField<16, 4, u32> add_additional_register;
|
||||
BitField<28, 4, u32> register_6;
|
||||
|
||||
u64 Address() const;
|
||||
u64 ValueWidth(u64 offset) const;
|
||||
u64 Value(u64 offset, u64 width) const;
|
||||
u32 KeypadValue() const;
|
||||
};
|
||||
|
||||
class CheatParser;
|
||||
|
||||
// Represents a full collection of cheats for a game. The Execute function should be called every
|
||||
// interval that all cheats should be executed. Clients should not directly instantiate this class
|
||||
// (hence private constructor), they should instead receive an instance from CheatParser, which
|
||||
// guarantees the list is always in an acceptable state.
|
||||
class CheatList {
|
||||
public:
|
||||
friend class CheatParser;
|
||||
|
||||
using Block = std::vector<Cheat>;
|
||||
using ProgramSegment = std::vector<std::pair<std::string, Block>>;
|
||||
|
||||
// (width in bytes, address, value)
|
||||
using MemoryWriter = void (*)(u32, VAddr, u64);
|
||||
// (width in bytes, address) -> value
|
||||
using MemoryReader = u64 (*)(u32, VAddr);
|
||||
|
||||
void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
|
||||
MemoryWriter writer, MemoryReader reader);
|
||||
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
|
||||
|
||||
void ProcessBlockPairs(const Block& block);
|
||||
void ExecuteSingleCheat(const Cheat& cheat);
|
||||
|
||||
void ExecuteBlock(const Block& block);
|
||||
|
||||
bool EvaluateConditional(const Cheat& cheat) const;
|
||||
|
||||
// Individual cheat operations
|
||||
void WriteImmediate(const Cheat& cheat);
|
||||
void BeginConditional(const Cheat& cheat);
|
||||
void EndConditional(const Cheat& cheat);
|
||||
void Loop(const Cheat& cheat);
|
||||
void LoadImmediate(const Cheat& cheat);
|
||||
void LoadIndexed(const Cheat& cheat);
|
||||
void StoreIndexed(const Cheat& cheat);
|
||||
void RegisterArithmetic(const Cheat& cheat);
|
||||
void BeginConditionalInput(const Cheat& cheat);
|
||||
|
||||
VAddr SanitizeAddress(VAddr in) const;
|
||||
|
||||
// Master Codes are defined as codes that cannot be disabled and are run prior to all
|
||||
// others.
|
||||
ProgramSegment master_list;
|
||||
// All other codes
|
||||
ProgramSegment standard_list;
|
||||
|
||||
bool in_standard = false;
|
||||
|
||||
// 16 (0x0-0xF) scratch registers that can be used by cheats
|
||||
std::array<u64, 16> scratch{};
|
||||
|
||||
MemoryWriter writer = nullptr;
|
||||
MemoryReader reader = nullptr;
|
||||
|
||||
u64 main_region_begin{};
|
||||
u64 heap_region_begin{};
|
||||
u64 main_region_end{};
|
||||
u64 heap_region_end{};
|
||||
|
||||
u64 current_block{};
|
||||
// The current index of the cheat within the current Block
|
||||
u64 current_index{};
|
||||
|
||||
// The 'stack' of the program. When a conditional or loop statement is encountered, its index is
|
||||
// pushed onto this queue. When a end block is encountered, the condition is checked.
|
||||
std::map<u64, u64> block_pairs;
|
||||
|
||||
std::set<u64> encountered_loops;
|
||||
|
||||
const Core::System* system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
|
||||
|
||||
protected:
|
||||
CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
|
||||
|
||||
private:
|
||||
std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
~CheatEngine();
|
||||
|
||||
private:
|
||||
void FrameCallback(u64 userdata, s64 cycles_late);
|
||||
|
||||
std::vector<CheatList> cheats;
|
||||
|
||||
Core::Timing::EventType* event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -24,13 +24,26 @@ namespace FileSys {
|
||||
|
||||
union NCASectionHeader;
|
||||
|
||||
/// Describes the type of content within an NCA archive.
|
||||
enum class NCAContentType : u8 {
|
||||
/// Executable-related data
|
||||
Program = 0,
|
||||
|
||||
/// Metadata.
|
||||
Meta = 1,
|
||||
|
||||
/// Access control data.
|
||||
Control = 2,
|
||||
|
||||
/// Information related to the game manual
|
||||
/// e.g. Legal information, etc.
|
||||
Manual = 3,
|
||||
|
||||
/// System data.
|
||||
Data = 4,
|
||||
Data_Unknown5 = 5, ///< Seems to be used on some system archives
|
||||
|
||||
/// Data that can be accessed by applications.
|
||||
PublicData = 5,
|
||||
};
|
||||
|
||||
enum class NCASectionCryptoType : u8 {
|
||||
|
||||
@@ -67,7 +67,7 @@ std::string NACP::GetDeveloperName(Language language) const {
|
||||
}
|
||||
|
||||
u64 NACP::GetTitleId() const {
|
||||
return raw.title_id;
|
||||
return raw.save_data_owner_id;
|
||||
}
|
||||
|
||||
u64 NACP::GetDLCBaseTitleId() const {
|
||||
@@ -80,11 +80,11 @@ std::string NACP::GetVersionString() const {
|
||||
}
|
||||
|
||||
u64 NACP::GetDefaultNormalSaveSize() const {
|
||||
return raw.normal_save_data_size;
|
||||
return raw.user_account_save_data_size;
|
||||
}
|
||||
|
||||
u64 NACP::GetDefaultJournalSaveSize() const {
|
||||
return raw.journal_sava_data_size;
|
||||
return raw.user_account_save_data_journal_size;
|
||||
}
|
||||
|
||||
std::vector<u8> NACP::GetRawBytes() const {
|
||||
|
||||
@@ -38,23 +38,35 @@ struct RawNACP {
|
||||
u8 video_capture_mode;
|
||||
bool data_loss_confirmation;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u64_le title_id;
|
||||
u64_le presence_group_id;
|
||||
std::array<u8, 0x20> rating_age;
|
||||
std::array<char, 0x10> version_string;
|
||||
u64_le dlc_base_title_id;
|
||||
u64_le title_id_2;
|
||||
u64_le normal_save_data_size;
|
||||
u64_le journal_sava_data_size;
|
||||
INSERT_PADDING_BYTES(0x18);
|
||||
u64_le product_code;
|
||||
u64_le save_data_owner_id;
|
||||
u64_le user_account_save_data_size;
|
||||
u64_le user_account_save_data_journal_size;
|
||||
u64_le device_save_data_size;
|
||||
u64_le device_save_data_journal_size;
|
||||
u64_le bcat_delivery_cache_storage_size;
|
||||
char application_error_code_category[8];
|
||||
std::array<u64_le, 0x8> local_communication;
|
||||
u8 logo_type;
|
||||
u8 logo_handling;
|
||||
bool runtime_add_on_content_install;
|
||||
INSERT_PADDING_BYTES(5);
|
||||
u64_le title_id_update;
|
||||
std::array<u8, 0x40> bcat_passphrase;
|
||||
INSERT_PADDING_BYTES(0xEC0);
|
||||
u64_le seed_for_pseudo_device_id;
|
||||
std::array<u8, 0x41> bcat_passphrase;
|
||||
INSERT_PADDING_BYTES(7);
|
||||
u64_le user_account_save_data_max_size;
|
||||
u64_le user_account_save_data_max_journal_size;
|
||||
u64_le device_save_data_max_size;
|
||||
u64_le device_save_data_max_journal_size;
|
||||
u64_le temporary_storage_size;
|
||||
u64_le cache_storage_size;
|
||||
u64_le cache_storage_journal_size;
|
||||
u64_le cache_storage_data_and_journal_max_size;
|
||||
u64_le cache_storage_max_index;
|
||||
INSERT_PADDING_BYTES(0xE70);
|
||||
};
|
||||
static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size.");
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace FileSys {
|
||||
constexpr ResultCode ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
|
||||
constexpr ResultCode ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
|
||||
constexpr ResultCode ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
|
||||
constexpr ResultCode ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005};
|
||||
constexpr ResultCode ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223};
|
||||
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
|
||||
constexpr ResultCode ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
|
||||
constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/fsmitm_romfsbuild.h"
|
||||
@@ -97,7 +98,8 @@ struct RomFSBuildFileContext {
|
||||
VirtualFile source;
|
||||
};
|
||||
|
||||
static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::size_t path_len) {
|
||||
static u32 romfs_calc_path_hash(u32 parent, std::string_view path, u32 start,
|
||||
std::size_t path_len) {
|
||||
u32 hash = parent ^ 123456789;
|
||||
for (u32 i = 0; i < path_len; i++) {
|
||||
hash = (hash >> 5) | (hash << 27);
|
||||
|
||||
@@ -10,14 +10,6 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
bool operator>=(TitleType lhs, TitleType rhs) {
|
||||
return static_cast<std::size_t>(lhs) >= static_cast<std::size_t>(rhs);
|
||||
}
|
||||
|
||||
bool operator<=(TitleType lhs, TitleType rhs) {
|
||||
return static_cast<std::size_t>(lhs) <= static_cast<std::size_t>(rhs);
|
||||
}
|
||||
|
||||
CNMT::CNMT(VirtualFile file) {
|
||||
if (file->ReadObject(&header) != sizeof(CNMTHeader))
|
||||
return;
|
||||
|
||||
@@ -29,9 +29,6 @@ enum class TitleType : u8 {
|
||||
DeltaTitle = 0x83,
|
||||
};
|
||||
|
||||
bool operator>=(TitleType lhs, TitleType rhs);
|
||||
bool operator<=(TitleType lhs, TitleType rhs);
|
||||
|
||||
enum class ContentRecordType : u8 {
|
||||
Meta = 0,
|
||||
Program = 1,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -31,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
|
||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
||||
};
|
||||
|
||||
struct NSOBuildHeader {
|
||||
u32_le magic;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
std::array<u8, 0x20> build_id;
|
||||
INSERT_PADDING_BYTES(0xA0);
|
||||
};
|
||||
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
|
||||
|
||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
bytes[0] = version % SINGLE_BYTE_MODULUS;
|
||||
@@ -162,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
if (nso.size() < 0x100)
|
||||
if (nso.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
NSOBuildHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
|
||||
Loader::NSOHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(header));
|
||||
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
const auto build_id_raw = Common::HexArrayToString(header.build_id);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
@@ -212,9 +208,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() < 0x100)
|
||||
if (out.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
|
||||
}
|
||||
|
||||
std::memcpy(out.data(), &header, sizeof(header));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -232,6 +230,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
|
||||
const std::array<u8, 0x20>& build_id_,
|
||||
const VirtualDir& base_path, bool upper) {
|
||||
const auto build_id_raw = Common::HexArrayToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
|
||||
if (file == nullptr) {
|
||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextCheatParser parser;
|
||||
return parser.Parse(system, data);
|
||||
}
|
||||
|
||||
std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 32>& build_id_) const {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<CheatList> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto cheats_dir = subdir->GetSubdirectory("cheats");
|
||||
if (cheats_dir != nullptr) {
|
||||
auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
|
||||
if (res.has_value()) {
|
||||
out.push_back(std::move(*res));
|
||||
continue;
|
||||
}
|
||||
|
||||
res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
|
||||
if (res.has_value())
|
||||
out.push_back(std::move(*res));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
|
||||
@@ -403,6 +452,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
|
||||
@@ -8,9 +8,14 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
@@ -45,6 +50,10 @@ public:
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
std::vector<CheatList> CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
// - LayeredFS
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
@@ -17,28 +16,30 @@ ProgramMetadata::ProgramMetadata() = default;
|
||||
ProgramMetadata::~ProgramMetadata() = default;
|
||||
|
||||
Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
||||
std::size_t total_size = static_cast<std::size_t>(file->GetSize());
|
||||
if (total_size < sizeof(Header))
|
||||
const std::size_t total_size = file->GetSize();
|
||||
if (total_size < sizeof(Header)) {
|
||||
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())
|
||||
if (sizeof(Header) != file->ReadObject(&npdm_header)) {
|
||||
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())
|
||||
if (sizeof(AcidHeader) != file->ReadObject(&acid_header, npdm_header.acid_offset)) {
|
||||
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))
|
||||
if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadACIHeader;
|
||||
}
|
||||
|
||||
if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset))
|
||||
if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
|
||||
}
|
||||
|
||||
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
|
||||
aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32));
|
||||
const u64 read_size = aci_header.kac_size;
|
||||
|
||||
@@ -58,7 +58,6 @@ public:
|
||||
void Print() const;
|
||||
|
||||
private:
|
||||
// TODO(DarkLordZach): BitField is not trivially copyable.
|
||||
struct Header {
|
||||
std::array<char, 4> magic;
|
||||
std::array<u8, 8> reserved;
|
||||
@@ -85,7 +84,6 @@ private:
|
||||
|
||||
static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong");
|
||||
|
||||
// TODO(DarkLordZach): BitField is not trivially copyable.
|
||||
struct AcidHeader {
|
||||
std::array<u8, 0x100> signature;
|
||||
std::array<u8, 0x100> nca_modulus;
|
||||
|
||||
@@ -94,7 +94,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
case NCAContentType::Control:
|
||||
return ContentRecordType::Control;
|
||||
case NCAContentType::Data:
|
||||
case NCAContentType::Data_Unknown5:
|
||||
case NCAContentType::PublicData:
|
||||
return ContentRecordType::Data;
|
||||
case NCAContentType::Manual:
|
||||
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
|
||||
|
||||
@@ -16,8 +16,10 @@ namespace FileSys {
|
||||
constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
|
||||
|
||||
std::string SaveDataDescriptor::DebugInfo() const {
|
||||
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}]",
|
||||
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
|
||||
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, "
|
||||
"rank={}, index={}]",
|
||||
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
|
||||
static_cast<u8>(rank), index);
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
|
||||
@@ -28,7 +30,7 @@ SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescriptor meta) {
|
||||
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) {
|
||||
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
|
||||
if (meta.zero_1 != 0) {
|
||||
LOG_WARNING(Service_FS,
|
||||
|
||||
@@ -32,12 +32,19 @@ enum class SaveDataType : u8 {
|
||||
CacheStorage = 5,
|
||||
};
|
||||
|
||||
enum class SaveDataRank : u8 {
|
||||
Primary,
|
||||
Secondary,
|
||||
};
|
||||
|
||||
struct SaveDataDescriptor {
|
||||
u64_le title_id;
|
||||
u128 user_id;
|
||||
u64_le save_id;
|
||||
SaveDataType type;
|
||||
INSERT_PADDING_BYTES(7);
|
||||
SaveDataRank rank;
|
||||
u16_le index;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u64_le zero_1;
|
||||
u64_le zero_2;
|
||||
u64_le zero_3;
|
||||
@@ -57,7 +64,7 @@ public:
|
||||
explicit SaveDataFactory(VirtualDir dir);
|
||||
~SaveDataFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
|
||||
ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta);
|
||||
|
||||
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/system_archive/ng_word.h"
|
||||
#include "core/file_sys/system_archive/system_archive.h"
|
||||
#include "core/file_sys/system_archive/system_version.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
@@ -30,7 +31,7 @@ constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHI
|
||||
{0x0100000000000806, "NgWord", &NgWord1},
|
||||
{0x0100000000000807, "SsidList", nullptr},
|
||||
{0x0100000000000808, "Dictionary", nullptr},
|
||||
{0x0100000000000809, "SystemVersion", nullptr},
|
||||
{0x0100000000000809, "SystemVersion", &SystemVersion},
|
||||
{0x010000000000080A, "AvatarImage", nullptr},
|
||||
{0x010000000000080B, "LocalNews", nullptr},
|
||||
{0x010000000000080C, "Eula", nullptr},
|
||||
|
||||
52
src/core/file_sys/system_archive/system_version.cpp
Normal file
52
src/core/file_sys/system_archive/system_version.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/file_sys/system_archive/system_version.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
namespace SystemVersionData {
|
||||
|
||||
// This section should reflect the best system version to describe yuzu's HLE api.
|
||||
// TODO(DarkLordZach): Update when HLE gets better.
|
||||
|
||||
constexpr u8 VERSION_MAJOR = 5;
|
||||
constexpr u8 VERSION_MINOR = 1;
|
||||
constexpr u8 VERSION_MICRO = 0;
|
||||
|
||||
constexpr u8 REVISION_MAJOR = 3;
|
||||
constexpr u8 REVISION_MINOR = 0;
|
||||
|
||||
constexpr char PLATFORM_STRING[] = "NX";
|
||||
constexpr char VERSION_HASH[] = "23f9df53e25709d756e0c76effcb2473bd3447dd";
|
||||
constexpr char DISPLAY_VERSION[] = "5.1.0";
|
||||
constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 5.1.0-3.0";
|
||||
|
||||
} // namespace SystemVersionData
|
||||
|
||||
std::string GetLongDisplayVersion() {
|
||||
return SystemVersionData::DISPLAY_TITLE;
|
||||
}
|
||||
|
||||
VirtualDir SystemVersion() {
|
||||
VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file");
|
||||
file->WriteObject(SystemVersionData::VERSION_MAJOR, 0);
|
||||
file->WriteObject(SystemVersionData::VERSION_MINOR, 1);
|
||||
file->WriteObject(SystemVersionData::VERSION_MICRO, 2);
|
||||
file->WriteObject(SystemVersionData::REVISION_MAJOR, 4);
|
||||
file->WriteObject(SystemVersionData::REVISION_MINOR, 5);
|
||||
file->WriteArray(SystemVersionData::PLATFORM_STRING,
|
||||
std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8);
|
||||
file->WriteArray(SystemVersionData::VERSION_HASH,
|
||||
std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28);
|
||||
file->WriteArray(SystemVersionData::DISPLAY_VERSION,
|
||||
std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68);
|
||||
file->WriteArray(SystemVersionData::DISPLAY_TITLE,
|
||||
std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80);
|
||||
return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file},
|
||||
std::vector<VirtualDir>{}, "data");
|
||||
}
|
||||
|
||||
} // namespace FileSys::SystemArchive
|
||||
16
src/core/file_sys/system_archive/system_version.h
Normal file
16
src/core/file_sys/system_archive/system_version.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
std::string GetLongDisplayVersion();
|
||||
|
||||
VirtualDir SystemVersion();
|
||||
|
||||
} // namespace FileSys::SystemArchive
|
||||
@@ -47,7 +47,7 @@ std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_
|
||||
if (offset + length > data.size())
|
||||
data.resize(offset + length);
|
||||
const auto write = std::min(length, data.size() - offset);
|
||||
std::memcpy(data.data(), data_, write);
|
||||
std::memcpy(data.data() + offset, data_, write);
|
||||
return write;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ private:
|
||||
explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {}
|
||||
std::tuple<float, float, bool> GetStatus() const override {
|
||||
if (auto state = touch_state.lock()) {
|
||||
std::lock_guard<std::mutex> guard(state->mutex);
|
||||
std::lock_guard guard{state->mutex};
|
||||
return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed);
|
||||
}
|
||||
return std::make_tuple(0.0f, 0.0f, false);
|
||||
@@ -67,7 +67,7 @@ static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigne
|
||||
framebuffer_x >= layout.screen.left && framebuffer_x < layout.screen.right);
|
||||
}
|
||||
|
||||
std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) {
|
||||
std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) const {
|
||||
new_x = std::max(new_x, framebuffer_layout.screen.left);
|
||||
new_x = std::min(new_x, framebuffer_layout.screen.right - 1);
|
||||
|
||||
@@ -81,7 +81,7 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> guard(touch_state->mutex);
|
||||
std::lock_guard guard{touch_state->mutex};
|
||||
touch_state->touch_x = static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
|
||||
(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
|
||||
touch_state->touch_y = static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
|
||||
@@ -91,7 +91,7 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||
}
|
||||
|
||||
void EmuWindow::TouchReleased() {
|
||||
std::lock_guard<std::mutex> guard(touch_state->mutex);
|
||||
std::lock_guard guard{touch_state->mutex};
|
||||
touch_state->touch_pressed = false;
|
||||
touch_state->touch_x = 0;
|
||||
touch_state->touch_y = 0;
|
||||
|
||||
@@ -166,7 +166,7 @@ private:
|
||||
/**
|
||||
* Clip the provided coordinates to be inside the touchscreen area.
|
||||
*/
|
||||
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
|
||||
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y) const;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Layout {
|
||||
|
||||
// Finds the largest size subrectangle contained in window area that is confined to the aspect ratio
|
||||
template <class T>
|
||||
static MathUtil::Rectangle<T> maxRectangle(MathUtil::Rectangle<T> window_area,
|
||||
float screen_aspect_ratio) {
|
||||
static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
|
||||
float screen_aspect_ratio) {
|
||||
float scale = std::min(static_cast<float>(window_area.GetWidth()),
|
||||
window_area.GetHeight() / screen_aspect_ratio);
|
||||
return MathUtil::Rectangle<T>{0, 0, static_cast<T>(std::round(scale)),
|
||||
static_cast<T>(std::round(scale * screen_aspect_ratio))};
|
||||
return Common::Rectangle<T>{0, 0, static_cast<T>(std::round(scale)),
|
||||
static_cast<T>(std::round(scale * screen_aspect_ratio))};
|
||||
}
|
||||
|
||||
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
|
||||
@@ -29,8 +29,8 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
|
||||
|
||||
const float emulation_aspect_ratio{static_cast<float>(ScreenUndocked::Height) /
|
||||
ScreenUndocked::Width};
|
||||
MathUtil::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
MathUtil::Rectangle<unsigned> screen = maxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
Common::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<unsigned> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
|
||||
float window_aspect_ratio = static_cast<float>(height) / width;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ struct FramebufferLayout {
|
||||
unsigned width{ScreenUndocked::Width};
|
||||
unsigned height{ScreenUndocked::Height};
|
||||
|
||||
MathUtil::Rectangle<unsigned> screen;
|
||||
Common::Rectangle<unsigned> screen;
|
||||
|
||||
/**
|
||||
* Returns the ration of pixel size of the screen, compared to the native size of the undocked
|
||||
|
||||
@@ -124,7 +124,7 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>;
|
||||
* Orientation is determined by right-hand rule.
|
||||
* Units: deg/sec
|
||||
*/
|
||||
using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>;
|
||||
using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>;
|
||||
|
||||
/**
|
||||
* A touch device is an input device that returns a tuple of two floats and a bool. The floats are
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
@@ -39,10 +39,10 @@ struct CommandHeader {
|
||||
union {
|
||||
u32_le raw_low;
|
||||
BitField<0, 16, CommandType> type;
|
||||
BitField<16, 4, u32_le> num_buf_x_descriptors;
|
||||
BitField<20, 4, u32_le> num_buf_a_descriptors;
|
||||
BitField<24, 4, u32_le> num_buf_b_descriptors;
|
||||
BitField<28, 4, u32_le> num_buf_w_descriptors;
|
||||
BitField<16, 4, u32> num_buf_x_descriptors;
|
||||
BitField<20, 4, u32> num_buf_a_descriptors;
|
||||
BitField<24, 4, u32> num_buf_b_descriptors;
|
||||
BitField<28, 4, u32> num_buf_w_descriptors;
|
||||
};
|
||||
|
||||
enum class BufferDescriptorCFlag : u32 {
|
||||
@@ -53,28 +53,28 @@ struct CommandHeader {
|
||||
|
||||
union {
|
||||
u32_le raw_high;
|
||||
BitField<0, 10, u32_le> data_size;
|
||||
BitField<0, 10, u32> data_size;
|
||||
BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags;
|
||||
BitField<31, 1, u32_le> enable_handle_descriptor;
|
||||
BitField<31, 1, u32> enable_handle_descriptor;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect");
|
||||
|
||||
union HandleDescriptorHeader {
|
||||
u32_le raw_high;
|
||||
BitField<0, 1, u32_le> send_current_pid;
|
||||
BitField<1, 4, u32_le> num_handles_to_copy;
|
||||
BitField<5, 4, u32_le> num_handles_to_move;
|
||||
BitField<0, 1, u32> send_current_pid;
|
||||
BitField<1, 4, u32> num_handles_to_copy;
|
||||
BitField<5, 4, u32> num_handles_to_move;
|
||||
};
|
||||
static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect");
|
||||
|
||||
struct BufferDescriptorX {
|
||||
union {
|
||||
BitField<0, 6, u32_le> counter_bits_0_5;
|
||||
BitField<6, 3, u32_le> address_bits_36_38;
|
||||
BitField<9, 3, u32_le> counter_bits_9_11;
|
||||
BitField<12, 4, u32_le> address_bits_32_35;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<0, 6, u32> counter_bits_0_5;
|
||||
BitField<6, 3, u32> address_bits_36_38;
|
||||
BitField<9, 3, u32> counter_bits_9_11;
|
||||
BitField<12, 4, u32> address_bits_32_35;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
|
||||
u32_le address_bits_0_31;
|
||||
@@ -103,10 +103,10 @@ struct BufferDescriptorABW {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
BitField<0, 2, u32_le> flags;
|
||||
BitField<2, 3, u32_le> address_bits_36_38;
|
||||
BitField<24, 4, u32_le> size_bits_32_35;
|
||||
BitField<28, 4, u32_le> address_bits_32_35;
|
||||
BitField<0, 2, u32> flags;
|
||||
BitField<2, 3, u32> address_bits_36_38;
|
||||
BitField<24, 4, u32> size_bits_32_35;
|
||||
BitField<28, 4, u32> address_bits_32_35;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -128,8 +128,8 @@ struct BufferDescriptorC {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
BitField<0, 16, u32_le> address_bits_32_47;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<0, 16, u32> address_bits_32_47;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -167,8 +167,8 @@ struct DomainMessageHeader {
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 8, CommandType> command;
|
||||
BitField<8, 8, u32_le> input_object_count;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<8, 8, u32> input_object_count;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
u32_le object_id;
|
||||
INSERT_PADDING_WORDS(2);
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301};
|
||||
|
||||
class RequestHelperBase {
|
||||
protected:
|
||||
Kernel::HLERequestContext* context = nullptr;
|
||||
@@ -271,6 +274,20 @@ inline void ResponseBuilder::Push(u64 value) {
|
||||
Push(static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(float value) {
|
||||
u32 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u32));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(double value) {
|
||||
u64 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u64));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(bool value) {
|
||||
Push(static_cast<u8>(value));
|
||||
@@ -350,7 +367,7 @@ public:
|
||||
template <class T>
|
||||
std::shared_ptr<T> PopIpcInterface() {
|
||||
ASSERT(context->Session()->IsDomain());
|
||||
ASSERT(context->GetDomainMessageHeader()->input_object_count > 0);
|
||||
ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
|
||||
return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
|
||||
}
|
||||
};
|
||||
@@ -362,6 +379,11 @@ inline u32 RequestParser::Pop() {
|
||||
return cmdbuf[index++];
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s32 RequestParser::Pop() {
|
||||
return static_cast<s32>(Pop<u32>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RequestParser::PopRaw(T& value) {
|
||||
std::memcpy(&value, cmdbuf + index, sizeof(T));
|
||||
@@ -392,11 +414,37 @@ inline u64 RequestParser::Pop() {
|
||||
return msw << 32 | lsw;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s8 RequestParser::Pop() {
|
||||
return static_cast<s8>(Pop<u8>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s16 RequestParser::Pop() {
|
||||
return static_cast<s16>(Pop<u16>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s64 RequestParser::Pop() {
|
||||
return static_cast<s64>(Pop<u64>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float RequestParser::Pop() {
|
||||
const u32 value = Pop<u32>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline double RequestParser::Pop() {
|
||||
const u64 value = Pop<u64>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool RequestParser::Pop() {
|
||||
return Pop<u8>() != 0;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -18,32 +19,171 @@
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
namespace AddressArbiter {
|
||||
namespace {
|
||||
// Wake up num_to_wake (or all) threads in a vector.
|
||||
void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
|
||||
// Only process up to 'target' threads, unless 'target' is <= 0, in which case process
|
||||
// them all.
|
||||
std::size_t last = waiting_threads.size();
|
||||
if (num_to_wake > 0) {
|
||||
last = std::min(last, static_cast<std::size_t>(num_to_wake));
|
||||
}
|
||||
|
||||
// Performs actual address waiting logic.
|
||||
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
// Signal the waiting threads.
|
||||
for (std::size_t i = 0; i < last; i++) {
|
||||
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->SetArbiterWaitAddress(0);
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
AddressArbiter::AddressArbiter(Core::System& system) : system{system} {}
|
||||
AddressArbiter::~AddressArbiter() = default;
|
||||
|
||||
ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 value,
|
||||
s32 num_to_wake) {
|
||||
switch (type) {
|
||||
case SignalType::Signal:
|
||||
return SignalToAddressOnly(address, num_to_wake);
|
||||
case SignalType::IncrementAndSignalIfEqual:
|
||||
return IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
case SignalType::ModifyByWaitingCountAndSignalIfEqual:
|
||||
return ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
|
||||
const std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
Memory::Write32(address, static_cast<u32>(value + 1));
|
||||
return SignalToAddressOnly(address, num_to_wake);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
// Get threads waiting on the address.
|
||||
const std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
// Determine the modified value depending on the waiting count.
|
||||
s32 updated_value;
|
||||
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;
|
||||
} else {
|
||||
updated_value = value;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
Memory::Write32(address, static_cast<u32>(updated_value));
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s32 value,
|
||||
s64 timeout_ns) {
|
||||
switch (type) {
|
||||
case ArbitrationType::WaitIfLessThan:
|
||||
return WaitForAddressIfLessThan(address, value, timeout_ns, false);
|
||||
case ArbitrationType::DecrementAndWaitIfLessThan:
|
||||
return WaitForAddressIfLessThan(address, value, timeout_ns, true);
|
||||
case ArbitrationType::WaitIfEqual:
|
||||
return WaitForAddressIfEqual(address, value, timeout_ns);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
|
||||
bool should_decrement) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
const s32 cur_value = static_cast<s32>(Memory::Read32(address));
|
||||
if (cur_value >= value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (should_decrement) {
|
||||
Memory::Write32(address, static_cast<u32>(cur_value - 1));
|
||||
}
|
||||
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddressImpl(address, timeout);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
// Only wait for the address if equal.
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddressImpl(address, timeout);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->SetArbiterWaitAddress(address);
|
||||
current_thread->SetStatus(ThreadStatus::WaitArb);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
current_thread->WakeAfterDelay(timeout);
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
system.CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
// Gets the threads waiting on an address.
|
||||
static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) {
|
||||
const auto RetrieveWaitingThreads = [](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
std::vector<SharedPtr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(VAddr address) const {
|
||||
const auto RetrieveWaitingThreads = [this](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = system.Scheduler(core_index);
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr)
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr) {
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -62,119 +202,4 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
|
||||
|
||||
return threads;
|
||||
}
|
||||
|
||||
// Wake up num_to_wake (or all) threads in a vector.
|
||||
static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
|
||||
// Only process up to 'target' threads, unless 'target' is <= 0, in which case process
|
||||
// them all.
|
||||
std::size_t last = waiting_threads.size();
|
||||
if (num_to_wake > 0)
|
||||
last = num_to_wake;
|
||||
|
||||
// Signal the waiting threads.
|
||||
for (std::size_t i = 0; i < last; i++) {
|
||||
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->SetArbiterWaitAddress(0);
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
|
||||
// Signals an address being waited on.
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake) {
|
||||
std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Signals an address being waited on and increments its value if equal to the value argument.
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) == value) {
|
||||
Memory::Write32(address, static_cast<u32>(value + 1));
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return SignalToAddress(address, num_to_wake);
|
||||
}
|
||||
|
||||
// Signals an address being waited on and modifies its value based on waiting thread count if equal
|
||||
// to the value argument.
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
// Get threads waiting on the address.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
// Determine the modified value depending on the waiting count.
|
||||
s32 updated_value;
|
||||
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;
|
||||
} else {
|
||||
updated_value = value;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) == value) {
|
||||
Memory::Write32(address, static_cast<u32>(updated_value));
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Waits on an address if the value passed is less than the argument value, optionally decrementing.
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
s32 cur_value = static_cast<s32>(Memory::Read32(address));
|
||||
if (cur_value < value) {
|
||||
if (should_decrement) {
|
||||
Memory::Write32(address, static_cast<u32>(cur_value - 1));
|
||||
}
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
}
|
||||
|
||||
// Waits on an address if the value passed is equal to the argument value.
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
// Only wait for the address if equal.
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
}
|
||||
} // namespace AddressArbiter
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -4,31 +4,77 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
namespace AddressArbiter {
|
||||
enum class ArbitrationType {
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2,
|
||||
class Thread;
|
||||
|
||||
class AddressArbiter {
|
||||
public:
|
||||
enum class ArbitrationType {
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2,
|
||||
};
|
||||
|
||||
enum class SignalType {
|
||||
Signal = 0,
|
||||
IncrementAndSignalIfEqual = 1,
|
||||
ModifyByWaitingCountAndSignalIfEqual = 2,
|
||||
};
|
||||
|
||||
explicit AddressArbiter(Core::System& system);
|
||||
~AddressArbiter();
|
||||
|
||||
AddressArbiter(const AddressArbiter&) = delete;
|
||||
AddressArbiter& operator=(const AddressArbiter&) = delete;
|
||||
|
||||
AddressArbiter(AddressArbiter&&) = default;
|
||||
AddressArbiter& operator=(AddressArbiter&&) = delete;
|
||||
|
||||
/// Signals an address being waited on with a particular signaling type.
|
||||
ResultCode SignalToAddress(VAddr address, SignalType type, s32 value, s32 num_to_wake);
|
||||
|
||||
/// Waits on an address with a particular arbitration type.
|
||||
ResultCode WaitForAddress(VAddr address, ArbitrationType type, s32 value, s64 timeout_ns);
|
||||
|
||||
private:
|
||||
/// Signals an address being waited on.
|
||||
ResultCode SignalToAddressOnly(VAddr address, s32 num_to_wake);
|
||||
|
||||
/// Signals an address being waited on and increments its value if equal to the value argument.
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
|
||||
/// Signals an address being waited on and modifies its value based on waiting thread count if
|
||||
/// equal to the value argument.
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake);
|
||||
|
||||
/// Waits on an address if the value passed is less than the argument value,
|
||||
/// optionally decrementing.
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
|
||||
bool should_decrement);
|
||||
|
||||
/// Waits on an address if the value passed is equal to the argument value.
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
|
||||
|
||||
// Waits on the given address with a timeout in nanoseconds
|
||||
ResultCode WaitForAddressImpl(VAddr address, s64 timeout);
|
||||
|
||||
// Gets the threads waiting on an address.
|
||||
std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
enum class SignalType {
|
||||
Signal = 0,
|
||||
IncrementAndSignalIfEqual = 1,
|
||||
ModifyByWaitingCountAndSignalIfEqual = 2,
|
||||
};
|
||||
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake);
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement);
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
|
||||
} // namespace AddressArbiter
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -33,10 +33,11 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
|
||||
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
|
||||
auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
|
||||
|
||||
if (server_port->hle_handler)
|
||||
server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
else
|
||||
server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
if (server_port->HasHLEHandler()) {
|
||||
server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
} else {
|
||||
server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
}
|
||||
|
||||
// Wake the threads waiting on the ServerPort
|
||||
server_port->WakeupAllWaitingThreads();
|
||||
|
||||
@@ -17,21 +17,11 @@ ClientSession::~ClientSession() {
|
||||
// This destructor will be called automatically when the last ClientSession handle is closed by
|
||||
// the emulated application.
|
||||
|
||||
// Local references to ServerSession and SessionRequestHandler are necessary to guarantee they
|
||||
// A local reference to the ServerSession is necessary to guarantee it
|
||||
// will be kept alive until after ClientDisconnected() returns.
|
||||
SharedPtr<ServerSession> server = parent->server;
|
||||
if (server) {
|
||||
std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler;
|
||||
if (hle_handler)
|
||||
hle_handler->ClientDisconnected(server);
|
||||
|
||||
// TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
|
||||
// their WaitSynchronization result to 0xC920181A.
|
||||
|
||||
// Clean up the list of client threads with pending requests, they are unneeded now that the
|
||||
// client endpoint is closed.
|
||||
server->pending_requesting_threads.clear();
|
||||
server->currently_handling = nullptr;
|
||||
server->ClientDisconnected();
|
||||
}
|
||||
|
||||
parent->client = nullptr;
|
||||
|
||||
@@ -36,14 +36,15 @@ public:
|
||||
|
||||
ResultCode SendSyncRequest(SharedPtr<Thread> thread);
|
||||
|
||||
std::string name; ///< Name of client port (optional)
|
||||
private:
|
||||
explicit ClientSession(KernelCore& kernel);
|
||||
~ClientSession() override;
|
||||
|
||||
/// The parent session, which links to the server endpoint.
|
||||
std::shared_ptr<Session> parent;
|
||||
|
||||
private:
|
||||
explicit ClientSession(KernelCore& kernel);
|
||||
~ClientSession() override;
|
||||
/// Name of the client session (optional)
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
12
src/core/hle/kernel/code_set.cpp
Normal file
12
src/core/hle/kernel/code_set.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
} // namespace Kernel
|
||||
89
src/core/hle/kernel/code_set.h
Normal file
89
src/core/hle/kernel/code_set.h
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
/**
|
||||
* Represents executable data that may be loaded into a kernel process.
|
||||
*
|
||||
* A code set consists of three basic segments:
|
||||
* - A code (AKA text) segment,
|
||||
* - A read-only data segment (rodata)
|
||||
* - A data segment
|
||||
*
|
||||
* The code segment is the portion of the object file that contains
|
||||
* executable instructions.
|
||||
*
|
||||
* The read-only data segment in the portion of the object file that
|
||||
* contains (as one would expect) read-only data, such as fixed constant
|
||||
* values and data structures.
|
||||
*
|
||||
* The data segment is similar to the read-only data segment -- it contains
|
||||
* variables and data structures that have predefined values, however,
|
||||
* entities within this segment can be modified.
|
||||
*/
|
||||
struct CodeSet final {
|
||||
/// A single segment within a code set.
|
||||
struct Segment final {
|
||||
/// The byte offset that this segment is located at.
|
||||
std::size_t offset = 0;
|
||||
|
||||
/// The address to map this segment to.
|
||||
VAddr addr = 0;
|
||||
|
||||
/// The size of this segment in bytes.
|
||||
u32 size = 0;
|
||||
};
|
||||
|
||||
explicit CodeSet();
|
||||
~CodeSet();
|
||||
|
||||
CodeSet(const CodeSet&) = delete;
|
||||
CodeSet& operator=(const CodeSet&) = delete;
|
||||
|
||||
CodeSet(CodeSet&&) = default;
|
||||
CodeSet& operator=(CodeSet&&) = default;
|
||||
|
||||
Segment& CodeSegment() {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
const Segment& CodeSegment() const {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
Segment& RODataSegment() {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
const Segment& RODataSegment() const {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
Segment& DataSegment() {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
const Segment& DataSegment() const {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
/// The overall data that backs this code set.
|
||||
std::vector<u8> memory;
|
||||
|
||||
/// The segments that comprise this code set.
|
||||
std::array<Segment, 3> segments;
|
||||
|
||||
/// The entry point address for this code set.
|
||||
VAddr entrypoint = 0;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -14,6 +14,7 @@ constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7};
|
||||
constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14};
|
||||
constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101};
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102};
|
||||
constexpr ResultCode ERR_OUT_OF_MEMORY{ErrorModule::Kernel, 104};
|
||||
constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105};
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE{ErrorModule::Kernel, 106};
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS{ErrorModule::Kernel, 108};
|
||||
|
||||
@@ -14,32 +14,47 @@
|
||||
namespace Kernel {
|
||||
namespace {
|
||||
constexpr u16 GetSlot(Handle handle) {
|
||||
return handle >> 15;
|
||||
return static_cast<u16>(handle >> 15);
|
||||
}
|
||||
|
||||
constexpr u16 GetGeneration(Handle handle) {
|
||||
return handle & 0x7FFF;
|
||||
return static_cast<u16>(handle & 0x7FFF);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
HandleTable::HandleTable() {
|
||||
next_generation = 1;
|
||||
Clear();
|
||||
}
|
||||
|
||||
HandleTable::~HandleTable() = default;
|
||||
|
||||
ResultCode HandleTable::SetSize(s32 handle_table_size) {
|
||||
if (static_cast<u32>(handle_table_size) > MAX_COUNT) {
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Values less than or equal to zero indicate to use the maximum allowable
|
||||
// size for the handle table in the actual kernel, so we ignore the given
|
||||
// value in that case, since we assume this by default unless this function
|
||||
// is called.
|
||||
if (handle_table_size > 0) {
|
||||
table_size = static_cast<u16>(handle_table_size);
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
|
||||
DEBUG_ASSERT(obj != nullptr);
|
||||
|
||||
u16 slot = next_free_slot;
|
||||
if (slot >= generations.size()) {
|
||||
const u16 slot = next_free_slot;
|
||||
if (slot >= table_size) {
|
||||
LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use.");
|
||||
return ERR_HANDLE_TABLE_FULL;
|
||||
}
|
||||
next_free_slot = generations[slot];
|
||||
|
||||
u16 generation = next_generation++;
|
||||
const u16 generation = next_generation++;
|
||||
|
||||
// Overflow count so it fits in the 15 bits dedicated to the generation in the handle.
|
||||
// Horizon OS uses zero to represent an invalid handle, so skip to 1.
|
||||
@@ -64,10 +79,11 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
|
||||
}
|
||||
|
||||
ResultCode HandleTable::Close(Handle handle) {
|
||||
if (!IsValid(handle))
|
||||
if (!IsValid(handle)) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
u16 slot = GetSlot(handle);
|
||||
const u16 slot = GetSlot(handle);
|
||||
|
||||
objects[slot] = nullptr;
|
||||
|
||||
@@ -77,10 +93,10 @@ ResultCode HandleTable::Close(Handle handle) {
|
||||
}
|
||||
|
||||
bool HandleTable::IsValid(Handle handle) const {
|
||||
std::size_t slot = GetSlot(handle);
|
||||
u16 generation = GetGeneration(handle);
|
||||
const std::size_t slot = GetSlot(handle);
|
||||
const u16 generation = GetGeneration(handle);
|
||||
|
||||
return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation;
|
||||
return slot < table_size && objects[slot] != nullptr && generations[slot] == generation;
|
||||
}
|
||||
|
||||
SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
|
||||
@@ -97,7 +113,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
|
||||
}
|
||||
|
||||
void HandleTable::Clear() {
|
||||
for (u16 i = 0; i < MAX_COUNT; ++i) {
|
||||
for (u16 i = 0; i < table_size; ++i) {
|
||||
generations[i] = i + 1;
|
||||
objects[i] = nullptr;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,20 @@ public:
|
||||
HandleTable();
|
||||
~HandleTable();
|
||||
|
||||
/**
|
||||
* Sets the number of handles that may be in use at one time
|
||||
* for this handle table.
|
||||
*
|
||||
* @param handle_table_size The desired size to limit the handle table to.
|
||||
*
|
||||
* @returns an error code indicating if initialization was successful.
|
||||
* If initialization was not successful, then ERR_OUT_OF_MEMORY
|
||||
* will be returned.
|
||||
*
|
||||
* @pre handle_table_size must be within the range [0, 1024]
|
||||
*/
|
||||
ResultCode SetSize(s32 handle_table_size);
|
||||
|
||||
/**
|
||||
* Allocates a handle for the given object.
|
||||
* @return The created Handle or one of the following errors:
|
||||
@@ -103,14 +117,21 @@ private:
|
||||
*/
|
||||
std::array<u16, MAX_COUNT> generations;
|
||||
|
||||
/**
|
||||
* The limited size of the handle table. This can be specified by process
|
||||
* capabilities in order to restrict the overall number of handles that
|
||||
* can be created in a process instance
|
||||
*/
|
||||
u16 table_size = static_cast<u16>(MAX_COUNT);
|
||||
|
||||
/**
|
||||
* Global counter of the number of created handles. Stored in `generations` when a handle is
|
||||
* created, and wraps around to 1 when it hits 0x8000.
|
||||
*/
|
||||
u16 next_generation;
|
||||
u16 next_generation = 1;
|
||||
|
||||
/// Head of the free slots linked list.
|
||||
u16 next_free_slot;
|
||||
u16 next_free_slot = 0;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -86,7 +86,7 @@ HLERequestContext::~HLERequestContext() = default;
|
||||
void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf,
|
||||
bool incoming) {
|
||||
IPC::RequestParser rp(src_cmdbuf);
|
||||
command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
|
||||
command_header = rp.PopRaw<IPC::CommandHeader>();
|
||||
|
||||
if (command_header->type == IPC::CommandType::Close) {
|
||||
// Close does not populate the rest of the IPC header
|
||||
@@ -95,8 +95,7 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
|
||||
|
||||
// If handle descriptor is present, add size of it
|
||||
if (command_header->enable_handle_descriptor) {
|
||||
handle_descriptor_header =
|
||||
std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
|
||||
handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>();
|
||||
if (handle_descriptor_header->send_current_pid) {
|
||||
rp.Skip(2, false);
|
||||
}
|
||||
@@ -140,16 +139,15 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
domain_message_header =
|
||||
std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
} else {
|
||||
if (Session()->IsDomain())
|
||||
if (Session()->IsDomain()) {
|
||||
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_payload_header =
|
||||
std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
|
||||
data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>();
|
||||
|
||||
data_payload_offset = rp.GetCurrentOffset();
|
||||
|
||||
@@ -264,11 +262,11 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
|
||||
// Write the domain objects to the command buffer, these go after the raw untranslated data.
|
||||
// TODO(Subv): This completely ignores C buffers.
|
||||
std::size_t domain_offset = size - domain_message_header->num_objects;
|
||||
auto& request_handlers = server_session->domain_request_handlers;
|
||||
|
||||
for (auto& object : domain_objects) {
|
||||
request_handlers.emplace_back(object);
|
||||
dst_cmdbuf[domain_offset++] = static_cast<u32_le>(request_handlers.size());
|
||||
for (const auto& object : domain_objects) {
|
||||
server_session->AppendDomainRequestHandler(object);
|
||||
dst_cmdbuf[domain_offset++] =
|
||||
static_cast<u32_le>(server_session->NumDomainRequestHandlers());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
@@ -15,6 +16,8 @@
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Service {
|
||||
class ServiceFrameworkBase;
|
||||
}
|
||||
@@ -166,12 +169,12 @@ public:
|
||||
return buffer_c_desciptors;
|
||||
}
|
||||
|
||||
const IPC::DomainMessageHeader* GetDomainMessageHeader() const {
|
||||
return domain_message_header.get();
|
||||
const IPC::DomainMessageHeader& GetDomainMessageHeader() const {
|
||||
return domain_message_header.value();
|
||||
}
|
||||
|
||||
bool HasDomainMessageHeader() const {
|
||||
return domain_message_header != nullptr;
|
||||
return domain_message_header.has_value();
|
||||
}
|
||||
|
||||
/// Helper function to read a buffer using the appropriate buffer descriptor
|
||||
@@ -208,14 +211,12 @@ public:
|
||||
|
||||
template <typename T>
|
||||
SharedPtr<T> GetCopyObject(std::size_t index) {
|
||||
ASSERT(index < copy_objects.size());
|
||||
return DynamicObjectCast<T>(copy_objects[index]);
|
||||
return DynamicObjectCast<T>(copy_objects.at(index));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SharedPtr<T> GetMoveObject(std::size_t index) {
|
||||
ASSERT(index < move_objects.size());
|
||||
return DynamicObjectCast<T>(move_objects[index]);
|
||||
return DynamicObjectCast<T>(move_objects.at(index));
|
||||
}
|
||||
|
||||
void AddMoveObject(SharedPtr<Object> object) {
|
||||
@@ -232,7 +233,7 @@ public:
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> GetDomainRequestHandler(std::size_t index) const {
|
||||
return std::static_pointer_cast<T>(domain_request_handlers[index]);
|
||||
return std::static_pointer_cast<T>(domain_request_handlers.at(index));
|
||||
}
|
||||
|
||||
void SetDomainRequestHandlers(
|
||||
@@ -272,10 +273,10 @@ private:
|
||||
boost::container::small_vector<SharedPtr<Object>, 8> copy_objects;
|
||||
boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects;
|
||||
|
||||
std::shared_ptr<IPC::CommandHeader> command_header;
|
||||
std::shared_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header;
|
||||
std::shared_ptr<IPC::DataPayloadHeader> data_payload_header;
|
||||
std::shared_ptr<IPC::DomainMessageHeader> domain_message_header;
|
||||
std::optional<IPC::CommandHeader> command_header;
|
||||
std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
|
||||
std::optional<IPC::DataPayloadHeader> data_payload_header;
|
||||
std::optional<IPC::DomainMessageHeader> domain_message_header;
|
||||
std::vector<IPC::BufferDescriptorX> buffer_x_desciptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -28,12 +29,12 @@ namespace Kernel {
|
||||
* @param thread_handle The handle of the thread that's been awoken
|
||||
* @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
|
||||
*/
|
||||
static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_late) {
|
||||
static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_late) {
|
||||
const auto proper_handle = static_cast<Handle>(thread_handle);
|
||||
const auto& system = Core::System::GetInstance();
|
||||
|
||||
// Lock the global kernel mutex when we enter the kernel HLE.
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
std::lock_guard lock{HLE::g_hle_lock};
|
||||
|
||||
SharedPtr<Thread> thread =
|
||||
system.Kernel().RetrieveThreadFromWakeupCallbackHandleTable(proper_handle);
|
||||
@@ -61,7 +62,8 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
|
||||
if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
|
||||
thread->GetWaitHandle() != 0) {
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex ||
|
||||
thread->GetStatus() == ThreadStatus::WaitCondVar);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
@@ -86,11 +88,13 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
}
|
||||
|
||||
struct KernelCore::Impl {
|
||||
void Initialize(KernelCore& kernel, Core::Timing::CoreTiming& core_timing) {
|
||||
explicit Impl(Core::System& system) : system{system} {}
|
||||
|
||||
void Initialize(KernelCore& kernel) {
|
||||
Shutdown();
|
||||
|
||||
InitializeSystemResourceLimit(kernel);
|
||||
InitializeThreads(core_timing);
|
||||
InitializeThreads();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
@@ -111,7 +115,7 @@ struct KernelCore::Impl {
|
||||
|
||||
// Creates the default system resource limit
|
||||
void InitializeSystemResourceLimit(KernelCore& kernel) {
|
||||
system_resource_limit = ResourceLimit::Create(kernel, "System");
|
||||
system_resource_limit = ResourceLimit::Create(kernel);
|
||||
|
||||
// If setting the default system values fails, then something seriously wrong has occurred.
|
||||
ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x200000000)
|
||||
@@ -122,9 +126,9 @@ struct KernelCore::Impl {
|
||||
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess());
|
||||
}
|
||||
|
||||
void InitializeThreads(Core::Timing::CoreTiming& core_timing) {
|
||||
void InitializeThreads() {
|
||||
thread_wakeup_event_type =
|
||||
core_timing.RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||
system.CoreTiming().RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||
}
|
||||
|
||||
std::atomic<u32> next_object_id{0};
|
||||
@@ -145,15 +149,18 @@ struct KernelCore::Impl {
|
||||
/// Map of named ports managed by the kernel, which can be retrieved using
|
||||
/// the ConnectToPort SVC.
|
||||
NamedPortTable named_ports;
|
||||
|
||||
// System context
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
KernelCore::KernelCore() : impl{std::make_unique<Impl>()} {}
|
||||
KernelCore::KernelCore(Core::System& system) : impl{std::make_unique<Impl>(system)} {}
|
||||
KernelCore::~KernelCore() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void KernelCore::Initialize(Core::Timing::CoreTiming& core_timing) {
|
||||
impl->Initialize(*this, core_timing);
|
||||
void KernelCore::Initialize() {
|
||||
impl->Initialize(*this);
|
||||
}
|
||||
|
||||
void KernelCore::Shutdown() {
|
||||
@@ -184,6 +191,10 @@ const Process* KernelCore::CurrentProcess() const {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
const std::vector<SharedPtr<Process>>& KernelCore::GetProcessList() const {
|
||||
return impl->process_list;
|
||||
}
|
||||
|
||||
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
impl->named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#include <unordered_map>
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
template <typename T>
|
||||
class ResultVal;
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
@@ -18,6 +19,7 @@ struct EventType;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class AddressArbiter;
|
||||
class ClientPort;
|
||||
class HandleTable;
|
||||
class Process;
|
||||
@@ -30,7 +32,14 @@ private:
|
||||
using NamedPortTable = std::unordered_map<std::string, SharedPtr<ClientPort>>;
|
||||
|
||||
public:
|
||||
KernelCore();
|
||||
/// Constructs an instance of the kernel using the given System
|
||||
/// instance as a context for any necessary system-related state,
|
||||
/// such as threads, CPU core state, etc.
|
||||
///
|
||||
/// @post After execution of the constructor, the provided System
|
||||
/// object *must* outlive the kernel instance itself.
|
||||
///
|
||||
explicit KernelCore(Core::System& system);
|
||||
~KernelCore();
|
||||
|
||||
KernelCore(const KernelCore&) = delete;
|
||||
@@ -40,11 +49,7 @@ public:
|
||||
KernelCore& operator=(KernelCore&&) = delete;
|
||||
|
||||
/// Resets the kernel to a clean slate for use.
|
||||
///
|
||||
/// @param core_timing CoreTiming instance used to create any necessary
|
||||
/// kernel-specific callback events.
|
||||
///
|
||||
void Initialize(Core::Timing::CoreTiming& core_timing);
|
||||
void Initialize();
|
||||
|
||||
/// Clears all resources in use by the kernel instance.
|
||||
void Shutdown();
|
||||
@@ -67,6 +72,9 @@ public:
|
||||
/// Retrieves a const pointer to the current process.
|
||||
const Process* CurrentProcess() const;
|
||||
|
||||
/// Retrieves the list of processes.
|
||||
const std::vector<SharedPtr<Process>>& GetProcessList() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -10,8 +9,11 @@
|
||||
#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.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
@@ -57,41 +59,47 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle,
|
||||
Mutex::Mutex(Core::System& system) : system{system} {}
|
||||
Mutex::~Mutex() = default;
|
||||
|
||||
ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||
Thread* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
|
||||
SharedPtr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle);
|
||||
|
||||
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
|
||||
// thread.
|
||||
ASSERT(requesting_thread == GetCurrentThread());
|
||||
ASSERT(requesting_thread == current_thread);
|
||||
|
||||
u32 addr_value = Memory::Read32(address);
|
||||
const u32 addr_value = Memory::Read32(address);
|
||||
|
||||
// If the mutex isn't being held, just return success.
|
||||
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
if (holding_thread == nullptr)
|
||||
if (holding_thread == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Wait until the mutex is released
|
||||
GetCurrentThread()->SetMutexWaitAddress(address);
|
||||
GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
|
||||
current_thread->SetMutexWaitAddress(address);
|
||||
current_thread->SetWaitHandle(requesting_thread_handle);
|
||||
|
||||
GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
|
||||
GetCurrentThread()->InvalidateWakeupCallback();
|
||||
current_thread->SetStatus(ThreadStatus::WaitMutex);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
// Update the lock holder thread's priority to prevent priority inversion.
|
||||
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||
holding_thread->AddMutexWaiter(current_thread);
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -102,7 +110,8 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
|
||||
|
||||
// There are no more threads waiting for the mutex, release it completely.
|
||||
if (thread == nullptr) {
|
||||
@@ -111,7 +120,7 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
}
|
||||
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||
TransferMutexOwnership(address, current_thread, thread);
|
||||
|
||||
u32 mutex_value = thread->GetWaitHandle();
|
||||
|
||||
|
||||
@@ -5,32 +5,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Kernel {
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
class HandleTable;
|
||||
class Thread;
|
||||
namespace Kernel {
|
||||
|
||||
class Mutex final {
|
||||
public:
|
||||
explicit Mutex(Core::System& system);
|
||||
~Mutex();
|
||||
|
||||
/// Flag that indicates that a mutex still has threads waiting for it.
|
||||
static constexpr u32 MutexHasWaitersFlag = 0x40000000;
|
||||
/// Mask of the bits in a mutex address value that contain the mutex owner.
|
||||
static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
|
||||
|
||||
/// Attempts to acquire a mutex at the specified address.
|
||||
static ResultCode TryAcquire(HandleTable& handle_table, VAddr address,
|
||||
Handle holding_thread_handle, Handle requesting_thread_handle);
|
||||
ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle);
|
||||
|
||||
/// Releases the mutex at the specified address.
|
||||
static ResultCode Release(VAddr address);
|
||||
ResultCode Release(VAddr address);
|
||||
|
||||
private:
|
||||
Mutex() = default;
|
||||
~Mutex() = default;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -23,7 +23,7 @@ bool Object::IsWaitable() const {
|
||||
case HandleType::Unknown:
|
||||
case HandleType::WritableEvent:
|
||||
case HandleType::SharedMemory:
|
||||
case HandleType::AddressArbiter:
|
||||
case HandleType::TransferMemory:
|
||||
case HandleType::ResourceLimit:
|
||||
case HandleType::ClientPort:
|
||||
case HandleType::ClientSession:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user