Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
763d3be3a9 | ||
|
|
d8c4a9af38 | ||
|
|
6aa3846fb2 | ||
|
|
415387f00d | ||
|
|
890393fbe6 | ||
|
|
9f88043ebe | ||
|
|
c2173801d4 | ||
|
|
56d03f16e9 | ||
|
|
3ecc6b7d1b | ||
|
|
5bf37f52ad | ||
|
|
57791645e4 | ||
|
|
d1fe1324c8 | ||
|
|
b485dd24be | ||
|
|
ae25558321 | ||
|
|
ec08ea197b | ||
|
|
f6f1ac8b40 | ||
|
|
b82512be06 | ||
|
|
ba8b0c85b8 | ||
|
|
b6bd9ecae4 | ||
|
|
b95ea4bb5f | ||
|
|
e93b4b0378 | ||
|
|
0fd41ae206 | ||
|
|
9202bddad2 | ||
|
|
32587337b5 | ||
|
|
02ead0e2e1 | ||
|
|
4f1f3b1bb3 | ||
|
|
42bb0da520 | ||
|
|
5dec523143 | ||
|
|
d01633e18e | ||
|
|
e7ac242039 | ||
|
|
d7974dc763 | ||
|
|
b899717900 | ||
|
|
d58aefde49 | ||
|
|
4c6be6cd1d | ||
|
|
584c2e8ca6 | ||
|
|
717b50155a | ||
|
|
57b161019d | ||
|
|
3ff6606fcb | ||
|
|
6a0a666a41 | ||
|
|
83db2efec3 | ||
|
|
13e4c315b6 | ||
|
|
4242ec5a64 | ||
|
|
c6b8210d7c | ||
|
|
6568fe9a84 | ||
|
|
57c4a6ee11 | ||
|
|
54abbf48f2 | ||
|
|
56d3ce98f0 | ||
|
|
cbe5b62947 | ||
|
|
f7ae86c982 | ||
|
|
30a4673c60 | ||
|
|
a5ceb48184 | ||
|
|
c8caa27936 | ||
|
|
e4019af522 | ||
|
|
f7226ca7dc | ||
|
|
d172ab75ca | ||
|
|
bcf13e101e | ||
|
|
2caf2f7e4e | ||
|
|
df92a08990 | ||
|
|
e291589177 | ||
|
|
2285a25182 | ||
|
|
a50e4de389 | ||
|
|
959bee4799 | ||
|
|
232e2f2593 | ||
|
|
b4406b0467 | ||
|
|
fb213e8ae5 | ||
|
|
87d938139d | ||
|
|
2a4ccaa5d5 | ||
|
|
9c04abacc5 | ||
|
|
5916a184cc | ||
|
|
fcab4e5d0d | ||
|
|
fe23334ff0 | ||
|
|
81169d77ea | ||
|
|
7d24972b8f | ||
|
|
1d3fd07fe6 | ||
|
|
28766a915b | ||
|
|
279bc95941 | ||
|
|
12c522bba5 | ||
|
|
65a6f30f51 | ||
|
|
bb2d6dbeb3 | ||
|
|
50844badbf | ||
|
|
c1f6838e40 | ||
|
|
6455546f26 | ||
|
|
dcc642551f | ||
|
|
dd429ef858 | ||
|
|
bc2664cefa | ||
|
|
8b81f665a2 | ||
|
|
1039c54f5d | ||
|
|
b24ce3440d | ||
|
|
2f6ee0e91d | ||
|
|
6b1c4a7a71 | ||
|
|
166b9a5eea | ||
|
|
5313181ecf | ||
|
|
ceccd746d8 | ||
|
|
82470c7181 | ||
|
|
4843d8d942 | ||
|
|
c914435f20 | ||
|
|
7468e4f60a | ||
|
|
b6da068313 | ||
|
|
127cf8d46f | ||
|
|
e0586cc85a | ||
|
|
302240da70 | ||
|
|
892e70c8a1 | ||
|
|
7c9da0e56f | ||
|
|
6d6da57a95 | ||
|
|
04cb4c6553 | ||
|
|
487bbf95fd | ||
|
|
3a925efddf | ||
|
|
aeecc9343a | ||
|
|
8c7fe539b8 | ||
|
|
c88f05f7b5 | ||
|
|
f3c017b168 | ||
|
|
4b549b9cb3 | ||
|
|
9cc0bba80b | ||
|
|
150bcb06a9 | ||
|
|
280b87adec | ||
|
|
26d326d1b0 | ||
|
|
4cf5a96b79 | ||
|
|
7bba785963 | ||
|
|
8d18be71db | ||
|
|
6abfe3bbe4 | ||
|
|
1905b28b3c | ||
|
|
8e4c3b5e14 | ||
|
|
be5915c248 | ||
|
|
9e41d8320a | ||
|
|
e998f21e71 | ||
|
|
3197ef093c | ||
|
|
fa0e81bfba | ||
|
|
01ce973882 | ||
|
|
f57d2c0965 | ||
|
|
735a229236 | ||
|
|
9ade7e3582 | ||
|
|
259a40abfc | ||
|
|
f1577a5a94 | ||
|
|
d08a6cef64 | ||
|
|
907c65f54c | ||
|
|
fe6499b86e | ||
|
|
6309ac838d | ||
|
|
19799b3079 | ||
|
|
4e16234d32 | ||
|
|
3c9da9c3f4 | ||
|
|
ff6dffe298 | ||
|
|
5f6dc0a8d0 | ||
|
|
34893dd8c8 | ||
|
|
50595d1c13 | ||
|
|
717c54e085 | ||
|
|
31b7be9ff2 | ||
|
|
40692dfb64 | ||
|
|
9a3aca963a | ||
|
|
1fa79d5cac | ||
|
|
c9ec82e32e | ||
|
|
2f88a0a3b2 | ||
|
|
7e884f55e0 | ||
|
|
d70cbd4184 | ||
|
|
9bcf80bf67 | ||
|
|
ed1195d1e7 | ||
|
|
25e1cc7ec9 | ||
|
|
a287a54f26 | ||
|
|
56738aa4e1 | ||
|
|
c11254a996 | ||
|
|
aec8c88755 | ||
|
|
68d7bc94e2 | ||
|
|
8ee31abd9d | ||
|
|
9a7c54da58 | ||
|
|
5dc6914688 | ||
|
|
a7a734b5d3 | ||
|
|
e2c8b61de6 | ||
|
|
145578ac03 | ||
|
|
f01a3750d4 | ||
|
|
0e9c77b0a2 | ||
|
|
d692d8ee08 | ||
|
|
460a196fca | ||
|
|
652154aead | ||
|
|
49ebbfd627 | ||
|
|
7688093ca3 | ||
|
|
df08b83287 | ||
|
|
fa68e104ea | ||
|
|
fd66f580b4 | ||
|
|
8f7d745383 | ||
|
|
f4692a450e | ||
|
|
df5b9b92be | ||
|
|
5b0ef07742 | ||
|
|
5ce9a52936 | ||
|
|
dad900c61d | ||
|
|
5b730d4df0 | ||
|
|
e699a35f20 | ||
|
|
41beda4367 | ||
|
|
048fe9f413 | ||
|
|
e51de54ee4 | ||
|
|
984cfcebd1 | ||
|
|
df0412afb5 | ||
|
|
f28c0fccb2 | ||
|
|
4c87dbebb2 | ||
|
|
16a7fe689a | ||
|
|
6f0cddd440 | ||
|
|
4084a008ea | ||
|
|
c0ebf22476 | ||
|
|
cb375ec35a | ||
|
|
6f3bebcb50 | ||
|
|
1029511bbc | ||
|
|
65144ce4ac | ||
|
|
f848d2761b | ||
|
|
730e1a3cf3 | ||
|
|
d4bb7fd786 | ||
|
|
fbea51c684 | ||
|
|
a6f4e8e7f4 | ||
|
|
448a4c8a6a | ||
|
|
ece4e28740 | ||
|
|
58ba1e4cb4 | ||
|
|
cf77fb4353 | ||
|
|
7e4e142aae | ||
|
|
b7b335ac63 | ||
|
|
9a5dd9faff | ||
|
|
947627d10d | ||
|
|
1cfb9522a3 | ||
|
|
f63d3befdc | ||
|
|
307fdcf7ad | ||
|
|
679be25c37 | ||
|
|
fa56c5eae9 | ||
|
|
3b9e176b70 | ||
|
|
053f5fd3cc | ||
|
|
f3836bb831 | ||
|
|
2f4d0f383e | ||
|
|
d247d3d60c | ||
|
|
93452b24e3 | ||
|
|
5559cfcbb2 | ||
|
|
1c301c9aab | ||
|
|
59183dbfa8 | ||
|
|
c1926f915d | ||
|
|
7da3c4ed21 | ||
|
|
3e9f88bf79 | ||
|
|
8c81386568 | ||
|
|
bb5edbd490 | ||
|
|
1818aae562 | ||
|
|
165cd884ca | ||
|
|
973809cde0 | ||
|
|
71c7261740 | ||
|
|
5b68a26cce | ||
|
|
3b1a94707a | ||
|
|
488c0acae9 | ||
|
|
4c8fc53a85 | ||
|
|
3ae5a9b70a | ||
|
|
a31e2cd361 | ||
|
|
1bd282ec0a | ||
|
|
1fc520cd62 | ||
|
|
7b9997baea | ||
|
|
393257eaae | ||
|
|
13e3ca314c | ||
|
|
b4853a0e08 | ||
|
|
ee4dd90649 | ||
|
|
3df456675d | ||
|
|
8abf99c56e | ||
|
|
7c6121cba5 | ||
|
|
46997dfc7a | ||
|
|
97fda7b10f | ||
|
|
dbe5353f3a | ||
|
|
e1ac893a0c | ||
|
|
b35cdc39b7 | ||
|
|
52dbc724f8 | ||
|
|
2f4b8a335d | ||
|
|
b38b7454a6 | ||
|
|
b65dc12208 | ||
|
|
58978d154a | ||
|
|
1f3adf5109 | ||
|
|
107d5f750a | ||
|
|
f40755173f | ||
|
|
4e14eed770 | ||
|
|
aa3b2cd57f | ||
|
|
4865dd92ca | ||
|
|
3c62359e05 | ||
|
|
62643c3424 | ||
|
|
210d4dcd99 | ||
|
|
5a8808086a | ||
|
|
861dd76d7f | ||
|
|
db41d0482f | ||
|
|
de62664908 | ||
|
|
34f6fd1f5f | ||
|
|
79a91ecfaf | ||
|
|
420bb51d6c | ||
|
|
c86c8b4d93 | ||
|
|
e43aadf75f | ||
|
|
9f25e3c23e | ||
|
|
7881c69a54 | ||
|
|
9e666e4dd2 | ||
|
|
c3a12397b0 | ||
|
|
45b6d2c597 | ||
|
|
7e011fa565 | ||
|
|
4d10ea17a0 | ||
|
|
006875ed4f | ||
|
|
a87889f032 | ||
|
|
9c79d9d298 | ||
|
|
c30ac7c904 | ||
|
|
038d2257de | ||
|
|
190f838ea4 | ||
|
|
470669c677 | ||
|
|
dd754065d8 | ||
|
|
743fdc6990 | ||
|
|
9f83fd5f0c | ||
|
|
5297abb7ac | ||
|
|
a8beac190f | ||
|
|
b96871e2bf | ||
|
|
21a1346e27 | ||
|
|
3ba6ebae81 | ||
|
|
fbf6ae400c | ||
|
|
d2413fc2eb | ||
|
|
96eb681b90 | ||
|
|
26cce9909a | ||
|
|
5af370c257 | ||
|
|
67d7db685a | ||
|
|
bb98f26433 | ||
|
|
cc44a43ebc | ||
|
|
2a18e66a4c | ||
|
|
e7cfe5a2f7 | ||
|
|
7415febd93 | ||
|
|
7492cb9577 | ||
|
|
60e53a39cc | ||
|
|
1b21161aac | ||
|
|
1195af2510 | ||
|
|
9a5658c038 | ||
|
|
199d0a9d92 | ||
|
|
e679486ed3 | ||
|
|
6ef406542c | ||
|
|
35c6e8fff6 | ||
|
|
4852721005 | ||
|
|
fb7ff7ec03 | ||
|
|
7d2422abbe | ||
|
|
b929bbe3e0 | ||
|
|
ae1e92621d | ||
|
|
5a491abbda | ||
|
|
afbb322b32 | ||
|
|
5601861d67 | ||
|
|
cd06601022 | ||
|
|
c23de62184 | ||
|
|
9caec9f2c8 | ||
|
|
cdb64bfd4f | ||
|
|
e5dcfdb87b | ||
|
|
984010227f | ||
|
|
57c97ebd86 | ||
|
|
dc3cd2d2c9 | ||
|
|
e6fce45d3a | ||
|
|
5b826c6e4a | ||
|
|
77d7722173 | ||
|
|
266c12cec6 | ||
|
|
7a0e1dafd7 | ||
|
|
1d41ddb378 | ||
|
|
5c83060481 | ||
|
|
72e4989b1e | ||
|
|
1d55272467 | ||
|
|
2b1b7ed0e4 | ||
|
|
af3011cd69 | ||
|
|
de9c15fa79 | ||
|
|
e85279ea7b | ||
|
|
d8be245c22 | ||
|
|
bbb98bd490 | ||
|
|
f96a14a0ab | ||
|
|
c753efdde7 | ||
|
|
8e46a3ccfa | ||
|
|
b27b89f513 | ||
|
|
e56899902b | ||
|
|
0885f43a6a | ||
|
|
a98a39c575 | ||
|
|
94e7d239dd | ||
|
|
8274e517b2 | ||
|
|
96011cc5c9 | ||
|
|
f7bd0b436a | ||
|
|
b7d3c572c1 | ||
|
|
b0bbaef1eb | ||
|
|
0ee8800905 | ||
|
|
0bef3fc0d3 | ||
|
|
45fab265d5 | ||
|
|
a2f851788f | ||
|
|
5940e623d5 | ||
|
|
07c84876a1 | ||
|
|
96c067d0fb | ||
|
|
deb45a8bb2 | ||
|
|
fdac55a5ef | ||
|
|
719e02574a | ||
|
|
c885da0a3f | ||
|
|
2984c41235 | ||
|
|
9313a0886f | ||
|
|
39248a1f87 | ||
|
|
98557a83b6 | ||
|
|
c3f4962715 | ||
|
|
0ce84e59b6 | ||
|
|
efbaa17f71 | ||
|
|
783d9f8d6b | ||
|
|
57cf086886 | ||
|
|
dbf66b7f4f | ||
|
|
33e74cab2c | ||
|
|
3ba0e971e9 | ||
|
|
92ac54a493 | ||
|
|
8b05b6730e | ||
|
|
cd9fc6ff2d | ||
|
|
c17fa5b936 | ||
|
|
46900d8042 | ||
|
|
1f6be3d570 | ||
|
|
b183cb1011 | ||
|
|
919c058fee | ||
|
|
5f01308b20 | ||
|
|
e0c90acee0 | ||
|
|
843b577880 | ||
|
|
139b564c01 | ||
|
|
a55a28380d | ||
|
|
181123ea08 | ||
|
|
0258bd61ea | ||
|
|
b8946fef94 | ||
|
|
a5eeab98de | ||
|
|
7992aecb7c | ||
|
|
b4d32394b6 | ||
|
|
f18679e0cd | ||
|
|
284d58d1d5 | ||
|
|
068d7621db | ||
|
|
d890bb190c | ||
|
|
ec3e48cdde | ||
|
|
e4aca7a60b | ||
|
|
f58014ff0e | ||
|
|
42a315f0e4 | ||
|
|
429d0af62b | ||
|
|
db17585277 | ||
|
|
0e8ded37b9 | ||
|
|
ab98fb1ff9 | ||
|
|
e7f6327068 | ||
|
|
19a9464b7d | ||
|
|
a4d26cf81a | ||
|
|
b11aea803c | ||
|
|
feeced81d2 | ||
|
|
820856b301 | ||
|
|
45f55413e0 | ||
|
|
c3330caba2 | ||
|
|
92c22d60ef | ||
|
|
a0858abe35 | ||
|
|
5766b241c3 | ||
|
|
7f158c656e | ||
|
|
8b0cc79acf | ||
|
|
8cbee7f4b7 | ||
|
|
52c8ff18d0 | ||
|
|
f53e5d2283 | ||
|
|
bd611bfe15 | ||
|
|
0bb2bdad17 | ||
|
|
0ec3655f1c | ||
|
|
190608ae20 | ||
|
|
ebe401d052 | ||
|
|
0d4f993358 | ||
|
|
ef4c5baf3a | ||
|
|
e6cb880adb | ||
|
|
8fa116eeb1 | ||
|
|
e317465d8a | ||
|
|
684330c9e4 | ||
|
|
f3dc5ee4a9 | ||
|
|
2545723065 | ||
|
|
95c89229ad | ||
|
|
7804eef9b6 | ||
|
|
82a2f4ad42 | ||
|
|
e1be2ead54 | ||
|
|
89792a3bca | ||
|
|
39e23ac92e | ||
|
|
a67a033ff8 | ||
|
|
d1c99bbfb0 | ||
|
|
a9620f98f4 | ||
|
|
28f29b8e6f | ||
|
|
3353c1ff3b | ||
|
|
70a475ec8c | ||
|
|
b3e419ff11 | ||
|
|
4428640b44 | ||
|
|
2b2915e271 | ||
|
|
7101741637 | ||
|
|
b86cf0a820 | ||
|
|
f29bd0fe35 | ||
|
|
64eea6b32d | ||
|
|
706eec3ca9 | ||
|
|
35759e48cf | ||
|
|
6409be2874 | ||
|
|
0d0b215fab | ||
|
|
11001c97f7 | ||
|
|
337f432016 | ||
|
|
581dad7dd3 | ||
|
|
64dcbe0ea1 | ||
|
|
0ecd32299c | ||
|
|
c2f8e64f6d | ||
|
|
44ba657573 | ||
|
|
44798085bd | ||
|
|
b7a1fac898 | ||
|
|
bc193f5272 | ||
|
|
17100ec968 | ||
|
|
8ba2caf58a | ||
|
|
48b52d31b5 | ||
|
|
840b9f5753 | ||
|
|
05057c9441 | ||
|
|
af4edc2865 | ||
|
|
9d4da48320 | ||
|
|
2650478d92 | ||
|
|
18a5f04d05 | ||
|
|
da9b38af2b | ||
|
|
c045f08904 | ||
|
|
5bf9218b32 | ||
|
|
f9fd400dbb | ||
|
|
650dc1e0af | ||
|
|
59a1760427 | ||
|
|
73615c31ff | ||
|
|
6799ecbc5b | ||
|
|
35c4dca2ce | ||
|
|
6f24b031f5 | ||
|
|
0c844e4bc2 | ||
|
|
e57c37fa9c | ||
|
|
6d6666819a | ||
|
|
0fbeac6d3f | ||
|
|
5e4f6afc30 | ||
|
|
37501b5a78 | ||
|
|
b43bf9f7fb | ||
|
|
8e42e99788 | ||
|
|
539c0f7bf8 | ||
|
|
62a0222e24 | ||
|
|
6f241bef49 | ||
|
|
21d7e8e1f0 | ||
|
|
49db3ea54c | ||
|
|
4b0de1b672 | ||
|
|
8e61798bfd | ||
|
|
6ee1bf9db0 | ||
|
|
0471c9991c | ||
|
|
b10bd53dd3 | ||
|
|
4cf456fb77 | ||
|
|
22ff4693c5 | ||
|
|
a107d1e333 | ||
|
|
05f92b6422 | ||
|
|
a892097ff0 | ||
|
|
c8baa7b909 | ||
|
|
34a3eb6b58 | ||
|
|
a20b64d3e5 | ||
|
|
2016f8e2fe | ||
|
|
c91fe80d64 | ||
|
|
ab2c1c4c03 | ||
|
|
0a5774be7b | ||
|
|
0791f921f6 | ||
|
|
421684e75f | ||
|
|
025b39c018 | ||
|
|
7c8583d204 | ||
|
|
9229c04ebf | ||
|
|
1b6af301b5 | ||
|
|
e51f252b75 | ||
|
|
0f1ff37f23 | ||
|
|
da33a1c4a3 | ||
|
|
f53f0ca483 | ||
|
|
7b6bfdec64 | ||
|
|
a88181b897 | ||
|
|
041874f663 | ||
|
|
496432bbb4 | ||
|
|
bec67b7863 | ||
|
|
a22a9d9348 | ||
|
|
81f312638f | ||
|
|
f1f930e06c | ||
|
|
e0b682e4b2 | ||
|
|
16b83a034c | ||
|
|
cf268af839 | ||
|
|
fd2e15ee73 | ||
|
|
4e8d8c849d | ||
|
|
1e438136fa | ||
|
|
313b60e922 | ||
|
|
cda236dbb3 | ||
|
|
1216fa36cf | ||
|
|
497166009c | ||
|
|
0fb3ac5605 | ||
|
|
e056d55dd5 | ||
|
|
1371219f5c | ||
|
|
cf423df624 | ||
|
|
5ce4f08364 | ||
|
|
7e1c2d1532 | ||
|
|
0ec43a8d3d | ||
|
|
bfdbba3aa4 | ||
|
|
f7ae6e0a83 | ||
|
|
3585cd8f03 | ||
|
|
ff54608569 | ||
|
|
6bb68a3fd9 | ||
|
|
93185953f1 | ||
|
|
7855c980cb | ||
|
|
5a705dd15f | ||
|
|
e73324c2e0 | ||
|
|
a20fb7f9f8 | ||
|
|
1cc0ac7ed8 | ||
|
|
83628a436e | ||
|
|
2165c710c3 | ||
|
|
b9a0bd2791 | ||
|
|
96874909ab | ||
|
|
d646fd92d1 | ||
|
|
d72a79af2d | ||
|
|
dc9280d75d | ||
|
|
6a9ae4ae43 | ||
|
|
392f39e2f6 | ||
|
|
cce0cd950c | ||
|
|
e43c242683 | ||
|
|
732057b072 | ||
|
|
2aaf0c9ba3 | ||
|
|
db7745ef41 | ||
|
|
02578f3c6b | ||
|
|
fcf0f3b67e | ||
|
|
84d015120c | ||
|
|
a6248c7c7c | ||
|
|
3bd5de646d | ||
|
|
0382d0ca58 | ||
|
|
a670dc84b3 | ||
|
|
963a0817dc | ||
|
|
1617ee77e5 | ||
|
|
66cc2fe26d | ||
|
|
51c34d1a24 | ||
|
|
3b89e36d1e | ||
|
|
3dc3fd0197 | ||
|
|
fc86509aac | ||
|
|
569ec04d1a | ||
|
|
7d7267cc56 | ||
|
|
2aa0a64f63 | ||
|
|
fc04db7596 | ||
|
|
1275d65bb3 | ||
|
|
3742ad9980 | ||
|
|
c437de9721 | ||
|
|
ba579088d7 | ||
|
|
019929587c | ||
|
|
f68c77d785 | ||
|
|
b5ba482fc2 | ||
|
|
9860c2bbf8 | ||
|
|
ee3749d9b6 | ||
|
|
3fad77fd8b | ||
|
|
d6b2abe26f | ||
|
|
79530b4c5e | ||
|
|
d8a4412498 | ||
|
|
5ed5fdd36e | ||
|
|
ef83ba65ad | ||
|
|
d30dca78be | ||
|
|
66f1a82365 | ||
|
|
eb8a5504a3 | ||
|
|
8a5e5c5476 | ||
|
|
03c55e5712 | ||
|
|
5d1ad31c49 | ||
|
|
13fcb3a133 | ||
|
|
d6dac92da1 | ||
|
|
90471836f7 | ||
|
|
8252caee04 | ||
|
|
8b095caefe | ||
|
|
8dc9b9d532 | ||
|
|
e40438f95e | ||
|
|
a6a56c8a44 | ||
|
|
4b074cf9e9 | ||
|
|
7c9e18e73b | ||
|
|
d2df2cad74 | ||
|
|
b2a80a3a96 | ||
|
|
cfb8f7b246 | ||
|
|
d792dfa885 | ||
|
|
12ff571609 | ||
|
|
d3c182f385 | ||
|
|
bf4dcdce52 | ||
|
|
476204cd4b | ||
|
|
f9e56ef8a0 | ||
|
|
61761b65f2 | ||
|
|
ba55ba5dc5 | ||
|
|
c997cdb837 | ||
|
|
fe4f9423e3 | ||
|
|
81af130147 | ||
|
|
ada0514084 | ||
|
|
d7da244475 | ||
|
|
549495575c | ||
|
|
c3cc8454b9 | ||
|
|
b8ad1d8c4b | ||
|
|
cfb110e0aa | ||
|
|
e930657bf6 | ||
|
|
0ad9149c90 | ||
|
|
25f2699523 | ||
|
|
a849f25376 | ||
|
|
fa51da4d76 | ||
|
|
6b79b7a003 | ||
|
|
102a10f0fe | ||
|
|
8d66c4ef6d | ||
|
|
8cecaf4e30 | ||
|
|
82b2711cdf | ||
|
|
35a275e97d | ||
|
|
303a0203b6 | ||
|
|
cfbaee07f0 | ||
|
|
fe45265fcd | ||
|
|
8b294eb5b8 | ||
|
|
07c9978ac6 | ||
|
|
b61b3f2a40 | ||
|
|
001dfea501 | ||
|
|
f100f785da | ||
|
|
ba1854bbdc | ||
|
|
ea6aae8577 | ||
|
|
5b7d3343cf | ||
|
|
394e638905 | ||
|
|
c2b7b0b58c | ||
|
|
1d2f59dfa4 | ||
|
|
007d7ea2eb | ||
|
|
95d3d9d3ad | ||
|
|
bfc8cd0e0b | ||
|
|
165234b159 | ||
|
|
6493674726 | ||
|
|
cbedf5707f | ||
|
|
57c5b206ca | ||
|
|
87b4417aca | ||
|
|
104530eafc | ||
|
|
6cfde37e98 | ||
|
|
c5cebc1d19 | ||
|
|
697b18b366 | ||
|
|
5b2935e36c | ||
|
|
5dc644edb4 | ||
|
|
1211958b4e | ||
|
|
218e7200bb | ||
|
|
2bf78a94e0 | ||
|
|
cc8e02380f | ||
|
|
c66d977c1d | ||
|
|
4b77686566 | ||
|
|
a6144f794b | ||
|
|
bab1c3614c | ||
|
|
de1545e19a | ||
|
|
ce629e0391 | ||
|
|
6c03b4aaa5 | ||
|
|
0627e23e92 | ||
|
|
4e6f4f99d3 | ||
|
|
f30afc184e | ||
|
|
228cf3fbf4 | ||
|
|
47957faa16 | ||
|
|
235e24d44f | ||
|
|
e0ecffed70 | ||
|
|
fa913ec1a7 | ||
|
|
49b2eedd1c | ||
|
|
63fb822f8a | ||
|
|
f38d738ee2 | ||
|
|
eda6ba37ec | ||
|
|
b594f8c834 | ||
|
|
bc45aaf604 | ||
|
|
fb94a819c7 | ||
|
|
8589731a27 | ||
|
|
c1dccd7db1 | ||
|
|
af3a3f4bf6 | ||
|
|
349fae00d9 | ||
|
|
10cae50b34 | ||
|
|
5b541375a3 | ||
|
|
ded6964953 | ||
|
|
9b2111dc01 | ||
|
|
71f52f9d90 | ||
|
|
86e5e01403 | ||
|
|
77680ce9d2 | ||
|
|
32e6a433e8 | ||
|
|
7c9148dd47 | ||
|
|
957452d6f9 | ||
|
|
e6156bef05 | ||
|
|
441367b1a1 | ||
|
|
629ae1ac05 | ||
|
|
0d9e95d96a | ||
|
|
9cf7963f44 | ||
|
|
acd7435d31 | ||
|
|
7af28c267d | ||
|
|
79120d53ec | ||
|
|
6a2987a7b2 | ||
|
|
d1372abf46 | ||
|
|
98b5d88d67 | ||
|
|
86414760e6 | ||
|
|
a1fbc41146 | ||
|
|
5702cfc935 | ||
|
|
7b5a33f878 | ||
|
|
ea092a9472 | ||
|
|
18a1e0fadc | ||
|
|
437e69ef1b | ||
|
|
dd408ffd00 | ||
|
|
bce8924dd6 | ||
|
|
fe26c1de17 | ||
|
|
01df42a5f3 | ||
|
|
968cf3c03f | ||
|
|
586cbd4a0d | ||
|
|
940e42f130 | ||
|
|
3f80151a6f | ||
|
|
7e89b97daf | ||
|
|
d63fb62367 | ||
|
|
29e9ea6344 | ||
|
|
9a74ef15c3 | ||
|
|
a9ae04c152 | ||
|
|
8e35e1233b | ||
|
|
5610976720 | ||
|
|
1133272668 | ||
|
|
bbebb6419b | ||
|
|
b67cb81359 | ||
|
|
f3c82a317a | ||
|
|
f426d60ff1 | ||
|
|
b2fdad9e47 | ||
|
|
87971a28f5 | ||
|
|
96d62b3367 | ||
|
|
7516068662 | ||
|
|
6d1ba5870a | ||
|
|
00a16f3120 | ||
|
|
d3d7bd33f8 | ||
|
|
069f928edc | ||
|
|
0f3674f531 | ||
|
|
a21ffde34e | ||
|
|
1208d22547 | ||
|
|
3a27f8a76a | ||
|
|
dff2b19c56 | ||
|
|
a58db4f023 | ||
|
|
7bcd43dbee | ||
|
|
7c5473400e | ||
|
|
23c8a43e37 | ||
|
|
2d37634ee9 | ||
|
|
e026bfc9a2 | ||
|
|
5a31de7955 | ||
|
|
20e57994cf | ||
|
|
59ef88c959 | ||
|
|
1d1f00bdef | ||
|
|
47b26e497f | ||
|
|
733842cad4 | ||
|
|
73363f1272 | ||
|
|
0cd7682012 | ||
|
|
25a44371e3 | ||
|
|
f2455c875e | ||
|
|
2e315a3c62 | ||
|
|
d2fa86579b | ||
|
|
23e7df21b1 | ||
|
|
a305a6c1c6 | ||
|
|
635fb8882b | ||
|
|
fe6f49632c | ||
|
|
c3546577b6 | ||
|
|
c913951f7b | ||
|
|
3902b42096 | ||
|
|
ae573f941a | ||
|
|
20ea54cf0c | ||
|
|
0b4e78c0b2 | ||
|
|
57844a07e2 | ||
|
|
ba18551d6e | ||
|
|
c7b0d77970 | ||
|
|
1af19eabf0 | ||
|
|
32ea0869e5 | ||
|
|
e04007a7c6 | ||
|
|
1a3a15e4cf | ||
|
|
679adbfa5e | ||
|
|
d285efe3b5 | ||
|
|
a64c8d3205 | ||
|
|
82c52c3fab | ||
|
|
dd1e90fcd7 | ||
|
|
ef0155636a | ||
|
|
9d550fbffa | ||
|
|
fcef691e0a | ||
|
|
839c172612 | ||
|
|
9f49ac8249 | ||
|
|
fde550ef66 | ||
|
|
2b3993a595 | ||
|
|
de3e86791c | ||
|
|
6f66f77b09 | ||
|
|
d853aba955 | ||
|
|
cd62693792 | ||
|
|
cb4c8f3878 | ||
|
|
4d371d5f5f | ||
|
|
72963b01e1 | ||
|
|
80a18e82b7 | ||
|
|
574e687734 | ||
|
|
c725ec50ff | ||
|
|
c6f6e306e0 | ||
|
|
b847135321 | ||
|
|
ca3dc8792a | ||
|
|
ac3156df3a | ||
|
|
6aa6e390ee | ||
|
|
e4c10de8b6 | ||
|
|
b3b3aeef73 | ||
|
|
056dcee70c | ||
|
|
13c5bbe916 | ||
|
|
bbe3bd0304 | ||
|
|
f9b279c49d | ||
|
|
b61360a0c0 | ||
|
|
27ee6f02d6 | ||
|
|
2097ebb414 | ||
|
|
587df9145f | ||
|
|
2c5060e14f | ||
|
|
75aaa963d2 | ||
|
|
34ebfe40c3 | ||
|
|
2a54310876 | ||
|
|
8bccd325ec | ||
|
|
ad935311f0 | ||
|
|
19fc0274de | ||
|
|
6cc9e5de11 | ||
|
|
bf7ca29487 | ||
|
|
3cf426662e | ||
|
|
1fd07c2c83 | ||
|
|
f880c58ccc | ||
|
|
49e59e572e | ||
|
|
9d1fc8328f | ||
|
|
c9ff09c7c2 | ||
|
|
ceb2b664e0 | ||
|
|
b5b4c06179 | ||
|
|
d70f9c0b2b | ||
|
|
e847f82fab | ||
|
|
807a594c2d | ||
|
|
ced73c56cc | ||
|
|
9c6f6fa624 | ||
|
|
533176f9ff | ||
|
|
66c1663495 | ||
|
|
30c7715d3f | ||
|
|
093911a1dc | ||
|
|
d83f1f8235 | ||
|
|
50e78d717d | ||
|
|
6ca8201982 | ||
|
|
99a87bd65b | ||
|
|
f9f8e12c35 | ||
|
|
dcaf4205cb | ||
|
|
4735359466 | ||
|
|
211242452b | ||
|
|
6ae5cb7b64 | ||
|
|
997ad60c50 | ||
|
|
44f982bca7 | ||
|
|
0456f5a06b | ||
|
|
b346f58b73 | ||
|
|
ace877ce28 | ||
|
|
cf0cbee02d | ||
|
|
98e7eeb72a | ||
|
|
803c124136 | ||
|
|
d9d17737c3 | ||
|
|
46850f9574 | ||
|
|
314d37155a | ||
|
|
ddcdb654ad | ||
|
|
cdec5c515b | ||
|
|
1eb13dfa5e | ||
|
|
dda628ca81 | ||
|
|
13ad9f510d | ||
|
|
4c769dfbf9 | ||
|
|
ccd19b3e56 | ||
|
|
81d0ea5697 | ||
|
|
43de8bfaac | ||
|
|
86b539a9ea | ||
|
|
81a5dd612f | ||
|
|
9c00c7077b | ||
|
|
49ff962f69 | ||
|
|
c83aac966e | ||
|
|
9918dc2591 | ||
|
|
3ab372ccec | ||
|
|
843c0c59be | ||
|
|
d93812b0bd | ||
|
|
1c68c94c05 | ||
|
|
c66060be10 | ||
|
|
e7b269a9f6 | ||
|
|
d5b19850fb | ||
|
|
e7fe8192bb | ||
|
|
ebe0979563 | ||
|
|
a379304362 | ||
|
|
bec7c510c2 | ||
|
|
6a187ac67e | ||
|
|
9641b51925 | ||
|
|
b96a30ed88 | ||
|
|
2ec0e9e8f3 | ||
|
|
51afce3a97 | ||
|
|
dea80f34ca | ||
|
|
994271e2f6 | ||
|
|
59902d1fd8 | ||
|
|
b92f0a5458 | ||
|
|
251f6d6121 | ||
|
|
0d8c83a5cb | ||
|
|
3624d96c78 | ||
|
|
3213934d42 | ||
|
|
246212789f | ||
|
|
c7f7fd974b | ||
|
|
5cf2f5767a | ||
|
|
e8e580cf6c | ||
|
|
74a78f37d0 | ||
|
|
8d3ebb5a55 | ||
|
|
3628d2b4c1 | ||
|
|
d96d0eeb18 | ||
|
|
bd401a1407 | ||
|
|
070730be0a | ||
|
|
94e7d0a189 | ||
|
|
c28b71f8e0 | ||
|
|
27f015ecd1 | ||
|
|
8596627ced | ||
|
|
d4cca82a22 | ||
|
|
dd9ecd5f65 | ||
|
|
81b0c1302d | ||
|
|
b9b0b87545 | ||
|
|
040b2b9653 | ||
|
|
eccd6f6cac | ||
|
|
0ce19357f6 | ||
|
|
d762d24f73 | ||
|
|
eb917463ad | ||
|
|
c2189de88f | ||
|
|
2fe5c150bc | ||
|
|
5b8e3b51b0 | ||
|
|
f27a136430 | ||
|
|
fb4d97e8a9 | ||
|
|
5ffcd28ca4 | ||
|
|
b3c8f91a84 | ||
|
|
c78a5a9fdf | ||
|
|
858c18468b | ||
|
|
cd74809086 | ||
|
|
e21c5f37d0 | ||
|
|
2d5023fda2 | ||
|
|
98200fed19 | ||
|
|
c48a8bd6ae | ||
|
|
16ea36eb4b | ||
|
|
8f6f65f4c9 | ||
|
|
b81f1fc774 | ||
|
|
c85bd45604 | ||
|
|
5c86f894be | ||
|
|
86e3c90e32 | ||
|
|
93be8c201b | ||
|
|
894bc7de3c | ||
|
|
fc91fd2b1a | ||
|
|
a06a005124 | ||
|
|
e9425e101d | ||
|
|
04b404f3c9 | ||
|
|
80225015c5 | ||
|
|
0c4dfd565e | ||
|
|
0c8d7a1e49 | ||
|
|
a2150c70bb | ||
|
|
160cf5352c | ||
|
|
3b6b3c2040 | ||
|
|
63dd3917e2 | ||
|
|
2333c235f1 | ||
|
|
dd062ceeeb | ||
|
|
e591a66b67 | ||
|
|
b8a459e83d | ||
|
|
7684c343d6 | ||
|
|
e0c055fd74 | ||
|
|
d1eee7b3a9 | ||
|
|
1342c6f1e1 | ||
|
|
6453bafd1d | ||
|
|
beb8047083 | ||
|
|
1df06e230d | ||
|
|
39a34fa343 | ||
|
|
96c64d113e | ||
|
|
66ca225c76 | ||
|
|
0de4cfbf80 | ||
|
|
42980fe6df | ||
|
|
1592cf2b1b | ||
|
|
a9166f8546 | ||
|
|
b77978fd1e | ||
|
|
4330cb9462 | ||
|
|
af8aecfdde | ||
|
|
13533bd053 | ||
|
|
cd0f0a428a | ||
|
|
6758d67942 | ||
|
|
dc5b7c19a6 | ||
|
|
2a54306e8b | ||
|
|
7a6aaafe52 | ||
|
|
1469c53048 | ||
|
|
9992ab18fc | ||
|
|
18af08460f | ||
|
|
39ee9972b8 | ||
|
|
e500333bcd | ||
|
|
4498d75b9b | ||
|
|
1df53b169f | ||
|
|
ff61c1cecd | ||
|
|
60a01817ce | ||
|
|
1a87cfedb5 | ||
|
|
5bd447d2a2 | ||
|
|
a4fc58e908 | ||
|
|
4e8e335b7b | ||
|
|
947576363e | ||
|
|
9da390bc0a | ||
|
|
ef639610e7 | ||
|
|
6c3b562822 | ||
|
|
f1413569ce | ||
|
|
fcc9dff5b9 | ||
|
|
d9ae3ed64c | ||
|
|
618926b124 | ||
|
|
dca7ece2ca | ||
|
|
ccecebfeff | ||
|
|
1752f1382f | ||
|
|
53a59434d8 | ||
|
|
18b6c9059b | ||
|
|
715539804b | ||
|
|
b02eb28fd1 | ||
|
|
07f1e77d23 | ||
|
|
6980e0863d | ||
|
|
42bfeec2e9 | ||
|
|
795743ce22 | ||
|
|
ccddd6fb94 | ||
|
|
e559771a08 | ||
|
|
b870a77176 | ||
|
|
62eaead3a3 | ||
|
|
d7808d9cb4 | ||
|
|
8267c422b5 | ||
|
|
b46e141762 | ||
|
|
cfa140fb12 | ||
|
|
c52bf450f5 | ||
|
|
4cc2f00273 | ||
|
|
ade32e7ba7 | ||
|
|
cc5c0ec6b8 | ||
|
|
c167375fe1 | ||
|
|
65871a53a6 | ||
|
|
1ff580498c | ||
|
|
ae896d0bf6 | ||
|
|
e738535294 | ||
|
|
8072718462 | ||
|
|
7adade0df5 | ||
|
|
ab50c10d08 | ||
|
|
ffa69d48de | ||
|
|
e2c840cb23 | ||
|
|
fa891536df | ||
|
|
7012634f32 | ||
|
|
663b524fb3 | ||
|
|
0b3302af70 | ||
|
|
ffd07dbfce | ||
|
|
6eed46b024 | ||
|
|
e6ad87072d | ||
|
|
dd2fbd6c45 | ||
|
|
1779142805 | ||
|
|
186621c6ff | ||
|
|
11d4c0d2a2 | ||
|
|
aa69b5b184 | ||
|
|
cb378365e1 | ||
|
|
30fa9dddc5 | ||
|
|
4d8d3b6d25 | ||
|
|
523cc17ffe | ||
|
|
825e5f1162 | ||
|
|
3c016bead1 | ||
|
|
800a7719cf | ||
|
|
e3fb05c1a8 | ||
|
|
bc9c70d50f | ||
|
|
a8fe4e7a2d | ||
|
|
bed19e5d8c | ||
|
|
4adbf40861 | ||
|
|
8d01fb1c18 | ||
|
|
a610528075 | ||
|
|
3e25eb0998 | ||
|
|
0d92ce49da | ||
|
|
4179bb5b06 | ||
|
|
d581703ab4 | ||
|
|
9241fdc32a | ||
|
|
a2baaccc6e | ||
|
|
c130d74efc | ||
|
|
5e64efce4f | ||
|
|
17c2b4cc7d | ||
|
|
bad6726f5e | ||
|
|
26c4168160 | ||
|
|
49ea1680f8 | ||
|
|
79443aa104 | ||
|
|
342fae06a6 | ||
|
|
7022afa86e | ||
|
|
a44dd88806 | ||
|
|
d0a4c908a1 | ||
|
|
61dd8a3031 | ||
|
|
770bdb9571 | ||
|
|
719e20d470 | ||
|
|
fedd1a4335 | ||
|
|
ebd47081ca | ||
|
|
1546e93d67 | ||
|
|
94325b5ed6 | ||
|
|
86dbf89188 | ||
|
|
f119e4baca | ||
|
|
494467406a | ||
|
|
0966938dc8 | ||
|
|
bc5709e1bd | ||
|
|
767d0b7da9 | ||
|
|
d5410a93c8 | ||
|
|
129c64235f | ||
|
|
1190d5de7d | ||
|
|
8021150ea2 | ||
|
|
88c351f0fa | ||
|
|
e78bda60a0 | ||
|
|
22bdafecbb | ||
|
|
6f9ed6ffe5 | ||
|
|
fbdc1e8714 | ||
|
|
bac6bf65af | ||
|
|
5ad7d0ebc2 | ||
|
|
7f66a61801 | ||
|
|
2bd9f4124f | ||
|
|
705fc93363 | ||
|
|
77db9164b8 | ||
|
|
a42e3bb09e | ||
|
|
69156b2f0e | ||
|
|
56a15c2036 | ||
|
|
2db0b3cfa7 | ||
|
|
41bfba515c | ||
|
|
4710522e87 | ||
|
|
85cf965d54 | ||
|
|
87166eed94 | ||
|
|
6e9f162bd4 | ||
|
|
8063822243 | ||
|
|
c236ba713d | ||
|
|
c9e942a972 | ||
|
|
5383972e96 | ||
|
|
3ea2099245 | ||
|
|
49145c1902 | ||
|
|
d34e30d892 | ||
|
|
e1bcc298a0 | ||
|
|
33545ca6c4 | ||
|
|
562f8a0895 | ||
|
|
7e42513faf | ||
|
|
596ecdcca0 | ||
|
|
5d83ce82af | ||
|
|
4fb72e9db7 | ||
|
|
c4ca8f4bf8 | ||
|
|
a4b970d97d | ||
|
|
8cfc9cd7a8 | ||
|
|
e39162fc52 | ||
|
|
6bf69297c6 | ||
|
|
95d282854d | ||
|
|
c59a4565bc | ||
|
|
79e424182b | ||
|
|
fe867d03f6 | ||
|
|
ae4e695425 | ||
|
|
6407add888 | ||
|
|
840d742230 | ||
|
|
a798e12d7f | ||
|
|
d80e914b34 | ||
|
|
395c34ecab | ||
|
|
1e8317159e | ||
|
|
d0e08496be | ||
|
|
377fb8f50a | ||
|
|
4220a747cb | ||
|
|
e24f29d514 | ||
|
|
54aa8efc98 | ||
|
|
89fdb1214a | ||
|
|
01275fb211 | ||
|
|
5970c4ef95 | ||
|
|
24b40d4c03 | ||
|
|
146253afb6 | ||
|
|
c759236ccb | ||
|
|
ce39238210 | ||
|
|
330889e50b | ||
|
|
1be9568a02 | ||
|
|
8ce06ccdf6 | ||
|
|
1dbac4fd79 | ||
|
|
d80fbea51d | ||
|
|
e239210279 | ||
|
|
e7c8bcb3a4 | ||
|
|
0f3540b1ff | ||
|
|
5ea92d95a7 | ||
|
|
5a32e270ad | ||
|
|
e857e332ac | ||
|
|
44302cbf96 | ||
|
|
6b306f64d5 | ||
|
|
e1366eb91d | ||
|
|
2a1a8e72e3 | ||
|
|
191cdff231 | ||
|
|
7d8ee8b6b5 | ||
|
|
01a1036640 | ||
|
|
7850cbeaef | ||
|
|
af931d73c7 | ||
|
|
3a7cd93765 | ||
|
|
a34bcbd951 | ||
|
|
b0bd6470f3 | ||
|
|
93398d6d0f | ||
|
|
4e343408dc | ||
|
|
1375d650a7 | ||
|
|
d7f1bcaa19 | ||
|
|
2bb396f58f | ||
|
|
af1ab60786 | ||
|
|
430de9bd15 | ||
|
|
1c4171c9eb | ||
|
|
443bbcbf90 | ||
|
|
83ee040b4d | ||
|
|
980d89f739 | ||
|
|
f785974cc1 | ||
|
|
547b9b96cc | ||
|
|
989477efad | ||
|
|
6d273e4d9c | ||
|
|
d7fd4a2864 | ||
|
|
5e27a28ab0 | ||
|
|
67e550f2e7 | ||
|
|
aec9b97b0c | ||
|
|
e4a95b2c26 | ||
|
|
6e82c376f6 | ||
|
|
4f1bc505cc | ||
|
|
484433b180 | ||
|
|
70f6af407e | ||
|
|
7556a74f19 | ||
|
|
b4809e7170 | ||
|
|
0df20eb300 | ||
|
|
5bfdf4267e | ||
|
|
f3cdf217ea | ||
|
|
f453a752bb | ||
|
|
28a913894d | ||
|
|
0ffacf22e6 | ||
|
|
13e87a8e58 | ||
|
|
30d57bed06 | ||
|
|
965c30956d | ||
|
|
09fec6815f | ||
|
|
00bd19194a | ||
|
|
4a7bc55afa | ||
|
|
9c316e6988 | ||
|
|
a3c3cbb722 | ||
|
|
b42380ced6 | ||
|
|
bc2f077c37 | ||
|
|
498d63b620 | ||
|
|
566a45017e | ||
|
|
7a40dad90f | ||
|
|
a9e843b38e | ||
|
|
9a76719544 | ||
|
|
472c8213db | ||
|
|
a4f9c022ff | ||
|
|
97514884b1 | ||
|
|
4980efa9b7 | ||
|
|
34cf130e4d | ||
|
|
21aa7b66ce | ||
|
|
11b95bed20 | ||
|
|
ca7c8f0074 | ||
|
|
d7063d0b81 | ||
|
|
091e6b31dc | ||
|
|
4cd6132758 | ||
|
|
9c263a9886 | ||
|
|
c11fc96019 | ||
|
|
4f2b5eb749 | ||
|
|
d7013dc2b3 | ||
|
|
0bbe301120 | ||
|
|
37aa6f88b0 | ||
|
|
e3890febaa | ||
|
|
a4da2a9f00 | ||
|
|
3005a210c6 | ||
|
|
027433fcdc | ||
|
|
7ff9e4907f | ||
|
|
87428d7a4b | ||
|
|
822ffba313 | ||
|
|
3c850b65c8 | ||
|
|
698c9f802f | ||
|
|
1d5f3c8f66 | ||
|
|
ec78afb966 | ||
|
|
3a7656336e | ||
|
|
90bf3c9236 | ||
|
|
d4f8338937 | ||
|
|
5125cd09c7 | ||
|
|
67cfec9063 | ||
|
|
81e52df7bf | ||
|
|
4b26d03dba | ||
|
|
216c580d13 | ||
|
|
7907a50817 | ||
|
|
769edf470b | ||
|
|
68fffdfb66 | ||
|
|
26781e6fea | ||
|
|
d5c95bc3ee | ||
|
|
ace549ab21 | ||
|
|
0cbae39e41 | ||
|
|
e9f5da27f8 | ||
|
|
3052dac82e | ||
|
|
2bb0483473 | ||
|
|
389fd1e5be | ||
|
|
a76f3ac2fe | ||
|
|
2fc4855f29 | ||
|
|
869329ee68 | ||
|
|
b2963aa6e0 | ||
|
|
9859b3e1d6 | ||
|
|
cd19fa283f | ||
|
|
b6af7c73ec | ||
|
|
ac64d202af | ||
|
|
df44ad3fac | ||
|
|
0a2f4814ac | ||
|
|
e6b4707a5d | ||
|
|
9a3468e3a0 | ||
|
|
73da43a7be | ||
|
|
aa88d94755 | ||
|
|
51562cd8d2 | ||
|
|
822ca3c10b | ||
|
|
2b6fd699e7 | ||
|
|
b9aa0dc3b3 | ||
|
|
f380d69b9d | ||
|
|
f06d7e6084 | ||
|
|
00a5dd32c0 | ||
|
|
a9ddd44d73 | ||
|
|
2f9fcc63c3 | ||
|
|
0bbd0bdd99 | ||
|
|
6396dc9a85 | ||
|
|
a3a00464bc | ||
|
|
1da99a0671 | ||
|
|
9304921132 | ||
|
|
c13a1d752b | ||
|
|
51d0e02543 | ||
|
|
e8752aca7e | ||
|
|
7dd50ea127 | ||
|
|
d09c81d1c6 | ||
|
|
26f0c7b0bf | ||
|
|
2e9e2ae566 | ||
|
|
53feb6cb19 | ||
|
|
05d14f5421 | ||
|
|
1a164c3628 | ||
|
|
86a586025f | ||
|
|
06cbd602c1 | ||
|
|
055d0920d7 | ||
|
|
f2ee98fe31 | ||
|
|
207cef9602 | ||
|
|
c4134a95e2 | ||
|
|
fb5ecec455 | ||
|
|
650d9c91a1 | ||
|
|
fc8d2a3b83 |
No files matched your search
@@ -0,0 +1,37 @@
|
|||||||
|
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||||
|
|
||||||
|
# Keep inherited review profiles, automatic reviews, tools, and pre-merge checks.
|
||||||
|
inheritance: true
|
||||||
|
|
||||||
|
reviews:
|
||||||
|
# Trim supplemental output and keep the remaining walkthrough collapsed.
|
||||||
|
high_level_summary: false
|
||||||
|
review_status: false
|
||||||
|
review_details: false
|
||||||
|
collapse_walkthrough: true
|
||||||
|
changed_files_summary: false
|
||||||
|
sequence_diagrams: false
|
||||||
|
estimate_code_review_effort: false
|
||||||
|
related_issues: false
|
||||||
|
related_prs: false
|
||||||
|
in_progress_fortune: false
|
||||||
|
poem: false
|
||||||
|
enable_prompt_for_ai_agents: false
|
||||||
|
|
||||||
|
# Hide standard finishing-touch controls; this also disables their commands.
|
||||||
|
finishing_touches:
|
||||||
|
docstrings:
|
||||||
|
enabled: false
|
||||||
|
unit_tests:
|
||||||
|
enabled: false
|
||||||
|
simplify:
|
||||||
|
enabled: false
|
||||||
|
autofix:
|
||||||
|
enabled: false
|
||||||
|
fix_ci:
|
||||||
|
enabled: false
|
||||||
|
resolve_merge_conflict:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
chat:
|
||||||
|
art: false
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
name: Game Emulation Bug Report
|
name: Game Emulation Status Report
|
||||||
description: Report KytyPS5 game boot, crash, freeze, rendering, audio, input, or gameplay behavior problems.
|
description: Submit a KytyPS5 game compatibility result, including boot, rendering, audio, input, or gameplay behavior.
|
||||||
title: "[GAME BUG]: "
|
title: "[GAME STATUS]: "
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -10,6 +10,7 @@ body:
|
|||||||
Before submitting, please:
|
Before submitting, please:
|
||||||
- Search existing issues first.
|
- Search existing issues first.
|
||||||
- Re-test with a clean game dump.
|
- Re-test with a clean game dump.
|
||||||
|
- Use the latest KytyPS5 build available to you.
|
||||||
- Attach the complete log file instead of edited snippets.
|
- Attach the complete log file instead of edited snippets.
|
||||||
|
|
||||||
- type: markdown
|
- type: markdown
|
||||||
@@ -44,6 +45,28 @@ body:
|
|||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: compatibility-status
|
||||||
|
attributes:
|
||||||
|
label: Compatibility status
|
||||||
|
description: Select the furthest point the game consistently reaches on this build.
|
||||||
|
options:
|
||||||
|
- "Doesn't boot"
|
||||||
|
- Logo
|
||||||
|
- Main menu
|
||||||
|
- In game
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
**Status guide**
|
||||||
|
- **Doesn't boot:** The game does not show its first logo or startup screen.
|
||||||
|
- **Logo:** The game shows a logo or startup screen but does not reach the main menu.
|
||||||
|
- **Main menu:** The game reaches its menus but does not enter gameplay.
|
||||||
|
- **In game:** The game reaches controllable gameplay, even if bugs prevent completion.
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: regression-builds
|
id: regression-builds
|
||||||
attributes:
|
attributes:
|
||||||
@@ -54,22 +77,22 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## Problem details
|
## Test result
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: what-happened
|
id: what-happened
|
||||||
attributes:
|
attributes:
|
||||||
label: What happened?
|
label: Result details
|
||||||
description: Describe the failure or incorrect behavior as directly as possible.
|
description: Describe what worked, what failed, and any visible or audible problems.
|
||||||
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue."
|
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue. Audio plays correctly until the freeze."
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: steps-to-reproduce
|
id: steps-to-reproduce
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to reproduce
|
label: Steps to reproduce the result
|
||||||
description: List the shortest path that triggers the problem from a fresh KytyPS5 start.
|
description: List the shortest path to the reported status or failure from a fresh KytyPS5 start.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1. Open KytyPS5.
|
1. Open KytyPS5.
|
||||||
2. Boot the game.
|
2. Boot the game.
|
||||||
@@ -81,7 +104,7 @@ body:
|
|||||||
id: expected-behavior
|
id: expected-behavior
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected behavior
|
label: Expected behavior
|
||||||
description: Explain what should happen at the same point in the game.
|
description: Explain what should happen at the same point in the game. If there is no known problem, write "No known issue".
|
||||||
placeholder: "Example: The save should load and gameplay should continue normally."
|
placeholder: "Example: The save should load and gameplay should continue normally."
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -6,9 +6,32 @@ on:
|
|||||||
branches: [ main, master ]
|
branches: [ main, master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
|
||||||
|
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
metadata:
|
||||||
|
name: Prepare build metadata
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
release_tag: ${{ steps.release_tag.outputs.value }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Generate release tag
|
||||||
|
id: release_tag
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "value=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
name: Build KytyPS5 (Windows)
|
name: Build KytyPS5 (Windows)
|
||||||
|
needs: metadata
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -20,9 +43,6 @@ jobs:
|
|||||||
- name: Setup Visual Studio environment
|
- name: Setup Visual Studio environment
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
|
||||||
- name: Setup Ninja
|
|
||||||
uses: seanmiddleditch/gha-setup-ninja@v5
|
|
||||||
|
|
||||||
- name: Locate vcpkg
|
- name: Locate vcpkg
|
||||||
id: vcpkg
|
id: vcpkg
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -60,6 +80,9 @@ jobs:
|
|||||||
arch: win64_msvc2022_64
|
arch: win64_msvc2022_64
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
- name: Setup sccache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.11
|
||||||
|
|
||||||
- name: Verify toolchain
|
- name: Verify toolchain
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
@@ -73,22 +96,32 @@ jobs:
|
|||||||
- name: Configure
|
- name: Configure
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
cmake -S src -B _Build/windows ^
|
cmake -S . -B _Build/windows ^
|
||||||
-G Ninja ^
|
-G Ninja ^
|
||||||
-DCMAKE_BUILD_TYPE=Release ^
|
-DCMAKE_BUILD_TYPE=Release ^
|
||||||
|
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON ^
|
||||||
|
-DKYTY_BUILD_ORIGIN=%KYTY_BUILD_ORIGIN% ^
|
||||||
|
-DKYTY_BUILD_REPOSITORY=%KYTY_BUILD_REPOSITORY% ^
|
||||||
|
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
|
||||||
-DCMAKE_C_COMPILER=clang-cl ^
|
-DCMAKE_C_COMPILER=clang-cl ^
|
||||||
-DCMAKE_CXX_COMPILER=clang-cl ^
|
-DCMAKE_CXX_COMPILER=clang-cl ^
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=sccache ^
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^
|
||||||
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
|
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
cmake --build _Build/windows --target launcher audio_out2_port_tests virtual_memory_allocation_tests --parallel
|
cmake --build _Build/windows --target launcher ^
|
||||||
|
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
|
||||||
|
shader_recompiler_compute_tests ^
|
||||||
|
resource_tracking_tests scalar_provenance_tests ^
|
||||||
|
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|virtual_memory_allocation)$"
|
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -104,6 +137,7 @@ jobs:
|
|||||||
|
|
||||||
macos:
|
macos:
|
||||||
name: Build KytyPS5 (macOS)
|
name: Build KytyPS5 (macOS)
|
||||||
|
needs: metadata
|
||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -122,7 +156,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: brew install glslang ninja
|
run: brew install glslang
|
||||||
|
|
||||||
- name: Install Qt 6.10.3
|
- name: Install Qt 6.10.3
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
@@ -133,6 +167,9 @@ jobs:
|
|||||||
arch: clang_64
|
arch: clang_64
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
- name: Setup sccache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.11
|
||||||
|
|
||||||
- name: Verify toolchain
|
- name: Verify toolchain
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -147,26 +184,35 @@ jobs:
|
|||||||
- name: Configure
|
- name: Configure
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cmake -S src -B _Build/macos \
|
cmake -S . -B _Build/macos \
|
||||||
-G Ninja \
|
-G Ninja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
|
||||||
|
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
|
||||||
|
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
|
||||||
|
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
|
||||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||||
-DCMAKE_C_COMPILER=clang \
|
-DCMAKE_C_COMPILER=clang \
|
||||||
-DCMAKE_CXX_COMPILER=clang++ \
|
-DCMAKE_CXX_COMPILER=clang++ \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||||
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
|
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cmake --build _Build/macos \
|
cmake --build _Build/macos \
|
||||||
--target launcher audio_out2_port_tests virtual_memory_allocation_tests \
|
--target launcher audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||||
|
memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \
|
||||||
|
resource_tracking_tests scalar_provenance_tests \
|
||||||
|
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||||
--parallel
|
--parallel
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ctest --test-dir _Build/macos --output-on-failure \
|
ctest --test-dir _Build/macos --output-on-failure \
|
||||||
-R '^(audio_out2_port|virtual_memory_allocation)$'
|
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -181,38 +227,73 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
archive="$RUNNER_TEMP/MoltenVK-macos.tar"
|
archive="$RUNNER_TEMP/MoltenVK-macos.tar"
|
||||||
package="$RUNNER_TEMP/MoltenVK"
|
package="$RUNNER_TEMP/MoltenVK"
|
||||||
|
install_dir="_Build/macos/install"
|
||||||
|
app_dir="$install_dir/KytyPS5.app"
|
||||||
|
moltenvk="$install_dir/libMoltenVK.dylib"
|
||||||
curl --fail --location --retry 3 \
|
curl --fail --location --retry 3 \
|
||||||
--output "$archive" \
|
--output "$archive" \
|
||||||
"https://github.com/KhronosGroup/MoltenVK/releases/download/$MOLTENVK_VERSION/MoltenVK-macos.tar"
|
"https://github.com/KhronosGroup/MoltenVK/releases/download/$MOLTENVK_VERSION/MoltenVK-macos.tar"
|
||||||
echo "$MOLTENVK_SHA256 $archive" | shasum -a 256 --check
|
echo "$MOLTENVK_SHA256 $archive" | shasum -a 256 --check
|
||||||
tar -xf "$archive" -C "$RUNNER_TEMP"
|
tar -xf "$archive" -C "$RUNNER_TEMP"
|
||||||
|
test -d "$app_dir"
|
||||||
install -m 755 \
|
install -m 755 \
|
||||||
"$package/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" \
|
"$package/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" \
|
||||||
_Build/macos/install/libMoltenVK.dylib
|
"$moltenvk"
|
||||||
install -m 644 "$package/LICENSE" _Build/macos/install/LICENSE.MoltenVK
|
install -m 644 "$package/LICENSE" "$install_dir/LICENSE.MoltenVK"
|
||||||
codesign --force --sign - --timestamp=none _Build/macos/install/libMoltenVK.dylib
|
codesign --force --sign - --timestamp=none "$moltenvk"
|
||||||
|
install -d -m 755 "$app_dir/Contents/Frameworks" "$app_dir/Contents/Resources"
|
||||||
|
install -m 755 "$moltenvk" "$app_dir/Contents/Frameworks/libMoltenVK.dylib"
|
||||||
|
install -m 644 "$package/LICENSE" "$app_dir/Contents/Resources/LICENSE.MoltenVK"
|
||||||
|
codesign --force --sign - --timestamp=none "$app_dir"
|
||||||
|
|
||||||
- name: Verify artifacts
|
- name: Verify artifacts
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
file _Build/macos/install/launcher
|
install_dir="_Build/macos/install"
|
||||||
file _Build/macos/install/kyty_emulator
|
app_dir="$install_dir/KytyPS5.app"
|
||||||
file _Build/macos/install/libMoltenVK.dylib
|
test -d "$app_dir"
|
||||||
lipo _Build/macos/install/launcher -verify_arch x86_64
|
for binary in \
|
||||||
lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
|
"$install_dir/kyty_emulator" \
|
||||||
lipo _Build/macos/install/libMoltenVK.dylib -verify_arch x86_64
|
"$install_dir/libMoltenVK.dylib" \
|
||||||
codesign --verify --strict _Build/macos/install/kyty_emulator
|
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||||
codesign --verify --strict _Build/macos/install/libMoltenVK.dylib
|
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||||
|
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||||
|
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||||
|
file "$binary"
|
||||||
|
lipo "$binary" -verify_arch x86_64
|
||||||
|
done
|
||||||
|
for binary in \
|
||||||
|
"$install_dir/kyty_emulator" \
|
||||||
|
"$install_dir/libMoltenVK.dylib" \
|
||||||
|
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||||
|
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||||
|
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||||
|
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||||
|
codesign --verify --strict "$binary"
|
||||||
|
done
|
||||||
|
codesign --verify --deep --strict --verbose=2 "$app_dir"
|
||||||
|
|
||||||
|
- name: Package macOS artifacts
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
install_dir="_Build/macos/install"
|
||||||
|
archive="$RUNNER_TEMP/KytyPS5-macOS-x86_64.zip"
|
||||||
|
(
|
||||||
|
cd "$install_dir"
|
||||||
|
zip -qry "$archive" .
|
||||||
|
)
|
||||||
|
unzip -t "$archive"
|
||||||
|
|
||||||
- name: Upload macOS artifacts
|
- name: Upload macOS artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: KytyPS5-macOS-x86_64
|
name: KytyPS5-macOS-x86_64
|
||||||
path: _Build/macos/install/**
|
path: ${{ runner.temp }}/KytyPS5-macOS-x86_64.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
name: Build KytyPS5 (Linux)
|
name: Build KytyPS5 (Linux)
|
||||||
|
needs: metadata
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -242,8 +323,9 @@ jobs:
|
|||||||
libxkbcommon-dev \
|
libxkbcommon-dev \
|
||||||
libxrandr-dev \
|
libxrandr-dev \
|
||||||
libxss-dev \
|
libxss-dev \
|
||||||
|
libxtst-dev \
|
||||||
lld \
|
lld \
|
||||||
ninja-build \
|
pkg-config \
|
||||||
wayland-protocols
|
wayland-protocols
|
||||||
|
|
||||||
- name: Install Qt 6.10.3
|
- name: Install Qt 6.10.3
|
||||||
@@ -255,6 +337,9 @@ jobs:
|
|||||||
arch: linux_gcc_64
|
arch: linux_gcc_64
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
- name: Setup sccache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.11
|
||||||
|
|
||||||
- name: Verify toolchain
|
- name: Verify toolchain
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -270,15 +355,21 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir -p _Build
|
mkdir -p _Build
|
||||||
cmake -S src -B _Build/linux \
|
cmake -S . -B _Build/linux \
|
||||||
-G Ninja \
|
-G Ninja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
|
||||||
|
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
|
||||||
|
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
|
||||||
|
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
|
||||||
-DCMAKE_C_COMPILER=clang \
|
-DCMAKE_C_COMPILER=clang \
|
||||||
-DCMAKE_CXX_COMPILER=clang++ \
|
-DCMAKE_CXX_COMPILER=clang++ \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
|
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
|
||||||
exit "${PIPESTATUS[0]}"
|
exit "${PIPESTATUS[0]}"
|
||||||
|
|
||||||
- name: Verify SDL2 backends
|
- name: Verify SDL3 backends
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
status=0
|
status=0
|
||||||
@@ -297,14 +388,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake --build _Build/linux \
|
cmake --build _Build/linux \
|
||||||
--target launcher page_manager_tests memory_tracker_tests \
|
--target launcher page_manager_tests memory_tracker_tests \
|
||||||
audio_out2_port_tests virtual_memory_allocation_tests \
|
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||||
|
resource_tracking_tests scalar_provenance_tests \
|
||||||
|
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||||
--parallel
|
--parallel
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ctest --test-dir _Build/linux --output-on-failure \
|
ctest --test-dir _Build/linux --output-on-failure \
|
||||||
-R '^(audio_out2_port|page_manager|memory_tracker|virtual_memory_allocation)$'
|
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -314,11 +407,12 @@ jobs:
|
|||||||
- name: Verify artifacts
|
- name: Verify artifacts
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
file _Build/linux/install/launcher
|
file _Build/linux/install/kyty_launcher
|
||||||
file _Build/linux/install/kyty_emulator
|
file _Build/linux/install/kyty_emulator
|
||||||
|
test -x _Build/linux/install/kyty_updater
|
||||||
file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
|
file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
|
||||||
ldd _Build/linux/install/kyty_emulator > /dev/null
|
ldd _Build/linux/install/kyty_emulator > /dev/null
|
||||||
readelf -d _Build/linux/install/launcher | grep -q 'RPATH.*\$ORIGIN/lib'
|
readelf -d _Build/linux/install/kyty_launcher | grep -q 'RPATH.*\$ORIGIN/lib'
|
||||||
while IFS= read -r -d '' binary; do
|
while IFS= read -r -d '' binary; do
|
||||||
while read -r dependency; do
|
while read -r dependency; do
|
||||||
test -e "_Build/linux/install/lib/$dependency"
|
test -e "_Build/linux/install/lib/$dependency"
|
||||||
@@ -327,8 +421,8 @@ jobs:
|
|||||||
sed -n 's/.*Shared library: \[\(libQt6[^]]*\|libicu[^]]*\)\].*/\1/p'
|
sed -n 's/.*Shared library: \[\(libQt6[^]]*\|libicu[^]]*\)\].*/\1/p'
|
||||||
)
|
)
|
||||||
done < <(
|
done < <(
|
||||||
find _Build/linux/install/launcher _Build/linux/install/plugins \
|
find _Build/linux/install/kyty_launcher _Build/linux/install/plugins \
|
||||||
-type f \( -name launcher -o -name '*.so' \) -print0
|
-type f \( -name kyty_launcher -o -name '*.so' \) -print0
|
||||||
)
|
)
|
||||||
_Build/linux/install/kyty_emulator --help > /dev/null
|
_Build/linux/install/kyty_emulator --help > /dev/null
|
||||||
|
|
||||||
@@ -341,46 +435,71 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release KytyPS5
|
name: Release KytyPS5
|
||||||
if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5'
|
if: >-
|
||||||
needs: [windows, macos, linux]
|
always() &&
|
||||||
|
github.event_name == 'push' &&
|
||||||
|
github.repository == 'KytyPS5/KytyPS5' &&
|
||||||
|
(needs.windows.result == 'success' ||
|
||||||
|
needs.macos.result == 'success' ||
|
||||||
|
needs.linux.result == 'success')
|
||||||
|
needs: [metadata, windows, macos, linux]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
env:
|
||||||
|
RELEASE_NAME: ${{ needs.metadata.outputs.release_tag }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download builds
|
- name: Download Windows build
|
||||||
|
if: needs.windows.result == 'success'
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
name: KytyPS5-Windows-x64
|
||||||
|
path: artifacts/KytyPS5-Windows-x64
|
||||||
|
|
||||||
- name: Set release name
|
- name: Download macOS build
|
||||||
shell: bash
|
if: needs.macos.result == 'success'
|
||||||
run: |
|
uses: actions/download-artifact@v4
|
||||||
echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
|
with:
|
||||||
|
name: KytyPS5-macOS-x86_64
|
||||||
|
path: artifacts/KytyPS5-macOS-x86_64
|
||||||
|
|
||||||
- name: Package builds
|
- name: Download Linux build
|
||||||
|
if: needs.linux.result == 'success'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: KytyPS5-Linux-x86_64
|
||||||
|
path: artifacts/KytyPS5-Linux-x86_64
|
||||||
|
|
||||||
|
- name: Package Windows build
|
||||||
|
if: needs.windows.result == 'success'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
windows_dir="artifacts/KytyPS5-Windows-x64"
|
windows_dir="artifacts/KytyPS5-Windows-x64"
|
||||||
macos_dir="artifacts/KytyPS5-macOS-x86_64"
|
|
||||||
linux_dir="artifacts/KytyPS5-Linux-x86_64"
|
|
||||||
test -d "$windows_dir"
|
test -d "$windows_dir"
|
||||||
test -d "$macos_dir"
|
|
||||||
test -d "$linux_dir"
|
|
||||||
chmod a+x \
|
|
||||||
"$macos_dir/launcher" \
|
|
||||||
"$macos_dir/kyty_emulator" \
|
|
||||||
"$macos_dir/libMoltenVK.dylib" \
|
|
||||||
"$linux_dir/launcher" \
|
|
||||||
"$linux_dir/kyty_emulator"
|
|
||||||
(
|
(
|
||||||
cd "$windows_dir"
|
cd "$windows_dir"
|
||||||
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
|
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
|
||||||
)
|
)
|
||||||
(
|
|
||||||
cd "$macos_dir"
|
- name: Package macOS build
|
||||||
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
|
if: needs.macos.result == 'success'
|
||||||
)
|
shell: bash
|
||||||
|
run: |
|
||||||
|
macos_archive="artifacts/KytyPS5-macOS-x86_64/KytyPS5-macOS-x86_64.zip"
|
||||||
|
test -f "$macos_archive"
|
||||||
|
cp "$macos_archive" "$RELEASE_NAME-macOS-x86_64.zip"
|
||||||
|
|
||||||
|
- name: Package Linux build
|
||||||
|
if: needs.linux.result == 'success'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
linux_dir="artifacts/KytyPS5-Linux-x86_64"
|
||||||
|
test -d "$linux_dir"
|
||||||
|
chmod a+x \
|
||||||
|
"$linux_dir/kyty_launcher" \
|
||||||
|
"$linux_dir/kyty_emulator" \
|
||||||
|
"$linux_dir/kyty_updater"
|
||||||
tar -C "$linux_dir" -czf \
|
tar -C "$linux_dir" -czf \
|
||||||
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
|
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
|
||||||
|
|
||||||
@@ -389,11 +508,16 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
assets=(
|
assets=()
|
||||||
"$RELEASE_NAME-Windows-x64.zip"
|
for artifact in \
|
||||||
"$RELEASE_NAME-macOS-x86_64.zip"
|
"$RELEASE_NAME-Windows-x64.zip" \
|
||||||
"$RELEASE_NAME-Linux-x86_64.tar.gz"
|
"$RELEASE_NAME-macOS-x86_64.zip" \
|
||||||
)
|
"$RELEASE_NAME-Linux-x86_64.tar.gz"; do
|
||||||
|
if [[ -f "$artifact" ]]; then
|
||||||
|
assets+=("$artifact")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
test "${#assets[@]}" -gt 0
|
||||||
if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
|
if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
|
||||||
gh release upload "$RELEASE_NAME" "${assets[@]}" \
|
gh release upload "$RELEASE_NAME" "${assets[@]}" \
|
||||||
--clobber \
|
--clobber \
|
||||||
|
|||||||
@@ -3,4 +3,6 @@
|
|||||||
.idea/
|
.idea/
|
||||||
build/
|
build/
|
||||||
_Build/vscode-clang/
|
_Build/vscode-clang/
|
||||||
_Build/
|
_Build/
|
||||||
|
cmake-build-debug/
|
||||||
|
_PipelineCache/
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[submodule "3rdparty/SDL2"]
|
[submodule "3rdparty/SDL3"]
|
||||||
path = 3rdparty/SDL2
|
path = 3rdparty/SDL3
|
||||||
url = https://github.com/libsdl-org/SDL.git
|
url = https://github.com/libsdl-org/SDL.git
|
||||||
branch = SDL2
|
branch = release-3.4.x
|
||||||
[submodule "3rdparty/magic_enum"]
|
[submodule "3rdparty/magic_enum"]
|
||||||
path = 3rdparty/magic_enum
|
path = 3rdparty/magic_enum
|
||||||
url = https://github.com/Neargye/magic_enum.git
|
url = https://github.com/Neargye/magic_enum.git
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||||
[submodule "3rdparty/ffmpeg-core"]
|
[submodule "3rdparty/ffmpeg-core"]
|
||||||
path = 3rdparty/ffmpeg-core
|
path = 3rdparty/ffmpeg-core
|
||||||
url = https://github.com/shadps4-emu/ext-ffmpeg-core.git
|
url = https://github.com/KytyPS5/ext-ffmpeg-core.git
|
||||||
shallow = true
|
shallow = true
|
||||||
[submodule "3rdparty/fmt"]
|
[submodule "3rdparty/fmt"]
|
||||||
path = 3rdparty/fmt
|
path = 3rdparty/fmt
|
||||||
@@ -39,3 +39,6 @@
|
|||||||
[submodule "3rdparty/nlohmann_json"]
|
[submodule "3rdparty/nlohmann_json"]
|
||||||
path = 3rdparty/nlohmann_json
|
path = 3rdparty/nlohmann_json
|
||||||
url = https://github.com/nlohmann/json.git
|
url = https://github.com/nlohmann/json.git
|
||||||
|
[submodule "3rdparty/imgui"]
|
||||||
|
path = 3rdparty/imgui
|
||||||
|
url = https://github.com/ocornut/imgui.git
|
||||||
@@ -5,12 +5,19 @@
|
|||||||
"name": "Debug launcher UI",
|
"name": "Debug launcher UI",
|
||||||
"type": "cppvsdbg",
|
"type": "cppvsdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}\\_Build\\vscode-clang\\launcher\\launcher.exe",
|
"program": "${workspaceFolder}\\_Build\\vscode-clang\\launcher\\kyty_launcher.exe",
|
||||||
"args": [],
|
"args": [],
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}\\_Build\\vscode-clang\\launcher",
|
"cwd": "${workspaceFolder}\\_Build\\vscode-clang\\launcher",
|
||||||
"environment": [],
|
"environment": [],
|
||||||
"externalConsole": false
|
"externalConsole": false,
|
||||||
|
"linux": {
|
||||||
|
"type": "cppdbg",
|
||||||
|
"program": "${workspaceFolder}/_Build/vscode-clang/launcher/kyty_launcher",
|
||||||
|
"cwd": "${workspaceFolder}/_Build/vscode-clang/launcher",
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug kyty_emulator",
|
"name": "Debug kyty_emulator",
|
||||||
@@ -25,13 +32,40 @@
|
|||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
|
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
|
||||||
"environment": [],
|
"environment": [],
|
||||||
"externalConsole": true
|
"externalConsole": true,
|
||||||
|
"linux": {
|
||||||
|
"type": "cppdbg",
|
||||||
|
"program": "${workspaceFolder}/_Build/vscode-clang/kyty_emulator",
|
||||||
|
"cwd": "${workspaceFolder}/_Build/vscode-clang",
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Pass Kyty's internal signals to its handlers",
|
||||||
|
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
|
||||||
|
"ignoreFailures": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Attach to running kyty_emulator",
|
"name": "Attach to running kyty_emulator",
|
||||||
"type": "cppvsdbg",
|
"type": "cppvsdbg",
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
"processId": "${command:pickProcess}"
|
"processId": "${command:pickProcess}",
|
||||||
|
"linux": {
|
||||||
|
"type": "cppdbg",
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Pass Kyty's internal signals to its handlers",
|
||||||
|
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
|
||||||
|
"ignoreFailures": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
{
|
{
|
||||||
"cmake.sourceDirectory": "${workspaceFolder}/src",
|
"cmake.sourceDirectory": "${workspaceFolder}",
|
||||||
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
|
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
|
||||||
"cmake.generator": "Ninja",
|
"cmake.generator": "Ninja",
|
||||||
|
"cmake.useVsDeveloperEnvironment": "always",
|
||||||
"cmake.environment": {
|
"cmake.environment": {
|
||||||
"CC": "clang-cl",
|
"CC": "clang-cl",
|
||||||
"CXX": "clang-cl",
|
"CXX": "clang-cl"
|
||||||
"PATH": "${env:ProgramFiles}/Microsoft Visual Studio/18/Community/VC/Tools/Llvm/x64/bin;${env:ProgramFiles}/Microsoft Visual Studio/18/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja;${env:PATH}"
|
|
||||||
},
|
},
|
||||||
"cmake.configureSettings": {
|
"cmake.configureSettings": {
|
||||||
"CMAKE_C_COMPILER": "clang-cl",
|
"CMAKE_C_COMPILER": "clang-cl",
|
||||||
"CMAKE_CXX_COMPILER": "clang-cl",
|
"CMAKE_CXX_COMPILER": "clang-cl",
|
||||||
"CMAKE_PREFIX_PATH": "C:/Qt/6.11.0/msvc2022_64"
|
"CMAKE_PREFIX_PATH": "C:/Qt/6.11.0/msvc2022_64"
|
||||||
}
|
},
|
||||||
|
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
|
||||||
|
"C_Cpp.default.compileCommands": "${workspaceFolder}/_Build/vscode-clang/compile_commands.json",
|
||||||
|
"C_Cpp.default.cppStandard": "c++20"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,8 +2,103 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
|||||||
|
|
||||||
add_subdirectory(cpuinfo)
|
add_subdirectory(cpuinfo)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
# The Bluetooth DualSense transports speaker audio as fixed-size Opus packets.
|
||||||
|
set(kyty_build_testing_saved "${BUILD_TESTING}")
|
||||||
|
set(BUILD_TESTING OFF)
|
||||||
|
set(OPUS_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF CACHE BOOL "" FORCE)
|
||||||
|
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF CACHE BOOL "" FORCE)
|
||||||
|
if(MSVC AND CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||||
|
# Opus assumes MSVC accepts SSE4.1 intrinsics without per-file flags, but
|
||||||
|
# clang-cl rejects them in its baseline x64 files. Its AVX2 dispatch also
|
||||||
|
# depends on the SSE4.1 declarations, so retain the x64 SSE2 baseline only.
|
||||||
|
set(OPUS_X86_MAY_HAVE_SSE4_1 OFF CACHE BOOL "" FORCE)
|
||||||
|
set(OPUS_X86_MAY_HAVE_AVX2 OFF CACHE BOOL "" FORCE)
|
||||||
|
endif()
|
||||||
|
FetchContent_Declare(opus
|
||||||
|
GIT_REPOSITORY https://github.com/xiph/opus.git
|
||||||
|
GIT_TAG ddbe48383984d56acd9e1ab6a090c54ca6b735a6
|
||||||
|
GIT_SHALLOW FALSE)
|
||||||
|
FetchContent_MakeAvailable(opus)
|
||||||
|
set(BUILD_TESTING "${kyty_build_testing_saved}")
|
||||||
|
install(FILES "${opus_SOURCE_DIR}/COPYING" DESTINATION "licenses/opus")
|
||||||
|
|
||||||
|
# The guest instruction patcher needs a decoder/encoder and a small
|
||||||
|
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
||||||
|
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_Declare(
|
||||||
|
xbyak
|
||||||
|
GIT_REPOSITORY https://github.com/herumi/xbyak.git
|
||||||
|
GIT_TAG 44a72f369268f7d552650891b296693e91db86bb
|
||||||
|
GIT_SHALLOW FALSE
|
||||||
|
)
|
||||||
|
|
||||||
|
set(ZYDIS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ZYDIS_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ZYDIS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ZYDIS_BUILD_DOXYGEN OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_Declare(
|
||||||
|
zydis
|
||||||
|
GIT_REPOSITORY https://github.com/zyantific/zydis.git
|
||||||
|
GIT_TAG 120e0e705f8e3b507dc49377ac2879979f0d545c
|
||||||
|
GIT_SUBMODULES_RECURSE TRUE
|
||||||
|
GIT_SHALLOW FALSE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(xbyak zydis)
|
||||||
|
target_compile_definitions(xbyak INTERFACE XBYAK_NO_EXCEPTION)
|
||||||
|
|
||||||
|
# ZArchive provides random-access reads for compressed game dumps. Build only
|
||||||
|
# the reader/writer library and its zstd dependency; the command-line tool is
|
||||||
|
# not part of KytyPS5's runtime distribution.
|
||||||
|
set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
||||||
|
set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ZSTD_LEGACY_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||||
|
set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_Declare(
|
||||||
|
zstd
|
||||||
|
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||||
|
GIT_TAG f8745da6ff1ad1e7bab384bd1f9d742439278e99
|
||||||
|
GIT_SHALLOW FALSE
|
||||||
|
SOURCE_SUBDIR build/cmake
|
||||||
|
)
|
||||||
|
FetchContent_Declare(
|
||||||
|
zarchive_source
|
||||||
|
GIT_REPOSITORY https://github.com/Exzap/ZArchive.git
|
||||||
|
GIT_TAG d2c717730092c7bf8cbb033b12fd4001b7c4d932
|
||||||
|
GIT_SHALLOW FALSE
|
||||||
|
PATCH_COMMAND "${GIT_EXECUTABLE}" apply --ignore-whitespace
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/patches/zarchive-reader.patch"
|
||||||
|
SOURCE_SUBDIR kyty-library-only
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(zstd zarchive_source)
|
||||||
|
|
||||||
|
add_library(zarchive STATIC
|
||||||
|
"${zarchive_source_SOURCE_DIR}/src/zarchivereader.cpp"
|
||||||
|
"${zarchive_source_SOURCE_DIR}/src/zarchivewriter.cpp"
|
||||||
|
"${zarchive_source_SOURCE_DIR}/src/sha_256.c"
|
||||||
|
)
|
||||||
|
add_library(ZArchive::zarchive ALIAS zarchive)
|
||||||
|
target_include_directories(zarchive SYSTEM PUBLIC "${zarchive_source_SOURCE_DIR}/include")
|
||||||
|
target_include_directories(zarchive PRIVATE "${zstd_SOURCE_DIR}/lib")
|
||||||
|
target_link_libraries(zarchive PRIVATE libzstd_static)
|
||||||
|
target_compile_features(zarchive PRIVATE cxx_std_20)
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(zarchive PRIVATE /W0)
|
||||||
|
else()
|
||||||
|
target_compile_options(zarchive PRIVATE -w)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(SDL2)
|
if(APPLE)
|
||||||
|
# SDL's built-in iconv fallback does not support CP932.
|
||||||
|
set(SDL_SYSTEM_ICONV ON CACHE BOOL "Use system iconv for CP932 conversion" FORCE)
|
||||||
|
endif()
|
||||||
|
add_subdirectory(SDL3)
|
||||||
|
|
||||||
add_library(Vulkan-Headers INTERFACE)
|
add_library(Vulkan-Headers INTERFACE)
|
||||||
add_library(Vulkan::Headers ALIAS Vulkan-Headers)
|
add_library(Vulkan::Headers ALIAS Vulkan-Headers)
|
||||||
@@ -11,6 +106,25 @@ target_include_directories(Vulkan-Headers INTERFACE
|
|||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include"
|
"${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_library(imgui STATIC
|
||||||
|
imgui/imgui.cpp
|
||||||
|
imgui/imgui_draw.cpp
|
||||||
|
imgui/imgui_tables.cpp
|
||||||
|
imgui/imgui_widgets.cpp
|
||||||
|
imgui/backends/imgui_impl_vulkan.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(imgui PUBLIC
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/imgui"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/backends"
|
||||||
|
)
|
||||||
|
target_compile_definitions(imgui PRIVATE IMGUI_IMPL_VULKAN_NO_PROTOTYPES)
|
||||||
|
target_link_libraries(imgui PUBLIC Vulkan::Headers)
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(imgui PRIVATE /W0)
|
||||||
|
else()
|
||||||
|
target_compile_options(imgui PRIVATE -w)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE)
|
set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE)
|
||||||
set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE)
|
set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE)
|
||||||
set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE)
|
set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE)
|
||||||
@@ -41,6 +155,9 @@ set(SPDLOG_BUILD_WARNINGS OFF CACHE BOOL "" FORCE)
|
|||||||
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
|
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
|
||||||
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
|
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
|
||||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||||
|
if(WIN32)
|
||||||
|
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE)
|
||||||
|
endif()
|
||||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
if (NOT TARGET FFmpeg::ffmpeg)
|
if (NOT TARGET FFmpeg::ffmpeg)
|
||||||
@@ -72,6 +189,7 @@ endif()
|
|||||||
|
|
||||||
set(TRACY_STATIC ON CACHE BOOL "" FORCE)
|
set(TRACY_STATIC ON CACHE BOOL "" FORCE)
|
||||||
set(TRACY_ENABLE ON CACHE BOOL "" FORCE)
|
set(TRACY_ENABLE ON CACHE BOOL "" FORCE)
|
||||||
|
set(TRACY_NO_CRASH_HANDLER ON CACHE BOOL "" FORCE)
|
||||||
set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE)
|
set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE)
|
||||||
set(TRACY_DELAYED_INIT ON CACHE BOOL "" FORCE)
|
set(TRACY_DELAYED_INIT ON CACHE BOOL "" FORCE)
|
||||||
set(TRACY_MANUAL_LIFETIME ON CACHE BOOL "" FORCE)
|
set(TRACY_MANUAL_LIFETIME ON CACHE BOOL "" FORCE)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include <cpuinfo/internal-api.h>
|
#include <cpuinfo/internal-api.h>
|
||||||
#include <cpuinfo/log.h>
|
#include <cpuinfo/log.h>
|
||||||
|
|
||||||
/* Polyfill recent CPUFAMILY_ARM_* values for older SDKs */
|
/* Polyfill recent CPUFAMILY_ARM_* values for older platform headers */
|
||||||
#ifndef CPUFAMILY_ARM_MONSOON_MISTRAL
|
#ifndef CPUFAMILY_ARM_MONSOON_MISTRAL
|
||||||
#define CPUFAMILY_ARM_MONSOON_MISTRAL 0xE81E7EF6
|
#define CPUFAMILY_ARM_MONSOON_MISTRAL 0xE81E7EF6
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
--- a/include/zarchive/zarchivecommon.h
|
||||||
|
+++ b/include/zarchive/zarchivecommon.h
|
||||||
|
@@ -256,7 +256,7 @@
|
||||||
|
|
||||||
|
bool IsWithinValidRange(uint64_t fileSize) const
|
||||||
|
{
|
||||||
|
- return (offset + size) <= fileSize;
|
||||||
|
+ return offset <= fileSize && size <= fileSize - offset;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
--- a/src/zarchivereader.cpp
|
||||||
|
+++ b/src/zarchivereader.cpp
|
||||||
|
@@ -14,6 +14,7 @@
|
||||||
|
|
||||||
|
static bool _ifstream_readBytes(std::ifstream& file, uint64_t offset, void* buffer, uint32_t size)
|
||||||
|
{
|
||||||
|
+ file.clear();
|
||||||
|
file.seekg(offset, std::ios_base::beg);
|
||||||
|
file.read((char*)buffer, size);
|
||||||
|
return file.gcount() == size;
|
||||||
|
@@ -80,9 +81,27 @@
|
||||||
|
// verify file tree
|
||||||
|
if (fileTree[0].IsFile())
|
||||||
|
return nullptr; // first entry must be root directory
|
||||||
|
- auto rootName = GetName(nameTable, fileTree[0].GetNameOffset());
|
||||||
|
- if (!rootName.empty())
|
||||||
|
+ if (fileTree[0].GetNameOffset() != 0x7FFFFFFF)
|
||||||
|
return nullptr; // root node must not have a name
|
||||||
|
+ const uint64_t dataCapacity = (uint64_t)offsetRecords.size() * _ZARCHIVE::ENTRIES_PER_OFFSETRECORD * _ZARCHIVE::COMPRESSED_BLOCK_SIZE;
|
||||||
|
+ for (size_t i = 0; i < fileTree.size(); ++i)
|
||||||
|
+ {
|
||||||
|
+ auto& entry = fileTree[i];
|
||||||
|
+ if (i != 0)
|
||||||
|
+ {
|
||||||
|
+ auto name = GetName(nameTable, entry.GetNameOffset());
|
||||||
|
+ if (name.empty() || name == "." || name == ".." || name.find_first_of("/\\") != std::string_view::npos || name.find('\0') != std::string_view::npos)
|
||||||
|
+ return nullptr;
|
||||||
|
+ }
|
||||||
|
+ if (entry.IsFile())
|
||||||
|
+ {
|
||||||
|
+ if (entry.GetFileOffset() > dataCapacity || entry.GetFileSize() > dataCapacity - entry.GetFileOffset())
|
||||||
|
+ return nullptr;
|
||||||
|
+ }
|
||||||
|
+ else if (entry.directoryRecord.nodeStartIndex > fileTree.size() ||
|
||||||
|
+ entry.directoryRecord.count > fileTree.size() - entry.directoryRecord.nodeStartIndex)
|
||||||
|
+ return nullptr;
|
||||||
|
+ }
|
||||||
|
// read meta data
|
||||||
|
// todo
|
||||||
|
|
||||||
|
@@ -238,7 +257,7 @@
|
||||||
|
uint32_t stepSize = std::min<uint32_t>(remainingBytes, _ZARCHIVE::COMPRESSED_BLOCK_SIZE - blockOffset);
|
||||||
|
CacheBlock* block = GetCachedBlock(blockIdx);
|
||||||
|
if (!block)
|
||||||
|
- return 0;
|
||||||
|
+ return bytesToRead - remainingBytes;
|
||||||
|
std::memcpy(bufferU8, block->data + blockOffset, stepSize);
|
||||||
|
rawReadOffset += stepSize;
|
||||||
|
remainingBytes -= stepSize;
|
||||||
|
@@ -325,14 +344,18 @@
|
||||||
|
// determine offset and size of compressed block
|
||||||
|
auto& record = m_offsetRecords[recordIndex];
|
||||||
|
uint64_t offset = record.baseOffset;
|
||||||
|
+ if (offset > m_compressedDataSize)
|
||||||
|
+ return false;
|
||||||
|
for (uint32_t i = 0; i < recordSubIndex; i++)
|
||||||
|
{
|
||||||
|
- offset += (uint64_t)record.size[i];
|
||||||
|
- offset++;
|
||||||
|
+ const uint64_t size = (uint64_t)record.size[i] + 1;
|
||||||
|
+ if (size > m_compressedDataSize - offset)
|
||||||
|
+ return false;
|
||||||
|
+ offset += size;
|
||||||
|
}
|
||||||
|
uint32_t compressedSize = (uint32_t)record.size[recordSubIndex] + 1;
|
||||||
|
// load file data
|
||||||
|
- if ((offset + compressedSize) > m_compressedDataSize)
|
||||||
|
+ if (compressedSize > m_compressedDataSize - offset)
|
||||||
|
return false;
|
||||||
|
offset += m_compressedDataOffset;
|
||||||
|
if (compressedSize == _ZARCHIVE::COMPRESSED_BLOCK_SIZE)
|
||||||
|
@@ -350,7 +373,7 @@
|
||||||
|
// returns empty view on failure
|
||||||
|
std::string_view ZArchiveReader::GetName(const std::vector<uint8_t>& nameTable, uint32_t nameOffset)
|
||||||
|
{
|
||||||
|
- if (nameOffset == 0x7FFFFFFF || nameOffset > nameTable.size())
|
||||||
|
+ if (nameOffset == 0x7FFFFFFF || nameOffset >= nameTable.size())
|
||||||
|
return "";
|
||||||
|
// parse header
|
||||||
|
uint16_t nameLength = nameTable[nameOffset] & 0x7F;
|
||||||
|
@@ -359,7 +382,7 @@
|
||||||
|
// extended 2-byte length
|
||||||
|
if (nameOffset + 1 >= nameTable.size())
|
||||||
|
return "";
|
||||||
|
- nameLength |= ((uint16_t)nameTable[nameOffset] << 7);
|
||||||
|
+ nameLength |= ((uint16_t)nameTable[nameOffset + 1] << 7);
|
||||||
|
nameOffset += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
@@ -0,0 +1,875 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015-2026 Baldur Karlsson
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#if !defined(RENDERDOC_NO_STDINT)
|
||||||
|
#include <stdint.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
|
||||||
|
#define RENDERDOC_CC __cdecl
|
||||||
|
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__OpenBSD__)
|
||||||
|
#define RENDERDOC_CC
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#define RENDERDOC_CC
|
||||||
|
#else
|
||||||
|
#error "Unknown platform"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constants not used directly in below API
|
||||||
|
|
||||||
|
// This is a GUID/magic value used for when applications pass a path where shader debug
|
||||||
|
// information can be found to match up with a stripped shader.
|
||||||
|
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
|
||||||
|
// RENDERDOC_ShaderDebugMagicValue_value
|
||||||
|
#define RENDERDOC_ShaderDebugMagicValue_struct \
|
||||||
|
{ \
|
||||||
|
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||||
|
}
|
||||||
|
|
||||||
|
// as an alternative when you want a byte array (assuming x86 endianness):
|
||||||
|
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
|
||||||
|
{ \
|
||||||
|
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
|
||||||
|
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
|
||||||
|
|
||||||
|
// this is a magic value for vulkan user tags to indicate which dispatchable API objects are which
|
||||||
|
// for object annotations
|
||||||
|
#define RENDERDOC_APIObjectAnnotationHelper 0xfbb3b337b664d0adULL
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// RenderDoc capture options
|
||||||
|
//
|
||||||
|
|
||||||
|
typedef enum RENDERDOC_CaptureOption
|
||||||
|
{
|
||||||
|
// Allow the application to enable vsync
|
||||||
|
//
|
||||||
|
// Default - enabled
|
||||||
|
//
|
||||||
|
// 1 - The application can enable or disable vsync at will
|
||||||
|
// 0 - vsync is force disabled
|
||||||
|
eRENDERDOC_Option_AllowVSync = 0,
|
||||||
|
|
||||||
|
// Allow the application to enable fullscreen
|
||||||
|
//
|
||||||
|
// Default - enabled
|
||||||
|
//
|
||||||
|
// 1 - The application can enable or disable fullscreen at will
|
||||||
|
// 0 - fullscreen is force disabled
|
||||||
|
eRENDERDOC_Option_AllowFullscreen = 1,
|
||||||
|
|
||||||
|
// Record API debugging events and messages
|
||||||
|
//
|
||||||
|
// Default - disabled
|
||||||
|
//
|
||||||
|
// 1 - Enable built-in API debugging features and records the results into
|
||||||
|
// the capture, which is matched up with events on replay
|
||||||
|
// 0 - no API debugging is forcibly enabled
|
||||||
|
eRENDERDOC_Option_APIValidation = 2,
|
||||||
|
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
|
||||||
|
|
||||||
|
// Capture CPU callstacks for API events
|
||||||
|
//
|
||||||
|
// Default - disabled
|
||||||
|
//
|
||||||
|
// 1 - Enables capturing of callstacks
|
||||||
|
// 0 - no callstacks are captured
|
||||||
|
eRENDERDOC_Option_CaptureCallstacks = 3,
|
||||||
|
|
||||||
|
// When capturing CPU callstacks, only capture them from actions.
|
||||||
|
// This option does nothing without the above option being enabled
|
||||||
|
//
|
||||||
|
// Default - disabled
|
||||||
|
//
|
||||||
|
// 1 - Only captures callstacks for actions.
|
||||||
|
// Ignored if CaptureCallstacks is disabled
|
||||||
|
// 0 - Callstacks, if enabled, are captured for every event.
|
||||||
|
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
|
||||||
|
eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
|
||||||
|
|
||||||
|
// Specify a delay in seconds to wait for a debugger to attach, after
|
||||||
|
// creating or injecting into a process, before continuing to allow it to run.
|
||||||
|
//
|
||||||
|
// 0 indicates no delay, and the process will run immediately after injection
|
||||||
|
//
|
||||||
|
// Default - 0 seconds
|
||||||
|
//
|
||||||
|
eRENDERDOC_Option_DelayForDebugger = 5,
|
||||||
|
|
||||||
|
// Verify buffer access. This includes checking the memory returned by a Map() call to
|
||||||
|
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
|
||||||
|
// to a marker value to catch use of uninitialised memory.
|
||||||
|
//
|
||||||
|
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
|
||||||
|
// not do the same kind of interception & checking and undefined contents are really undefined.
|
||||||
|
//
|
||||||
|
// Default - disabled
|
||||||
|
//
|
||||||
|
// 1 - Verify buffer access
|
||||||
|
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
|
||||||
|
// RenderDoc.
|
||||||
|
eRENDERDOC_Option_VerifyBufferAccess = 6,
|
||||||
|
|
||||||
|
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
|
||||||
|
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
|
||||||
|
// previously always enabled
|
||||||
|
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
|
||||||
|
|
||||||
|
// Hooks any system API calls that create child processes, and injects
|
||||||
|
// RenderDoc into them recursively with the same options.
|
||||||
|
//
|
||||||
|
// Default - disabled
|
||||||
|
//
|
||||||
|
// 1 - Hooks into spawned child processes
|
||||||
|
// 0 - Child processes are not hooked by RenderDoc
|
||||||
|
eRENDERDOC_Option_HookIntoChildren = 7,
|
||||||
|
|
||||||
|
// By default RenderDoc only includes resources in the final capture necessary
|
||||||
|
// for that frame, this allows you to override that behaviour.
|
||||||
|
//
|
||||||
|
// Default - disabled
|
||||||
|
//
|
||||||
|
// 1 - all live resources at the time of capture are included in the capture
|
||||||
|
// and available for inspection
|
||||||
|
// 0 - only the resources referenced by the captured frame are included
|
||||||
|
eRENDERDOC_Option_RefAllResources = 8,
|
||||||
|
|
||||||
|
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
|
||||||
|
// getting it will be ignored, to allow compatibility with older versions.
|
||||||
|
// In v1.1 the option acts as if it's always enabled.
|
||||||
|
//
|
||||||
|
// By default RenderDoc skips saving initial states for resources where the
|
||||||
|
// previous contents don't appear to be used, assuming that writes before
|
||||||
|
// reads indicate previous contents aren't used.
|
||||||
|
//
|
||||||
|
// Default - disabled
|
||||||
|
//
|
||||||
|
// 1 - initial contents at the start of each captured frame are saved, even if
|
||||||
|
// they are later overwritten or cleared before being used.
|
||||||
|
// 0 - unless a read is detected, initial contents will not be saved and will
|
||||||
|
// appear as black or empty data.
|
||||||
|
eRENDERDOC_Option_SaveAllInitials = 9,
|
||||||
|
|
||||||
|
// In APIs that allow for the recording of command lists to be replayed later,
|
||||||
|
// RenderDoc may choose to not capture command lists before a frame capture is
|
||||||
|
// triggered, to reduce overheads. This means any command lists recorded once
|
||||||
|
// and replayed many times will not be available and may cause a failure to
|
||||||
|
// capture.
|
||||||
|
//
|
||||||
|
// NOTE: This is only true for APIs where multithreading is difficult or
|
||||||
|
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
|
||||||
|
// and always capture all command lists since the API is heavily oriented
|
||||||
|
// around it and the overheads have been reduced by API design.
|
||||||
|
//
|
||||||
|
// 1 - All command lists are captured from the start of the application
|
||||||
|
// 0 - Command lists are only captured if their recording begins during
|
||||||
|
// the period when a frame capture is in progress.
|
||||||
|
eRENDERDOC_Option_CaptureAllCmdLists = 10,
|
||||||
|
|
||||||
|
// Mute API debugging output when the API validation mode option is enabled
|
||||||
|
//
|
||||||
|
// Default - enabled
|
||||||
|
//
|
||||||
|
// 1 - Mute any API debug messages from being displayed or passed through
|
||||||
|
// 0 - API debugging is displayed as normal
|
||||||
|
eRENDERDOC_Option_DebugOutputMute = 11,
|
||||||
|
|
||||||
|
// Option to allow vendor extensions to be used even when they may be
|
||||||
|
// incompatible with RenderDoc and cause corrupted replays or crashes.
|
||||||
|
//
|
||||||
|
// Default - inactive
|
||||||
|
//
|
||||||
|
// No values are documented, this option should only be used when absolutely
|
||||||
|
// necessary as directed by a RenderDoc developer.
|
||||||
|
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
|
||||||
|
|
||||||
|
// Define a soft memory limit which some APIs may aim to keep overhead under where
|
||||||
|
// possible. Anything above this limit will where possible be saved directly to disk during
|
||||||
|
// capture.
|
||||||
|
// This will cause increased disk space use (which may cause a capture to fail if disk space is
|
||||||
|
// exhausted) as well as slower capture times.
|
||||||
|
//
|
||||||
|
// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
|
||||||
|
// limit.
|
||||||
|
//
|
||||||
|
// Units are in MBs, suggested values would range from 200MB to 1000MB.
|
||||||
|
//
|
||||||
|
// Default - 0 Megabytes
|
||||||
|
eRENDERDOC_Option_SoftMemoryLimit = 13,
|
||||||
|
} RENDERDOC_CaptureOption;
|
||||||
|
|
||||||
|
// Sets an option that controls how RenderDoc behaves on capture.
|
||||||
|
//
|
||||||
|
// Returns 1 if the option and value are valid
|
||||||
|
// Returns 0 if either is invalid and the option is unchanged
|
||||||
|
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
|
||||||
|
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
|
||||||
|
|
||||||
|
// Gets the current value of an option as a uint32_t
|
||||||
|
//
|
||||||
|
// If the option is invalid, 0xffffffff is returned
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
|
||||||
|
|
||||||
|
// Gets the current value of an option as a float
|
||||||
|
//
|
||||||
|
// If the option is invalid, -FLT_MAX is returned
|
||||||
|
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
|
||||||
|
|
||||||
|
typedef enum RENDERDOC_InputButton
|
||||||
|
{
|
||||||
|
// '0' - '9' matches ASCII values
|
||||||
|
eRENDERDOC_Key_0 = 0x30,
|
||||||
|
eRENDERDOC_Key_1 = 0x31,
|
||||||
|
eRENDERDOC_Key_2 = 0x32,
|
||||||
|
eRENDERDOC_Key_3 = 0x33,
|
||||||
|
eRENDERDOC_Key_4 = 0x34,
|
||||||
|
eRENDERDOC_Key_5 = 0x35,
|
||||||
|
eRENDERDOC_Key_6 = 0x36,
|
||||||
|
eRENDERDOC_Key_7 = 0x37,
|
||||||
|
eRENDERDOC_Key_8 = 0x38,
|
||||||
|
eRENDERDOC_Key_9 = 0x39,
|
||||||
|
|
||||||
|
// 'A' - 'Z' matches ASCII values
|
||||||
|
eRENDERDOC_Key_A = 0x41,
|
||||||
|
eRENDERDOC_Key_B = 0x42,
|
||||||
|
eRENDERDOC_Key_C = 0x43,
|
||||||
|
eRENDERDOC_Key_D = 0x44,
|
||||||
|
eRENDERDOC_Key_E = 0x45,
|
||||||
|
eRENDERDOC_Key_F = 0x46,
|
||||||
|
eRENDERDOC_Key_G = 0x47,
|
||||||
|
eRENDERDOC_Key_H = 0x48,
|
||||||
|
eRENDERDOC_Key_I = 0x49,
|
||||||
|
eRENDERDOC_Key_J = 0x4A,
|
||||||
|
eRENDERDOC_Key_K = 0x4B,
|
||||||
|
eRENDERDOC_Key_L = 0x4C,
|
||||||
|
eRENDERDOC_Key_M = 0x4D,
|
||||||
|
eRENDERDOC_Key_N = 0x4E,
|
||||||
|
eRENDERDOC_Key_O = 0x4F,
|
||||||
|
eRENDERDOC_Key_P = 0x50,
|
||||||
|
eRENDERDOC_Key_Q = 0x51,
|
||||||
|
eRENDERDOC_Key_R = 0x52,
|
||||||
|
eRENDERDOC_Key_S = 0x53,
|
||||||
|
eRENDERDOC_Key_T = 0x54,
|
||||||
|
eRENDERDOC_Key_U = 0x55,
|
||||||
|
eRENDERDOC_Key_V = 0x56,
|
||||||
|
eRENDERDOC_Key_W = 0x57,
|
||||||
|
eRENDERDOC_Key_X = 0x58,
|
||||||
|
eRENDERDOC_Key_Y = 0x59,
|
||||||
|
eRENDERDOC_Key_Z = 0x5A,
|
||||||
|
|
||||||
|
// leave the rest of the ASCII range free
|
||||||
|
// in case we want to use it later
|
||||||
|
eRENDERDOC_Key_NonPrintable = 0x100,
|
||||||
|
|
||||||
|
eRENDERDOC_Key_Divide,
|
||||||
|
eRENDERDOC_Key_Multiply,
|
||||||
|
eRENDERDOC_Key_Subtract,
|
||||||
|
eRENDERDOC_Key_Plus,
|
||||||
|
|
||||||
|
eRENDERDOC_Key_F1,
|
||||||
|
eRENDERDOC_Key_F2,
|
||||||
|
eRENDERDOC_Key_F3,
|
||||||
|
eRENDERDOC_Key_F4,
|
||||||
|
eRENDERDOC_Key_F5,
|
||||||
|
eRENDERDOC_Key_F6,
|
||||||
|
eRENDERDOC_Key_F7,
|
||||||
|
eRENDERDOC_Key_F8,
|
||||||
|
eRENDERDOC_Key_F9,
|
||||||
|
eRENDERDOC_Key_F10,
|
||||||
|
eRENDERDOC_Key_F11,
|
||||||
|
eRENDERDOC_Key_F12,
|
||||||
|
|
||||||
|
eRENDERDOC_Key_Home,
|
||||||
|
eRENDERDOC_Key_End,
|
||||||
|
eRENDERDOC_Key_Insert,
|
||||||
|
eRENDERDOC_Key_Delete,
|
||||||
|
eRENDERDOC_Key_PageUp,
|
||||||
|
eRENDERDOC_Key_PageDn,
|
||||||
|
|
||||||
|
eRENDERDOC_Key_Backspace,
|
||||||
|
eRENDERDOC_Key_Tab,
|
||||||
|
eRENDERDOC_Key_PrtScrn,
|
||||||
|
eRENDERDOC_Key_Pause,
|
||||||
|
|
||||||
|
eRENDERDOC_Key_Max,
|
||||||
|
} RENDERDOC_InputButton;
|
||||||
|
|
||||||
|
// Sets which key or keys can be used to toggle focus between multiple windows
|
||||||
|
//
|
||||||
|
// If keys is NULL or num is 0, toggle keys will be disabled
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
|
||||||
|
|
||||||
|
// Sets which key or keys can be used to capture the next frame
|
||||||
|
//
|
||||||
|
// If keys is NULL or num is 0, captures keys will be disabled
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
|
||||||
|
|
||||||
|
typedef enum RENDERDOC_OverlayBits
|
||||||
|
{
|
||||||
|
// This single bit controls whether the overlay is enabled or disabled globally
|
||||||
|
eRENDERDOC_Overlay_Enabled = 0x1,
|
||||||
|
|
||||||
|
// Show the average framerate over several seconds as well as min/max
|
||||||
|
eRENDERDOC_Overlay_FrameRate = 0x2,
|
||||||
|
|
||||||
|
// Show the current frame number
|
||||||
|
eRENDERDOC_Overlay_FrameNumber = 0x4,
|
||||||
|
|
||||||
|
// Show a list of recent captures, and how many captures have been made
|
||||||
|
eRENDERDOC_Overlay_CaptureList = 0x8,
|
||||||
|
|
||||||
|
// Default values for the overlay mask
|
||||||
|
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
|
||||||
|
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
|
||||||
|
|
||||||
|
// Enable all bits
|
||||||
|
eRENDERDOC_Overlay_All = 0x7ffffff,
|
||||||
|
|
||||||
|
// Disable all bits
|
||||||
|
eRENDERDOC_Overlay_None = 0,
|
||||||
|
} RENDERDOC_OverlayBits;
|
||||||
|
|
||||||
|
// returns the overlay bits that have been set
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(void);
|
||||||
|
// sets the overlay bits with an and & or mask
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
|
||||||
|
|
||||||
|
// this function will attempt to remove RenderDoc's hooks in the application.
|
||||||
|
//
|
||||||
|
// Note: that this can only work correctly if done immediately after
|
||||||
|
// the module is loaded, before any API work happens. RenderDoc will remove its
|
||||||
|
// injected hooks and shut down. Behaviour is undefined if this is called
|
||||||
|
// after any API functions have been called, and there is still no guarantee of
|
||||||
|
// success.
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(void);
|
||||||
|
|
||||||
|
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
|
||||||
|
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
|
||||||
|
|
||||||
|
// This function will unload RenderDoc's crash handler.
|
||||||
|
//
|
||||||
|
// If you use your own crash handler and don't want RenderDoc's handler to
|
||||||
|
// intercede, you can call this function to unload it and any unhandled
|
||||||
|
// exceptions will pass to the next handler.
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(void);
|
||||||
|
|
||||||
|
// Sets the capture file path template
|
||||||
|
//
|
||||||
|
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
|
||||||
|
// and where they will be saved.
|
||||||
|
//
|
||||||
|
// Any extension is stripped off the path, and captures are saved in the directory
|
||||||
|
// specified, and named with the filename and the frame number appended. If the
|
||||||
|
// directory does not exist it will be created, including any parent directories.
|
||||||
|
//
|
||||||
|
// If pathtemplate is NULL, the template will remain unchanged
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// SetCaptureFilePathTemplate("my_captures/example");
|
||||||
|
//
|
||||||
|
// Capture #1 -> my_captures/example_frame123.rdc
|
||||||
|
// Capture #2 -> my_captures/example_frame456.rdc
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
|
||||||
|
|
||||||
|
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
|
||||||
|
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(void);
|
||||||
|
|
||||||
|
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
|
||||||
|
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
|
||||||
|
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
|
||||||
|
|
||||||
|
// returns the number of captures that have been made
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(void);
|
||||||
|
|
||||||
|
// This function returns the details of a capture, by index. New captures are added
|
||||||
|
// to the end of the list.
|
||||||
|
//
|
||||||
|
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
|
||||||
|
// pathlength will be written with the length in bytes of the filename string
|
||||||
|
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
|
||||||
|
//
|
||||||
|
// Any of the parameters can be NULL and they'll be skipped.
|
||||||
|
//
|
||||||
|
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
|
||||||
|
// If the index is invalid, the values will be unchanged
|
||||||
|
//
|
||||||
|
// Note: when captures are deleted in the UI they will remain in this list, so the
|
||||||
|
// capture path may not exist anymore.
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
|
||||||
|
uint32_t *pathlength, uint64_t *timestamp);
|
||||||
|
|
||||||
|
// Sets the comments associated with a capture file. These comments are displayed in the
|
||||||
|
// UI program when opening.
|
||||||
|
//
|
||||||
|
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
|
||||||
|
// the most recent capture file created made will be used instead.
|
||||||
|
// comments should be a NULL-terminated UTF-8 string to add as comments.
|
||||||
|
//
|
||||||
|
// Any existing comments will be overwritten.
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
|
||||||
|
const char *comments);
|
||||||
|
|
||||||
|
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(void);
|
||||||
|
|
||||||
|
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
|
||||||
|
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
|
||||||
|
// backwards compatibility with old code, it is castable either way since it's ABI compatible
|
||||||
|
// as the same function pointer type.
|
||||||
|
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
|
||||||
|
|
||||||
|
// This function will launch the Replay UI associated with the RenderDoc library injected
|
||||||
|
// into the running application.
|
||||||
|
//
|
||||||
|
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
|
||||||
|
// to connect to this application
|
||||||
|
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
|
||||||
|
// if cmdline is NULL, the command line will be empty.
|
||||||
|
//
|
||||||
|
// returns the PID of the replay UI if successful, 0 if not successful.
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
|
||||||
|
const char *cmdline);
|
||||||
|
|
||||||
|
// RenderDoc can return a higher version than requested if it's backwards compatible,
|
||||||
|
// this function returns the actual version returned. If a parameter is NULL, it will be
|
||||||
|
// ignored and the others will be filled out.
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
|
||||||
|
|
||||||
|
// Requests that the replay UI show itself (if hidden or not the current top window). This can be
|
||||||
|
// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
|
||||||
|
// showing the UI after making a capture.
|
||||||
|
//
|
||||||
|
// This will return 1 if the request was successfully passed on, though it's not guaranteed that
|
||||||
|
// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
|
||||||
|
// target control connection to make such a request, or if there was another error
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(void);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Capturing functions
|
||||||
|
//
|
||||||
|
|
||||||
|
// A device pointer is a pointer to the API's root handle.
|
||||||
|
//
|
||||||
|
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
|
||||||
|
typedef void *RENDERDOC_DevicePointer;
|
||||||
|
|
||||||
|
// A window handle is the OS's native window handle
|
||||||
|
//
|
||||||
|
// This would be an HWND, GLXDrawable, etc
|
||||||
|
typedef void *RENDERDOC_WindowHandle;
|
||||||
|
|
||||||
|
// A helper macro for Vulkan, where the device handle cannot be used directly.
|
||||||
|
//
|
||||||
|
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
|
||||||
|
//
|
||||||
|
// Specifically, the value needed is the dispatch table pointer, which sits as the first
|
||||||
|
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
|
||||||
|
// indirect once.
|
||||||
|
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
|
||||||
|
|
||||||
|
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
|
||||||
|
// respond to keypresses. Neither parameter can be NULL
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
|
||||||
|
RENDERDOC_WindowHandle wndHandle);
|
||||||
|
|
||||||
|
// capture the next frame on whichever window and API is currently considered active
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(void);
|
||||||
|
|
||||||
|
// capture the next N frames on whichever window and API is currently considered active
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
|
||||||
|
|
||||||
|
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
|
||||||
|
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
|
||||||
|
// any API rendering to a specific window, or a specific API instance rendering to any window,
|
||||||
|
// or in the simplest case of one window and one API, you can just pass NULL for both.
|
||||||
|
//
|
||||||
|
// In either case, if there are two or more possible matching (device,window) pairs it
|
||||||
|
// is undefined which one will be captured.
|
||||||
|
//
|
||||||
|
// Note: for headless rendering you can pass NULL for the window handle and either specify
|
||||||
|
// a device pointer or leave it NULL as above.
|
||||||
|
|
||||||
|
// Immediately starts capturing API calls on the specified device pointer and window handle.
|
||||||
|
//
|
||||||
|
// If there is no matching thing to capture (e.g. no supported API has been initialised),
|
||||||
|
// this will do nothing.
|
||||||
|
//
|
||||||
|
// The results are undefined (including crashes) if two captures are started overlapping,
|
||||||
|
// even on separate devices and/oror windows.
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
|
||||||
|
RENDERDOC_WindowHandle wndHandle);
|
||||||
|
|
||||||
|
// Returns whether or not a frame capture is currently ongoing anywhere.
|
||||||
|
//
|
||||||
|
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(void);
|
||||||
|
|
||||||
|
// Ends capturing immediately.
|
||||||
|
//
|
||||||
|
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
|
||||||
|
RENDERDOC_WindowHandle wndHandle);
|
||||||
|
|
||||||
|
// Ends capturing immediately and discard any data stored without saving to disk.
|
||||||
|
//
|
||||||
|
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
|
||||||
|
// was in progress
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
|
||||||
|
RENDERDOC_WindowHandle wndHandle);
|
||||||
|
|
||||||
|
// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
|
||||||
|
// title to the capture produced which will be displayed in the UI.
|
||||||
|
//
|
||||||
|
// If multiple captures are ongoing, this title will be applied to the first capture to end after
|
||||||
|
// this call. The second capture to end will have no title, unless this function is called again.
|
||||||
|
//
|
||||||
|
// Calling this function has no effect if no capture is currently running, and if it is called
|
||||||
|
// multiple times only the last title will be used.
|
||||||
|
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
|
||||||
|
|
||||||
|
// Annotations API:
|
||||||
|
//
|
||||||
|
// These functions allow you to specify annotations either on a per-command level, or a per-object
|
||||||
|
// level.
|
||||||
|
//
|
||||||
|
// Basic types of annotations are supported, as well as vector versions and references to API objects.
|
||||||
|
//
|
||||||
|
// The annotations are stored as keys, with the key being a dot-separated path allowing arbitrary
|
||||||
|
// nesting and user organisation. The keys are sorted in human order so `foo.2.bar` will be displayed
|
||||||
|
// before `foo.10.bar` to allow creation of arrays if desired.
|
||||||
|
//
|
||||||
|
// Deleting an annotation can be done by assigning an empty value to it.
|
||||||
|
|
||||||
|
// the type of an annotation value, or Empty to delete an annotation
|
||||||
|
typedef enum RENDERDOC_AnnotationType
|
||||||
|
{
|
||||||
|
eRENDERDOC_Empty,
|
||||||
|
eRENDERDOC_Bool,
|
||||||
|
eRENDERDOC_Int32,
|
||||||
|
eRENDERDOC_UInt32,
|
||||||
|
eRENDERDOC_Int64,
|
||||||
|
eRENDERDOC_UInt64,
|
||||||
|
eRENDERDOC_Float,
|
||||||
|
eRENDERDOC_Double,
|
||||||
|
eRENDERDOC_String,
|
||||||
|
eRENDERDOC_APIObject,
|
||||||
|
eRENDERDOC_AnnotationMax = 0x7FFFFFFF,
|
||||||
|
} RENDERDOC_AnnotationType;
|
||||||
|
|
||||||
|
// a union with vector annotation value data
|
||||||
|
typedef union RENDERDOC_AnnotationVectorValue
|
||||||
|
{
|
||||||
|
bool boolean[4];
|
||||||
|
int32_t int32[4];
|
||||||
|
int64_t int64[4];
|
||||||
|
uint32_t uint32[4];
|
||||||
|
uint64_t uint64[4];
|
||||||
|
float float32[4];
|
||||||
|
double float64[4];
|
||||||
|
} RENDERDOC_AnnotationVectorValue;
|
||||||
|
|
||||||
|
// a union with scalar annotation value data
|
||||||
|
typedef union RENDERDOC_AnnotationValue
|
||||||
|
{
|
||||||
|
bool boolean;
|
||||||
|
int32_t int32;
|
||||||
|
int64_t int64;
|
||||||
|
uint32_t uint32;
|
||||||
|
uint64_t uint64;
|
||||||
|
float float32;
|
||||||
|
double float64;
|
||||||
|
|
||||||
|
RENDERDOC_AnnotationVectorValue vector;
|
||||||
|
|
||||||
|
const char *string;
|
||||||
|
void *apiObject;
|
||||||
|
} RENDERDOC_AnnotationValue;
|
||||||
|
|
||||||
|
// a struct for specifying a GL object, as we don't have pointers we can use so instead we specify a
|
||||||
|
// pointer to this struct giving both the type and the name
|
||||||
|
typedef struct RENDERDOC_GLResourceReference
|
||||||
|
{
|
||||||
|
// this is the same GLenum identifier as passed to glObjectLabel
|
||||||
|
uint32_t identifier;
|
||||||
|
uint32_t name;
|
||||||
|
} GLResourceReference;
|
||||||
|
|
||||||
|
// simple C++ helpers to avoid the need for a temporary objects for value passing and GL object specification
|
||||||
|
#ifdef __cplusplus
|
||||||
|
struct RDGLObjectHelper
|
||||||
|
{
|
||||||
|
RENDERDOC_GLResourceReference gl;
|
||||||
|
|
||||||
|
RDGLObjectHelper(uint32_t identifier, uint32_t name)
|
||||||
|
{
|
||||||
|
gl.identifier = identifier;
|
||||||
|
gl.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator RENDERDOC_GLResourceReference *() { return ≷ }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RDAnnotationHelper
|
||||||
|
{
|
||||||
|
RENDERDOC_AnnotationValue val;
|
||||||
|
|
||||||
|
RDAnnotationHelper(bool b) { val.boolean = b; }
|
||||||
|
RDAnnotationHelper(int32_t i) { val.int32 = i; }
|
||||||
|
RDAnnotationHelper(int64_t i) { val.int64 = i; }
|
||||||
|
RDAnnotationHelper(uint32_t i) { val.uint32 = i; }
|
||||||
|
RDAnnotationHelper(uint64_t i) { val.uint64 = i; }
|
||||||
|
RDAnnotationHelper(float f) { val.float32 = f; }
|
||||||
|
RDAnnotationHelper(double d) { val.float64 = d; }
|
||||||
|
RDAnnotationHelper(const char *s) { val.string = s; }
|
||||||
|
|
||||||
|
operator RENDERDOC_AnnotationValue *() { return &val; }
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The device is specified in the same way as other API calls that take a RENDERDOC_DevicePointer
|
||||||
|
// to specify the device.
|
||||||
|
//
|
||||||
|
// The object or queue/commandbuffer will depend on the graphics API in question.
|
||||||
|
//
|
||||||
|
// Return value:
|
||||||
|
// 0 - The annotation was applied successfully.
|
||||||
|
// 1 - The device is unknown/invalid
|
||||||
|
// 2 - The device is valid but the annotation is not supported for API-specific reasons, such as an
|
||||||
|
// unrecognised or invalid object or queue/commandbuffer
|
||||||
|
// 3 - The call is ill-formed or invalid e.g. empty is specified with a value pointer, or non-empty
|
||||||
|
// is specified with a NULL value pointer
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetObjectAnnotation)(RENDERDOC_DevicePointer device,
|
||||||
|
void *object, const char *key,
|
||||||
|
RENDERDOC_AnnotationType valueType,
|
||||||
|
uint32_t valueVectorWidth,
|
||||||
|
const RENDERDOC_AnnotationValue *value);
|
||||||
|
|
||||||
|
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetCommandAnnotation)(
|
||||||
|
RENDERDOC_DevicePointer device, void *queueOrCommandBuffer, const char *key,
|
||||||
|
RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth,
|
||||||
|
const RENDERDOC_AnnotationValue *value);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// RenderDoc API versions
|
||||||
|
//
|
||||||
|
|
||||||
|
// RenderDoc uses semantic versioning (http://semver.org/).
|
||||||
|
//
|
||||||
|
// MAJOR version is incremented when incompatible API changes happen.
|
||||||
|
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
|
||||||
|
// PATCH version is incremented when backwards-compatible bug fixes happen.
|
||||||
|
//
|
||||||
|
// Note that this means the API returned can be higher than the one you might have requested.
|
||||||
|
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
|
||||||
|
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
|
||||||
|
typedef enum RENDERDOC_Version
|
||||||
|
{
|
||||||
|
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
|
||||||
|
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
|
||||||
|
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
|
||||||
|
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
|
||||||
|
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
|
||||||
|
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
|
||||||
|
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
|
||||||
|
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
|
||||||
|
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
|
||||||
|
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
|
||||||
|
eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
|
||||||
|
eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
|
||||||
|
eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
|
||||||
|
eRENDERDOC_API_Version_1_7_0 = 10700, // RENDERDOC_API_1_7_0 = 1 07 00
|
||||||
|
} RENDERDOC_Version;
|
||||||
|
|
||||||
|
// API version changelog:
|
||||||
|
//
|
||||||
|
// 1.0.0 - initial release
|
||||||
|
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
|
||||||
|
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
|
||||||
|
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
|
||||||
|
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
|
||||||
|
// function pointer is added to the end of the struct, the original layout is identical
|
||||||
|
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
|
||||||
|
// replay/remote server concept in replay UI)
|
||||||
|
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
|
||||||
|
// are captures and not debug logging files. This is the first API version in the v1.0
|
||||||
|
// branch.
|
||||||
|
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
|
||||||
|
// displayed in the UI program on load.
|
||||||
|
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
|
||||||
|
// which allows users to opt-in to allowing unsupported vendor extensions to function.
|
||||||
|
// Should be used at the user's own risk.
|
||||||
|
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
|
||||||
|
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
|
||||||
|
// 0xdddddddd of uninitialised buffer contents.
|
||||||
|
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
|
||||||
|
// capturing without saving anything to disk.
|
||||||
|
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
|
||||||
|
// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
|
||||||
|
// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
|
||||||
|
// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
|
||||||
|
// capture made with StartFrameCapture() or EndFrameCapture()
|
||||||
|
// 1.7.0 - Added feature: SetObjectAnnotation() / SetCommandAnnotation() for adding rich
|
||||||
|
// annotations to objects and command streams
|
||||||
|
|
||||||
|
typedef struct RENDERDOC_API_1_7_0
|
||||||
|
{
|
||||||
|
pRENDERDOC_GetAPIVersion GetAPIVersion;
|
||||||
|
|
||||||
|
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
|
||||||
|
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
|
||||||
|
|
||||||
|
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
|
||||||
|
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
|
||||||
|
|
||||||
|
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
|
||||||
|
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
|
||||||
|
|
||||||
|
pRENDERDOC_GetOverlayBits GetOverlayBits;
|
||||||
|
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
|
||||||
|
|
||||||
|
// Shutdown was renamed to RemoveHooks in 1.4.1.
|
||||||
|
// These unions allow old code to continue compiling without changes
|
||||||
|
union
|
||||||
|
{
|
||||||
|
pRENDERDOC_Shutdown Shutdown;
|
||||||
|
pRENDERDOC_RemoveHooks RemoveHooks;
|
||||||
|
};
|
||||||
|
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
|
||||||
|
|
||||||
|
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
|
||||||
|
// These unions allow old code to continue compiling without changes
|
||||||
|
union
|
||||||
|
{
|
||||||
|
// deprecated name
|
||||||
|
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
|
||||||
|
// current name
|
||||||
|
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
|
||||||
|
};
|
||||||
|
union
|
||||||
|
{
|
||||||
|
// deprecated name
|
||||||
|
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
|
||||||
|
// current name
|
||||||
|
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
pRENDERDOC_GetNumCaptures GetNumCaptures;
|
||||||
|
pRENDERDOC_GetCapture GetCapture;
|
||||||
|
|
||||||
|
pRENDERDOC_TriggerCapture TriggerCapture;
|
||||||
|
|
||||||
|
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
|
||||||
|
// This union allows old code to continue compiling without changes
|
||||||
|
union
|
||||||
|
{
|
||||||
|
// deprecated name
|
||||||
|
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
|
||||||
|
// current name
|
||||||
|
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
|
||||||
|
};
|
||||||
|
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
|
||||||
|
|
||||||
|
pRENDERDOC_SetActiveWindow SetActiveWindow;
|
||||||
|
|
||||||
|
pRENDERDOC_StartFrameCapture StartFrameCapture;
|
||||||
|
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
|
||||||
|
pRENDERDOC_EndFrameCapture EndFrameCapture;
|
||||||
|
|
||||||
|
// new function in 1.1.0
|
||||||
|
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
|
||||||
|
|
||||||
|
// new function in 1.2.0
|
||||||
|
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
|
||||||
|
|
||||||
|
// new function in 1.4.0
|
||||||
|
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
|
||||||
|
|
||||||
|
// new function in 1.5.0
|
||||||
|
pRENDERDOC_ShowReplayUI ShowReplayUI;
|
||||||
|
|
||||||
|
// new function in 1.6.0
|
||||||
|
pRENDERDOC_SetCaptureTitle SetCaptureTitle;
|
||||||
|
|
||||||
|
// new functions in 1.7.0
|
||||||
|
pRENDERDOC_SetObjectAnnotation SetObjectAnnotation;
|
||||||
|
pRENDERDOC_SetCommandAnnotation SetCommandAnnotation;
|
||||||
|
} RENDERDOC_API_1_7_0;
|
||||||
|
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_0;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_1;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_2;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_0;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_1;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_2;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_2_0;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_3_0;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_0;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_1;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_2;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_5_0;
|
||||||
|
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_6_0;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// RenderDoc API entry point
|
||||||
|
//
|
||||||
|
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
|
||||||
|
//
|
||||||
|
// The name is the same as the typedef - "RENDERDOC_GetAPI"
|
||||||
|
//
|
||||||
|
// This function is not thread safe, and should not be called on multiple threads at once.
|
||||||
|
// Ideally, call this once as early as possible in your application's startup, before doing
|
||||||
|
// any API work, since some configuration functionality etc has to be done also before
|
||||||
|
// initialising any APIs.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// version is a single value from the RENDERDOC_Version above.
|
||||||
|
//
|
||||||
|
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
|
||||||
|
// pointers.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
|
||||||
|
// 0 - if the requested version is not supported or the arguments are invalid.
|
||||||
|
//
|
||||||
|
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,846 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.19...4.3)
|
||||||
|
|
||||||
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
||||||
|
set(CMAKE_POLICY_VERSION_MINIMUM 3.10 CACHE STRING "Minimum policy version for bundled dependencies")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_property(KYTY_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
|
if(NOT KYTY_GENERATOR_IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build" FORCE)
|
||||||
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(Kyty)
|
||||||
|
|
||||||
|
find_program(SCCACHE_PROGRAM sccache)
|
||||||
|
find_program(CCACHE_PROGRAM ccache)
|
||||||
|
|
||||||
|
foreach(lang IN ITEMS C CXX)
|
||||||
|
# An explicitly empty launcher disables caching for this language.
|
||||||
|
if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER)
|
||||||
|
if(SCCACHE_PROGRAM)
|
||||||
|
set(CMAKE_${lang}_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
|
||||||
|
message(STATUS "sccache enabled for ${lang}: ${SCCACHE_PROGRAM}")
|
||||||
|
elseif(CCACHE_PROGRAM)
|
||||||
|
set(CMAKE_${lang}_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||||
|
message(STATUS "ccache enabled for ${lang}: ${CCACHE_PROGRAM}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(KYTY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||||
|
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
||||||
|
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
|
||||||
|
|
||||||
|
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
||||||
|
set(LINUX TRUE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT (WIN32 OR LINUX OR APPLE))
|
||||||
|
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
# Prevent Windows headers from defining min/max macros in all targets.
|
||||||
|
add_compile_definitions(NOMINMAX)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||||
|
|
||||||
|
include("${KYTY_SOURCE_DIR}/utils.cmake")
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
|
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
|
||||||
|
|
||||||
|
set(KYTY_BUILD_ORIGIN "Source" CACHE STRING "Build provenance shown in diagnostics")
|
||||||
|
set_property(CACHE KYTY_BUILD_ORIGIN PROPERTY STRINGS Source Official Fork)
|
||||||
|
if(NOT KYTY_BUILD_ORIGIN STREQUAL "Source" AND NOT KYTY_BUILD_ORIGIN STREQUAL "Official" AND
|
||||||
|
NOT KYTY_BUILD_ORIGIN STREQUAL "Fork")
|
||||||
|
message(FATAL_ERROR "KYTY_BUILD_ORIGIN must be Source, Official, or Fork")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(KYTY_RELEASE_TAG "" CACHE STRING "Release tag embedded in official builds")
|
||||||
|
set(KYTY_BUILD_REPOSITORY "" CACHE STRING "GitHub repository embedded in fork builds")
|
||||||
|
set(KYTY_OFFICIAL_BUILD OFF)
|
||||||
|
set(KYTY_FORK_BUILD OFF)
|
||||||
|
if(KYTY_BUILD_ORIGIN STREQUAL "Official")
|
||||||
|
if(KYTY_RELEASE_TAG STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Official builds require KYTY_RELEASE_TAG")
|
||||||
|
endif()
|
||||||
|
set(KYTY_OFFICIAL_BUILD ON)
|
||||||
|
elseif(KYTY_BUILD_ORIGIN STREQUAL "Fork")
|
||||||
|
if(KYTY_BUILD_REPOSITORY STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Fork builds require KYTY_BUILD_REPOSITORY")
|
||||||
|
endif()
|
||||||
|
set(KYTY_FORK_BUILD ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
|
|
||||||
|
include(TestBigEndian)
|
||||||
|
|
||||||
|
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
message(FATAL_ERROR "only 64-bit builds are supported")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
test_big_endian(BIG_ENDIAN)
|
||||||
|
if(BIG_ENDIAN)
|
||||||
|
set(KYTY_ENDIAN KYTY_ENDIAN_BIG)
|
||||||
|
else()
|
||||||
|
set(KYTY_ENDIAN KYTY_ENDIAN_LITTLE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||||
|
set(KYTY_BUILD KYTY_BUILD_DEBUG)
|
||||||
|
else()
|
||||||
|
set(KYTY_BUILD KYTY_BUILD_RELEASE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LINUX OR APPLE)
|
||||||
|
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
|
||||||
|
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||||
|
else()
|
||||||
|
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CLANG FALSE)
|
||||||
|
set(GCC FALSE)
|
||||||
|
set(KYTY_CLANG_CL FALSE)
|
||||||
|
get_filename_component(KYTY_CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME_WE)
|
||||||
|
string(TOLOWER "${KYTY_CXX_COMPILER_NAME}" KYTY_CXX_COMPILER_NAME)
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
||||||
|
set(CLANG TRUE)
|
||||||
|
set(KYTY_COMPILER CLANG)
|
||||||
|
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
|
||||||
|
set(KYTY_CLANG_CL TRUE)
|
||||||
|
set(KYTY_LINKER LLD_LINK)
|
||||||
|
elseif(APPLE)
|
||||||
|
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
|
||||||
|
else()
|
||||||
|
set(KYTY_LINKER LLD)
|
||||||
|
endif()
|
||||||
|
if ((CMAKE_AR MATCHES "llvm") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0.0) AND (CMAKE_CXX_COMPILER MATCHES "winlibs"))
|
||||||
|
string(REPLACE "llvm-ar.exe" "ar.exe" CMAKE_AR ${CMAKE_AR}) # llvm-ar.exe 11.0.0 from winlibs is broken
|
||||||
|
endif()
|
||||||
|
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
|
set(GCC TRUE)
|
||||||
|
set(KYTY_COMPILER GCC)
|
||||||
|
set(KYTY_LINKER LD)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "only Clang and GCC are supported; found ${CMAKE_CXX_COMPILER_ID}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(KYTY_PROJECT_NAME "Emulator" CACHE STRING "Project name suffix")
|
||||||
|
|
||||||
|
string(TOLOWER ${KYTY_COMPILER} KYTY_COMPILER_ID)
|
||||||
|
string(TOLOWER ${KYTY_LINKER} KYTY_LINKER_ID)
|
||||||
|
|
||||||
|
if (CLANG AND (KYTY_LINKER STREQUAL LLD))
|
||||||
|
set(KYTY_LD_OPTIONS "-fuse-ld=lld")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
|
||||||
|
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.3.0)
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/gtest/include
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/gtest
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
|
||||||
|
${PROJECT_BINARY_DIR}
|
||||||
|
${KYTY_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
set(KYTY_VERSION "${PROJECT_VERSION}")
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
${KYTY_SOURCE_DIR}/cmake_config.h.in
|
||||||
|
${PROJECT_BINARY_DIR}/cmake_config.h
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(Git)
|
||||||
|
|
||||||
|
add_custom_target(KytyGitVersion
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-D INPUT_FILE=${KYTY_SOURCE_DIR}/kytyGitVersion.h.in
|
||||||
|
-D OUTPUT_FILE=${PROJECT_BINARY_DIR}/kytyGitVersion.h
|
||||||
|
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||||
|
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
-P ${KYTY_SOURCE_DIR}/generate_version.cmake
|
||||||
|
BYPRODUCTS ${PROJECT_BINARY_DIR}/kytyGitVersion.h
|
||||||
|
COMMENT "Generate kytyGitVersion.h"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(kyty_git_version INTERFACE)
|
||||||
|
add_dependencies(kyty_git_version KytyGitVersion)
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
||||||
|
list(APPEND KYTY_IWYU
|
||||||
|
kyty_emulator
|
||||||
|
common
|
||||||
|
#launcher
|
||||||
|
)
|
||||||
|
list(APPEND KYTY_CLANG_TIDY
|
||||||
|
kyty_emulator
|
||||||
|
#common
|
||||||
|
launcher
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
|
||||||
|
|
||||||
|
config_compiler_and_linker()
|
||||||
|
|
||||||
|
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
|
||||||
|
add_subdirectory("${KYTY_SOURCE_DIR}/common" "${CMAKE_CURRENT_BINARY_DIR}/common")
|
||||||
|
|
||||||
|
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||||
|
"${KYTY_SOURCE_DIR}/libs/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/libs/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.inc"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/presentation/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/presentation/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/kernel/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/kernel/*.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/*.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/*.h"
|
||||||
|
)
|
||||||
|
|
||||||
|
find_program(KYTY_GLSLANG_VALIDATOR NAMES glslangValidator glslang REQUIRED)
|
||||||
|
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
|
||||||
|
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
||||||
|
set(gpu_tiler_shader_names
|
||||||
|
standard256
|
||||||
|
standard4
|
||||||
|
standard4_3d
|
||||||
|
standard64
|
||||||
|
standard64_3d
|
||||||
|
prt
|
||||||
|
prt_3d
|
||||||
|
render_target
|
||||||
|
depth
|
||||||
|
promote_d16
|
||||||
|
demote_d16
|
||||||
|
color_transform
|
||||||
|
)
|
||||||
|
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||||
|
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||||
|
set(shader_source "${gpu_tiler_shader_dir}/gpu_tiler_${shader_name}.comp")
|
||||||
|
set(shader_spv "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}.spv")
|
||||||
|
set(shader_header "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}_spv.h")
|
||||||
|
string(TOUPPER "GPU_TILER_${shader_name}_SPV" shader_symbol)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${shader_header}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||||
|
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||||
|
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||||
|
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||||
|
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND gpu_tiler_shader_headers "${shader_header}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(fault_buffer_shader_source "${gpu_tiler_shader_dir}/fault_buffer_process.comp")
|
||||||
|
set(fault_buffer_shader_spv "${gpu_tiler_generated_dir}/fault_buffer_process.spv")
|
||||||
|
set(fault_buffer_shader_header "${gpu_tiler_generated_dir}/fault_buffer_process_spv.h")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${fault_buffer_shader_header}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||||
|
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||||
|
-DCACHING_PAGEBITS=14 -DMAX_PAGE_FAULTS=1024
|
||||||
|
-o "${fault_buffer_shader_spv}" "${fault_buffer_shader_source}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -DINPUT=${fault_buffer_shader_spv} -DOUTPUT=${fault_buffer_shader_header}
|
||||||
|
-DSYMBOL=FAULT_BUFFER_PROCESS_SPV -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||||
|
DEPENDS "${fault_buffer_shader_source}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
|
||||||
|
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||||
|
|
||||||
|
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||||
|
function(add_gpu_blit_shader shader_name shader_file)
|
||||||
|
set(shader_source "${gpu_tiler_shader_dir}/${shader_file}")
|
||||||
|
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
||||||
|
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
||||||
|
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${shader_header}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||||
|
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os ${ARGN}
|
||||||
|
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||||
|
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||||
|
DEPENDS "${shader_source}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
set(gpu_blit_shader_headers ${gpu_blit_shader_headers} "${shader_header}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
add_gpu_blit_shader(gpu_blit_fs_triangle gpu_blit_fs_triangle.vert)
|
||||||
|
add_gpu_blit_shader(gpu_blit_color_to_ms_depth gpu_blit_color_to_ms_depth.frag)
|
||||||
|
add_gpu_blit_shader(gpu_video_out_overlay gpu_video_out_overlay.frag)
|
||||||
|
add_gpu_blit_shader(gpu_imgui_hdr gpu_video_out_overlay.frag -DIMGUI_HDR=1)
|
||||||
|
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||||
|
|
||||||
|
list(APPEND kyty_emulator_src
|
||||||
|
"${KYTY_SOURCE_DIR}/emulator.h"
|
||||||
|
"${KYTY_SOURCE_DIR}/emulator.cpp"
|
||||||
|
)
|
||||||
|
list(REMOVE_DUPLICATES kyty_emulator_src)
|
||||||
|
|
||||||
|
if (KYTY_CLANG_CL)
|
||||||
|
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak opus)
|
||||||
|
|
||||||
|
# Linux system libraries.
|
||||||
|
if(LINUX)
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(inc_headers
|
||||||
|
${KYTY_SOURCE_DIR}
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/xxHash
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/renderdoc
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/stb
|
||||||
|
)
|
||||||
|
|
||||||
|
if (KYTY_CLANG_CL)
|
||||||
|
list(APPEND kyty_emulator_link_libraries winpthread)
|
||||||
|
list(APPEND inc_headers
|
||||||
|
${KYTY_THIRD_PARTY_DIR}/winpthread/include
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND check_headers
|
||||||
|
${KYTY_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
function(add_kyty_full_emulator_test target source)
|
||||||
|
add_executable(${target} EXCLUDE_FROM_ALL ${source} ${kyty_emulator_src})
|
||||||
|
target_link_libraries(${target} ${kyty_emulator_link_libraries})
|
||||||
|
target_include_directories(${target} PRIVATE ${inc_headers})
|
||||||
|
if (WIN32)
|
||||||
|
target_link_libraries(${target} iphlpapi)
|
||||||
|
endif()
|
||||||
|
if (KYTY_CLANG_CL)
|
||||||
|
target_link_libraries(${target} onecore)
|
||||||
|
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
|
||||||
|
endif()
|
||||||
|
# The macOS x86_64 guest address space needs its .zerofill segments anchored
|
||||||
|
# by linker flags, or the kernel kills the binary on load (posix_spawn EIO).
|
||||||
|
configure_macos_guest_address_space(${target})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(configure_macos_guest_address_space target)
|
||||||
|
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||||
|
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
||||||
|
target_sources(${target} PRIVATE "${KYTY_SOURCE_DIR}/kernel/macosGuestAddressSpace.cpp")
|
||||||
|
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
|
||||||
|
target_link_options(${target} PRIVATE
|
||||||
|
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.cpp")
|
||||||
|
|
||||||
|
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||||
|
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(page_manager_tests common)
|
||||||
|
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(archive_file_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ArchiveFileTests.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(archive_file_tests common ZArchive::zarchive)
|
||||||
|
target_include_directories(archive_file_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(trophy_system_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/TrophySystemTests.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(trophy_system_tests common)
|
||||||
|
target_include_directories(trophy_system_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
|
||||||
|
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(vdecsw_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/VdecswTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/libs/videoDec2Decoder.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(vdecsw_tests common FFmpeg::ffmpeg)
|
||||||
|
target_include_directories(vdecsw_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
|
||||||
|
)
|
||||||
|
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(lru_cache_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/LruCacheTests.cpp"
|
||||||
|
)
|
||||||
|
target_include_directories(lru_cache_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/libs/imeDialog.cpp"
|
||||||
|
)
|
||||||
|
target_include_directories(ime_dialog_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/memoryTracker.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(memory_tracker_tests fmt::fmt common)
|
||||||
|
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ShaderVertexMetadataTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/shaderVertexMetadata.cpp"
|
||||||
|
)
|
||||||
|
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ResourceMaterializationTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(resource_materialization_tests fmt::fmt xbyak::xbyak)
|
||||||
|
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/shaderPixelParameter.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(resource_tracking_tests fmt::fmt xbyak::xbyak)
|
||||||
|
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/libs/libAudio2.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||||
|
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(audio_out_timing_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/AudioOutTimingTests.cpp")
|
||||||
|
target_link_libraries(audio_out_timing_tests common fmt::fmt SDL3::SDL3-static)
|
||||||
|
target_include_directories(audio_out_timing_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/Ngs2SamplerTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
|
||||||
|
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(pad_haptics_tests common fmt::fmt SDL3::SDL3-static opus)
|
||||||
|
target_include_directories(pad_haptics_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(controller_settings_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ControllerSettingsTests.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(controller_settings_tests common fmt::fmt SDL3::SDL3-static)
|
||||||
|
target_include_directories(controller_settings_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(host_input_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/HostInputTests.cpp")
|
||||||
|
target_link_libraries(host_input_tests common fmt::fmt SDL3::SDL3-static)
|
||||||
|
target_include_directories(host_input_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||||
|
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(png_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/PngEncTests.cpp")
|
||||||
|
target_link_libraries(png_enc_tests common fmt::fmt)
|
||||||
|
target_include_directories(png_enc_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
|
||||||
|
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
|
||||||
|
target_include_directories(ces_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(save_data_dialog_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/SaveDataDialogTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/libs/dialog.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(save_data_dialog_tests common fmt::fmt)
|
||||||
|
target_include_directories(save_data_dialog_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_kyty_full_emulator_test(http_uri_parse_tests
|
||||||
|
"${KYTY_TESTS_DIR}/HttpUriParseTests.cpp")
|
||||||
|
|
||||||
|
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
|
||||||
|
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_kyty_full_emulator_test(kernel_file_system_tests
|
||||||
|
"${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp")
|
||||||
|
target_link_libraries(kernel_file_system_tests ZArchive::zarchive)
|
||||||
|
|
||||||
|
add_executable(sync_on_address_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/kernel/syncOnAddress.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(sync_on_address_tests common)
|
||||||
|
target_include_directories(sync_on_address_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/ImagePageTableTests.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(image_page_table_tests common fmt::fmt)
|
||||||
|
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_kyty_full_emulator_test(shader_recompiler_compute_tests "${KYTY_TESTS_DIR}/ShaderRecompilerComputeTests.cpp")
|
||||||
|
|
||||||
|
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
|
||||||
|
set(gpu_test_ms_depth_source
|
||||||
|
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
|
||||||
|
set(gpu_test_ms_depth_spv
|
||||||
|
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
|
||||||
|
set(gpu_test_ms_depth_header
|
||||||
|
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${gpu_test_ms_depth_header}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
|
||||||
|
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||||
|
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
|
||||||
|
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
|
||||||
|
-P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||||
|
DEPENDS "${gpu_test_ms_depth_source}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
target_sources(shader_recompiler_compute_tests PRIVATE
|
||||||
|
"${gpu_test_ms_depth_header}")
|
||||||
|
|
||||||
|
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
|
||||||
|
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||||
|
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||||
|
|
||||||
|
# These tests use exceptions.
|
||||||
|
if(NOT KYTY_CLANG_CL)
|
||||||
|
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
|
||||||
|
virtual_memory_allocation_tests)
|
||||||
|
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_test(NAME emulator_user_name_cli
|
||||||
|
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 1234567890123456)
|
||||||
|
add_test(NAME emulator_user_name_too_long
|
||||||
|
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 12345678901234567)
|
||||||
|
set_tests_properties(emulator_user_name_too_long PROPERTIES WILL_FAIL TRUE)
|
||||||
|
add_test(NAME emulator_present_mode_cli
|
||||||
|
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Immediate)
|
||||||
|
add_test(NAME emulator_present_mode_invalid
|
||||||
|
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Invalid)
|
||||||
|
set_tests_properties(emulator_present_mode_invalid PROPERTIES WILL_FAIL TRUE)
|
||||||
|
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
|
||||||
|
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||||
|
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
||||||
|
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||||
|
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||||
|
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||||
|
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
||||||
|
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
||||||
|
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||||
|
add_test(NAME vdecsw COMMAND $<TARGET_FILE:vdecsw_tests>)
|
||||||
|
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||||
|
add_test(NAME lru_cache COMMAND $<TARGET_FILE:lru_cache_tests>)
|
||||||
|
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||||
|
add_test(NAME resource_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
|
||||||
|
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||||
|
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
||||||
|
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
|
||||||
|
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
|
||||||
|
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||||
|
add_test(NAME audio_out_timing COMMAND $<TARGET_FILE:audio_out_timing_tests>)
|
||||||
|
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
|
||||||
|
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||||
|
add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>)
|
||||||
|
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
|
||||||
|
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||||
|
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
||||||
|
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
||||||
|
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
||||||
|
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||||
|
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||||
|
add_test(NAME shader_recompiler_alignbyte
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||||
|
add_test(NAME graphics_pipeline_rasterization
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --polygon-mode-only)
|
||||||
|
add_test(NAME graphics_draw_offsets
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
|
||||||
|
add_test(NAME virtual_memory_allocation
|
||||||
|
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||||
|
add_test(NAME guest_red_zone_patcher
|
||||||
|
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
|
||||||
|
add_test(NAME command_scheduler_timeline
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
|
||||||
|
add_test(NAME stream_buffer_ring
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||||
|
add_test(NAME gpu_command_lane
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||||
|
add_test(NAME pm4_context_state
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||||
|
add_test(NAME gpu_tiler
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
|
||||||
|
add_test(NAME image_transfer_layout
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-layout-only)
|
||||||
|
|
||||||
|
add_test(NAME texture_cache_layered_image
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
||||||
|
add_test(NAME host_image_allocation
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --host-image-allocation-only)
|
||||||
|
add_test(NAME texture_cache_image_views
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
|
||||||
|
add_test(NAME texture_cache_storage_sampled
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
|
||||||
|
add_test(NAME texture_cache_depth_readback
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
|
||||||
|
add_test(NAME compute_meta_clear_classification
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
|
||||||
|
add_test(NAME buffer_cache_dirty_gc
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
|
||||||
|
add_test(NAME color_render_target_1d
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --rt1d-only)
|
||||||
|
|
||||||
|
add_test(NAME texture_cache_image_overlap
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||||
|
add_test(NAME texture_cache_htile_clear
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||||
|
add_test(NAME buffer_cache_ranges
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||||
|
|
||||||
|
add_custom_target(kyty_tests DEPENDS
|
||||||
|
ime_dialog_tests
|
||||||
|
shader_cfg_tests
|
||||||
|
scalar_provenance_tests
|
||||||
|
image_page_table_tests
|
||||||
|
memory_tracker_tests
|
||||||
|
page_manager_tests
|
||||||
|
archive_file_tests
|
||||||
|
trophy_system_tests
|
||||||
|
avplayer_file_tests
|
||||||
|
vdecsw_tests
|
||||||
|
bit_array_tests
|
||||||
|
lru_cache_tests
|
||||||
|
shader_vertex_metadata_tests
|
||||||
|
resource_materialization_tests
|
||||||
|
resource_tracking_tests
|
||||||
|
event_queue_lifetime_tests
|
||||||
|
kernel_file_system_tests
|
||||||
|
sync_on_address_tests
|
||||||
|
shader_recompiler_compute_tests
|
||||||
|
virtual_memory_allocation_tests
|
||||||
|
audio_out2_port_tests
|
||||||
|
audio_out_timing_tests
|
||||||
|
ngs2_sampler_tests
|
||||||
|
pad_haptics_tests
|
||||||
|
controller_settings_tests
|
||||||
|
host_input_tests
|
||||||
|
save_data_memory_tests
|
||||||
|
png_enc_tests
|
||||||
|
ces_tests
|
||||||
|
save_data_dialog_tests
|
||||||
|
http_uri_parse_tests
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Test Darwin signal-context restoration for SSE4a instructions.
|
||||||
|
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||||
|
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
||||||
|
add_executable(macos_sse4a_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/macosSse4aTests.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/x64InstructionEmulator.cpp"
|
||||||
|
"${KYTY_SOURCE_DIR}/loader/guestInstructionPatcher.cpp"
|
||||||
|
)
|
||||||
|
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis xbyak::xbyak)
|
||||||
|
target_include_directories(macos_sse4a_tests PRIVATE ${inc_headers})
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_test(NAME macos_sse4a COMMAND $<TARGET_FILE:macos_sse4a_tests>)
|
||||||
|
set_tests_properties(macos_sse4a PROPERTIES TIMEOUT 15)
|
||||||
|
add_dependencies(kyty_tests macos_sse4a_tests)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
|
||||||
|
configure_macos_guest_address_space(kyty_emulator)
|
||||||
|
|
||||||
|
set(KYTY_TROPHY_UNLOCK_SOUND "${CMAKE_CURRENT_SOURCE_DIR}/assets/sounds/trophy-unlock.wav")
|
||||||
|
add_custom_target(kyty_trophy_sound
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:kyty_emulator>/assets/sounds"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_TROPHY_UNLOCK_SOUND}"
|
||||||
|
"$<TARGET_FILE_DIR:kyty_emulator>/assets/sounds/trophy-unlock.wav"
|
||||||
|
VERBATIM)
|
||||||
|
add_dependencies(kyty_emulator kyty_trophy_sound)
|
||||||
|
install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION assets/sounds)
|
||||||
|
|
||||||
|
add_custom_target(kyty_touch_cursors
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors"
|
||||||
|
"$<TARGET_FILE_DIR:kyty_emulator>/assets/cursors"
|
||||||
|
VERBATIM)
|
||||||
|
add_dependencies(kyty_emulator kyty_touch_cursors)
|
||||||
|
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors" DESTINATION assets)
|
||||||
|
|
||||||
|
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||||
|
if (WIN32)
|
||||||
|
target_link_libraries(kyty_emulator opengl32)
|
||||||
|
target_link_libraries(kyty_emulator iphlpapi)
|
||||||
|
target_link_libraries(kyty_emulator setupapi)
|
||||||
|
target_link_libraries(kyty_emulator ws2_32)
|
||||||
|
endif()
|
||||||
|
if (KYTY_CLANG_CL)
|
||||||
|
target_link_libraries(kyty_emulator onecore)
|
||||||
|
endif()
|
||||||
|
if (CLANG AND NOT KYTY_CLANG_CL)
|
||||||
|
target_link_libraries(kyty_emulator pthread)
|
||||||
|
endif()
|
||||||
|
# dlopen/dlsym/dladdr for RenderDoc.
|
||||||
|
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
|
||||||
|
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
|
||||||
|
|
||||||
|
include_what_you_use(kyty_emulator "${inc_headers}")
|
||||||
|
|
||||||
|
set(KYTY_EMULATOR_MAP_NAME "kyty_emulator_${KYTY_COMPILER_ID}_${KYTY_LINKER_ID}.map")
|
||||||
|
|
||||||
|
set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP_NAME}")
|
||||||
|
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
||||||
|
|
||||||
|
if(KYTY_CLANG_CL)
|
||||||
|
target_link_options(kyty_emulator PRIVATE
|
||||||
|
"/DYNAMICBASE:NO"
|
||||||
|
"/DEBUG:FULL"
|
||||||
|
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
|
||||||
|
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
|
||||||
|
)
|
||||||
|
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
||||||
|
elseif(WIN32 OR LINUX)
|
||||||
|
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS}")
|
||||||
|
# LINK_FLAGS is a space-delimited string: a -Map path containing spaces would
|
||||||
|
# be split into bogus arguments, so pass it as a discrete link option instead.
|
||||||
|
target_link_options(kyty_emulator PRIVATE "-Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LINUX)
|
||||||
|
target_link_options(kyty_emulator PRIVATE
|
||||||
|
$<$<CONFIG:Release>:-Wl,--strip-all>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
|
||||||
|
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
|
||||||
|
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
|
||||||
|
# reverts to the hardened defaults and aborts when it first executes written code).
|
||||||
|
add_custom_command(TARGET kyty_emulator POST_BUILD
|
||||||
|
COMMAND codesign -s - --force --options runtime
|
||||||
|
--entitlements "${KYTY_SOURCE_DIR}/macos_jit.entitlements"
|
||||||
|
$<TARGET_FILE:kyty_emulator>
|
||||||
|
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS kyty_emulator DESTINATION .)
|
||||||
|
if(KYTY_BUILD_LAUNCHER)
|
||||||
|
add_subdirectory("${KYTY_SOURCE_DIR}/launcher" "${CMAKE_CURRENT_BINARY_DIR}/launcher")
|
||||||
|
|
||||||
|
if(LINUX)
|
||||||
|
target_link_options(launcher PRIVATE $<$<CONFIG:Release>:-Wl,--strip-all>)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if(KYTY_CLANG_CL)
|
||||||
|
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
||||||
|
endif()
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
# KytyPS5
|
# KytyPS5
|
||||||
|
|
||||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||||
[](#system-requirements)
|
[](#system-requirements)
|
||||||
[](#current-status)
|
[](#current-status)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
|
**[Weekly updates](https://github.com/KytyPS5/KytyPS5/discussions/862)** — game progress, recent fixes, and ongoing development.
|
||||||
with experimental macOS support. It is based on a heavily modified version of
|
|
||||||
[Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
|
**[Development on Discord](https://discord.gg/UNrkMqGaBg)** — KytyPS5 development.
|
||||||
compatibility is limited and behavior may change significantly between builds.
|
|
||||||
|
KytyPS5 is a free and open-source PlayStation 5 translation layer written in C++ for Windows
|
||||||
|
and Linux, with experimental macOS support. It is based on a heavily modified version of
|
||||||
|
[Kyty](https://github.com/InoriRus/Kyty). The project is in active development, and behavior
|
||||||
|
can change significantly between builds.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does
|
> KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does
|
||||||
@@ -19,40 +23,43 @@ compatibility is limited and behavior may change significantly between builds.
|
|||||||
|
|
||||||
## Current Status
|
## Current Status
|
||||||
|
|
||||||
KytyPS5 can boot 2D games and a selection of 3D games, including titles built with Unreal Engine
|
KytyPS5 can boot 2D and 3D games, including titles built with Unreal Engine
|
||||||
4/5, Unity, and custom engines. No external low-level emulation modules are currently required.
|
4/5, Unity, and custom engines. External low-level emulation modules are neither required nor
|
||||||
|
planned.
|
||||||
|
|
||||||
Development is focused on compatibility and boot reliability.
|
Development is currently focused on expanding game compatibility and improving boot reliability.
|
||||||
|
|
||||||
Windows is the primary platform and receives the most testing. Linux builds and runs; see
|
Windows and Linux are the primary target platforms and receive the most testing.
|
||||||
[Building on Linux](#building-on-linux).
|
|
||||||
|
|
||||||
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
|
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
|
||||||
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
|
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
|
||||||
on Apple Silicon hardware; see [Building on macOS](#building-on-macos).
|
on Apple Silicon hardware; see [Building on macOS](#building-on-macos).
|
||||||
|
|
||||||
|
Community game test results are available in the
|
||||||
|
[KytyPS5 Compatibility List](https://kytyps5.github.io/).
|
||||||
|
|
||||||
## Bugs and Issues
|
## Bugs and Issues
|
||||||
|
|
||||||
The project is in an early stage, so please be mindful when opening new issues. Expect crashes,
|
Compatibility, stability, and performance can vary between versions. You may encounter crashes
|
||||||
graphical glitches, low compatibility, and poor performance.
|
or graphical glitches, so please include the version you tested when reporting an issue.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<table align="center">
|
<table align="center">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<strong>Disgaea 6</strong><br>
|
<strong>Astro Bot</strong><br>
|
||||||
<img src="docs/screenshots/ps5-01.png" width="300" alt="Disgaea 6 running in KytyPS5">
|
<img src="docs/screenshots/ps5-01.png" width="300" alt="Astro Bot running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<strong>Dreaming Sarah</strong><br>
|
<strong>Saros</strong><br>
|
||||||
<img src="docs/screenshots/ps5-03.png" width="300" alt="Dreaming Sarah running in KytyPS5">
|
<img src="docs/screenshots/ps5-03.png" width="300" alt="Saros running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<strong>Neptunia ReVerse</strong><br>
|
<strong>Neptunia ReVerse</strong><br>
|
||||||
<img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5">
|
<img src="docs/screenshots/ps5-04.png" width="300" alt="Neptunia ReVerse running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<strong>SILENT HILL: The Short Message</strong><br>
|
<strong>SILENT HILL: The Short Message</strong><br>
|
||||||
@@ -61,12 +68,12 @@ graphical glitches, low compatibility, and poor performance.
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<strong>Hellboy</strong><br>
|
<strong>Demon's Souls</strong><br>
|
||||||
<img src="docs/screenshots/ps5-02.png" width="300" alt="Disgaea 6 running in KytyPS5">
|
<img src="docs/screenshots/ps5-02.png" width="300" alt="Demon's Souls running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<strong>Paleo Pines</strong><br>
|
<strong>UFC 6</strong><br>
|
||||||
<img src="docs/screenshots/ps5-06.png" width="300" alt="Dreaming Sarah running in KytyPS5">
|
<img src="docs/screenshots/ps5-06.jpg" width="300" alt="UFC 6 running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -76,19 +83,31 @@ graphical glitches, low compatibility, and poor performance.
|
|||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
|
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
|
||||||
issues first, then use the **Game Emulation Bug Report** template and attach the complete log file.
|
issues first, then use the **Game Emulation Status Report** template and attach the complete log file.
|
||||||
|
|
||||||
Code contributions should be focused, build successfully on the platforms they touch, and include
|
**Pull requests for general fixes, architectural changes, or refactoring that do not address a concrete bug or launch problem in a specific game will be automatically closed.**
|
||||||
relevant tests where practical. Windows is the primary target, so a change that alters shared code
|
|
||||||
should not regress it; changes confined to a platform's own code paths only need to build there. Because KytyPS5 is still evolving quickly, consider opening an issue before
|
**Large, unfocused commits or pull requests that bundle unrelated changes without a clear purpose or game-related benefit will also be automatically closed.**
|
||||||
starting a large change.
|
|
||||||
|
Keep each PR focused. Name the affected game, describe the exact problem, and provide evidence showing what the change fixes.
|
||||||
|
|
||||||
|
Windows and Linux are the primary target platforms, so changes to shared code should not
|
||||||
|
introduce regressions on either platform. Changes confined to a platform's own code paths
|
||||||
|
only need to build on that platform. Because KytyPS5 is still evolving quickly, consider
|
||||||
|
opening an issue before starting a large change.
|
||||||
|
|
||||||
### Formatting
|
### Formatting
|
||||||
|
|
||||||
Set up the clang-format hook after cloning:
|
Set up the clang-format hook after cloning.
|
||||||
|
|
||||||
```powershell
|
Install `pre-commit` using the method appropriate for your platform:
|
||||||
python -m pip install pre-commit
|
|
||||||
|
- **Arch Linux / CachyOS:** `sudo pacman -S pre-commit`
|
||||||
|
- **Other Linux / macOS / Windows:** `python -m pip install pre-commit`
|
||||||
|
|
||||||
|
Then install the Git hook:
|
||||||
|
|
||||||
|
```bash
|
||||||
python -m pre_commit install --install-hooks
|
python -m pre_commit install --install-hooks
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -123,11 +142,13 @@ the Vulkan/SPIR-V validation rules.
|
|||||||
### Build requirements (Windows)
|
### Build requirements (Windows)
|
||||||
|
|
||||||
- Git
|
- Git
|
||||||
- CMake 3.12 or newer
|
- CMake 3.22.1 or newer
|
||||||
- Ninja
|
- Ninja
|
||||||
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
|
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
|
||||||
**C++ Clang tools for Windows** component
|
**C++ Clang tools for Windows** component
|
||||||
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
|
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
|
||||||
|
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslang` or `glslangValidator`) on `PATH`
|
||||||
|
- Python 3 on `PATH`; the bundled SPIRV-Tools runs it at configure time
|
||||||
|
|
||||||
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
|
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
|
||||||
|
|
||||||
@@ -141,7 +162,7 @@ git submodule update --init --recursive
|
|||||||
Configure the project. Replace the Qt path with the version installed on your system:
|
Configure the project. Replace the Qt path with the version installed on your system:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cmake -S src -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
|
cmake -S . -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
|
||||||
```
|
```
|
||||||
|
|
||||||
Build the launcher and stage a runnable installation:
|
Build the launcher and stage a runnable installation:
|
||||||
@@ -156,25 +177,25 @@ The finished application and its runtime dependencies will be placed in
|
|||||||
|
|
||||||
### Building on Linux
|
### Building on Linux
|
||||||
|
|
||||||
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
|
Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland, and
|
||||||
udev development packages SDL2 quietly configures itself without those backends, and the resulting
|
udev development packages, SDL3 quietly configures itself without those backends, and the resulting
|
||||||
build has no working sound and no gamepad hotplug:
|
build has no working audio or gamepad hotplug support:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install --no-install-recommends \
|
sudo apt-get install --no-install-recommends \
|
||||||
clang lld ninja-build cmake git glslang-tools \
|
clang lld ninja-build cmake git glslang-tools python3 pkg-config \
|
||||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
|
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
|
||||||
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
|
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
|
||||||
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
||||||
```
|
```
|
||||||
|
|
||||||
Qt 6 (Concurrent, Network, Widgets) is also required — either the distribution packages
|
Qt 6 (Concurrent, Network, and Widgets) is required for the launcher. Use your distribution's
|
||||||
(`qt6-base-dev`) or an official Qt installation.
|
packages (`qt6-base-dev`) or an official Qt installation.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
cmake -S src -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
||||||
|
|
||||||
@@ -183,12 +204,86 @@ cmake --install _Build/linux --prefix _Build/linux/install
|
|||||||
```
|
```
|
||||||
|
|
||||||
The install step copies the Qt libraries and plugins next to the binaries, so
|
The install step copies the Qt libraries and plugins next to the binaries, so
|
||||||
`_Build/linux/install` runs without a matching system Qt.
|
`_Build/linux/install` runs without a matching system Qt installation. FFmpeg is linked statically
|
||||||
|
from the pinned [KytyPS5 FFmpeg core](https://github.com/KytyPS5/ext-ffmpeg-core)
|
||||||
|
release, including VP9 and WebM support. System FFmpeg packages are not required.
|
||||||
|
|
||||||
|
To build `kyty_emulator` and the `kyty_tests` target without Qt, use a separate build directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule update --init --recursive
|
||||||
|
|
||||||
|
cmake -S . -B _Build/linux-no-qt -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||||
|
-DKYTY_BUILD_LAUNCHER=OFF
|
||||||
|
|
||||||
|
cmake --build _Build/linux-no-qt --target kyty_emulator kyty_tests --parallel
|
||||||
|
```
|
||||||
|
|
||||||
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
|
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
|
||||||
time.
|
time.
|
||||||
|
|
||||||
Note that the CMake source root is `src`, not the repository root.
|
The CMake source root is the repository root.
|
||||||
|
|
||||||
|
### Building on NixOS
|
||||||
|
|
||||||
|
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL3 backend
|
||||||
|
libraries. Enter it and configure exactly as on other Linux distributions; the shell exports
|
||||||
|
`CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not
|
||||||
|
needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix-shell # or: nix develop
|
||||||
|
git submodule update --init --recursive
|
||||||
|
|
||||||
|
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||||
|
|
||||||
|
cmake --build _Build/linux --target launcher --parallel
|
||||||
|
cmake --install _Build/linux --prefix _Build/linux/install
|
||||||
|
```
|
||||||
|
|
||||||
|
The configure step downloads the FFmpeg prebuilts and the `xbyak`, `zydis`, `zstd`, and ZArchive
|
||||||
|
sources, so it needs network access; a fully sandboxed `nix build` would require vendoring those
|
||||||
|
inputs. A Vulkan 1.3 driver must be available at runtime (on NixOS,
|
||||||
|
`hardware.graphics.enable = true`).
|
||||||
|
|
||||||
|
### Building on Fedora and Fedora Atomic (Bazzite, Bluefin, Silverblue)
|
||||||
|
|
||||||
|
On regular Fedora, install dependencies on the host. On Fedora Atomic variants such as Bazzite,
|
||||||
|
Bluefin and Silverblue, use [Distrobox](https://distrobox.it/) and run the install and build steps
|
||||||
|
inside the container. Keep the checkout in your home directory so it is shared with the container.
|
||||||
|
Add `--nvidia` to the create command for hosts using the proprietary NVIDIA driver:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
distrobox create --name kyty-build --image registry.fedoraproject.org/fedora:latest
|
||||||
|
distrobox enter kyty-build
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the toolchain, Qt 6 and SDL3 dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install -y \
|
||||||
|
clang lld ninja-build cmake git glslang python3 pkgconf-pkg-config \
|
||||||
|
mesa-libGL-devel libX11-devel libXcursor-devel libXext-devel libXfixes-devel \
|
||||||
|
libXi-devel libXrandr-devel libXScrnSaver-devel libXtst-devel libxkbcommon-devel \
|
||||||
|
alsa-lib-devel pulseaudio-libs-devel systemd-devel dbus-devel \
|
||||||
|
wayland-devel wayland-protocols-devel qt6-qtbase-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
From the repository root, follow the launcher configure, build and install steps in
|
||||||
|
[Building on Linux](#building-on-linux), omitting `-DCMAKE_PREFIX_PATH="$Qt6_DIR"`; CMake finds
|
||||||
|
Fedora's packaged Qt 6 automatically.
|
||||||
|
|
||||||
|
Run `./_Build/linux/install/kyty_launcher` on regular Fedora or inside the container. To launch a
|
||||||
|
container build from the host, leave the container with `exit`, then run from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
distrobox enter kyty-build -- ./_Build/linux/install/kyty_launcher
|
||||||
|
```
|
||||||
|
|
||||||
|
Qt libraries and plugins are bundled. You can also launch directly on a Fedora Atomic host
|
||||||
|
if its other system libraries are compatible with the container build.
|
||||||
|
|
||||||
### Building on macOS
|
### Building on macOS
|
||||||
|
|
||||||
@@ -200,14 +295,14 @@ Requirements:
|
|||||||
|
|
||||||
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
|
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
|
||||||
- Xcode (or the Command Line Tools)
|
- Xcode (or the Command Line Tools)
|
||||||
- Homebrew packages: `brew install cmake ninja glslang`
|
- Homebrew packages: `brew install cmake ninja glslang python`
|
||||||
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
|
- Qt 6 (Concurrent, Network, and Widgets) with x86-64 support. The official Qt installation is
|
||||||
universal and works; Homebrew's Qt is arm64-only and will not link
|
universal and works; Homebrew's Qt is arm64-only and will not link
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
cmake -S src -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
cmake -S . -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
||||||
@@ -217,17 +312,23 @@ cmake --install _Build/macos --prefix _Build/macos/install
|
|||||||
```
|
```
|
||||||
|
|
||||||
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
|
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
|
||||||
guest code; no manual signing step is required.
|
guest code; no manual signing step is required. When the launcher is built, the installation
|
||||||
|
also produces `_Build/macos/install/KytyPS5.app` — double-click to launch the GUI.
|
||||||
|
A flat `kyty_emulator` is kept for CLI usage.
|
||||||
|
|
||||||
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
|
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
|
||||||
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
|
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
|
||||||
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to `kyty_emulator` and ad-hoc sign it:
|
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to the flat `kyty_emulator`
|
||||||
|
(and, for the bundle, into `KytyPS5.app/Contents/Frameworks/`) and sign it with an ad hoc signature:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
|
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
|
||||||
|
# For the bundle (if present):
|
||||||
|
codesign --force --sign - _Build/macos/install/KytyPS5.app/Contents/Frameworks/libMoltenVK.dylib
|
||||||
|
codesign --force --sign - _Build/macos/install/KytyPS5.app
|
||||||
```
|
```
|
||||||
|
|
||||||
Release archives already include a signed `libMoltenVK.dylib`.
|
Release archives already include a signed `libMoltenVK.dylib` (both flat and inside the bundle).
|
||||||
|
|
||||||
### Regression tests
|
### Regression tests
|
||||||
|
|
||||||
@@ -244,7 +345,7 @@ Use `_Build/linux` instead of `_Build/windows` for a Linux build.
|
|||||||
|
|
||||||
A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake
|
A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake
|
||||||
Tools to build the project with Ninja and `clang-cl` and provides launch profiles for both
|
Tools to build the project with Ninja and `clang-cl` and provides launch profiles for both
|
||||||
`launcher.exe` and `kyty_emulator.exe`. It is Windows-only: VS Code settings cannot select a
|
`kyty_launcher.exe` and `kyty_emulator.exe`. It is Windows-only: VS Code settings cannot select a
|
||||||
compiler per platform, so on Linux configure from the command line as shown above.
|
compiler per platform, so on Linux configure from the command line as shown above.
|
||||||
|
|
||||||
Before using it:
|
Before using it:
|
||||||
@@ -264,33 +365,54 @@ Update your graphics driver before reporting rendering problems.
|
|||||||
To use the graphical launcher:
|
To use the graphical launcher:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\_Build\windows\install\launcher.exe
|
.\_Build\windows\install\kyty_launcher.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./_Build/linux/install/launcher
|
./_Build/linux/install/kyty_launcher
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open _Build/macos/install/KytyPS5.app # or double-click in Finder
|
||||||
```
|
```
|
||||||
|
|
||||||
On first launch, add one or more game folders in the global settings. The launcher searches those
|
On first launch, add one or more game folders in the global settings. The launcher searches those
|
||||||
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
|
folders recursively for game directories containing `eboot.bin` and ZArchive (`.zar`) game dumps
|
||||||
from the game list.
|
whose archive root contains `eboot.bin`. Select a detected game and run it from the game list.
|
||||||
|
ZArchive dumps are mounted read-only and streamed directly; they do not need to be extracted first.
|
||||||
|
|
||||||
The emulator can also be started directly with a legally obtained game directory or ELF file:
|
Official release builds include a built-in updater. Leave **Check for updates on startup** enabled,
|
||||||
|
or click **Check for updates**, then choose **Install Update**. The launcher downloads the package
|
||||||
|
for your platform from GitHub, checks its SHA-256 checksum, and restarts after installation. Close
|
||||||
|
any running games first. Settings, saves, patches, and other files outside the release package are
|
||||||
|
preserved; replacement failures restore the previous files. The installation folder must be writable.
|
||||||
|
If a package cannot be installed automatically, the updater offers the GitHub release page.
|
||||||
|
Source builds and builds from forks do not replace themselves with official releases.
|
||||||
|
|
||||||
|
The emulator can also be started directly with a legally obtained game directory, ELF file, or
|
||||||
|
ZArchive dump:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame"
|
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame"
|
||||||
|
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame.zar"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
|
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
|
||||||
|
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame.zar"
|
||||||
```
|
```
|
||||||
|
|
||||||
On macOS, point SDL at the MoltenVK library explicitly; the hardened runtime prevents it from
|
On macOS, the adjacent flat or app-bundled `libMoltenVK.dylib` is found automatically; no
|
||||||
being picked up from the executable's directory:
|
environment variable is required:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd _Build/macos/install
|
./_Build/macos/install/kyty_emulator --game "/games/ExampleGame"
|
||||||
SDL_VULKAN_LIBRARY="$PWD/libMoltenVK.dylib" ./kyty_emulator --game "/games/ExampleGame"
|
```
|
||||||
|
|
||||||
|
To override the Vulkan loader, set `SDL_VULKAN_LIBRARY`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SDL_VULKAN_LIBRARY=/path/to/libMoltenVK.dylib ./kyty_emulator --game "/games/ExampleGame"
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
|
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
|
||||||
@@ -321,5 +443,6 @@ licenses included with those components.
|
|||||||
|
|
||||||
- [InoriRus/Kyty](https://github.com/InoriRus/Kyty) — KytyPS5 is based on a heavily modified version
|
- [InoriRus/Kyty](https://github.com/InoriRus/Kyty) — KytyPS5 is based on a heavily modified version
|
||||||
of the original Kyty project.
|
of the original Kyty project.
|
||||||
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for memory-model
|
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for understanding PS4
|
||||||
understanding and the AVPlayer implementation.
|
memory behavior, GPU resource aliasing, cache coherency,
|
||||||
|
and the AVPlayer implementation.
|
||||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "تم كسب الكأس!",
|
||||||
|
"user.guest": "ضيف"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofej získána!",
|
||||||
|
"user.guest": "Host"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofæ optjent!",
|
||||||
|
"user.guest": "Gæst"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trophäe erhalten!",
|
||||||
|
"user.guest": "Gast"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Κερδίσατε ένα τρόπαιο!",
|
||||||
|
"user.guest": "Επισκ."
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trophy earned!",
|
||||||
|
"user.guest": "Guest"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trophy earned!",
|
||||||
|
"user.guest": "Guest"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "¡Ganaste un trofeo!",
|
||||||
|
"user.guest": "Invitado"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "¡Ganaste un trofeo!",
|
||||||
|
"user.guest": "Invitado"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Pokaali ansaittu!",
|
||||||
|
"user.guest": "Vieras"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trophée obtenu !",
|
||||||
|
"user.guest": "Invité"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trophée obtenu !",
|
||||||
|
"user.guest": "Invité"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trófea megszerezve!",
|
||||||
|
"user.guest": "Vendég"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofi diperoleh!",
|
||||||
|
"user.guest": "Tamu"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofeo ottenuto!",
|
||||||
|
"user.guest": "Ospite"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "トロフィーを獲得しました!",
|
||||||
|
"user.guest": "ゲスト"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "트로피를 획득했습니다!",
|
||||||
|
"user.guest": "게스트"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofee behaald!",
|
||||||
|
"user.guest": "Gast"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofé opptjent!",
|
||||||
|
"user.guest": "Gjest"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Zdobyto trofeum!",
|
||||||
|
"user.guest": "Gość"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Troféu conquistado!",
|
||||||
|
"user.guest": "Convidado"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Troféu conquistado!",
|
||||||
|
"user.guest": "Convidado"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofeu câștigat!",
|
||||||
|
"user.guest": "Invitat"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Трофей получен!",
|
||||||
|
"user.guest": "Гость"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trofé intjänad!",
|
||||||
|
"user.guest": "Gäst"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "ได้รับถ้วยรางวัลแล้ว!",
|
||||||
|
"user.guest": "แขก"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Kupa kazanıldı!",
|
||||||
|
"user.guest": "Misafir"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "Đã nhận cúp!",
|
||||||
|
"user.guest": "Khách"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "已获得奖杯!",
|
||||||
|
"user.guest": "访客"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trophy.toast.earned": "已獲得獎盃!",
|
||||||
|
"user.guest": "訪客"
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Notification localization
|
||||||
|
|
||||||
|
Trophy notification subtitles use the configured console language. Trophy names still come
|
||||||
|
from the game's metadata. The launcher trophy dialogs are unchanged.
|
||||||
|
|
||||||
|
Edit `assets/localization/<locale>.json`, using flat UTF-8 string values:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"trophy.toast.earned": "Trophy earned!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
CMake converts the catalogs into C++ byte arrays in the build directory. The emulator parses
|
||||||
|
the embedded catalogs once and falls back to `en-US.json` for missing translations. No language
|
||||||
|
files or translation service are needed at runtime. Rebuild after editing a catalog.
|
||||||
|
|
||||||
|
For local testing, rebuild `kyty_emulator`, choose a game's Console Language in the launcher,
|
||||||
|
and unlock a trophy with notifications enabled. Installed system fonts provide non-Latin
|
||||||
|
glyphs; the selected language needs an appropriate font on the host.
|
||||||
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 2.4 MiB |
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1789426278,
|
||||||
|
"narHash": "sha256-GnotcvKtbTMnoVx6G4E7ZdUX6487Dky74dqq3pU7iRk=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "efe6f071ede9d21c37462d2d6682d5e670099684",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
description = "KytyPS5 development shell";
|
||||||
|
|
||||||
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }:
|
||||||
|
let
|
||||||
|
systems = [ "x86_64-linux" ];
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells = forAllSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = import ./nix/devshell.nix { inherit pkgs; };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Build-time dependencies for the bundled SDL3 audio, video and input backends.
|
||||||
|
buildDeps = with pkgs; [
|
||||||
|
qt6.qtbase # Concurrent, Network, Widgets (launcher)
|
||||||
|
vulkan-headers
|
||||||
|
vulkan-loader
|
||||||
|
vulkan-tools
|
||||||
|
vulkan-validation-layers
|
||||||
|
libglvnd
|
||||||
|
# X11 / input
|
||||||
|
libX11
|
||||||
|
libXcursor
|
||||||
|
libXext
|
||||||
|
libXfixes
|
||||||
|
libXi
|
||||||
|
libXrandr
|
||||||
|
libXScrnSaver
|
||||||
|
libXtst
|
||||||
|
libxkbcommon
|
||||||
|
# Wayland
|
||||||
|
wayland
|
||||||
|
wayland-protocols
|
||||||
|
libdecor
|
||||||
|
# audio + device/dbus
|
||||||
|
alsa-lib
|
||||||
|
libpulseaudio
|
||||||
|
systemd # libudev
|
||||||
|
dbus
|
||||||
|
];
|
||||||
|
|
||||||
|
# Libraries the built binaries need at runtime.
|
||||||
|
runtimeLibs = with pkgs; [
|
||||||
|
stdenv.cc.cc.lib # libstdc++ / libgcc_s
|
||||||
|
libgcc.lib
|
||||||
|
qt6.qtbase # libQt6Concurrent/Network/Widgets
|
||||||
|
vulkan-loader
|
||||||
|
libglvnd
|
||||||
|
alsa-lib
|
||||||
|
libpulseaudio
|
||||||
|
libX11
|
||||||
|
libXcursor
|
||||||
|
libXext
|
||||||
|
libXfixes
|
||||||
|
libXi
|
||||||
|
libXrandr
|
||||||
|
libXScrnSaver
|
||||||
|
libXtst
|
||||||
|
libxkbcommon
|
||||||
|
wayland
|
||||||
|
libdecor
|
||||||
|
systemd
|
||||||
|
dbus
|
||||||
|
];
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
name = "kytyps5";
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
clang
|
||||||
|
lld
|
||||||
|
cmake
|
||||||
|
ninja
|
||||||
|
pkg-config
|
||||||
|
git
|
||||||
|
glslang # provides glslangValidator for the bundled shaders
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = buildDeps;
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export CMAKE_PREFIX_PATH="${pkgs.qt6.qtbase}:''${CMAKE_PREFIX_PATH:-}"
|
||||||
|
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins:${pkgs.qt6.qtbase}/lib/qt6/plugins:''${QT_PLUGIN_PATH:-}"
|
||||||
|
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:''${LD_LIBRARY_PATH:-}"
|
||||||
|
|
||||||
|
if [ -z "''${KYTY_SHELL_QUIET:-}" ]; then
|
||||||
|
echo "KytyPS5 dev shell"
|
||||||
|
echo " cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \\"
|
||||||
|
echo " -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
|
||||||
|
echo " cmake --build _Build/linux --target launcher --parallel"
|
||||||
|
echo " cmake --install _Build/linux --prefix _Build/linux/install"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
let
|
||||||
|
pkgs = import <nixpkgs> { };
|
||||||
|
in
|
||||||
|
import ./nix/devshell.nix { inherit pkgs; }
|
||||||
@@ -1,584 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.12...4.3)
|
|
||||||
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
|
||||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
project(Kyty)
|
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
|
||||||
set(LINUX TRUE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (NOT (WIN32 OR LINUX OR APPLE))
|
|
||||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
|
||||||
|
|
||||||
include(utils.cmake)
|
|
||||||
include(CTest)
|
|
||||||
|
|
||||||
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
|
|
||||||
|
|
||||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
|
|
||||||
|
|
||||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
|
||||||
|
|
||||||
include(TestBigEndian)
|
|
||||||
|
|
||||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
|
||||||
message(FATAL_ERROR "only 64-bit builds are supported")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
test_big_endian(BIG_ENDIAN)
|
|
||||||
if(BIG_ENDIAN)
|
|
||||||
set(KYTY_ENDIAN KYTY_ENDIAN_BIG)
|
|
||||||
else()
|
|
||||||
set(KYTY_ENDIAN KYTY_ENDIAN_LITTLE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
|
||||||
set(KYTY_BUILD KYTY_BUILD_DEBUG)
|
|
||||||
else()
|
|
||||||
set(KYTY_BUILD KYTY_BUILD_RELEASE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(LINUX OR APPLE)
|
|
||||||
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
|
|
||||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
|
||||||
else()
|
|
||||||
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(CLANG FALSE)
|
|
||||||
set(GCC FALSE)
|
|
||||||
set(KYTY_CLANG_CL FALSE)
|
|
||||||
get_filename_component(KYTY_CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME_WE)
|
|
||||||
string(TOLOWER "${KYTY_CXX_COMPILER_NAME}" KYTY_CXX_COMPILER_NAME)
|
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
|
||||||
set(CLANG TRUE)
|
|
||||||
set(KYTY_COMPILER CLANG)
|
|
||||||
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
|
|
||||||
set(KYTY_CLANG_CL TRUE)
|
|
||||||
set(KYTY_LINKER LLD_LINK)
|
|
||||||
elseif(APPLE)
|
|
||||||
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
|
|
||||||
else()
|
|
||||||
set(KYTY_LINKER LLD)
|
|
||||||
endif()
|
|
||||||
if ((CMAKE_AR MATCHES "llvm") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0.0) AND (CMAKE_CXX_COMPILER MATCHES "winlibs"))
|
|
||||||
string(REPLACE "llvm-ar.exe" "ar.exe" CMAKE_AR ${CMAKE_AR}) # llvm-ar.exe 11.0.0 from winlibs is broken
|
|
||||||
endif()
|
|
||||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
||||||
set(GCC TRUE)
|
|
||||||
set(KYTY_COMPILER GCC)
|
|
||||||
set(KYTY_LINKER LD)
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "only Clang and GCC are supported; found ${CMAKE_CXX_COMPILER_ID}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(KYTY_PROJECT_NAME "Emulator" CACHE STRING "Project name suffix")
|
|
||||||
|
|
||||||
string(TOLOWER ${KYTY_COMPILER} KYTY_COMPILER_ID)
|
|
||||||
string(TOLOWER ${KYTY_LINKER} KYTY_LINKER_ID)
|
|
||||||
|
|
||||||
if (CLANG AND (KYTY_LINKER STREQUAL LLD))
|
|
||||||
set(KYTY_LD_OPTIONS "-fuse-ld=lld")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
|
|
||||||
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
|
|
||||||
|
|
||||||
include_directories(
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/gtest/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/gtest
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
|
|
||||||
${PROJECT_BINARY_DIR}
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
set(KYTY_VERSION "${PROJECT_VERSION}")
|
|
||||||
|
|
||||||
configure_file(
|
|
||||||
${PROJECT_SOURCE_DIR}/cmake_config.h.in
|
|
||||||
${PROJECT_BINARY_DIR}/cmake_config.h
|
|
||||||
)
|
|
||||||
|
|
||||||
find_package(Git)
|
|
||||||
|
|
||||||
add_custom_target( KytyGitVersion
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
|
|
||||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
|
|
||||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
|
||||||
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
|
|
||||||
COMMENT "Generate kytyGitVersion.h"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
|
||||||
list(APPEND KYTY_IWYU
|
|
||||||
kyty_emulator
|
|
||||||
common
|
|
||||||
#launcher
|
|
||||||
)
|
|
||||||
list(APPEND KYTY_CLANG_TIDY
|
|
||||||
kyty_emulator
|
|
||||||
#common
|
|
||||||
launcher
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
|
|
||||||
|
|
||||||
config_compiler_and_linker()
|
|
||||||
|
|
||||||
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
|
|
||||||
add_subdirectory(common)
|
|
||||||
|
|
||||||
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
|
||||||
libs/*.cpp
|
|
||||||
libs/*.h
|
|
||||||
graphics/*.cpp
|
|
||||||
graphics/*.h
|
|
||||||
graphics/guest_gpu/*.cpp
|
|
||||||
graphics/guest_gpu/*.h
|
|
||||||
graphics/guest_gpu/command_processor/*.cpp
|
|
||||||
graphics/guest_gpu/command_processor/*.h
|
|
||||||
graphics/host_gpu/*.cpp
|
|
||||||
graphics/host_gpu/*.h
|
|
||||||
graphics/host_gpu/renderer/*.cpp
|
|
||||||
graphics/host_gpu/renderer/*.h
|
|
||||||
graphics/host_gpu/renderer/cache/*.cpp
|
|
||||||
graphics/host_gpu/renderer/cache/*.h
|
|
||||||
graphics/host_gpu/renderer/image/*.cpp
|
|
||||||
graphics/host_gpu/renderer/image/*.h
|
|
||||||
graphics/host_gpu/renderer/pipeline/*.cpp
|
|
||||||
graphics/host_gpu/renderer/pipeline/*.h
|
|
||||||
graphics/shader/*.cpp
|
|
||||||
graphics/shader/*.h
|
|
||||||
graphics/shader/recompiler/*.cpp
|
|
||||||
graphics/shader/recompiler/*.h
|
|
||||||
graphics/shader/recompiler/cfg/*.cpp
|
|
||||||
graphics/shader/recompiler/cfg/*.h
|
|
||||||
graphics/shader/recompiler/decompiler/*.cpp
|
|
||||||
graphics/shader/recompiler/decompiler/*.h
|
|
||||||
graphics/shader/recompiler/emitter/*.cpp
|
|
||||||
graphics/shader/recompiler/emitter/*.h
|
|
||||||
graphics/shader/recompiler/ir/*.cpp
|
|
||||||
graphics/shader/recompiler/ir/*.h
|
|
||||||
graphics/presentation/*.cpp
|
|
||||||
graphics/presentation/*.h
|
|
||||||
graphics/presentation/window/*.cpp
|
|
||||||
graphics/presentation/window/*.h
|
|
||||||
kernel/*.cpp
|
|
||||||
kernel/*.h
|
|
||||||
loader/*.cpp
|
|
||||||
loader/*.h
|
|
||||||
)
|
|
||||||
|
|
||||||
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
|
|
||||||
set(gpu_tiler_shader_dir "${CMAKE_CURRENT_SOURCE_DIR}/graphics/host_gpu/shaders")
|
|
||||||
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
|
||||||
set(gpu_tiler_shader_names
|
|
||||||
standard256
|
|
||||||
standard4
|
|
||||||
standard4_3d
|
|
||||||
standard64
|
|
||||||
standard64_3d
|
|
||||||
prt
|
|
||||||
prt_3d
|
|
||||||
render_target
|
|
||||||
depth
|
|
||||||
promote_d16
|
|
||||||
demote_d16
|
|
||||||
swap_bgra16
|
|
||||||
)
|
|
||||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
|
||||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
|
||||||
set(shader_source "${gpu_tiler_shader_dir}/gpu_tiler_${shader_name}.comp")
|
|
||||||
set(shader_spv "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}.spv")
|
|
||||||
set(shader_header "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}_spv.h")
|
|
||||||
string(TOUPPER "GPU_TILER_${shader_name}_SPV" shader_symbol)
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${shader_header}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
|
||||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
|
||||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
|
||||||
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
|
||||||
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
list(APPEND gpu_tiler_shader_headers "${shader_header}")
|
|
||||||
endforeach()
|
|
||||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
|
||||||
|
|
||||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
|
||||||
set(gpu_blit_shader_sources
|
|
||||||
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
|
|
||||||
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
|
|
||||||
)
|
|
||||||
foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
|
||||||
get_filename_component(shader_name "${shader_source}" NAME_WE)
|
|
||||||
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
|
||||||
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
|
||||||
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${shader_header}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
|
||||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
|
||||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
|
||||||
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
|
||||||
DEPENDS "${shader_source}"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
list(APPEND gpu_blit_shader_headers "${shader_header}")
|
|
||||||
endforeach()
|
|
||||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
|
||||||
|
|
||||||
list(APPEND kyty_emulator_src
|
|
||||||
emulator.h
|
|
||||||
emulator.cpp
|
|
||||||
)
|
|
||||||
list(REMOVE_DUPLICATES kyty_emulator_src)
|
|
||||||
|
|
||||||
if (KYTY_CLANG_CL)
|
|
||||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9)
|
|
||||||
|
|
||||||
# Linux system libraries required by the static FFmpeg archive.
|
|
||||||
if(LINUX)
|
|
||||||
find_package(Threads REQUIRED)
|
|
||||||
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
|
|
||||||
|
|
||||||
# Optional FFmpeg dependencies.
|
|
||||||
find_package(ZLIB)
|
|
||||||
if(ZLIB_FOUND)
|
|
||||||
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
|
|
||||||
endif()
|
|
||||||
find_library(KYTY_BZ2_LIBRARY bz2)
|
|
||||||
if(KYTY_BZ2_LIBRARY)
|
|
||||||
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
|
|
||||||
endif()
|
|
||||||
find_library(KYTY_LZMA_LIBRARY lzma)
|
|
||||||
if(KYTY_LZMA_LIBRARY)
|
|
||||||
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(inc_headers
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/xxhash
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/stb
|
|
||||||
)
|
|
||||||
|
|
||||||
if (KYTY_CLANG_CL)
|
|
||||||
list(APPEND kyty_emulator_link_libraries winpthread)
|
|
||||||
list(APPEND inc_headers
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/winpthread/include
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
list(APPEND check_headers
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
function(add_kyty_full_emulator_test target source)
|
|
||||||
add_executable(${target} EXCLUDE_FROM_ALL ${source} ${kyty_emulator_src})
|
|
||||||
target_link_libraries(${target} ${kyty_emulator_link_libraries})
|
|
||||||
target_include_directories(${target} PRIVATE ${inc_headers})
|
|
||||||
if (WIN32)
|
|
||||||
target_link_libraries(${target} iphlpapi)
|
|
||||||
endif()
|
|
||||||
if (KYTY_CLANG_CL)
|
|
||||||
target_link_libraries(${target} onecore)
|
|
||||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
|
|
||||||
endif()
|
|
||||||
# The macOS x86_64 guest address space needs its .zerofill segments anchored
|
|
||||||
# by linker flags, or the kernel kills the binary on load (posix_spawn EIO).
|
|
||||||
configure_macos_guest_address_space(${target})
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
function(configure_macos_guest_address_space target)
|
|
||||||
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
|
||||||
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
|
||||||
target_sources(${target} PRIVATE kernel/macosGuestAddressSpace.cpp)
|
|
||||||
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
|
|
||||||
target_link_options(${target} PRIVATE
|
|
||||||
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
|
|
||||||
|
|
||||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/ScalarProvenanceTests.cpp
|
|
||||||
graphics/host_gpu/hostMemory.cpp
|
|
||||||
graphics/shader/recompiler/ir/ReadLaneElimination.cpp
|
|
||||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
|
||||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
|
||||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/PageManagerTests.cpp
|
|
||||||
graphics/host_gpu/pageManager.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/BitArrayTests.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/MemoryTrackerTests.cpp
|
|
||||||
graphics/host_gpu/pageManager.cpp
|
|
||||||
graphics/host_gpu/memoryTracker.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(memory_tracker_tests fmt::fmt common)
|
|
||||||
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/ShaderVertexMetadataTests.cpp
|
|
||||||
graphics/host_gpu/hostMemory.cpp
|
|
||||||
graphics/shader/shaderVertexMetadata.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/ShaderStageRuntimeTests.cpp
|
|
||||||
graphics/guest_gpu/gpu_format.cpp
|
|
||||||
graphics/host_gpu/hostMemory.cpp
|
|
||||||
graphics/shader/shaderStageRuntime.cpp
|
|
||||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
|
||||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
|
||||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
|
|
||||||
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/ResourceTrackingTests.cpp
|
|
||||||
graphics/guest_gpu/gpu_format.cpp
|
|
||||||
graphics/host_gpu/hostMemory.cpp
|
|
||||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
|
||||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
|
||||||
graphics/shader/recompiler/ir/SrtPatcher.cpp
|
|
||||||
graphics/shader/recompiler/ir/ResourceTracking.cpp
|
|
||||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
|
||||||
graphics/shader/recompiler/ir/ShaderInfoCollection.cpp
|
|
||||||
graphics/shader/recompiler/ir/BindingLayout.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
|
||||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/ResourceMutexTests.cpp
|
|
||||||
graphics/host_gpu/renderer/cache/resourceMutex.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(resource_mutex_tests common)
|
|
||||||
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/AudioOut2PortTests.cpp
|
|
||||||
libs/libAudio2.cpp
|
|
||||||
loader/timer.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
|
||||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/EventQueueLifetimeTests.cpp
|
|
||||||
kernel/eventQueue.cpp
|
|
||||||
loader/timer.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
|
|
||||||
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
|
|
||||||
../tests/ImagePageTableTests.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(image_page_table_tests common fmt::fmt)
|
|
||||||
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
|
|
||||||
|
|
||||||
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
|
|
||||||
set(gpu_test_ms_depth_source
|
|
||||||
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
|
|
||||||
set(gpu_test_ms_depth_spv
|
|
||||||
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
|
|
||||||
set(gpu_test_ms_depth_header
|
|
||||||
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${gpu_test_ms_depth_header}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
|
|
||||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
|
||||||
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
|
|
||||||
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
|
|
||||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
|
||||||
DEPENDS "${gpu_test_ms_depth_source}"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
target_sources(shader_recompiler_compute_tests PRIVATE
|
|
||||||
"${gpu_test_ms_depth_header}")
|
|
||||||
|
|
||||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
|
|
||||||
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
|
||||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
|
||||||
|
|
||||||
# These tests use exceptions.
|
|
||||||
if(NOT KYTY_CLANG_CL)
|
|
||||||
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
|
|
||||||
virtual_memory_allocation_tests)
|
|
||||||
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(BUILD_TESTING)
|
|
||||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
|
||||||
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
|
||||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
|
||||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
|
||||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
|
||||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
|
||||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
|
||||||
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
|
|
||||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
|
||||||
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
|
|
||||||
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
|
||||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
|
||||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
|
||||||
add_test(NAME virtual_memory_allocation
|
|
||||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
|
||||||
add_test(NAME command_scheduler_timeline
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
|
|
||||||
add_test(NAME stream_buffer_ring
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
|
||||||
add_test(NAME gpu_command_lane
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
|
||||||
add_test(NAME gpu_tiler
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
|
|
||||||
|
|
||||||
add_test(NAME texture_cache_layered_image
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
|
||||||
add_test(NAME texture_cache_image_views
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
|
|
||||||
add_test(NAME texture_cache_storage_sampled
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
|
|
||||||
add_test(NAME texture_cache_depth_readback
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
|
|
||||||
add_test(NAME buffer_cache_dirty_gc
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
# These tests still depend on the Windows multisample-depth path.
|
|
||||||
add_test(NAME texture_cache_image_overlap
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
|
||||||
add_test(NAME texture_cache_htile_clear
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
|
||||||
add_test(NAME buffer_cache_ranges
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_custom_target(kyty_tests DEPENDS
|
|
||||||
shader_cfg_tests
|
|
||||||
scalar_provenance_tests
|
|
||||||
image_page_table_tests
|
|
||||||
memory_tracker_tests
|
|
||||||
page_manager_tests
|
|
||||||
bit_array_tests
|
|
||||||
shader_vertex_metadata_tests
|
|
||||||
shader_stage_runtime_tests
|
|
||||||
resource_tracking_tests
|
|
||||||
resource_mutex_tests
|
|
||||||
event_queue_lifetime_tests
|
|
||||||
shader_recompiler_compute_tests
|
|
||||||
virtual_memory_allocation_tests
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
|
|
||||||
configure_macos_guest_address_space(kyty_emulator)
|
|
||||||
|
|
||||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
|
||||||
if (WIN32)
|
|
||||||
target_link_libraries(kyty_emulator opengl32)
|
|
||||||
target_link_libraries(kyty_emulator iphlpapi)
|
|
||||||
target_link_libraries(kyty_emulator setupapi)
|
|
||||||
target_link_libraries(kyty_emulator ws2_32)
|
|
||||||
endif()
|
|
||||||
if (KYTY_CLANG_CL)
|
|
||||||
target_link_libraries(kyty_emulator onecore)
|
|
||||||
endif()
|
|
||||||
if (CLANG AND NOT KYTY_CLANG_CL)
|
|
||||||
target_link_libraries(kyty_emulator pthread)
|
|
||||||
endif()
|
|
||||||
# dlopen/dlsym/dladdr for RenderDoc.
|
|
||||||
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
|
|
||||||
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
|
|
||||||
|
|
||||||
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
|
|
||||||
|
|
||||||
include_what_you_use(kyty_emulator "${inc_headers}")
|
|
||||||
|
|
||||||
set(KYTY_EMULATOR_MAP_NAME "kyty_emulator_${KYTY_COMPILER_ID}_${KYTY_LINKER_ID}.map")
|
|
||||||
|
|
||||||
set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP_NAME}")
|
|
||||||
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
|
||||||
|
|
||||||
if(KYTY_CLANG_CL)
|
|
||||||
target_link_options(kyty_emulator PRIVATE
|
|
||||||
"/DYNAMICBASE:NO"
|
|
||||||
"/DEBUG:FULL"
|
|
||||||
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
|
|
||||||
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
|
|
||||||
)
|
|
||||||
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
|
||||||
elseif(WIN32 OR LINUX)
|
|
||||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_dependencies(kyty_emulator KytyGitVersion)
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
|
|
||||||
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
|
|
||||||
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
|
|
||||||
# reverts to the hardened defaults and aborts when it first executes written code).
|
|
||||||
add_custom_command(TARGET kyty_emulator POST_BUILD
|
|
||||||
COMMAND codesign -s - --force --options runtime
|
|
||||||
--entitlements "${CMAKE_CURRENT_SOURCE_DIR}/macos_jit.entitlements"
|
|
||||||
$<TARGET_FILE:kyty_emulator>
|
|
||||||
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
install(TARGETS kyty_emulator DESTINATION .)
|
|
||||||
if(KYTY_BUILD_LAUNCHER)
|
|
||||||
add_subdirectory(launcher)
|
|
||||||
endif()
|
|
||||||
if(KYTY_CLANG_CL)
|
|
||||||
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
|
||||||
endif()
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
#define KYTY_VERSION "@KYTY_VERSION@"
|
#define KYTY_VERSION "@KYTY_VERSION@"
|
||||||
|
#define KYTY_BUILD_REPOSITORY "@KYTY_BUILD_REPOSITORY@"
|
||||||
|
#define KYTY_RELEASE_TAG "@KYTY_RELEASE_TAG@"
|
||||||
|
#cmakedefine KYTY_OFFICIAL_BUILD
|
||||||
|
#cmakedefine KYTY_FORK_BUILD
|
||||||
#define KYTY_ENDIAN @KYTY_ENDIAN@
|
#define KYTY_ENDIAN @KYTY_ENDIAN@
|
||||||
#define KYTY_PLATFORM @KYTY_PLATFORM@
|
#define KYTY_PLATFORM @KYTY_PLATFORM@
|
||||||
#define KYTY_COMPILER KYTY_COMPILER_@KYTY_COMPILER@
|
#define KYTY_COMPILER KYTY_COMPILER_@KYTY_COMPILER@
|
||||||
|
|||||||
@@ -12,9 +12,23 @@ endif()
|
|||||||
|
|
||||||
add_library(common STATIC ${common_src} ${common_headers})
|
add_library(common STATIC ${common_src} ${common_headers})
|
||||||
|
|
||||||
|
set(localization_dir "${PROJECT_SOURCE_DIR}/assets/localization")
|
||||||
|
file(GLOB localization_catalogs CONFIGURE_DEPENDS "${localization_dir}/*.json")
|
||||||
|
set(localization_data "${CMAKE_CURRENT_BINARY_DIR}/localizationData.h")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${localization_data}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} "-DINPUTS=${localization_catalogs}" "-DOUTPUT=${localization_data}"
|
||||||
|
-P "${KYTY_SOURCE_DIR}/embed_localization.cmake"
|
||||||
|
DEPENDS ${localization_catalogs} "${KYTY_SOURCE_DIR}/embed_localization.cmake"
|
||||||
|
COMMENT "Embed notification localization catalogs"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
target_sources(common PRIVATE "${localization_data}")
|
||||||
|
|
||||||
target_include_directories(common
|
target_include_directories(common
|
||||||
PUBLIC ${PROJECT_SOURCE_DIR}
|
PUBLIC ${KYTY_SOURCE_DIR}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,8 +38,15 @@ target_link_libraries(common
|
|||||||
Tracy::TracyClient
|
Tracy::TracyClient
|
||||||
PRIVATE
|
PRIVATE
|
||||||
cpuinfo
|
cpuinfo
|
||||||
|
kyty_git_version
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
|
ZArchive::zarchive
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
)
|
)
|
||||||
|
if(APPLE)
|
||||||
|
# hostStats.cpp reads GPU utilization from the IOAccelerator registry entries.
|
||||||
|
target_link_libraries(common PRIVATE "-framework CoreFoundation" "-framework IOKit")
|
||||||
|
endif()
|
||||||
|
|
||||||
get_property(inc_headers TARGET common PROPERTY INCLUDE_DIRECTORIES)
|
get_property(inc_headers TARGET common PROPERTY INCLUDE_DIRECTORIES)
|
||||||
set(check_headers ${CMAKE_CURRENT_SOURCE_DIR})
|
set(check_headers ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#include "common/additionalContent.h"
|
||||||
|
|
||||||
|
#include "common/stringUtils.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <regex>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string GetString(const nlohmann::json& object, const std::string& key) {
|
||||||
|
const auto it = object.find(key);
|
||||||
|
return it != object.end() && it->is_string() ? it->get<std::string>() : std::string {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetTitle(const nlohmann::json& param) {
|
||||||
|
const auto localized = param.find("localizedParameters");
|
||||||
|
if (localized != param.end() && localized->is_object()) {
|
||||||
|
const auto preferred = localized->find(GetString(*localized, "defaultLanguage"));
|
||||||
|
if (preferred != localized->end()) {
|
||||||
|
if (auto title = GetString(*preferred, "titleName"); !title.empty()) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& language: *localized) {
|
||||||
|
if (auto title = GetString(language, "titleName"); !title.empty()) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GetString(param, "titleName");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||||
|
const std::string& title_id) {
|
||||||
|
if (dir.empty() || title_id.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
const auto root = std::filesystem::absolute(dir, ec);
|
||||||
|
if (ec) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan directories separately so an unreadable child does not discard its siblings.
|
||||||
|
std::vector<std::filesystem::path> pending {root};
|
||||||
|
std::vector<std::filesystem::path> params;
|
||||||
|
while (!pending.empty()) {
|
||||||
|
const auto current = std::move(pending.back());
|
||||||
|
pending.pop_back();
|
||||||
|
std::filesystem::directory_iterator it(
|
||||||
|
current, std::filesystem::directory_options::skip_permission_denied, ec);
|
||||||
|
const std::filesystem::directory_iterator end;
|
||||||
|
for (; !ec && it != end; it.increment(ec)) {
|
||||||
|
std::error_code entry_ec;
|
||||||
|
if (it->is_directory(entry_ec) && !it->is_symlink(entry_ec) && !entry_ec) {
|
||||||
|
pending.push_back(it->path());
|
||||||
|
} else if (it->is_regular_file(entry_ec) && !entry_ec &&
|
||||||
|
EqualNoCase(PathToString(it->path().filename()), "param.json") &&
|
||||||
|
EqualNoCase(PathToString(it->path().parent_path().filename()), "sce_sys")) {
|
||||||
|
params.push_back(it->path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(params.begin(), params.end());
|
||||||
|
|
||||||
|
static const std::regex content_id_pattern(
|
||||||
|
"^[A-Z]{2}[0-9]{4}-[A-Z]{4}[0-9]{5}_[0-9]{2}-[A-Z0-9]{16}$");
|
||||||
|
std::map<std::string, AddContentEntry> by_label;
|
||||||
|
for (const auto& file: params) {
|
||||||
|
const auto size = std::filesystem::file_size(file, ec);
|
||||||
|
if (ec || size > 1024 * 1024) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::ifstream stream(file, std::ios::binary);
|
||||||
|
if (!stream) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto param = nlohmann::json::parse(stream, nullptr, false);
|
||||||
|
// applicationCategoryType is required for applications, not additional content.
|
||||||
|
if (!param.is_object() || param.contains("applicationCategoryType")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto content_id = GetString(param, "contentId");
|
||||||
|
if (!std::regex_match(content_id, content_id_pattern) ||
|
||||||
|
content_id.compare(7, 9, title_id) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto mount_dir = file.parent_path().parent_path();
|
||||||
|
const auto label = content_id.substr(20);
|
||||||
|
by_label.try_emplace(label,
|
||||||
|
AddContentEntry {content_id, label, mount_dir, GetTitle(param)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AddContentEntry> entries;
|
||||||
|
entries.reserve(by_label.size());
|
||||||
|
for (auto& [label, entry]: by_label) {
|
||||||
|
entries.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||||
|
#define KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
struct AddContentEntry {
|
||||||
|
std::string content_id;
|
||||||
|
std::string entitlement_label;
|
||||||
|
std::filesystem::path mount_dir;
|
||||||
|
std::string title;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Finds packages for this title, deduplicated by entitlement label.
|
||||||
|
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||||
|
const std::string& title_id);
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
||||||
|
#endif /* KYTY_COMMON_ADDITIONAL_CONTENT_H_ */
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
|
||||||
|
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||||
|
auto mod{static_cast<T>(value % size)};
|
||||||
|
value -= mod;
|
||||||
|
return static_cast<T>(mod == T{0} ? value : value + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
|
||||||
|
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||||
|
return static_cast<T>(value - value % size);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
#include "common/archive.h"
|
||||||
|
|
||||||
|
#include "common/stringUtils.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstring>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <zarchive/zarchivereader.h>
|
||||||
|
|
||||||
|
// POSIX hosts and MinGW's pthread-backed libstdc++ have cancellable condition waits.
|
||||||
|
// MSVC/ClangCL and MinGW's native Win32 threading backend do not use pthread waits.
|
||||||
|
#if !defined(_WIN32) || defined(_GLIBCXX_GCC_GTHR_POSIX_H)
|
||||||
|
#include <pthread.h>
|
||||||
|
#define KYTY_ARCHIVE_PTHREAD_CANCELLATION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
// Backends expose immutable metadata and offset-based reads. Cursor management, path routing,
|
||||||
|
// reader lifetime and the host-stack worker are shared by every archive format.
|
||||||
|
class ArchiveReader {
|
||||||
|
public:
|
||||||
|
struct Entry {
|
||||||
|
uint64_t id;
|
||||||
|
uint64_t size;
|
||||||
|
bool is_file;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual ~ArchiveReader() = default;
|
||||||
|
virtual std::optional<Entry> Find(std::string_view member) = 0;
|
||||||
|
virtual std::vector<File::DirEntry> List(std::string_view member) = 0;
|
||||||
|
virtual uint64_t Read(uint64_t id, uint64_t offset, uint32_t size, void* data) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ZArchiveReaderBackend final: public ArchiveReader {
|
||||||
|
public:
|
||||||
|
explicit ZArchiveReaderBackend(std::unique_ptr<ZArchiveReader> reader)
|
||||||
|
: m_reader(std::move(reader)) {}
|
||||||
|
|
||||||
|
std::optional<Entry> Find(std::string_view member) override {
|
||||||
|
const auto node = m_reader->LookUp(member);
|
||||||
|
if (node == ZARCHIVE_INVALID_NODE) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return Entry {node, m_reader->GetFileSize(node), m_reader->IsFile(node)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<File::DirEntry> List(std::string_view member) override {
|
||||||
|
const auto node = m_reader->LookUp(member);
|
||||||
|
if (!m_reader->IsDirectory(node)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto count = m_reader->GetDirEntryCount(node);
|
||||||
|
if (count > (1u << 20u)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<File::DirEntry> result;
|
||||||
|
result.reserve(count);
|
||||||
|
for (uint32_t index = 0; index < count; ++index) {
|
||||||
|
ZArchiveReader::DirEntry entry {};
|
||||||
|
if (m_reader->GetDirEntry(node, index, entry)) {
|
||||||
|
result.push_back({std::string(entry.name), entry.isFile});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t Read(uint64_t id, uint64_t offset, uint32_t size, void* data) override {
|
||||||
|
// ZArchiveReader already serializes access to its stream and decompression cache.
|
||||||
|
return m_reader->ReadFromFile(static_cast<ZArchiveNodeHandle>(id), offset, size, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ZArchiveReader> m_reader;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<ArchiveReader> OpenZArchive(const std::filesystem::path& path) {
|
||||||
|
auto reader = std::unique_ptr<ZArchiveReader>(ZArchiveReader::OpenFromFile(path));
|
||||||
|
return reader ? std::make_shared<ZArchiveReaderBackend>(std::move(reader)) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ArchiveFormat {
|
||||||
|
std::string_view extension;
|
||||||
|
std::shared_ptr<ArchiveReader> (*open)(const std::filesystem::path&);
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr ArchiveFormat Formats[] = {{".zar", OpenZArchive}};
|
||||||
|
using NativeView = std::basic_string_view<std::filesystem::path::value_type>;
|
||||||
|
|
||||||
|
const ArchiveFormat* FindFormat(NativeView path) {
|
||||||
|
for (const auto& format: Formats) {
|
||||||
|
if (path.size() < format.extension.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto extension = path.substr(path.size() - format.extension.size());
|
||||||
|
if (std::equal(
|
||||||
|
extension.begin(), extension.end(), format.extension.begin(),
|
||||||
|
[](auto a, char b) { return (a >= 'A' && a <= 'Z' ? a + ('a' - 'A') : a) == b; })) {
|
||||||
|
return &format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FindMarker(NativeView path) {
|
||||||
|
// Native files use this check on every operation. The usual path has no marker and allocates
|
||||||
|
// nothing.
|
||||||
|
for (auto marker = path.find('!'); marker != NativeView::npos;
|
||||||
|
marker = path.find('!', marker + 1)) {
|
||||||
|
if ((marker + 1 == path.size() || path[marker + 1] == '/' || path[marker + 1] == '\\') &&
|
||||||
|
FindFormat(path.substr(0, marker)) != nullptr) {
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NativeView::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ParsedPath {
|
||||||
|
std::filesystem::path archive;
|
||||||
|
std::string member;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<ParsedPath> ParsePath(const std::filesystem::path& path) {
|
||||||
|
const auto& native = path.native();
|
||||||
|
const auto marker = FindMarker(native);
|
||||||
|
if (marker == NativeView::npos) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
ParsedPath parsed {.archive = std::filesystem::path(native.substr(0, marker))};
|
||||||
|
auto member = PathToGenericString(std::filesystem::path(native.substr(marker + 1)));
|
||||||
|
std::replace(member.begin(), member.end(), '\\', '/');
|
||||||
|
std::string_view remaining = member;
|
||||||
|
while (!remaining.empty()) {
|
||||||
|
const auto separator = remaining.find('/');
|
||||||
|
const auto component = remaining.substr(0, separator);
|
||||||
|
remaining.remove_prefix(separator == std::string_view::npos ? remaining.size() : separator + 1);
|
||||||
|
if (component.empty() || component == ".") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (component == "..") {
|
||||||
|
if (parsed.member.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto separator = parsed.member.rfind('/');
|
||||||
|
parsed.member.resize(separator == std::string::npos ? 0 : separator);
|
||||||
|
} else {
|
||||||
|
if (!parsed.member.empty()) {
|
||||||
|
parsed.member += '/';
|
||||||
|
}
|
||||||
|
parsed.member += component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex g_readers_mutex;
|
||||||
|
std::unordered_map<std::filesystem::path, std::weak_ptr<ArchiveReader>> g_readers;
|
||||||
|
|
||||||
|
// Guest stacks can be only 64 KiB. Decompress on an ordinary host stack, then copy into guest
|
||||||
|
// memory on the caller so GPU-tracked writes use its fault handler. A single worker bounds host
|
||||||
|
// threads and avoids duplicating the archive's own block cache. Requests live on waiting callers.
|
||||||
|
class ArchiveIoThread {
|
||||||
|
public:
|
||||||
|
static ArchiveIoThread& Instance() {
|
||||||
|
// Guest exit can run static destructors while other guest threads are still reading.
|
||||||
|
// Keep the worker alive until process termination, as with the guest threads themselves.
|
||||||
|
static auto* instance = new ArchiveIoThread;
|
||||||
|
return *instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t Read(ArchiveReader& reader, uint64_t id, uint64_t offset, uint32_t size, void* data) {
|
||||||
|
Request request {reader, id, offset, size, data};
|
||||||
|
std::unique_lock lock(m_mutex);
|
||||||
|
if (m_last != nullptr) {
|
||||||
|
m_last->next = &request;
|
||||||
|
} else {
|
||||||
|
m_first = &request;
|
||||||
|
}
|
||||||
|
m_last = &request;
|
||||||
|
m_wake.notify_one();
|
||||||
|
request.ready.wait(lock, [&] { return request.done; });
|
||||||
|
return request.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
KYTY_CLASS_NO_COPY(ArchiveIoThread);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Request {
|
||||||
|
ArchiveReader& reader;
|
||||||
|
uint64_t id;
|
||||||
|
uint64_t offset;
|
||||||
|
uint32_t size;
|
||||||
|
void* data;
|
||||||
|
Request* next = nullptr;
|
||||||
|
uint64_t result = 0;
|
||||||
|
bool done = false;
|
||||||
|
std::condition_variable ready {};
|
||||||
|
};
|
||||||
|
|
||||||
|
ArchiveIoThread() {
|
||||||
|
std::thread([this] { Loop(); }).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loop() {
|
||||||
|
for (;;) {
|
||||||
|
Request* request;
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_mutex);
|
||||||
|
m_wake.wait(lock, [this] { return m_first != nullptr; });
|
||||||
|
request = m_first;
|
||||||
|
m_first = request->next;
|
||||||
|
if (m_first == nullptr) {
|
||||||
|
m_last = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto result =
|
||||||
|
request->reader.Read(request->id, request->offset, request->size, request->data);
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mutex);
|
||||||
|
request->result = result;
|
||||||
|
request->done = true;
|
||||||
|
// Notify before releasing the mutex: the waiting caller owns the condition
|
||||||
|
// variable.
|
||||||
|
request->ready.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex m_mutex;
|
||||||
|
std::condition_variable m_wake;
|
||||||
|
Request* m_first = nullptr;
|
||||||
|
Request* m_last = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct ArchiveFile::Private {
|
||||||
|
std::shared_ptr<ArchiveReader> reader;
|
||||||
|
ArchiveReader::Entry entry;
|
||||||
|
uint64_t position = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
ArchiveFile::ArchiveFile(std::unique_ptr<Private> p): m_p(std::move(p)) {}
|
||||||
|
ArchiveFile::~ArchiveFile() = default;
|
||||||
|
|
||||||
|
uint64_t ArchiveFile::Size() const {
|
||||||
|
return m_p->entry.size;
|
||||||
|
}
|
||||||
|
uint64_t ArchiveFile::Tell() const {
|
||||||
|
return m_p->position;
|
||||||
|
}
|
||||||
|
bool ArchiveFile::Seek(uint64_t offset) {
|
||||||
|
m_p->position = offset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArchiveFile::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||||
|
#if defined(KYTY_ARCHIVE_PTHREAD_CANCELLATION)
|
||||||
|
// A pending cancellation must not destroy the waiting request or its TLS buffer while the
|
||||||
|
// worker still uses them. Restore the caller's state only after the copy is complete.
|
||||||
|
int cancellation_state = PTHREAD_CANCEL_ENABLE;
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancellation_state);
|
||||||
|
#endif
|
||||||
|
constexpr uint32_t chunk_size = 1u << 20u;
|
||||||
|
thread_local std::vector<uint8_t> chunk;
|
||||||
|
uint32_t read = 0;
|
||||||
|
if (data != nullptr && size != 0 && m_p->position < Size()) {
|
||||||
|
const auto requested =
|
||||||
|
static_cast<uint32_t>(std::min<uint64_t>(size, Size() - m_p->position));
|
||||||
|
if (chunk.size() < std::min(chunk_size, requested)) {
|
||||||
|
chunk.resize(std::min(chunk_size, requested));
|
||||||
|
}
|
||||||
|
while (read < requested) {
|
||||||
|
const auto step = std::min(chunk_size, requested - read);
|
||||||
|
const auto got = ArchiveIoThread::Instance().Read(*m_p->reader, m_p->entry.id,
|
||||||
|
m_p->position, step, chunk.data());
|
||||||
|
if (got == 0 || got > step) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::memcpy(static_cast<uint8_t*>(data) + read, chunk.data(), got);
|
||||||
|
read += static_cast<uint32_t>(got);
|
||||||
|
m_p->position += got;
|
||||||
|
if (got < step) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bytes_read != nullptr) {
|
||||||
|
*bytes_read = read;
|
||||||
|
}
|
||||||
|
#if defined(KYTY_ARCHIVE_PTHREAD_CANCELLATION)
|
||||||
|
pthread_setcancelstate(cancellation_state, nullptr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path MakeArchivePath(const std::filesystem::path& archive,
|
||||||
|
const std::filesystem::path& member) {
|
||||||
|
auto root = archive;
|
||||||
|
root += "!";
|
||||||
|
return member.empty() ? root : root / member.relative_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSupportedArchive(const std::filesystem::path& path) {
|
||||||
|
return FindFormat(path.native()) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsArchivePath(const std::filesystem::path& path) {
|
||||||
|
return FindMarker(path.native()) != NativeView::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetArchiveHostPath(const std::filesystem::path& path) {
|
||||||
|
const auto marker = FindMarker(path.native());
|
||||||
|
return marker == NativeView::npos ? std::filesystem::path {}
|
||||||
|
: std::filesystem::path(path.native().substr(0, marker));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ArchiveReader> OpenArchive(const std::filesystem::path& path) {
|
||||||
|
const auto archive = IsArchivePath(path) ? GetArchiveHostPath(path) : path;
|
||||||
|
const auto* format = FindFormat(archive.native());
|
||||||
|
if (format == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::error_code error;
|
||||||
|
auto key = std::filesystem::absolute(archive, error);
|
||||||
|
if (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard lock(g_readers_mutex);
|
||||||
|
if (auto it = g_readers.find(key); it != g_readers.end()) {
|
||||||
|
if (auto reader = it->second.lock()) {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do not hold the cache mutex while opening an unrelated archive from disk.
|
||||||
|
auto reader = format->open(archive);
|
||||||
|
if (reader == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::lock_guard lock(g_readers_mutex);
|
||||||
|
std::erase_if(g_readers, [](const auto& entry) { return entry.second.expired(); });
|
||||||
|
auto& cached = g_readers[key];
|
||||||
|
if (auto existing = cached.lock()) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
cached = reader;
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<File::Info> GetArchiveInfo(const std::filesystem::path& path) {
|
||||||
|
const auto parsed = ParsePath(path);
|
||||||
|
if (parsed) {
|
||||||
|
if (auto reader = OpenArchive(parsed->archive)) {
|
||||||
|
if (auto entry = reader->Find(parsed->member)) {
|
||||||
|
return File::Info {entry->is_file, entry->size};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<File::DirEntry> GetArchiveDirEntries(const std::filesystem::path& path) {
|
||||||
|
const auto parsed = ParsePath(path);
|
||||||
|
if (parsed) {
|
||||||
|
if (auto reader = OpenArchive(parsed->archive)) {
|
||||||
|
return reader->List(parsed->member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path) {
|
||||||
|
auto parsed = ParsePath(path);
|
||||||
|
if (!parsed) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto reader = OpenArchive(parsed->archive);
|
||||||
|
const auto entry = reader ? reader->Find(parsed->member) : std::nullopt;
|
||||||
|
if (!entry || !entry->is_file) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto p = std::make_unique<ArchiveFile::Private>();
|
||||||
|
p->reader = std::move(reader);
|
||||||
|
p->entry = *entry;
|
||||||
|
return std::unique_ptr<ArchiveFile>(new ArchiveFile(std::move(p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#ifndef KYTY_COMMON_ARCHIVE_H_
|
||||||
|
#define KYTY_COMMON_ARCHIVE_H_
|
||||||
|
|
||||||
|
#include "common/file.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
class ArchiveReader;
|
||||||
|
|
||||||
|
class ArchiveFile {
|
||||||
|
public:
|
||||||
|
~ArchiveFile();
|
||||||
|
|
||||||
|
[[nodiscard]] uint64_t Size() const;
|
||||||
|
[[nodiscard]] uint64_t Tell() const;
|
||||||
|
bool Seek(uint64_t offset);
|
||||||
|
void Read(void* data, uint32_t size, uint32_t* bytes_read);
|
||||||
|
|
||||||
|
KYTY_CLASS_NO_COPY(ArchiveFile);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Private;
|
||||||
|
explicit ArchiveFile(std::unique_ptr<Private> p);
|
||||||
|
std::unique_ptr<Private> m_p;
|
||||||
|
|
||||||
|
friend std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Virtual paths use "archive.ext!/member" and retain native host-path encoding.
|
||||||
|
[[nodiscard]] std::filesystem::path MakeArchivePath(const std::filesystem::path& archive,
|
||||||
|
const std::filesystem::path& member = {});
|
||||||
|
[[nodiscard]] bool IsSupportedArchive(const std::filesystem::path& path);
|
||||||
|
[[nodiscard]] bool IsArchivePath(const std::filesystem::path& path);
|
||||||
|
[[nodiscard]] std::optional<File::Info> GetArchiveInfo(const std::filesystem::path& path);
|
||||||
|
[[nodiscard]] std::vector<File::DirEntry> GetArchiveDirEntries(const std::filesystem::path& path);
|
||||||
|
[[nodiscard]] std::filesystem::path GetArchiveHostPath(const std::filesystem::path& path);
|
||||||
|
[[nodiscard]] std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
// Retain this handle while a mount or metadata scan is active to reuse the index and block cache.
|
||||||
|
// Accepts either a host archive filename or a virtual path inside the archive.
|
||||||
|
[[nodiscard]] std::shared_ptr<ArchiveReader> OpenArchive(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
||||||
|
#endif /* KYTY_COMMON_ARCHIVE_H_ */
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
|
||||||
#include "common/debug.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/subsystems.h"
|
#include "common/subsystems.h"
|
||||||
|
#include "kytyGitVersion.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -11,30 +11,15 @@
|
|||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
|
||||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
|
||||||
constexpr int PRINT_STACK_FROM = 4;
|
|
||||||
#else
|
|
||||||
constexpr int PRINT_STACK_FROM = 2;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
|
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
|
||||||
int line) {
|
int line) {
|
||||||
DebugStack stack;
|
return fmt::format("--- Build ---\n{}\n{}\n{} in {}:{}\n", KYTY_BUILD_LABEL, title, text, file,
|
||||||
DebugStack::Trace(&stack);
|
line);
|
||||||
|
|
||||||
std::string report = "--- Stack Trace ---\n";
|
|
||||||
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
|
|
||||||
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
|
|
||||||
static_cast<uint64_t>(stack.GetAddr(i)));
|
|
||||||
}
|
|
||||||
report += fmt::format("{}\n{} in {}:{}\n", title, text, file, line);
|
|
||||||
return report;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
|
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
|
||||||
Log::WriteFatal(BuildFatalReport(title, text, file, line));
|
Log::WriteFatal(BuildFatalReport(title, text, file, line));
|
||||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
Subsystems::EmergencyShutdownActive();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +43,7 @@ int DbgExitHandler(const char* file, int line, fmt::text_style style, std::strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DbgExit(int status) {
|
void DbgExit(int status) {
|
||||||
|
Subsystems::EmergencyShutdownActive();
|
||||||
std::fflush(nullptr);
|
std::fflush(nullptr);
|
||||||
std::_Exit(status);
|
std::_Exit(status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,7 @@ void DbgExit(int status);
|
|||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
|
||||||
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
||||||
#else
|
|
||||||
#define EXIT_HALT() (std::_Exit(321), 1)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef KYTY_FINAL
|
#ifndef KYTY_FINAL
|
||||||
#define EXIT_IF(x) \
|
#define EXIT_IF(x) \
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
#ifndef KYTY_COMMON_BYTEBUFFER_H_
|
|
||||||
#define KYTY_COMMON_BYTEBUFFER_H_
|
|
||||||
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/common.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstddef> // IWYU pragma: export
|
|
||||||
#include <cstring>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
using Byte = std::byte;
|
|
||||||
|
|
||||||
class ByteBuffer {
|
|
||||||
public:
|
|
||||||
using DataType = std::vector<Byte>;
|
|
||||||
|
|
||||||
static constexpr uint32_t INVALID_INDEX = static_cast<uint32_t>(-1);
|
|
||||||
|
|
||||||
using iterator = DataType::iterator;
|
|
||||||
using const_iterator = DataType::const_iterator;
|
|
||||||
|
|
||||||
ByteBuffer() = default;
|
|
||||||
ByteBuffer(const ByteBuffer&) = default;
|
|
||||||
ByteBuffer(ByteBuffer&&) = default;
|
|
||||||
|
|
||||||
ByteBuffer& operator=(const ByteBuffer&) = default;
|
|
||||||
ByteBuffer& operator=(ByteBuffer&&) = default;
|
|
||||||
|
|
||||||
explicit ByteBuffer(uint32_t size): m_data(size) {}
|
|
||||||
|
|
||||||
ByteBuffer(std::initializer_list<uint8_t> list) {
|
|
||||||
for (const uint8_t& e: list) {
|
|
||||||
m_data.emplace_back(static_cast<Byte>(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit ByteBuffer(const void* buf, uint32_t size)
|
|
||||||
: ByteBuffer(size) // @suppress("Ambiguous problem")
|
|
||||||
{
|
|
||||||
EXIT_IF(buf == nullptr && size != 0);
|
|
||||||
|
|
||||||
if (size != 0) {
|
|
||||||
std::memcpy(m_data.data(), buf, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] uint32_t Size() const { return static_cast<uint32_t>(m_data.size()); }
|
|
||||||
[[nodiscard]] bool IsEmpty() const { return m_data.empty(); }
|
|
||||||
|
|
||||||
void Clear() { m_data.clear(); }
|
|
||||||
|
|
||||||
void Add(const Byte& val) { m_data.push_back(val); }
|
|
||||||
void Add(Byte&& val) { m_data.push_back(std::move(val)); }
|
|
||||||
|
|
||||||
void Add(const Byte* val, uint32_t num) {
|
|
||||||
EXIT_IF(val == nullptr && num != 0);
|
|
||||||
m_data.insert(m_data.end(), val, val + num);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Add(const ByteBuffer& v) {
|
|
||||||
if (this == &v) {
|
|
||||||
const ByteBuffer copy(v);
|
|
||||||
Add(copy.GetDataConst(), copy.Size());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Add(v.GetDataConst(), v.Size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void InsertAt(uint32_t index, const Byte& val) {
|
|
||||||
EXIT_IF(index > Size());
|
|
||||||
m_data.insert(m_data.begin() + index, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InsertAt(uint32_t index, Byte&& val) {
|
|
||||||
EXIT_IF(index > Size());
|
|
||||||
m_data.insert(m_data.begin() + index, std::move(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] uint32_t Find(const Byte& t) const {
|
|
||||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
|
||||||
return (it == m_data.end() ? INVALID_INDEX
|
|
||||||
: static_cast<uint32_t>(std::distance(m_data.begin(), it)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool Contains(const Byte& t) const { return Find(t) != INVALID_INDEX; }
|
|
||||||
|
|
||||||
bool Remove(const Byte& t) {
|
|
||||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
|
||||||
if (it == m_data.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_data.erase(it);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RemoveAt(uint32_t index, uint32_t count = 1) {
|
|
||||||
if (index >= Size() || count == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t erase_count = std::min(count, Size() - index);
|
|
||||||
m_data.erase(m_data.begin() + index, m_data.begin() + index + erase_count);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool IndexValid(uint32_t index) const { return index < Size(); }
|
|
||||||
|
|
||||||
Byte& operator[](uint32_t index) {
|
|
||||||
EXIT_IF(index >= Size());
|
|
||||||
return m_data[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Byte& operator[](uint32_t index) const {
|
|
||||||
EXIT_IF(index >= Size());
|
|
||||||
return m_data[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] const Byte& At(uint32_t index) const {
|
|
||||||
EXIT_IF(index >= Size());
|
|
||||||
return m_data[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
Byte* GetData() { return m_data.data(); }
|
|
||||||
[[nodiscard]] const Byte* GetData() const { return m_data.data(); }
|
|
||||||
[[nodiscard]] const Byte* GetDataConst() const { return m_data.data(); }
|
|
||||||
|
|
||||||
void Sort() {
|
|
||||||
std::sort(m_data.begin(), m_data.end(), [](Byte a, Byte b) {
|
|
||||||
return std::to_integer<uint8_t>(a) < std::to_integer<uint8_t>(b);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OP>
|
|
||||||
void Sort(OP&& comp_func) {
|
|
||||||
std::sort(m_data.begin(), m_data.end(), std::forward<OP>(comp_func));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const ByteBuffer& s) const { return m_data == s.m_data; }
|
|
||||||
bool operator!=(const ByteBuffer& s) const { return !(*this == s); }
|
|
||||||
|
|
||||||
iterator begin() { return m_data.begin(); } // NOLINT(readability-identifier-naming)
|
|
||||||
iterator end() { return m_data.end(); } // NOLINT(readability-identifier-naming)
|
|
||||||
[[nodiscard]] const_iterator begin() const {
|
|
||||||
return m_data.begin();
|
|
||||||
} // NOLINT(readability-identifier-naming)
|
|
||||||
[[nodiscard]] const_iterator end() const {
|
|
||||||
return m_data.end();
|
|
||||||
} // NOLINT(readability-identifier-naming)
|
|
||||||
[[nodiscard]] const_iterator cbegin() const {
|
|
||||||
return m_data.cbegin();
|
|
||||||
} // NOLINT(readability-identifier-naming)
|
|
||||||
[[nodiscard]] const_iterator cend() const {
|
|
||||||
return m_data.cend();
|
|
||||||
} // NOLINT(readability-identifier-naming)
|
|
||||||
|
|
||||||
private:
|
|
||||||
DataType m_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Common
|
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_BYTEBUFFER_H_ */
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "common/commonSubsystem.h"
|
|
||||||
|
|
||||||
#include "common/byteBuffer.h" // IWYU pragma: associated
|
|
||||||
#include "common/common.h" // IWYU pragma: associated
|
|
||||||
#include "common/debug.h" // IWYU pragma: associated
|
|
||||||
#include "common/hash.h" // IWYU pragma: associated
|
|
||||||
#include "common/magicEnum.h" // IWYU pragma: associated
|
|
||||||
#include "common/singleton.h" // IWYU pragma: associated
|
|
||||||
#include "common/virtualMemory.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_INIT(Common) {
|
|
||||||
VirtualMemory::Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Common) {}
|
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_DESTROY(Common) {}
|
|
||||||
|
|
||||||
} // namespace Common
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#ifndef KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
|
||||||
#define KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
|
||||||
|
|
||||||
#include "common/subsystems.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_DEFINE(Common);
|
|
||||||
|
|
||||||
} // namespace Common
|
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_COMMON_SUBSYSTEM_H_ */
|
|
||||||
@@ -1,20 +1,7 @@
|
|||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
|
|
||||||
#include "common/platform/sysDbg.h"
|
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
void DebugStack::Print(int from, [[maybe_unused]] bool with_name) const {
|
|
||||||
for (int i = from; i < depth; i++) {
|
|
||||||
printf("[%d] %016" PRIx64 "\n", i - from, static_cast<uint64_t>(GetAddr(i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugStack::Trace(DebugStack* stack) {
|
|
||||||
stack->depth = DEBUG_MAX_STACK_DEPTH;
|
|
||||||
SysStackWalk(stack->stack.data(), &stack->depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Debug::GetCompiler() {
|
std::string Debug::GetCompiler() {
|
||||||
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
|
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||||
return "clang";
|
return "clang";
|
||||||
@@ -37,8 +24,4 @@ std::string Debug::GetLinker() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Debug::GetBitness() {
|
|
||||||
return "64";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
@@ -2,42 +2,16 @@
|
|||||||
#define KYTY_COMMON_DEBUG_H_
|
#define KYTY_COMMON_DEBUG_H_
|
||||||
|
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/stringUtils.h"
|
|
||||||
|
|
||||||
#include <array>
|
#include <string>
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
|
||||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
|
||||||
constexpr int DEBUG_MAX_STACK_DEPTH = 20;
|
|
||||||
#else
|
|
||||||
constexpr int DEBUG_MAX_STACK_DEPTH = 15;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Debug {
|
namespace Debug {
|
||||||
std::string GetCompiler();
|
std::string GetCompiler();
|
||||||
std::string GetLinker();
|
std::string GetLinker();
|
||||||
std::string GetBitness();
|
|
||||||
} // namespace Debug
|
} // namespace Debug
|
||||||
|
|
||||||
struct DebugStack {
|
|
||||||
int depth {0};
|
|
||||||
std::array<void*, DEBUG_MAX_STACK_DEPTH> stack {};
|
|
||||||
|
|
||||||
[[nodiscard]] uintptr_t GetAddr(int i) const { return reinterpret_cast<uintptr_t>(stack[i]); }
|
|
||||||
|
|
||||||
void Print(int from, bool with_name = true) const;
|
|
||||||
|
|
||||||
void CopyTo(DebugStack* s) const {
|
|
||||||
std::memcpy(s->stack.data(), stack.data(), sizeof(void*) * depth);
|
|
||||||
s->depth = depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Trace(DebugStack* stack);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_DEBUG_H_ */
|
#endif /* KYTY_COMMON_DEBUG_H_ */
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#ifndef KYTY_COMMON_DEBUG_COUNTERS_H_
|
||||||
|
#define KYTY_COMMON_DEBUG_COUNTERS_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Common::DebugCounters {
|
||||||
|
|
||||||
|
static_assert(std::atomic<uint64_t>::is_always_lock_free && std::atomic<bool>::is_always_lock_free);
|
||||||
|
|
||||||
|
// Collect events only while the panel is visible. Relaxed, lock-free atomics keep updates
|
||||||
|
// cheap and safe in signal handlers. Rates come from comparing two snapshots.
|
||||||
|
enum class Counter {
|
||||||
|
// GPU work.
|
||||||
|
Draws,
|
||||||
|
DrawInstances,
|
||||||
|
IndirectDraws,
|
||||||
|
Dispatches,
|
||||||
|
QueueSubmits,
|
||||||
|
GpuWaits, // CPU blocked on a GPU timeline semaphore.
|
||||||
|
GpuWaitNs,
|
||||||
|
GpuFaults, // Guest writes to GPU-tracked memory.
|
||||||
|
ShadersCompiled,
|
||||||
|
ShaderCompileNs,
|
||||||
|
PipelinesCreated,
|
||||||
|
PipelineCreateNs,
|
||||||
|
// Caches.
|
||||||
|
TextureUploadBytes,
|
||||||
|
TextureDownloadBytes,
|
||||||
|
TextureEvictions,
|
||||||
|
BufferUploadBytes,
|
||||||
|
BufferDownloadBytes,
|
||||||
|
BufferEvictions,
|
||||||
|
// Presentation.
|
||||||
|
FlipsRequested,
|
||||||
|
FlipsRejected, // Guest flip queue was full.
|
||||||
|
LateVblanks, // The present thread fell behind the vblank rate.
|
||||||
|
SwapchainRecreations,
|
||||||
|
DroppedPresents,
|
||||||
|
// Emulator.
|
||||||
|
EmulatedInstructions, // Host CPU lacked an instruction the guest used.
|
||||||
|
UnresolvedImportCalls,
|
||||||
|
GuestFileReadBytes,
|
||||||
|
GuestFileWriteBytes,
|
||||||
|
AudioUnderruns,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Peak { ShaderCompileNs, PipelineCreateNs, Count };
|
||||||
|
|
||||||
|
namespace Detail {
|
||||||
|
inline std::atomic<bool> g_enabled {false};
|
||||||
|
inline std::atomic<uint64_t> g_counters[static_cast<int>(Counter::Count)] {};
|
||||||
|
inline std::atomic<uint64_t> g_peaks[static_cast<int>(Peak::Count)] {};
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool Enabled() noexcept {
|
||||||
|
return Detail::g_enabled.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SetEnabled(bool enabled) noexcept {
|
||||||
|
Detail::g_enabled.store(enabled, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Add(Counter counter, uint64_t value = 1) noexcept {
|
||||||
|
if (!Enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Detail::g_counters[static_cast<int>(counter)].fetch_add(value, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScopedTimer final {
|
||||||
|
public:
|
||||||
|
ScopedTimer(Counter count, Counter duration, Peak peak = Peak::Count)
|
||||||
|
: m_count(count), m_duration(duration), m_peak(peak),
|
||||||
|
m_start(Enabled() ? std::chrono::steady_clock::now()
|
||||||
|
: std::chrono::steady_clock::time_point {}) {}
|
||||||
|
~ScopedTimer() {
|
||||||
|
if (m_start == std::chrono::steady_clock::time_point {} || !Enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto elapsed =
|
||||||
|
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now() - m_start)
|
||||||
|
.count());
|
||||||
|
Add(m_count);
|
||||||
|
Add(m_duration, elapsed);
|
||||||
|
if (m_peak != Peak::Count) {
|
||||||
|
auto& peak = Detail::g_peaks[static_cast<int>(m_peak)];
|
||||||
|
auto previous = peak.load(std::memory_order_relaxed);
|
||||||
|
while (previous < elapsed &&
|
||||||
|
!peak.compare_exchange_weak(previous, elapsed, std::memory_order_relaxed)) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScopedTimer(const ScopedTimer&) = delete;
|
||||||
|
ScopedTimer& operator=(const ScopedTimer&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Counter m_count;
|
||||||
|
Counter m_duration;
|
||||||
|
Peak m_peak;
|
||||||
|
std::chrono::steady_clock::time_point m_start;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline uint64_t Get(Counter counter) noexcept {
|
||||||
|
return Detail::g_counters[static_cast<int>(counter)].load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline uint64_t Get(Peak peak) noexcept {
|
||||||
|
return Detail::g_peaks[static_cast<int>(peak)].load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::DebugCounters
|
||||||
|
|
||||||
|
#endif /* KYTY_COMMON_DEBUG_COUNTERS_H_ */
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "common/emulatorConfig.h"
|
#include "common/emulatorConfig.h"
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/debugCounters.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -9,20 +10,26 @@ namespace Config {
|
|||||||
|
|
||||||
static std::unique_ptr<ConfigOptions> g_config;
|
static std::unique_ptr<ConfigOptions> g_config;
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_INIT(Config) {
|
void Initialize() {
|
||||||
EXIT_IF(g_config != nullptr);
|
EXIT_IF(g_config != nullptr);
|
||||||
|
|
||||||
g_config = std::make_unique<ConfigOptions>();
|
g_config = std::make_unique<ConfigOptions>();
|
||||||
}
|
}
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Config) {}
|
void Shutdown() {
|
||||||
|
Common::DebugCounters::SetEnabled(false);
|
||||||
KYTY_SUBSYSTEM_DESTROY(Config) {}
|
g_config.reset();
|
||||||
|
}
|
||||||
|
|
||||||
void Load(const ConfigOptions& cfg) {
|
void Load(const ConfigOptions& cfg) {
|
||||||
EXIT_IF(g_config == nullptr);
|
EXIT_IF(g_config == nullptr);
|
||||||
|
EXIT_IF(cfg.user_name.empty() || cfg.user_name.size() > MAX_USER_NAME_LENGTH);
|
||||||
|
EXIT_IF(!IsConfiguredUserIdValid(cfg.user_id));
|
||||||
|
EXIT_IF(cfg.controller_speaker_volume > 100 || cfg.controller_vibration_intensity > 100);
|
||||||
|
EXIT_IF(cfg.keyboard_player > static_cast<uint32_t>(MAX_PLAYERS));
|
||||||
|
|
||||||
*g_config = cfg;
|
*g_config = cfg;
|
||||||
|
Common::DebugCounters::SetEnabled(cfg.perf_overlay_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t GetScreenWidth() {
|
uint32_t GetScreenWidth() {
|
||||||
@@ -33,14 +40,95 @@ uint32_t GetScreenHeight() {
|
|||||||
return g_config->screen_height;
|
return g_config->screen_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string& GetUserName() {
|
||||||
|
return g_config->user_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t GetUserId() {
|
||||||
|
return g_config->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& GetAudioInputDevice() {
|
||||||
|
return g_config->audio_input_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<ControllerColor>& GetControllerColor() {
|
||||||
|
return g_config->controller_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetControllerSpeakerVolume() {
|
||||||
|
return g_config->controller_speaker_volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetControllerVibrationIntensity() {
|
||||||
|
return g_config->controller_vibration_intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetKeyboardPlayer() {
|
||||||
|
return g_config->keyboard_player;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t GetPlayerUserId(int player) {
|
||||||
|
return g_config->user_id + player * PLAYER_USER_ID_STEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetPlayerFromUserId(int32_t user_id) {
|
||||||
|
for (int player = 0; player < MAX_PLAYERS; player++) {
|
||||||
|
if (user_id == GetPlayerUserId(player)) {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsGuestUserId(int32_t user_id) {
|
||||||
|
return GetPlayerFromUserId(user_id) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PresentMode GetPresentMode() {
|
||||||
|
return g_config->present_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t GetGpuIndex() {
|
||||||
|
return g_config->gpu_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FullscreenEnabled() {
|
||||||
|
return g_config->fullscreen_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HdrEnabled() {
|
||||||
|
return g_config->hdr_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HideCursorEnabled() {
|
||||||
|
return g_config->hide_cursor_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VrEnabled() {
|
||||||
|
return g_config->vr_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AmdCpuEnabled() {
|
||||||
|
return g_config->amd_cpu_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t GetVblankFrequency() {
|
uint32_t GetVblankFrequency() {
|
||||||
return std::clamp(g_config->vblank_frequency, 30u, 360u);
|
return std::clamp(g_config->vblank_frequency, 30u, 360u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t GetConsoleLanguage() {
|
||||||
|
return g_config->console_language;
|
||||||
|
}
|
||||||
|
|
||||||
bool VulkanValidationEnabled() {
|
bool VulkanValidationEnabled() {
|
||||||
return g_config->vulkan_validation_enabled;
|
return g_config->vulkan_validation_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsNetworkConnected() {
|
||||||
|
return g_config->network_connected;
|
||||||
|
}
|
||||||
|
|
||||||
bool ShaderValidationEnabled() {
|
bool ShaderValidationEnabled() {
|
||||||
return g_config->shader_validation_enabled;
|
return g_config->shader_validation_enabled;
|
||||||
}
|
}
|
||||||
@@ -49,7 +137,7 @@ ShaderOptimizationType GetShaderOptimizationType() {
|
|||||||
return g_config->shader_optimization_type;
|
return g_config->shader_optimization_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderLogDirection GetShaderLogDirection() {
|
LogDirection GetShaderLogDirection() {
|
||||||
return g_config->shader_log_direction;
|
return g_config->shader_log_direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +157,7 @@ bool GraphicsDebugDumpEnabled() {
|
|||||||
return g_config->graphics_debug_dump_enabled;
|
return g_config->graphics_debug_dump_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputDirection GetPrintfDirection() {
|
LogDirection GetPrintfDirection() {
|
||||||
return g_config->printf_direction;
|
return g_config->printf_direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,24 +165,66 @@ std::filesystem::path GetPrintfOutputFile() {
|
|||||||
return g_config->printf_output_file;
|
return g_config->printf_output_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfilerDirection GetProfilerDirection() {
|
bool ProfilerEnabled() {
|
||||||
return g_config->profiler_direction;
|
return g_config->profiler_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpirvDebugPrintfEnabled() {
|
bool SpirvDebugPrintfEnabled() {
|
||||||
return g_config->spirv_debug_printf_enabled;
|
return g_config->spirv_debug_printf_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderDocEnabled() {
|
bool GpuAssistedValidationEnabled() {
|
||||||
return g_config->renderdoc_enabled;
|
return g_config->gpu_assisted_validation_enabled && g_config->vulkan_validation_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NggRectlistDrawEnabled() {
|
bool RenderDocEnabled() {
|
||||||
return g_config->ngg_rectlist_draw_enabled;
|
return g_config->renderdoc_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadbackLinearImagesEnabled() {
|
bool ReadbackLinearImagesEnabled() {
|
||||||
return g_config->readback_linear_images;
|
return g_config->readback_linear_images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SyncRawImageBuffersEnabled() {
|
||||||
|
return g_config->sync_raw_image_buffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SupportZmaskEnabled() {
|
||||||
|
return g_config->support_zmask;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TessellationEnabled() {
|
||||||
|
return g_config->tessellation_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrophyEnabled() {
|
||||||
|
return g_config->trophy_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetTrophyDurationSeconds() {
|
||||||
|
return std::clamp(g_config->trophy_duration_seconds, 5u, 10u);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayGoHackEnabled() {
|
||||||
|
return g_config->playgo_hack_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SkipNoticeScreen() {
|
||||||
|
return g_config->skip_notice_screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
|
bool RedZoneProtectionEnabled() {
|
||||||
|
return g_config->red_zone_protection_enabled;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const Keymap& GetKeymap() {
|
||||||
|
return g_config->keymap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& GetGuestArguments() {
|
||||||
|
return g_config->guest_arguments;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Config
|
} // namespace Config
|
||||||
@@ -2,53 +2,137 @@
|
|||||||
#define KYTY_COMMON_EMULATOR_CONFIG_H_
|
#define KYTY_COMMON_EMULATOR_CONFIG_H_
|
||||||
|
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/subsystems.h"
|
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Config {
|
namespace Config {
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_DEFINE(Config);
|
void Initialize();
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
struct Lifecycle {
|
||||||
|
static constexpr const char* name = "Config";
|
||||||
|
static constexpr auto initialize = Config::Initialize;
|
||||||
|
static constexpr auto shutdown = Config::Shutdown;
|
||||||
|
};
|
||||||
|
|
||||||
enum class ShaderOptimizationType { None, Size, Performance };
|
enum class ShaderOptimizationType { None, Size, Performance };
|
||||||
|
|
||||||
enum class ShaderLogDirection { Silent, Console, File };
|
enum class LogDirection { Silent, Console, File };
|
||||||
|
|
||||||
enum class ProfilerDirection { None, Network };
|
enum class PresentMode { Fifo, Mailbox, Immediate };
|
||||||
|
|
||||||
enum class OutputDirection { Silent, Console, File };
|
using Keymap = std::vector<std::string>;
|
||||||
|
using ControllerColor = std::array<uint8_t, 3>;
|
||||||
|
|
||||||
|
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||||
|
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||||
|
constexpr std::size_t MAX_USER_NAME_LENGTH = 16;
|
||||||
|
constexpr int32_t DEFAULT_USER_ID = 1000;
|
||||||
|
|
||||||
|
constexpr int MAX_PLAYERS = 4;
|
||||||
|
constexpr int32_t PLAYER_USER_ID_STEP = 0x100;
|
||||||
|
constexpr int32_t MAX_USER_ID =
|
||||||
|
std::numeric_limits<int32_t>::max() - (MAX_PLAYERS - 1) * PLAYER_USER_ID_STEP;
|
||||||
|
|
||||||
|
constexpr bool IsConfiguredUserIdValid(int32_t user_id) {
|
||||||
|
constexpr int32_t USER_ID_EVERYONE = 0xfe;
|
||||||
|
constexpr int32_t USER_ID_SYSTEM = 0xff;
|
||||||
|
return user_id >= 0 && user_id <= MAX_USER_ID && user_id != USER_ID_EVERYONE &&
|
||||||
|
user_id != USER_ID_SYSTEM;
|
||||||
|
}
|
||||||
|
|
||||||
struct ConfigOptions {
|
struct ConfigOptions {
|
||||||
uint32_t screen_width = 1280;
|
uint32_t screen_width = 1280;
|
||||||
uint32_t screen_height = 720;
|
uint32_t screen_height = 720;
|
||||||
|
std::string user_name = "Kyty";
|
||||||
|
int32_t user_id = DEFAULT_USER_ID;
|
||||||
|
std::string audio_input_device;
|
||||||
|
std::optional<ControllerColor> controller_color;
|
||||||
|
uint32_t controller_speaker_volume = 50;
|
||||||
|
uint32_t controller_vibration_intensity = 100;
|
||||||
|
// 0 shares player 1 with the first gamepad; 1..MAX_PLAYERS dedicates that player to the keyboard.
|
||||||
|
uint32_t keyboard_player = 0;
|
||||||
|
PresentMode present_mode = PresentMode::Mailbox;
|
||||||
|
int32_t gpu_index = -1;
|
||||||
|
bool fullscreen_enabled = false;
|
||||||
|
bool hdr_enabled = false;
|
||||||
|
bool hide_cursor_enabled = false;
|
||||||
|
bool splash_enabled = true;
|
||||||
|
bool vr_enabled = false;
|
||||||
|
bool amd_cpu_enabled = false;
|
||||||
|
bool network_connected = false;
|
||||||
uint32_t vblank_frequency = 60;
|
uint32_t vblank_frequency = 60;
|
||||||
|
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||||
bool vulkan_validation_enabled = false;
|
bool vulkan_validation_enabled = false;
|
||||||
bool shader_validation_enabled = false;
|
bool shader_validation_enabled = false;
|
||||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
||||||
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
|
LogDirection shader_log_direction = LogDirection::Silent;
|
||||||
std::filesystem::path shader_log_folder = "_Shaders";
|
std::filesystem::path shader_log_folder = "_Shaders";
|
||||||
bool command_buffer_dump_enabled = false;
|
bool command_buffer_dump_enabled = false;
|
||||||
std::filesystem::path command_buffer_dump_folder = "_Buffers";
|
std::filesystem::path command_buffer_dump_folder = "_Buffers";
|
||||||
bool graphics_debug_dump_enabled = false;
|
bool graphics_debug_dump_enabled = false;
|
||||||
OutputDirection printf_direction = OutputDirection::Console;
|
LogDirection printf_direction = LogDirection::Silent;
|
||||||
std::filesystem::path printf_output_file = "_kyty.txt";
|
std::filesystem::path printf_output_file = "_kyty.txt";
|
||||||
ProfilerDirection profiler_direction = ProfilerDirection::None;
|
bool profiler_enabled = false;
|
||||||
bool spirv_debug_printf_enabled = false;
|
bool spirv_debug_printf_enabled = false;
|
||||||
|
bool gpu_assisted_validation_enabled = false;
|
||||||
bool renderdoc_enabled = false;
|
bool renderdoc_enabled = false;
|
||||||
bool ngg_rectlist_draw_enabled = true;
|
|
||||||
bool readback_linear_images = false;
|
bool readback_linear_images = false;
|
||||||
|
bool sync_raw_image_buffers = false;
|
||||||
|
bool support_zmask = false;
|
||||||
|
bool tessellation_enabled = false;
|
||||||
|
bool trophy_enabled = true;
|
||||||
|
uint32_t trophy_duration_seconds = 6;
|
||||||
|
bool perf_overlay_enabled = false;
|
||||||
|
bool playgo_hack_enabled = false;
|
||||||
|
bool skip_notice_screen = false;
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
|
bool red_zone_protection_enabled = false;
|
||||||
|
#endif
|
||||||
|
Keymap keymap;
|
||||||
|
// Ordinary boots need a non-null argv[0]; --guest-exec supplies the caller's arguments.
|
||||||
|
std::vector<std::string> guest_arguments {""};
|
||||||
};
|
};
|
||||||
|
|
||||||
void Load(const ConfigOptions& cfg);
|
void Load(const ConfigOptions& cfg);
|
||||||
|
|
||||||
uint32_t GetScreenWidth();
|
uint32_t GetScreenWidth();
|
||||||
uint32_t GetScreenHeight();
|
uint32_t GetScreenHeight();
|
||||||
|
const std::string& GetUserName();
|
||||||
|
int32_t GetUserId();
|
||||||
|
const std::string& GetAudioInputDevice();
|
||||||
|
const std::optional<ControllerColor>& GetControllerColor();
|
||||||
|
uint32_t GetControllerSpeakerVolume();
|
||||||
|
uint32_t GetControllerVibrationIntensity();
|
||||||
|
uint32_t GetKeyboardPlayer();
|
||||||
|
// Player 0 is the configured user; later players derive their IDs from it.
|
||||||
|
int32_t GetPlayerUserId(int player);
|
||||||
|
// Returns -1 when the user ID does not belong to any player.
|
||||||
|
int GetPlayerFromUserId(int32_t user_id);
|
||||||
|
// Players after the first are guests: they have no profile, trophies or NP account.
|
||||||
|
bool IsGuestUserId(int32_t user_id);
|
||||||
|
PresentMode GetPresentMode();
|
||||||
|
int32_t GetGpuIndex();
|
||||||
|
bool FullscreenEnabled();
|
||||||
|
bool HdrEnabled();
|
||||||
|
bool HideCursorEnabled();
|
||||||
|
bool VrEnabled();
|
||||||
|
bool AmdCpuEnabled();
|
||||||
|
bool IsNetworkConnected();
|
||||||
uint32_t GetVblankFrequency();
|
uint32_t GetVblankFrequency();
|
||||||
|
uint32_t GetConsoleLanguage();
|
||||||
bool VulkanValidationEnabled();
|
bool VulkanValidationEnabled();
|
||||||
|
|
||||||
bool ShaderValidationEnabled();
|
bool ShaderValidationEnabled();
|
||||||
ShaderOptimizationType GetShaderOptimizationType();
|
ShaderOptimizationType GetShaderOptimizationType();
|
||||||
ShaderLogDirection GetShaderLogDirection();
|
LogDirection GetShaderLogDirection();
|
||||||
std::filesystem::path GetShaderLogFolder();
|
std::filesystem::path GetShaderLogFolder();
|
||||||
|
|
||||||
bool CommandBufferDumpEnabled();
|
bool CommandBufferDumpEnabled();
|
||||||
@@ -56,16 +140,30 @@ std::filesystem::path GetCommandBufferDumpFolder();
|
|||||||
|
|
||||||
bool GraphicsDebugDumpEnabled();
|
bool GraphicsDebugDumpEnabled();
|
||||||
|
|
||||||
OutputDirection GetPrintfDirection();
|
LogDirection GetPrintfDirection();
|
||||||
std::filesystem::path GetPrintfOutputFile();
|
std::filesystem::path GetPrintfOutputFile();
|
||||||
|
|
||||||
ProfilerDirection GetProfilerDirection();
|
bool ProfilerEnabled();
|
||||||
|
|
||||||
bool SpirvDebugPrintfEnabled();
|
bool SpirvDebugPrintfEnabled();
|
||||||
|
|
||||||
|
bool GpuAssistedValidationEnabled();
|
||||||
|
|
||||||
bool RenderDocEnabled();
|
bool RenderDocEnabled();
|
||||||
bool NggRectlistDrawEnabled();
|
|
||||||
bool ReadbackLinearImagesEnabled();
|
bool ReadbackLinearImagesEnabled();
|
||||||
|
bool SyncRawImageBuffersEnabled();
|
||||||
|
bool SupportZmaskEnabled();
|
||||||
|
bool TessellationEnabled();
|
||||||
|
bool TrophyEnabled();
|
||||||
|
uint32_t GetTrophyDurationSeconds();
|
||||||
|
bool PlayGoHackEnabled();
|
||||||
|
bool SkipNoticeScreen();
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
|
bool RedZoneProtectionEnabled();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const Keymap& GetKeymap();
|
||||||
|
const std::vector<std::string>& GetGuestArguments();
|
||||||
|
|
||||||
} // namespace Config
|
} // namespace Config
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "common/file.h"
|
#include "common/file.h"
|
||||||
|
|
||||||
|
#include "common/archive.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/platform/sysFileIO.h"
|
#include "common/platform/sysFileIO.h"
|
||||||
@@ -25,9 +26,6 @@
|
|||||||
#ifdef CopyFile
|
#ifdef CopyFile
|
||||||
#undef CopyFile
|
#undef CopyFile
|
||||||
#endif
|
#endif
|
||||||
#ifdef MoveFile
|
|
||||||
#undef MoveFile
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
@@ -64,7 +62,8 @@ SysFileTimeStruct DateTimeToFileTimeUtc(const DateTime& date_time) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
struct File::FilePrivate {
|
struct File::FilePrivate {
|
||||||
sys_file_t* f;
|
sys_file_t* f;
|
||||||
|
std::unique_ptr<ArchiveFile> archive;
|
||||||
};
|
};
|
||||||
|
|
||||||
File::File(): m_p(std::make_unique<FilePrivate>()) {
|
File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||||
@@ -72,7 +71,7 @@ File::File(): m_p(std::make_unique<FilePrivate>()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File::~File() {
|
File::~File() {
|
||||||
EXIT_IF(m_p->f != nullptr);
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
||||||
@@ -88,11 +87,15 @@ File::File(const std::filesystem::path& name, Mode mode): m_p(std::make_unique<F
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool File::IsInvalid() const {
|
bool File::IsInvalid() const {
|
||||||
return m_p->f == nullptr;
|
return m_p->f == nullptr && m_p->archive == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::Create(const std::filesystem::path& name) {
|
bool File::Create(const std::filesystem::path& name) {
|
||||||
EXIT_IF(m_p->f != nullptr);
|
EXIT_IF(!IsInvalid());
|
||||||
|
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
m_file_name = name;
|
m_file_name = name;
|
||||||
|
|
||||||
@@ -107,9 +110,16 @@ bool File::Create(const std::filesystem::path& name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool File::Open(const std::filesystem::path& name, Mode mode) {
|
bool File::Open(const std::filesystem::path& name, Mode mode) {
|
||||||
EXIT_IF(m_p->f != nullptr);
|
EXIT_IF(!IsInvalid());
|
||||||
|
|
||||||
m_file_name = name;
|
m_file_name = name;
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
if (mode != Mode::Read) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_p->archive = OpenArchiveFile(name);
|
||||||
|
return m_p->archive != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case Mode::Read: m_p->f = SysFileOpenR(name); break;
|
case Mode::Read: m_p->f = SysFileOpenR(name); break;
|
||||||
@@ -127,7 +137,7 @@ bool File::Open(const std::filesystem::path& name, Mode mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
||||||
EXIT_IF(m_p->f != nullptr);
|
EXIT_IF(!IsInvalid());
|
||||||
|
|
||||||
m_p->f = SysFileOpen(static_cast<uint8_t*>(buf), buf_size);
|
m_p->f = SysFileOpen(static_cast<uint8_t*>(buf), buf_size);
|
||||||
|
|
||||||
@@ -139,12 +149,8 @@ bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::OpenInMem(ByteBuffer& buf) {
|
|
||||||
return OpenInMem(buf.GetData(), buf.Size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool File::CreateInMem() {
|
bool File::CreateInMem() {
|
||||||
EXIT_IF(m_p->f != nullptr);
|
EXIT_IF(!IsInvalid());
|
||||||
|
|
||||||
m_p->f = SysFileCreate();
|
m_p->f = SysFileCreate();
|
||||||
|
|
||||||
@@ -157,6 +163,7 @@ bool File::CreateInMem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void File::Close() {
|
void File::Close() {
|
||||||
|
m_p->archive.reset();
|
||||||
if (m_p->f != nullptr) {
|
if (m_p->f != nullptr) {
|
||||||
SysFileClose(m_p->f);
|
SysFileClose(m_p->f);
|
||||||
m_p->f = nullptr;
|
m_p->f = nullptr;
|
||||||
@@ -164,7 +171,11 @@ void File::Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t File::Size() const {
|
uint64_t File::Size() const {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
return m_p->archive->Size();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_p->f == nullptr) {
|
if (m_p->f == nullptr) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -174,41 +185,76 @@ uint64_t File::Size() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t File::Remaining() const {
|
uint64_t File::Remaining() const {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
return Size() - Tell();
|
const auto size = Size();
|
||||||
|
const auto position = Tell();
|
||||||
|
return position < size ? size - position : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t File::Size(const std::filesystem::path& name) {
|
uint64_t File::Size(const std::filesystem::path& name) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
const auto info = GetArchiveInfo(name);
|
||||||
|
return info ? info->size : 0;
|
||||||
|
}
|
||||||
return SysFileSize(name);
|
return SysFileSize(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<File::Info> File::GetInfo(const std::filesystem::path& name) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return GetArchiveInfo(name);
|
||||||
|
}
|
||||||
|
Info info {};
|
||||||
|
return SysFileGetInfo(name, &info.is_file, &info.size) ? std::optional(info) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
bool File::Seek(uint64_t offset) {
|
bool File::Seek(uint64_t offset) {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
return m_p->archive->Seek(offset);
|
||||||
|
}
|
||||||
|
|
||||||
return SysFileSeek(*m_p->f, offset);
|
return SysFileSeek(*m_p->f, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::Truncate(uint64_t size) {
|
bool File::Truncate(uint64_t size) {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return SysFileTruncate(*m_p->f, size);
|
return SysFileTruncate(*m_p->f, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::Unlink() {
|
bool File::Unlink() {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return SysFileUnlink(*m_p->f, m_file_name);
|
return SysFileUnlink(*m_p->f, m_file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t File::Tell() const {
|
uint64_t File::Tell() const {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
return m_p->archive->Tell();
|
||||||
|
}
|
||||||
|
|
||||||
return SysFileTell(*m_p->f);
|
return SysFileTell(*m_p->f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
m_p->archive->Read(data, size, bytes_read);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_p->f != nullptr) {
|
if (m_p->f != nullptr) {
|
||||||
SysFileRead(data, size, *m_p->f, bytes_read);
|
SysFileRead(data, size, *m_p->f, bytes_read);
|
||||||
@@ -216,7 +262,14 @@ void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void File::Write(const void* data, uint32_t size, uint32_t* bytes_written) {
|
void File::Write(const void* data, uint32_t size, uint32_t* bytes_written) {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
if (bytes_written != nullptr) {
|
||||||
|
*bytes_written = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SysFileWrite(data, size, *m_p->f, bytes_written);
|
SysFileWrite(data, size, *m_p->f, bytes_written);
|
||||||
}
|
}
|
||||||
@@ -252,103 +305,120 @@ void File::Printf(const char* format, ...) {
|
|||||||
Write(s.data(), static_cast<uint32_t>(s.size()));
|
Write(s.data(), static_cast<uint32_t>(s.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::filesystem::path WithoutTrailingSeparator(
|
||||||
|
const std::filesystem::path& path) {
|
||||||
|
if (!path.empty() && !path.has_filename() &&
|
||||||
|
path != path.root_path()) {
|
||||||
|
return path.parent_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||||
auto path_str = PathToGenericString(path);
|
if (IsArchivePath(path)) {
|
||||||
return SysFileIsDirectoryExisting(
|
const auto info = GetArchiveInfo(path);
|
||||||
Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1) : path_str);
|
return info && !info->is_file;
|
||||||
|
}
|
||||||
|
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
const auto info = GetArchiveInfo(name);
|
||||||
|
return info && info->is_file;
|
||||||
|
}
|
||||||
return SysFileIsFileExisting(name);
|
return SysFileIsFileExisting(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::CreateDirectory(
|
bool File::CreateDirectory(const std::filesystem::path& path) {
|
||||||
const std::filesystem::path& path) // @suppress("Member declaration not found")
|
if (IsArchivePath(path)) {
|
||||||
{
|
return false;
|
||||||
auto path_str = PathToGenericString(path);
|
}
|
||||||
return SysFileCreateDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
return SysFileCreateDirectory(
|
||||||
: path_str);
|
WithoutTrailingSeparator(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||||
auto path_str = PathToGenericString(path);
|
if (IsArchivePath(path)) {
|
||||||
return SysFileDeleteDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
return false;
|
||||||
: path_str);
|
}
|
||||||
|
return SysFileDeleteDirectory(
|
||||||
|
WithoutTrailingSeparator(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
if (IsArchivePath(path)) {
|
||||||
|
return false;
|
||||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
|
||||||
|
|
||||||
std::string p;
|
|
||||||
|
|
||||||
for (uint32_t si = 0; si < list.size(); si++) {
|
|
||||||
const std::string& s = list[si];
|
|
||||||
|
|
||||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
|
||||||
p += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
p += s;
|
|
||||||
|
|
||||||
if (IsDirectoryExisting(p)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const auto normalized_path =
|
||||||
|
WithoutTrailingSeparator(path);
|
||||||
|
|
||||||
return true;
|
auto current_path = normalized_path.root_path();
|
||||||
|
|
||||||
|
for (const auto& component:
|
||||||
|
normalized_path.relative_path()) {
|
||||||
|
current_path /= component;
|
||||||
|
|
||||||
|
if (IsDirectoryExisting(current_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CreateDirectory(current_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
if (IsArchivePath(path)) {
|
||||||
|
return false;
|
||||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
|
||||||
|
|
||||||
std::string p;
|
|
||||||
std::vector<std::string> list2;
|
|
||||||
|
|
||||||
for (uint32_t si = 0; si < list.size(); si++) {
|
|
||||||
const std::string& s = list[si];
|
|
||||||
|
|
||||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
|
||||||
p += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
p += s;
|
|
||||||
|
|
||||||
list2.push_back(p);
|
|
||||||
}
|
}
|
||||||
|
const auto normalized_path =
|
||||||
|
WithoutTrailingSeparator(path);
|
||||||
|
|
||||||
uint32_t num = list2.size();
|
std::vector<std::filesystem::path> directories;
|
||||||
|
auto current_path = normalized_path.root_path();
|
||||||
|
|
||||||
for (uint32_t si = num - 1; si < num; si--) {
|
for (const auto& component:
|
||||||
if (!DeleteDirectory(list2[si])) {
|
normalized_path.relative_path()) {
|
||||||
return false;
|
current_path /= component;
|
||||||
}
|
directories.push_back(current_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
for (auto it = directories.rbegin();
|
||||||
|
it != directories.rend(); ++it) {
|
||||||
|
if (!DeleteDirectory(*it)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::DeleteFile(
|
bool File::DeleteFile(
|
||||||
const std::filesystem::path& name) // @suppress("Member declaration not found")
|
const std::filesystem::path& name) // @suppress("Member declaration not found")
|
||||||
{
|
{
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return SysFileDeleteFile(name);
|
return SysFileDeleteFile(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::Flush() {
|
bool File::Flush() {
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return SysFileFlush(*m_p->f);
|
return SysFileFlush(*m_p->f);
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer File::ReadWholeBuffer() {
|
std::vector<std::byte> File::ReadWholeBuffer() {
|
||||||
EXIT_IF(IsInvalid());
|
EXIT_IF(IsInvalid());
|
||||||
EXIT_IF(Tell() != 0);
|
EXIT_IF(Tell() != 0);
|
||||||
|
|
||||||
@@ -356,26 +426,18 @@ ByteBuffer File::ReadWholeBuffer() {
|
|||||||
|
|
||||||
EXIT_IF((s >> 32u) != 0);
|
EXIT_IF((s >> 32u) != 0);
|
||||||
|
|
||||||
ByteBuffer buf(s);
|
const auto read_size = static_cast<uint32_t>(s);
|
||||||
|
std::vector<std::byte> buf(read_size);
|
||||||
|
|
||||||
Read(buf.GetData(), s);
|
Read(buf.data(), read_size);
|
||||||
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer File::Read(uint32_t size) {
|
|
||||||
ByteBuffer buf(size);
|
|
||||||
uint32_t b = 0;
|
|
||||||
Read(buf.GetData(), size, &b);
|
|
||||||
buf.RemoveAt(b, size - b);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void File::Write(const ByteBuffer& buf, uint32_t* bytes_written) {
|
|
||||||
Write(buf.GetDataConst(), buf.Size(), bytes_written);
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return GetLastAccessTimeUTC(GetArchiveHostPath(name));
|
||||||
|
}
|
||||||
SysTimeStruct t {};
|
SysTimeStruct t {};
|
||||||
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
|
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
|
||||||
|
|
||||||
@@ -387,6 +449,9 @@ DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DateTime File::GetLastWriteTimeUTC(const std::filesystem::path& name) {
|
DateTime File::GetLastWriteTimeUTC(const std::filesystem::path& name) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return GetLastWriteTimeUTC(GetArchiveHostPath(name));
|
||||||
|
}
|
||||||
SysTimeStruct t {};
|
SysTimeStruct t {};
|
||||||
SysFileToSystemTimeUtc(SysFileGetLastWriteTimeUtc(name), t);
|
SysFileToSystemTimeUtc(SysFileGetLastWriteTimeUtc(name), t);
|
||||||
|
|
||||||
@@ -401,6 +466,10 @@ void File::GetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, DateT
|
|||||||
DateTime* write) {
|
DateTime* write) {
|
||||||
EXIT_IF(access == nullptr);
|
EXIT_IF(access == nullptr);
|
||||||
EXIT_IF(write == nullptr);
|
EXIT_IF(write == nullptr);
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
GetLastAccessAndWriteTimeUTC(GetArchiveHostPath(name), access, write);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SysTimeStruct at {};
|
SysTimeStruct at {};
|
||||||
SysTimeStruct wt {};
|
SysTimeStruct wt {};
|
||||||
@@ -427,7 +496,12 @@ void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
|||||||
EXIT_IF(access == nullptr);
|
EXIT_IF(access == nullptr);
|
||||||
EXIT_IF(write == nullptr);
|
EXIT_IF(write == nullptr);
|
||||||
|
|
||||||
EXIT_IF(m_p->f == nullptr);
|
EXIT_IF(IsInvalid());
|
||||||
|
|
||||||
|
if (m_p->archive != nullptr) {
|
||||||
|
GetLastAccessAndWriteTimeUTC(m_file_name, access, write);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_p->f == nullptr) {
|
if (m_p->f == nullptr) {
|
||||||
*access = DateTime();
|
*access = DateTime();
|
||||||
@@ -457,68 +531,35 @@ void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool File::SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
bool File::SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto f = DateTimeToFileTimeUtc(dt);
|
auto f = DateTimeToFileTimeUtc(dt);
|
||||||
return SysFileSetLastAccessTimeUtc(name, f);
|
return SysFileSetLastAccessTimeUtc(name, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
bool File::SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto f = DateTimeToFileTimeUtc(dt);
|
auto f = DateTimeToFileTimeUtc(dt);
|
||||||
return SysFileSetLastWriteTimeUtc(name, f);
|
return SysFileSetLastWriteTimeUtc(name, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const DateTime& access,
|
bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const DateTime& access,
|
||||||
const DateTime& write) {
|
const DateTime& write) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto af = DateTimeToFileTimeUtc(access);
|
auto af = DateTimeToFileTimeUtc(access);
|
||||||
auto wf = DateTimeToFileTimeUtc(write);
|
auto wf = DateTimeToFileTimeUtc(write);
|
||||||
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
|
||||||
std::vector<sys_file_find_t> files;
|
|
||||||
|
|
||||||
SysFileFindFiles(path, files);
|
|
||||||
|
|
||||||
auto path_str = PathToGenericString(path);
|
|
||||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
|
||||||
|
|
||||||
if (!Common::EndsWith(path_str, "/") && !Common::EndsWith(path_str, "\\")) {
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<File::FindInfo> ret;
|
|
||||||
ret.reserve(files.size());
|
|
||||||
|
|
||||||
for (const auto& f: files) {
|
|
||||||
File::FindInfo r {};
|
|
||||||
|
|
||||||
r.path_with_name = f.path_with_name;
|
|
||||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
|
||||||
r.size = f.size;
|
|
||||||
|
|
||||||
SysTimeStruct at {};
|
|
||||||
SysTimeStruct wt {};
|
|
||||||
|
|
||||||
SysFileToSystemTimeUtc(f.last_access_time, at);
|
|
||||||
SysFileToSystemTimeUtc(f.last_write_time, wt);
|
|
||||||
|
|
||||||
if (!at.is_invalid && !wt.is_invalid) {
|
|
||||||
r.last_access_time = DateTime(Date(at.Year, at.Month, at.Day),
|
|
||||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
|
||||||
r.last_write_time = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
|
||||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
// printf("%s, %s, %" PRIu64", %s, %s\n", r.path_with_name.c_str(),
|
|
||||||
// r.rel_path_with_name.c_str(), r.size, r.last_access_time.ToString().c_str(),
|
|
||||||
// r.last_write_time.ToString().c_str());
|
|
||||||
|
|
||||||
ret.push_back(std::move(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||||
|
if (IsArchivePath(path)) {
|
||||||
|
return GetArchiveDirEntries(path);
|
||||||
|
}
|
||||||
std::vector<sys_dir_entry_t> files;
|
std::vector<sys_dir_entry_t> files;
|
||||||
|
|
||||||
SysFileGetDents(path, files);
|
SysFileGetDents(path, files);
|
||||||
@@ -526,13 +567,8 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
|
|||||||
std::vector<File::DirEntry> ret;
|
std::vector<File::DirEntry> ret;
|
||||||
ret.reserve(files.size());
|
ret.reserve(files.size());
|
||||||
|
|
||||||
for (const auto& f: files) {
|
for (auto& f: files) {
|
||||||
File::DirEntry r {};
|
ret.emplace_back(std::move(f.name), f.is_file);
|
||||||
|
|
||||||
r.name = f.name;
|
|
||||||
r.is_file = f.is_file;
|
|
||||||
|
|
||||||
ret.push_back(std::move(r));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -541,20 +577,29 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
|
|||||||
bool File::CopyFile(const std::filesystem::path& src,
|
bool File::CopyFile(const std::filesystem::path& src,
|
||||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||||
{
|
{
|
||||||
|
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return SysFileCopyFile(src, dst);
|
return SysFileCopyFile(src, dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::MoveFile(const std::filesystem::path& src,
|
bool File::RenameFile(const std::filesystem::path& src,
|
||||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||||
{
|
{
|
||||||
|
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (IsFileExisting(dst)) {
|
if (IsFileExisting(dst)) {
|
||||||
DeleteFile(dst); // @suppress("Invalid arguments")
|
DeleteFile(dst); // @suppress("Invalid arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
return SysFileMoveFile(src, dst);
|
return SysFileRenameFile(src, dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::RemoveReadonly(const std::filesystem::path& name) {
|
void File::RemoveReadonly(const std::filesystem::path& name) {
|
||||||
|
if (IsArchivePath(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SysFileRemoveReadonly(name);
|
SysFileRemoveReadonly(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#ifndef KYTY_COMMON_FILE_H_
|
#ifndef KYTY_COMMON_FILE_H_
|
||||||
#define KYTY_COMMON_FILE_H_
|
#define KYTY_COMMON_FILE_H_
|
||||||
|
|
||||||
#include "common/byteBuffer.h"
|
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/dateTime.h"
|
#include "common/dateTime.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -20,9 +21,6 @@
|
|||||||
#ifdef CopyFile
|
#ifdef CopyFile
|
||||||
#undef CopyFile
|
#undef CopyFile
|
||||||
#endif
|
#endif
|
||||||
#ifdef MoveFile
|
|
||||||
#undef MoveFile
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
@@ -33,18 +31,14 @@ class File {
|
|||||||
public:
|
public:
|
||||||
enum class Mode { Read, Write, ReadWrite, WriteRead };
|
enum class Mode { Read, Write, ReadWrite, WriteRead };
|
||||||
|
|
||||||
struct FindInfo {
|
|
||||||
std::filesystem::path path_with_name;
|
|
||||||
std::filesystem::path rel_path_with_name;
|
|
||||||
DateTime last_access_time;
|
|
||||||
DateTime last_write_time;
|
|
||||||
uint64_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DirEntry {
|
struct DirEntry {
|
||||||
std::string name;
|
std::string name;
|
||||||
bool is_file;
|
bool is_file;
|
||||||
};
|
};
|
||||||
|
struct Info {
|
||||||
|
bool is_file;
|
||||||
|
uint64_t size;
|
||||||
|
};
|
||||||
|
|
||||||
File();
|
File();
|
||||||
explicit File(const std::filesystem::path& name);
|
explicit File(const std::filesystem::path& name);
|
||||||
@@ -54,7 +48,6 @@ public:
|
|||||||
bool Create(const std::filesystem::path& name);
|
bool Create(const std::filesystem::path& name);
|
||||||
bool Open(const std::filesystem::path& name, Mode mode);
|
bool Open(const std::filesystem::path& name, Mode mode);
|
||||||
bool OpenInMem(void* buf, uint32_t buf_size);
|
bool OpenInMem(void* buf, uint32_t buf_size);
|
||||||
bool OpenInMem(ByteBuffer& buf); // NOLINT(google-runtime-references)
|
|
||||||
bool CreateInMem();
|
bool CreateInMem();
|
||||||
|
|
||||||
void Close();
|
void Close();
|
||||||
@@ -76,16 +69,15 @@ public:
|
|||||||
|
|
||||||
void GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write);
|
void GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write);
|
||||||
|
|
||||||
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
||||||
ByteBuffer Read(uint32_t size);
|
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
||||||
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
|
||||||
void Write(const ByteBuffer& buf, uint32_t* bytes_written = nullptr);
|
|
||||||
|
|
||||||
void Printf(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
void Printf(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
||||||
|
|
||||||
ByteBuffer ReadWholeBuffer();
|
std::vector<std::byte> ReadWholeBuffer();
|
||||||
|
|
||||||
static uint64_t Size(const std::filesystem::path& name);
|
static uint64_t Size(const std::filesystem::path& name);
|
||||||
|
static std::optional<Info> GetInfo(const std::filesystem::path& name);
|
||||||
|
|
||||||
static bool IsDirectoryExisting(const std::filesystem::path& path);
|
static bool IsDirectoryExisting(const std::filesystem::path& path);
|
||||||
static bool IsFileExisting(const std::filesystem::path& name);
|
static bool IsFileExisting(const std::filesystem::path& name);
|
||||||
@@ -104,10 +96,9 @@ public:
|
|||||||
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
|
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
|
||||||
const DateTime& access, const DateTime& write);
|
const DateTime& access, const DateTime& write);
|
||||||
|
|
||||||
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
|
|
||||||
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
|
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
|
||||||
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||||
static bool MoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
static bool RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||||
static void RemoveReadonly(const std::filesystem::path& name);
|
static void RemoveReadonly(const std::filesystem::path& name);
|
||||||
|
|
||||||
KYTY_CLASS_NO_COPY(File);
|
KYTY_CLASS_NO_COPY(File);
|
||||||
|
|||||||
@@ -54,103 +54,6 @@ inline uint32_t hash(const void* key, uint32_t key_len) {
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t hash8(uint8_t key) {
|
|
||||||
uint32_t hash = 0;
|
|
||||||
|
|
||||||
uint8_t* ptr = &key;
|
|
||||||
|
|
||||||
hash += ptr[0];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
|
|
||||||
hash += (hash << 3u);
|
|
||||||
hash ^= (hash >> 11u);
|
|
||||||
hash += (hash << 15u);
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint32_t hash16(uint16_t key) {
|
|
||||||
uint32_t hash = 0;
|
|
||||||
|
|
||||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
|
||||||
|
|
||||||
hash += ptr[0];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[1];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
|
|
||||||
hash += (hash << 3u);
|
|
||||||
hash ^= (hash >> 11u);
|
|
||||||
hash += (hash << 15u);
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint32_t hash32(uint32_t key) {
|
|
||||||
uint32_t hash = 0;
|
|
||||||
|
|
||||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
|
||||||
|
|
||||||
hash += ptr[0];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[1];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[2];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[3];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
|
|
||||||
hash += (hash << 3u);
|
|
||||||
hash ^= (hash >> 11u);
|
|
||||||
hash += (hash << 15u);
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint32_t hash64(uint64_t key) {
|
|
||||||
uint32_t hash = 0;
|
|
||||||
|
|
||||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
|
||||||
|
|
||||||
hash += ptr[0];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[1];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[2];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[3];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[4];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[5];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[6];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
hash += ptr[7];
|
|
||||||
hash += (hash << 10u);
|
|
||||||
hash ^= (hash >> 6u);
|
|
||||||
|
|
||||||
hash += (hash << 3u);
|
|
||||||
hash ^= (hash >> 11u);
|
|
||||||
hash += (hash << 15u);
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_HASH_H_ */
|
#endif /* KYTY_COMMON_HASH_H_ */
|
||||||
@@ -2,18 +2,20 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
#include <windows.h> // IWYU pragma: keep
|
#include <windows.h> // IWYU pragma: keep
|
||||||
#elif defined(__APPLE__)
|
#else
|
||||||
|
#include <algorithm>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <unistd.h>
|
||||||
|
#if defined(__APPLE__)
|
||||||
#include <sys/ucontext.h>
|
#include <sys/ucontext.h>
|
||||||
#else
|
#else
|
||||||
#include <csignal>
|
|
||||||
#include <initializer_list>
|
|
||||||
#include <ucontext.h> // IWYU pragma: keep
|
#include <ucontext.h> // IWYU pragma: keep
|
||||||
#include <unistd.h>
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// IWYU pragma: no_include <errhandlingapi.h>
|
// IWYU pragma: no_include <errhandlingapi.h>
|
||||||
@@ -28,54 +30,59 @@ namespace Common::HostException {
|
|||||||
|
|
||||||
static std::atomic<Handler> g_handler {nullptr};
|
static std::atomic<Handler> g_handler {nullptr};
|
||||||
static std::atomic_uint32_t g_install_state {0};
|
static std::atomic_uint32_t g_install_state {0};
|
||||||
static thread_local bool g_in_exception_filter = false;
|
|
||||||
|
|
||||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||||
|
|
||||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
|
||||||
std::fputs("HostException fail-fast: ", stderr);
|
|
||||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
|
||||||
std::fputc('\n', stderr);
|
|
||||||
std::fflush(stderr);
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
||||||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
|
|
||||||
#endif
|
#endif
|
||||||
std::_Exit(321);
|
|
||||||
}
|
|
||||||
|
|
||||||
class FilterScope final {
|
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
|
|
||||||
|
// macOS uses the same POSIX platform setting and needs a signal stack too.
|
||||||
|
class ThreadSignalStack {
|
||||||
public:
|
public:
|
||||||
FilterScope() noexcept {
|
ThreadSignalStack() {
|
||||||
if (g_in_exception_filter) {
|
const auto page_size = static_cast<size_t>(::getpagesize());
|
||||||
FailFast("nested exception while resolving a host fault");
|
const auto stack_size =
|
||||||
|
(std::max<size_t>(64 * 1024, MINSIGSTKSZ) + page_size - 1) & ~(page_size - 1);
|
||||||
|
if (::posix_memalign(&m_memory, page_size, stack_size) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack_t stack {};
|
||||||
|
stack.ss_sp = m_memory;
|
||||||
|
stack.ss_size = stack_size;
|
||||||
|
if (::sigaltstack(&stack, &m_previous) != 0) {
|
||||||
|
std::free(m_memory);
|
||||||
|
m_memory = nullptr;
|
||||||
}
|
}
|
||||||
g_in_exception_filter = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~FilterScope() { g_in_exception_filter = false; }
|
~ThreadSignalStack() {
|
||||||
|
if (m_memory != nullptr && ::sigaltstack(&m_previous, nullptr) == 0) {
|
||||||
|
std::free(m_memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KYTY_CLASS_NO_COPY(FilterScope);
|
[[nodiscard]] bool IsInitialized() const { return m_memory != nullptr; }
|
||||||
|
|
||||||
|
KYTY_CLASS_NO_COPY(ThreadSignalStack)
|
||||||
|
|
||||||
|
private:
|
||||||
|
void* m_memory = nullptr;
|
||||||
|
stack_t m_previous {};
|
||||||
};
|
};
|
||||||
|
|
||||||
static Handler LoadInstalledHandler() noexcept {
|
bool InitializeThreadSignalStack() {
|
||||||
if (g_install_state.load(std::memory_order_acquire) == 0) {
|
// Keep fault handling off guest stacks, which GPU tracking can make read-only.
|
||||||
FailFast("host exception handler is not installed");
|
thread_local ThreadSignalStack signal_stack;
|
||||||
}
|
return signal_stack.IsInitialized();
|
||||||
|
|
||||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
|
||||||
if (handler == nullptr) {
|
|
||||||
FailFast("host exception callback is null");
|
|
||||||
}
|
|
||||||
return handler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
|
|
||||||
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
|
||||||
FilterScope filter_scope;
|
|
||||||
|
|
||||||
auto* exception_record = exception->ExceptionRecord;
|
auto* exception_record = exception->ExceptionRecord;
|
||||||
|
|
||||||
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
|
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
|
||||||
@@ -105,12 +112,6 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
|||||||
} else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
} else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
||||||
info.type = ExceptionType::IllegalInstruction;
|
info.type = ExceptionType::IllegalInstruction;
|
||||||
} else {
|
} else {
|
||||||
printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64
|
|
||||||
", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n",
|
|
||||||
static_cast<uint32_t>(exception_record->ExceptionCode),
|
|
||||||
reinterpret_cast<uint64_t>(exception_record->ExceptionAddress),
|
|
||||||
exception->ContextRecord->Rip, exception->ContextRecord->Rsp,
|
|
||||||
exception->ContextRecord->Rbp);
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,28 +132,21 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
|||||||
info.r14 = exception->ContextRecord->R14;
|
info.r14 = exception->ContextRecord->R14;
|
||||||
info.r15 = exception->ContextRecord->R15;
|
info.r15 = exception->ContextRecord->R15;
|
||||||
|
|
||||||
const auto handler = LoadInstalledHandler();
|
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||||
|
if (handler != nullptr && handler(info)) {
|
||||||
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
|
}
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
static std::atomic<Handler> g_handler {nullptr};
|
static std::atomic<Handler> g_handler {nullptr};
|
||||||
static std::atomic_uint32_t g_install_state {0};
|
static std::atomic_uint32_t g_install_state {0};
|
||||||
static thread_local bool g_in_exception_filter = false;
|
|
||||||
|
|
||||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||||
|
|
||||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
|
||||||
std::fputs("HostException fail-fast: ", stderr);
|
|
||||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
|
||||||
std::fputc('\n', stderr);
|
|
||||||
std::fflush(stderr);
|
|
||||||
std::_Exit(321);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
|
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
|
||||||
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
|
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
|
||||||
static AccessViolationType DecodeAccess(uint64_t err) {
|
static AccessViolationType DecodeAccess(uint64_t err) {
|
||||||
@@ -170,11 +164,6 @@ static AccessViolationType DecodeAccess(uint64_t err) {
|
|||||||
// re-executing the faulting instruction against the now-fixed protection. An unresolved
|
// re-executing the faulting instruction against the now-fixed protection. An unresolved
|
||||||
// fault restores the default disposition so the retry terminates the process.
|
// fault restores the default disposition so the retry terminates the process.
|
||||||
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||||
if (g_in_exception_filter) {
|
|
||||||
FailFast("nested exception while resolving a host fault");
|
|
||||||
}
|
|
||||||
g_in_exception_filter = true;
|
|
||||||
|
|
||||||
auto* uc = static_cast<ucontext_t*>(uctx);
|
auto* uc = static_cast<ucontext_t*>(uctx);
|
||||||
const auto* mc = uc->uc_mcontext;
|
const auto* mc = uc->uc_mcontext;
|
||||||
const auto& ss = mc->__ss;
|
const auto& ss = mc->__ss;
|
||||||
@@ -210,14 +199,7 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
|||||||
info.r15 = ss.__r15;
|
info.r15 = ss.__r15;
|
||||||
|
|
||||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||||
if (handler == nullptr) {
|
if (handler != nullptr && handler(info)) {
|
||||||
FailFast("host exception callback is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool resolved = handler(info);
|
|
||||||
g_in_exception_filter = false;
|
|
||||||
|
|
||||||
if (resolved) {
|
|
||||||
return; // retry the faulting instruction against the fixed mapping
|
return; // retry the faulting instruction against the fixed mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,8 +226,6 @@ static void ChainToDefault(int signal_number) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
|
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
|
||||||
FilterScope filter_scope;
|
|
||||||
|
|
||||||
auto* context = static_cast<ucontext_t*>(native_context);
|
auto* context = static_cast<ucontext_t*>(native_context);
|
||||||
auto* gregs = context->uc_mcontext.gregs;
|
auto* gregs = context->uc_mcontext.gregs;
|
||||||
|
|
||||||
@@ -289,9 +269,8 @@ static void SignalHandler(int signal_number, siginfo_t* signal_info, void* nativ
|
|||||||
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
|
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
|
||||||
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
|
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
|
||||||
|
|
||||||
const auto handler = LoadInstalledHandler();
|
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||||
|
if (handler != nullptr && handler(info)) {
|
||||||
if (handler(info)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +292,7 @@ bool InstallHandler(Handler handler) {
|
|||||||
g_handler.store(handler, std::memory_order_release);
|
g_handler.store(handler, std::memory_order_release);
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
|
if (AddVectoredExceptionHandler(0, ExceptionFilter) == nullptr) {
|
||||||
g_handler.store(nullptr, std::memory_order_release);
|
g_handler.store(nullptr, std::memory_order_release);
|
||||||
g_install_state.store(0, std::memory_order_release);
|
g_install_state.store(0, std::memory_order_release);
|
||||||
printf("AddVectoredExceptionHandler() failed\n");
|
printf("AddVectoredExceptionHandler() failed\n");
|
||||||
@@ -322,7 +301,7 @@ bool InstallHandler(Handler handler) {
|
|||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
struct sigaction sa {};
|
struct sigaction sa {};
|
||||||
sa.sa_sigaction = SignalHandler;
|
sa.sa_sigaction = SignalHandler;
|
||||||
sa.sa_flags = SA_SIGINFO;
|
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||||
sigemptyset(&sa.sa_mask);
|
sigemptyset(&sa.sa_mask);
|
||||||
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
|
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
|
||||||
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
|
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
|
||||||
@@ -343,8 +322,7 @@ bool InstallHandler(Handler handler) {
|
|||||||
struct sigaction action {};
|
struct sigaction action {};
|
||||||
action.sa_sigaction = SignalHandler;
|
action.sa_sigaction = SignalHandler;
|
||||||
sigemptyset(&action.sa_mask);
|
sigemptyset(&action.sa_mask);
|
||||||
// Fault resolution needs the normal thread stack.
|
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
|
||||||
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
||||||
|
|
||||||
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
|
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
|
||||||
if (::sigaction(signal_number, &action, nullptr) != 0) {
|
if (::sigaction(signal_number, &action, nullptr) != 0) {
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ using Handler = bool (*)(const ExceptionInfo&);
|
|||||||
|
|
||||||
bool InstallHandler(Handler handler);
|
bool InstallHandler(Handler handler);
|
||||||
|
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
|
bool InitializeThreadSignalStack();
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace Common::HostException
|
} // namespace Common::HostException
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
||||||
@@ -0,0 +1,580 @@
|
|||||||
|
#include "common/hostStats.h"
|
||||||
|
|
||||||
|
#include "common/config.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <windows.h> // IWYU pragma: keep
|
||||||
|
// clang-format off
|
||||||
|
#include <psapi.h>
|
||||||
|
// clang-format on
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <IOKit/IOKitLib.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <libproc.h>
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#include <notify.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#else
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Cumulative counters; rates come from the difference between two samples.
|
||||||
|
struct Counters {
|
||||||
|
struct Thread {
|
||||||
|
uint64_t cpu_ns;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
std::optional<uint64_t> process_cpu_ns;
|
||||||
|
std::optional<uint64_t> system_busy;
|
||||||
|
std::optional<uint64_t> system_total;
|
||||||
|
std::optional<uint64_t> context_switches;
|
||||||
|
std::optional<uint64_t> page_faults;
|
||||||
|
std::optional<uint64_t> page_ins;
|
||||||
|
std::optional<uint64_t> disk_read;
|
||||||
|
std::optional<uint64_t> disk_write;
|
||||||
|
std::unordered_map<uint64_t, Thread> threads;
|
||||||
|
uint64_t wall_ns = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t WallNanoseconds() {
|
||||||
|
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch())
|
||||||
|
.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
|
|
||||||
|
uint64_t FileTimeTo100Ns(const FILETIME& time) {
|
||||||
|
return (static_cast<uint64_t>(time.dwHighDateTime) << 32u) | time.dwLowDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlatformState {};
|
||||||
|
|
||||||
|
void SamplePlatform(PlatformState& /*state*/, HostStats& stats, Counters& now) {
|
||||||
|
const auto process = GetCurrentProcess();
|
||||||
|
FILETIME creation {};
|
||||||
|
FILETIME exit {};
|
||||||
|
FILETIME kernel {};
|
||||||
|
FILETIME user {};
|
||||||
|
if (GetProcessTimes(process, &creation, &exit, &kernel, &user) != 0) {
|
||||||
|
now.process_cpu_ns = (FileTimeTo100Ns(kernel) + FileTimeTo100Ns(user)) * 100u;
|
||||||
|
}
|
||||||
|
FILETIME idle {};
|
||||||
|
if (GetSystemTimes(&idle, &kernel, &user) != 0) {
|
||||||
|
// Kernel time includes idle time.
|
||||||
|
now.system_total = FileTimeTo100Ns(kernel) + FileTimeTo100Ns(user);
|
||||||
|
now.system_busy = *now.system_total - FileTimeTo100Ns(idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
PROCESS_MEMORY_COUNTERS memory {};
|
||||||
|
if (K32GetProcessMemoryInfo(process, &memory, sizeof(memory)) != 0) {
|
||||||
|
stats.process_memory = memory.WorkingSetSize;
|
||||||
|
stats.process_resident = memory.WorkingSetSize;
|
||||||
|
now.page_faults = memory.PageFaultCount;
|
||||||
|
}
|
||||||
|
IO_COUNTERS io {};
|
||||||
|
if (GetProcessIoCounters(process, &io) != 0) {
|
||||||
|
now.disk_read = io.ReadTransferCount;
|
||||||
|
now.disk_write = io.WriteTransferCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORYSTATUSEX system {};
|
||||||
|
system.dwLength = sizeof(system);
|
||||||
|
if (GlobalMemoryStatusEx(&system) != 0) {
|
||||||
|
stats.system_memory_total = system.ullTotalPhys;
|
||||||
|
stats.system_memory_used = system.ullTotalPhys - system.ullAvailPhys;
|
||||||
|
stats.system_free = system.ullAvailPhys;
|
||||||
|
stats.memory_pressure = system.dwMemoryLoad >= 90 ? MemoryPressure::Critical
|
||||||
|
: system.dwMemoryLoad >= 80 ? MemoryPressure::Warning
|
||||||
|
: MemoryPressure::Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
void SampleRusage(Counters& now) {
|
||||||
|
rusage usage {};
|
||||||
|
if (getrusage(RUSAGE_SELF, &usage) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto to_ns = [](const timeval& time) {
|
||||||
|
return static_cast<uint64_t>(time.tv_sec) * 1000000000u +
|
||||||
|
static_cast<uint64_t>(time.tv_usec) * 1000u;
|
||||||
|
};
|
||||||
|
now.process_cpu_ns = to_ns(usage.ru_utime) + to_ns(usage.ru_stime);
|
||||||
|
now.context_switches = static_cast<uint64_t>(usage.ru_nvcsw + usage.ru_nivcsw);
|
||||||
|
now.page_faults = static_cast<uint64_t>(usage.ru_minflt + usage.ru_majflt);
|
||||||
|
now.page_ins = static_cast<uint64_t>(usage.ru_majflt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::optional<T> SysctlValue(const char* name) {
|
||||||
|
T value {};
|
||||||
|
size_t size = sizeof(value);
|
||||||
|
if (sysctlbyname(name, &value, &size, nullptr, 0) != 0 || size != sizeof(value)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlatformState {
|
||||||
|
mach_port_t host = mach_host_self();
|
||||||
|
int thermal_token = -1;
|
||||||
|
bool thermal_ready = false;
|
||||||
|
std::string gpu_model;
|
||||||
|
std::optional<uint32_t> gpu_cores;
|
||||||
|
|
||||||
|
~PlatformState() {
|
||||||
|
if (thermal_ready) {
|
||||||
|
notify_cancel(thermal_token);
|
||||||
|
}
|
||||||
|
mach_port_deallocate(mach_task_self(), host);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<int64_t> DictionaryNumber(CFDictionaryRef dictionary, CFStringRef key) {
|
||||||
|
const auto* value = CFDictionaryGetValue(dictionary, key);
|
||||||
|
int64_t number = 0;
|
||||||
|
if (value == nullptr || CFGetTypeID(value) != CFNumberGetTypeID() ||
|
||||||
|
!CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberSInt64Type, &number) ||
|
||||||
|
number < 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RegistryString(io_object_t service, CFStringRef key) {
|
||||||
|
std::string result;
|
||||||
|
auto* value = IORegistryEntryCreateCFProperty(service, key, kCFAllocatorDefault, 0);
|
||||||
|
if (value == nullptr) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (CFGetTypeID(value) == CFStringGetTypeID()) {
|
||||||
|
char buffer[128] {};
|
||||||
|
if (CFStringGetCString(static_cast<CFStringRef>(value), buffer, sizeof(buffer),
|
||||||
|
kCFStringEncodingUTF8)) {
|
||||||
|
result = buffer;
|
||||||
|
}
|
||||||
|
} else if (CFGetTypeID(value) == CFDataGetTypeID()) {
|
||||||
|
// Intel Macs store the model as NUL-terminated data.
|
||||||
|
const auto* data = static_cast<CFDataRef>(value);
|
||||||
|
const auto* bytes = reinterpret_cast<const char*>(CFDataGetBytePtr(data));
|
||||||
|
const auto length = static_cast<size_t>(CFDataGetLength(data));
|
||||||
|
result.assign(bytes, strnlen(bytes, length));
|
||||||
|
}
|
||||||
|
CFRelease(value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IOAccelerator exposes whole-device statistics; there is no per-process figure.
|
||||||
|
void SampleGpu(PlatformState& state, HostStats& stats) {
|
||||||
|
io_iterator_t iterator = 0;
|
||||||
|
if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("IOAccelerator"),
|
||||||
|
&iterator) != KERN_SUCCESS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto take_max = [](std::optional<double>& target, std::optional<int64_t> value) {
|
||||||
|
if (value) {
|
||||||
|
target = std::max(target.value_or(0.0), std::min(100.0, static_cast<double>(*value)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto add = [](std::optional<uint64_t>& target, std::optional<int64_t> value) {
|
||||||
|
if (value) {
|
||||||
|
target = target.value_or(0) + static_cast<uint64_t>(*value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (io_object_t service = IOIteratorNext(iterator); service != 0;
|
||||||
|
service = IOIteratorNext(iterator)) {
|
||||||
|
if (state.gpu_model.empty()) {
|
||||||
|
state.gpu_model = RegistryString(service, CFSTR("model"));
|
||||||
|
auto* cores = IORegistryEntryCreateCFProperty(service, CFSTR("gpu-core-count"),
|
||||||
|
kCFAllocatorDefault, 0);
|
||||||
|
int64_t count = 0;
|
||||||
|
if (cores != nullptr) {
|
||||||
|
if (CFGetTypeID(cores) == CFNumberGetTypeID() &&
|
||||||
|
CFNumberGetValue(static_cast<CFNumberRef>(cores), kCFNumberSInt64Type,
|
||||||
|
&count) &&
|
||||||
|
count > 0) {
|
||||||
|
state.gpu_cores = static_cast<uint32_t>(count);
|
||||||
|
}
|
||||||
|
CFRelease(cores);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto* statistics = IORegistryEntryCreateCFProperty(service, CFSTR("PerformanceStatistics"),
|
||||||
|
kCFAllocatorDefault, 0);
|
||||||
|
if (statistics != nullptr) {
|
||||||
|
if (CFGetTypeID(statistics) == CFDictionaryGetTypeID()) {
|
||||||
|
const auto* values = static_cast<CFDictionaryRef>(statistics);
|
||||||
|
take_max(stats.gpu_percent,
|
||||||
|
DictionaryNumber(values, CFSTR("Device Utilization %")));
|
||||||
|
take_max(stats.gpu_renderer_percent,
|
||||||
|
DictionaryNumber(values, CFSTR("Renderer Utilization %")));
|
||||||
|
take_max(stats.gpu_tiler_percent,
|
||||||
|
DictionaryNumber(values, CFSTR("Tiler Utilization %")));
|
||||||
|
add(stats.gpu_memory_in_use,
|
||||||
|
DictionaryNumber(values, CFSTR("In use system memory")));
|
||||||
|
add(stats.gpu_memory_allocated,
|
||||||
|
DictionaryNumber(values, CFSTR("Alloc system memory")));
|
||||||
|
add(stats.gpu_recoveries, DictionaryNumber(values, CFSTR("recoveryCount")));
|
||||||
|
}
|
||||||
|
CFRelease(statistics);
|
||||||
|
}
|
||||||
|
IOObjectRelease(service);
|
||||||
|
}
|
||||||
|
IOObjectRelease(iterator);
|
||||||
|
stats.gpu_model = state.gpu_model;
|
||||||
|
stats.gpu_cores = state.gpu_cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleThreads(HostStats& stats, Counters& now) {
|
||||||
|
thread_act_array_t threads = nullptr;
|
||||||
|
mach_msg_type_number_t count = 0;
|
||||||
|
const auto task = mach_task_self();
|
||||||
|
if (task_threads(task, &threads, &count) != KERN_SUCCESS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stats.thread_count = count;
|
||||||
|
for (mach_msg_type_number_t i = 0; i < count; i++) {
|
||||||
|
thread_identifier_info_data_t identifier {};
|
||||||
|
mach_msg_type_number_t size = THREAD_IDENTIFIER_INFO_COUNT;
|
||||||
|
thread_basic_info_data_t basic {};
|
||||||
|
mach_msg_type_number_t basic_size = THREAD_BASIC_INFO_COUNT;
|
||||||
|
if (thread_info(threads[i], THREAD_IDENTIFIER_INFO,
|
||||||
|
reinterpret_cast<thread_info_t>(&identifier), &size) == KERN_SUCCESS &&
|
||||||
|
thread_info(threads[i], THREAD_BASIC_INFO, reinterpret_cast<thread_info_t>(&basic),
|
||||||
|
&basic_size) == KERN_SUCCESS) {
|
||||||
|
const auto to_ns = [](const time_value_t& time) {
|
||||||
|
return static_cast<uint64_t>(time.seconds) * 1000000000u +
|
||||||
|
static_cast<uint64_t>(time.microseconds) * 1000u;
|
||||||
|
};
|
||||||
|
auto& thread = now.threads[identifier.thread_id];
|
||||||
|
thread.cpu_ns = to_ns(basic.user_time) + to_ns(basic.system_time);
|
||||||
|
thread_extended_info_data_t extended {};
|
||||||
|
mach_msg_type_number_t extended_size = THREAD_EXTENDED_INFO_COUNT;
|
||||||
|
if (thread_info(threads[i], THREAD_EXTENDED_INFO,
|
||||||
|
reinterpret_cast<thread_info_t>(&extended),
|
||||||
|
&extended_size) == KERN_SUCCESS) {
|
||||||
|
thread.name = std::string(extended.pth_name,
|
||||||
|
strnlen(extended.pth_name, sizeof(extended.pth_name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mach_port_deallocate(task, threads[i]);
|
||||||
|
}
|
||||||
|
vm_deallocate(task, reinterpret_cast<vm_address_t>(threads), count * sizeof(thread_act_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SamplePlatform(PlatformState& state, HostStats& stats, Counters& now) {
|
||||||
|
SampleRusage(now);
|
||||||
|
|
||||||
|
host_cpu_load_info_data_t load {};
|
||||||
|
mach_msg_type_number_t load_count = HOST_CPU_LOAD_INFO_COUNT;
|
||||||
|
if (host_statistics(state.host, HOST_CPU_LOAD_INFO, reinterpret_cast<host_info_t>(&load),
|
||||||
|
&load_count) == KERN_SUCCESS) {
|
||||||
|
const uint64_t busy = static_cast<uint64_t>(load.cpu_ticks[CPU_STATE_USER]) +
|
||||||
|
load.cpu_ticks[CPU_STATE_SYSTEM] + load.cpu_ticks[CPU_STATE_NICE];
|
||||||
|
now.system_busy = busy;
|
||||||
|
now.system_total = busy + load.cpu_ticks[CPU_STATE_IDLE];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Physical footprint is what Activity Monitor shows in its Memory column.
|
||||||
|
task_vm_info_data_t vm_info {};
|
||||||
|
mach_msg_type_number_t vm_info_count = TASK_VM_INFO_COUNT;
|
||||||
|
if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&vm_info),
|
||||||
|
&vm_info_count) == KERN_SUCCESS) {
|
||||||
|
stats.process_memory = vm_info.phys_footprint;
|
||||||
|
stats.process_resident = vm_info.resident_size;
|
||||||
|
stats.process_compressed = vm_info.compressed;
|
||||||
|
stats.process_file_backed = vm_info.external;
|
||||||
|
stats.process_virtual = vm_info.virtual_size;
|
||||||
|
}
|
||||||
|
rusage_info_v2 usage {};
|
||||||
|
if (proc_pid_rusage(getpid(), RUSAGE_INFO_V2, reinterpret_cast<rusage_info_t*>(&usage)) == 0) {
|
||||||
|
now.disk_read = usage.ri_diskio_bytesread;
|
||||||
|
now.disk_write = usage.ri_diskio_byteswritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.system_memory_total = SysctlValue<uint64_t>("hw.memsize");
|
||||||
|
vm_size_t page = 0;
|
||||||
|
vm_statistics64_data_t vm {};
|
||||||
|
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
|
||||||
|
if (host_page_size(state.host, &page) == KERN_SUCCESS &&
|
||||||
|
host_statistics64(state.host, HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm),
|
||||||
|
&count) == KERN_SUCCESS) {
|
||||||
|
// Activity Monitor's "Memory Used": app memory + wired + compressed.
|
||||||
|
const uint64_t app = vm.internal_page_count > vm.purgeable_count
|
||||||
|
? vm.internal_page_count - vm.purgeable_count
|
||||||
|
: 0;
|
||||||
|
stats.system_memory_used = (app + vm.wire_count + vm.compressor_page_count) * page;
|
||||||
|
stats.system_wired = static_cast<uint64_t>(vm.wire_count) * page;
|
||||||
|
stats.system_compressed = static_cast<uint64_t>(vm.compressor_page_count) * page;
|
||||||
|
stats.system_cached =
|
||||||
|
(static_cast<uint64_t>(vm.external_page_count) + vm.purgeable_count) * page;
|
||||||
|
stats.system_free = static_cast<uint64_t>(vm.free_count) * page;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto swap = SysctlValue<xsw_usage>("vm.swapusage")) {
|
||||||
|
stats.swap_used = swap->xsu_used;
|
||||||
|
stats.swap_total = swap->xsu_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 = normal, 2 = warning, 4 = critical, as reported to dispatch memory-pressure sources.
|
||||||
|
if (const auto level = SysctlValue<int>("kern.memorystatus_vm_pressure_level")) {
|
||||||
|
stats.memory_pressure = *level >= 4 ? MemoryPressure::Critical
|
||||||
|
: *level >= 2 ? MemoryPressure::Warning
|
||||||
|
: MemoryPressure::Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 nominal, 1 moderate, 2 heavy, 3 trapping, 4 sleeping.
|
||||||
|
if (!state.thermal_ready) {
|
||||||
|
state.thermal_ready = notify_register_check("com.apple.system.thermalpressurelevel",
|
||||||
|
&state.thermal_token) == NOTIFY_STATUS_OK;
|
||||||
|
}
|
||||||
|
uint64_t thermal = 0;
|
||||||
|
if (state.thermal_ready &&
|
||||||
|
notify_get_state(state.thermal_token, &thermal) == NOTIFY_STATUS_OK) {
|
||||||
|
stats.thermal_pressure = thermal >= 3 ? ThermalPressure::Critical
|
||||||
|
: thermal == 2 ? ThermalPressure::Heavy
|
||||||
|
: thermal == 1 ? ThermalPressure::Moderate
|
||||||
|
: ThermalPressure::Nominal;
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleThreads(stats, now);
|
||||||
|
SampleGpu(state, stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
using ProcValues = std::unordered_map<std::string, uint64_t>;
|
||||||
|
|
||||||
|
// Reads "<key>: <number> [kB]" lines; kB values are returned in bytes.
|
||||||
|
ProcValues ReadProcValues(const char* path) {
|
||||||
|
ProcValues values;
|
||||||
|
std::ifstream file(path);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
const auto colon = line.find(':');
|
||||||
|
if (colon == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::istringstream stream(line.substr(colon + 1));
|
||||||
|
uint64_t number = 0;
|
||||||
|
std::string unit;
|
||||||
|
if (stream >> number) {
|
||||||
|
stream >> unit;
|
||||||
|
values[line.substr(0, colon)] = unit == "kB" ? number * 1024u : number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uint64_t> Find(const ProcValues& values, const char* key) {
|
||||||
|
const auto it = values.find(key);
|
||||||
|
return it != values.end() ? std::optional(it->second) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlatformState {};
|
||||||
|
|
||||||
|
void SampleThreads(HostStats& stats, Counters& now) {
|
||||||
|
static const double ns_per_tick = 1e9 / static_cast<double>(std::max(sysconf(_SC_CLK_TCK), 1L));
|
||||||
|
std::error_code error;
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (std::filesystem::directory_iterator it("/proc/self/task", error), end; !error && it != end;
|
||||||
|
it.increment(error)) {
|
||||||
|
count++;
|
||||||
|
std::ifstream stat_file(it->path() / "stat");
|
||||||
|
std::string stat;
|
||||||
|
uint64_t id = 0;
|
||||||
|
if (!(stat_file >> id)) {
|
||||||
|
continue; // The thread may have exited during enumeration.
|
||||||
|
}
|
||||||
|
std::getline(stat_file, stat);
|
||||||
|
const auto close = stat.rfind(')');
|
||||||
|
if (close == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Fields after the command name start at field 3 (state); utime and stime are 14 and 15.
|
||||||
|
std::istringstream fields(stat.substr(close + 2));
|
||||||
|
std::string field;
|
||||||
|
for (int index = 3; index < 14 && fields >> field; index++) {
|
||||||
|
}
|
||||||
|
uint64_t user = 0;
|
||||||
|
uint64_t system = 0;
|
||||||
|
if (!(fields >> user >> system)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto& thread = now.threads[id];
|
||||||
|
thread.cpu_ns = static_cast<uint64_t>(static_cast<double>(user + system) * ns_per_tick);
|
||||||
|
std::ifstream name_file(it->path() / "comm");
|
||||||
|
std::getline(name_file, thread.name);
|
||||||
|
}
|
||||||
|
if (!error) {
|
||||||
|
stats.thread_count = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SamplePlatform(PlatformState& /*state*/, HostStats& stats, Counters& now) {
|
||||||
|
SampleRusage(now);
|
||||||
|
|
||||||
|
std::ifstream cpu_file("/proc/stat");
|
||||||
|
std::string label;
|
||||||
|
if (cpu_file >> label && label == "cpu") {
|
||||||
|
uint64_t total = 0;
|
||||||
|
uint64_t idle = 0;
|
||||||
|
uint64_t value = 0;
|
||||||
|
for (int index = 0; index < 8 && cpu_file >> value; index++) {
|
||||||
|
total += value;
|
||||||
|
if (index == 3 || index == 4) { // idle, iowait
|
||||||
|
idle += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
now.system_total = total;
|
||||||
|
now.system_busy = total - idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto status = ReadProcValues("/proc/self/status");
|
||||||
|
stats.process_memory = Find(status, "VmRSS");
|
||||||
|
stats.process_resident = Find(status, "VmRSS");
|
||||||
|
stats.process_file_backed = Find(status, "RssFile");
|
||||||
|
stats.process_compressed = Find(status, "VmSwap");
|
||||||
|
stats.process_virtual = Find(status, "VmSize");
|
||||||
|
|
||||||
|
const auto io = ReadProcValues("/proc/self/io");
|
||||||
|
now.disk_read = Find(io, "read_bytes");
|
||||||
|
now.disk_write = Find(io, "write_bytes");
|
||||||
|
|
||||||
|
const auto memory = ReadProcValues("/proc/meminfo");
|
||||||
|
const auto total = Find(memory, "MemTotal");
|
||||||
|
const auto available = Find(memory, "MemAvailable");
|
||||||
|
if (total && available && *available <= *total) {
|
||||||
|
stats.system_memory_total = total;
|
||||||
|
stats.system_memory_used = *total - *available;
|
||||||
|
}
|
||||||
|
stats.system_free = Find(memory, "MemFree");
|
||||||
|
if (const auto cached = Find(memory, "Cached")) {
|
||||||
|
stats.system_cached = *cached + Find(memory, "Buffers").value_or(0);
|
||||||
|
}
|
||||||
|
const auto swap_total = Find(memory, "SwapTotal");
|
||||||
|
const auto swap_free = Find(memory, "SwapFree");
|
||||||
|
if (swap_total && swap_free && *swap_free <= *swap_total) {
|
||||||
|
stats.swap_total = swap_total;
|
||||||
|
stats.swap_used = *swap_total - *swap_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pressure stall information: share of the last 10 s some task waited on memory.
|
||||||
|
std::ifstream pressure("/proc/pressure/memory");
|
||||||
|
std::string kind;
|
||||||
|
std::string average;
|
||||||
|
double stalled = 0.0;
|
||||||
|
if (pressure >> kind >> average && kind == "some" && average.rfind("avg10=", 0) == 0 &&
|
||||||
|
std::istringstream(average.substr(6)) >> stalled) {
|
||||||
|
stats.memory_pressure = stalled >= 20.0 ? MemoryPressure::Critical
|
||||||
|
: stalled >= 5.0 ? MemoryPressure::Warning
|
||||||
|
: MemoryPressure::Normal;
|
||||||
|
} else if (total && available && *total != 0) {
|
||||||
|
const double free_share = static_cast<double>(*available) / static_cast<double>(*total);
|
||||||
|
stats.memory_pressure = free_share < 0.10 ? MemoryPressure::Critical
|
||||||
|
: free_share < 0.25 ? MemoryPressure::Warning
|
||||||
|
: MemoryPressure::Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleThreads(stats, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr size_t BUSIEST_THREADS = 6;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct HostStatsSampler::Impl {
|
||||||
|
PlatformState state;
|
||||||
|
Counters last;
|
||||||
|
uint32_t logical_cores = std::max(std::thread::hardware_concurrency(), 1u);
|
||||||
|
bool has_last = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
HostStatsSampler::HostStatsSampler(): m_impl(std::make_unique<Impl>()) {}
|
||||||
|
|
||||||
|
HostStatsSampler::~HostStatsSampler() = default;
|
||||||
|
|
||||||
|
HostStats HostStatsSampler::Sample() {
|
||||||
|
HostStats stats;
|
||||||
|
stats.logical_cores = m_impl->logical_cores;
|
||||||
|
|
||||||
|
Counters now;
|
||||||
|
now.wall_ns = WallNanoseconds();
|
||||||
|
SamplePlatform(m_impl->state, stats, now);
|
||||||
|
|
||||||
|
const auto& last = m_impl->last;
|
||||||
|
const auto elapsed = static_cast<double>(now.wall_ns - last.wall_ns) / 1e9;
|
||||||
|
if (m_impl->has_last && elapsed > 0.0) {
|
||||||
|
const auto rate = [&](std::optional<uint64_t> current,
|
||||||
|
std::optional<uint64_t> previous) -> std::optional<double> {
|
||||||
|
if (!current || !previous || *current < *previous) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return static_cast<double>(*current - *previous) / elapsed;
|
||||||
|
};
|
||||||
|
if (const auto cpu = rate(now.process_cpu_ns, last.process_cpu_ns)) {
|
||||||
|
stats.process_cpu_percent =
|
||||||
|
std::clamp(*cpu / 1e9 * 100.0 / stats.logical_cores, 0.0, 100.0);
|
||||||
|
}
|
||||||
|
if (now.system_total && last.system_total && now.system_busy && last.system_busy &&
|
||||||
|
*now.system_total > *last.system_total && *now.system_busy >= *last.system_busy) {
|
||||||
|
stats.system_cpu_percent =
|
||||||
|
std::clamp(static_cast<double>(*now.system_busy - *last.system_busy) /
|
||||||
|
static_cast<double>(*now.system_total - *last.system_total) * 100.0,
|
||||||
|
0.0, 100.0);
|
||||||
|
}
|
||||||
|
stats.context_switches_per_s = rate(now.context_switches, last.context_switches);
|
||||||
|
stats.page_faults_per_s = rate(now.page_faults, last.page_faults);
|
||||||
|
stats.page_ins_per_s = rate(now.page_ins, last.page_ins);
|
||||||
|
stats.disk_read_per_s = rate(now.disk_read, last.disk_read);
|
||||||
|
stats.disk_write_per_s = rate(now.disk_write, last.disk_write);
|
||||||
|
|
||||||
|
for (const auto& [id, thread]: now.threads) {
|
||||||
|
// A thread missing from the last sample started during this interval.
|
||||||
|
const auto previous = last.threads.find(id);
|
||||||
|
const uint64_t before = previous != last.threads.end() ? previous->second.cpu_ns : 0;
|
||||||
|
if (thread.cpu_ns < before) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double percent =
|
||||||
|
static_cast<double>(thread.cpu_ns - before) / 1e9 / elapsed * 100.0;
|
||||||
|
stats.busiest_threads.push_back(
|
||||||
|
{thread.name.empty() ? "thread " + std::to_string(id) : thread.name, percent});
|
||||||
|
}
|
||||||
|
std::sort(stats.busiest_threads.begin(), stats.busiest_threads.end(),
|
||||||
|
[](const auto& a, const auto& b) { return a.cpu_percent > b.cpu_percent; });
|
||||||
|
if (stats.busiest_threads.size() > BUSIEST_THREADS) {
|
||||||
|
stats.busiest_threads.resize(BUSIEST_THREADS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_impl->last = std::move(now);
|
||||||
|
m_impl->has_last = true;
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#ifndef KYTY_COMMON_HOST_STATS_H_
|
||||||
|
#define KYTY_COMMON_HOST_STATS_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
enum class MemoryPressure { Unknown, Normal, Warning, Critical };
|
||||||
|
enum class ThermalPressure { Unknown, Nominal, Moderate, Heavy, Critical };
|
||||||
|
|
||||||
|
struct ThreadUsage {
|
||||||
|
std::string name;
|
||||||
|
double cpu_percent = 0.0; // Of one core.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Host-wide and process-wide resource usage. Missing values are std::nullopt; rates are per
|
||||||
|
// second since the previous sample.
|
||||||
|
struct HostStats {
|
||||||
|
uint32_t logical_cores = 0;
|
||||||
|
|
||||||
|
// Processing.
|
||||||
|
std::optional<double> process_cpu_percent; // Share of all logical cores, 0-100.
|
||||||
|
std::optional<double> system_cpu_percent; // Whole machine, 0-100.
|
||||||
|
std::optional<uint32_t> thread_count;
|
||||||
|
std::vector<ThreadUsage> busiest_threads; // Busiest first.
|
||||||
|
std::optional<double> context_switches_per_s;
|
||||||
|
std::optional<double> page_faults_per_s;
|
||||||
|
std::optional<double> page_ins_per_s; // Faults that read from disk.
|
||||||
|
ThermalPressure thermal_pressure = ThermalPressure::Unknown;
|
||||||
|
|
||||||
|
// Emulator process memory.
|
||||||
|
std::optional<uint64_t> process_memory; // Physical footprint / resident set.
|
||||||
|
std::optional<uint64_t> process_resident;
|
||||||
|
std::optional<uint64_t> process_compressed; // Compressed or swapped out.
|
||||||
|
std::optional<uint64_t> process_file_backed;
|
||||||
|
std::optional<uint64_t> process_virtual; // Reserved address space.
|
||||||
|
|
||||||
|
// System memory.
|
||||||
|
std::optional<uint64_t> system_memory_used;
|
||||||
|
std::optional<uint64_t> system_memory_total;
|
||||||
|
std::optional<uint64_t> system_wired;
|
||||||
|
std::optional<uint64_t> system_compressed;
|
||||||
|
std::optional<uint64_t> system_cached; // File cache, reclaimable.
|
||||||
|
std::optional<uint64_t> system_free;
|
||||||
|
std::optional<uint64_t> swap_used;
|
||||||
|
std::optional<uint64_t> swap_total;
|
||||||
|
MemoryPressure memory_pressure = MemoryPressure::Unknown;
|
||||||
|
|
||||||
|
// Disk I/O of the emulator process.
|
||||||
|
std::optional<double> disk_read_per_s;
|
||||||
|
std::optional<double> disk_write_per_s;
|
||||||
|
|
||||||
|
// GPU, whole device (macOS IOAccelerator).
|
||||||
|
std::string gpu_model;
|
||||||
|
std::optional<uint32_t> gpu_cores;
|
||||||
|
std::optional<double> gpu_percent;
|
||||||
|
std::optional<double> gpu_renderer_percent;
|
||||||
|
std::optional<double> gpu_tiler_percent;
|
||||||
|
std::optional<uint64_t> gpu_memory_in_use; // System memory the GPU driver has in use.
|
||||||
|
std::optional<uint64_t> gpu_memory_allocated;
|
||||||
|
std::optional<uint64_t> gpu_recoveries; // GPU resets since boot.
|
||||||
|
};
|
||||||
|
|
||||||
|
// May block for a few milliseconds (thread enumeration, OS registry reads), so call it off the
|
||||||
|
// render thread. The first sample has no rates.
|
||||||
|
class HostStatsSampler final {
|
||||||
|
public:
|
||||||
|
HostStatsSampler();
|
||||||
|
~HostStatsSampler();
|
||||||
|
HostStatsSampler(const HostStatsSampler&) = delete;
|
||||||
|
HostStatsSampler& operator=(const HostStatsSampler&) = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] HostStats Sample();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> m_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
||||||
|
#endif /* KYTY_COMMON_HOST_STATS_H_ */
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#include "common/localization.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "localizationData.h"
|
||||||
|
|
||||||
|
namespace Common::Localization {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr std::array<std::string_view, 30> Locales = {
|
||||||
|
"ja-JP", "en-US", "fr-FR", "es-ES", "de-DE", "it-IT", "nl-NL", "pt-PT",
|
||||||
|
"ru-RU", "ko-KR", "zh-Hant", "zh-Hans", "fi-FI", "sv-SE", "da-DK", "no-NO",
|
||||||
|
"pl-PL", "pt-BR", "en-GB", "tr-TR", "es-419", "ar-AE", "fr-CA", "cs-CZ",
|
||||||
|
"hu-HU", "el-GR", "ro-RO", "th-TH", "vi-VN", "id-ID"};
|
||||||
|
|
||||||
|
size_t LanguageIndex(int console_language) {
|
||||||
|
return console_language >= 0 && console_language < static_cast<int>(Locales.size())
|
||||||
|
? static_cast<size_t>(console_language)
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::string_view LocaleName(int console_language) {
|
||||||
|
return Locales[LanguageIndex(console_language)];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view Get(int console_language, std::string_view key) {
|
||||||
|
static const auto catalogs = [] {
|
||||||
|
std::array<nlohmann::json, Locales.size()> result;
|
||||||
|
for (size_t i = 0; i < Locales.size(); ++i) {
|
||||||
|
const auto bytes = EmbeddedCatalog(Locales[i]);
|
||||||
|
result[i] = nlohmann::json::parse(bytes.begin(), bytes.end(), nullptr, false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}();
|
||||||
|
for (const auto index: std::array {LanguageIndex(console_language), size_t {1}}) {
|
||||||
|
const auto& catalog = catalogs[index];
|
||||||
|
const auto found = catalog.find(key);
|
||||||
|
if (found != catalog.end() && found->is_string()) {
|
||||||
|
const auto& text = found->get_ref<const std::string&>();
|
||||||
|
if (!text.empty()) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::Localization
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace Common::Localization {
|
||||||
|
|
||||||
|
// Console language indices follow the system configuration; invalid indices use English.
|
||||||
|
[[nodiscard]] std::string_view LocaleName(int console_language);
|
||||||
|
|
||||||
|
// Translations have static lifetime. Missing translations use English, then the supplied key.
|
||||||
|
[[nodiscard]] std::string_view Get(int console_language, std::string_view key);
|
||||||
|
|
||||||
|
} // namespace Common::Localization
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/emulatorConfig.h"
|
#include "common/emulatorConfig.h"
|
||||||
#include "common/stringUtils.h"
|
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -46,8 +46,8 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
|||||||
std::filesystem::create_directories(parent);
|
std::filesystem::create_directories(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sink =
|
const bool truncate = std::getenv(Log::RelaunchedVariable) == nullptr;
|
||||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), truncate);
|
||||||
return MakeLogger(std::move(name), std::move(sink));
|
return MakeLogger(std::move(name), std::move(sink));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +129,14 @@ static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WriteToConsoleAndLog(std::string_view text) {
|
||||||
|
WriteImpl(text);
|
||||||
|
if (g_initialized && g_direction != Direction::Console) {
|
||||||
|
WriteStdout(text);
|
||||||
|
}
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
void WriteFatal(std::string_view text) {
|
void WriteFatal(std::string_view text) {
|
||||||
if (g_direction == Direction::Silent || !g_initialized) {
|
if (g_direction == Direction::Silent || !g_initialized) {
|
||||||
WriteStdout(text);
|
WriteStdout(text);
|
||||||
@@ -149,25 +157,19 @@ void WriteFatal(fmt::text_style style, std::string_view text) {
|
|||||||
Flush();
|
Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_INIT(Log) {
|
void Initialize() {
|
||||||
g_initialized = true;
|
g_initialized = true;
|
||||||
switch (Config::GetPrintfDirection()) {
|
switch (Config::GetPrintfDirection()) {
|
||||||
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
|
case Config::LogDirection::Silent: g_direction = Direction::Silent; break;
|
||||||
case Config::OutputDirection::Console: g_direction = Direction::Console; break;
|
case Config::LogDirection::Console: g_direction = Direction::Console; break;
|
||||||
case Config::OutputDirection::File: g_direction = Direction::File; break;
|
case Config::LogDirection::File: g_direction = Direction::File; break;
|
||||||
}
|
}
|
||||||
g_output_file =
|
g_output_file =
|
||||||
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
|
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
|
||||||
SetupLogger();
|
SetupLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Log) {
|
void Shutdown() {
|
||||||
Flush();
|
|
||||||
std::lock_guard lock(g_logger_mutex);
|
|
||||||
g_logger.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_DESTROY(Log) {
|
|
||||||
Flush();
|
Flush();
|
||||||
std::lock_guard lock(g_logger_mutex);
|
std::lock_guard lock(g_logger_mutex);
|
||||||
g_logger.reset();
|
g_logger.reset();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#define KYTY_COMMON_LOGGING_LOG_H_
|
#define KYTY_COMMON_LOGGING_LOG_H_
|
||||||
|
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/subsystems.h"
|
|
||||||
|
|
||||||
#include <fmt/color.h>
|
#include <fmt/color.h>
|
||||||
#include <fmt/printf.h>
|
#include <fmt/printf.h>
|
||||||
@@ -10,14 +9,27 @@
|
|||||||
|
|
||||||
namespace Log {
|
namespace Log {
|
||||||
|
|
||||||
KYTY_SUBSYSTEM_DEFINE(Log);
|
void Initialize();
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
struct Lifecycle {
|
||||||
|
static constexpr const char* name = "Log";
|
||||||
|
static constexpr auto initialize = Log::Initialize;
|
||||||
|
static constexpr auto shutdown = Log::Shutdown;
|
||||||
|
static constexpr auto emergency_shutdown = Log::Shutdown;
|
||||||
|
};
|
||||||
|
|
||||||
enum class Direction { Silent, Console, File };
|
enum class Direction { Silent, Console, File };
|
||||||
|
|
||||||
|
// Set by LoadExec for the program it starts, which continues the log file of the one it replaces
|
||||||
|
// instead of starting it over.
|
||||||
|
inline constexpr const char* RelaunchedVariable = "KYTY_RELAUNCHED";
|
||||||
|
|
||||||
Direction GetDirection();
|
Direction GetDirection();
|
||||||
bool IsSilent();
|
bool IsSilent();
|
||||||
void Write(std::string_view text);
|
void Write(std::string_view text);
|
||||||
void Write(fmt::text_style style, std::string_view text);
|
void Write(fmt::text_style style, std::string_view text);
|
||||||
|
void WriteToConsoleAndLog(std::string_view text);
|
||||||
void WriteFatal(std::string_view text);
|
void WriteFatal(std::string_view text);
|
||||||
void WriteFatal(fmt::text_style style, std::string_view text);
|
void WriteFatal(fmt::text_style style, std::string_view text);
|
||||||
void Flush();
|
void Flush();
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#ifndef KYTY_COMMON_MAGICENUM_H_
|
|
||||||
#define KYTY_COMMON_MAGICENUM_H_
|
|
||||||
|
|
||||||
#include "common/common.h"
|
|
||||||
#include "common/stringUtils.h"
|
|
||||||
#include "magic_enum.hpp" // IWYU pragma: export
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
template <typename E>
|
|
||||||
inline std::string EnumName(E v) {
|
|
||||||
auto str = magic_enum::enum_name(v);
|
|
||||||
return std::string(str.data(), str.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename E>
|
|
||||||
inline std::string EnumName8(E v) {
|
|
||||||
auto str = magic_enum::enum_name(v);
|
|
||||||
return std::string(str.data(), str.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename E>
|
|
||||||
inline E EnumValue(const std::string& str, E default_value) {
|
|
||||||
auto v = magic_enum::enum_cast<E>(str.c_str());
|
|
||||||
if (v.has_value()) {
|
|
||||||
return v.value();
|
|
||||||
}
|
|
||||||
return default_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define KYTY_ENUM_RANGE(e, mx, mn) \
|
|
||||||
namespace magic_enum::customize { \
|
|
||||||
template <> \
|
|
||||||
struct enum_range<e> { \
|
|
||||||
static constexpr int min = (mx); \
|
|
||||||
static constexpr int max = (mn); \
|
|
||||||
}; \
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Common
|
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_MAGICENUM_H_ */
|
|
||||||
@@ -30,8 +30,6 @@ struct sys_dbg_stack_info_t {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
void SysStackWalk(void** stack, int* depth);
|
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
|
||||||
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
|
|
||||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack); // NOLINT(google-runtime-references)
|
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
|
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
|
||||||
@@ -18,14 +18,6 @@ enum sys_file_cache_type_t {
|
|||||||
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
|
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
||||||
struct sys_file_find_t {
|
|
||||||
std::filesystem::path path_with_name;
|
|
||||||
SysFileTimeStruct last_access_time;
|
|
||||||
SysFileTimeStruct last_write_time;
|
|
||||||
uint64_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
struct sys_dir_entry_t {
|
struct sys_dir_entry_t {
|
||||||
std::string name;
|
std::string name;
|
||||||
@@ -52,8 +44,8 @@ uint64_t SysFileTell(sys_file_t& f); // NOLINT(google-runt
|
|||||||
bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runtime-references)
|
bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runtime-references)
|
||||||
bool SysFileUnlink(sys_file_t& f,
|
bool SysFileUnlink(sys_file_t& f,
|
||||||
const std::filesystem::path& name); // NOLINT(google-runtime-references)
|
const std::filesystem::path& name); // NOLINT(google-runtime-references)
|
||||||
void SysFileWrite(uint32_t n, sys_file_t& f); // NOLINT(google-runtime-references)
|
|
||||||
uint64_t SysFileSize(const std::filesystem::path& file_name);
|
uint64_t SysFileSize(const std::filesystem::path& file_name);
|
||||||
|
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size);
|
||||||
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
|
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
|
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
|
||||||
bool SysFileIsFileExisting(const std::filesystem::path& name);
|
bool SysFileIsFileExisting(const std::filesystem::path& name);
|
||||||
@@ -76,11 +68,9 @@ bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeSt
|
|||||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||||
SysFileTimeStruct& access, SysFileTimeStruct& write);
|
SysFileTimeStruct& access, SysFileTimeStruct& write);
|
||||||
// NOLINTNEXTLINE(google-runtime-references)
|
// NOLINTNEXTLINE(google-runtime-references)
|
||||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out);
|
|
||||||
// NOLINTNEXTLINE(google-runtime-references)
|
|
||||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
|
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
|
||||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||||
void SysFileRemoveReadonly(const std::filesystem::path& name);
|
void SysFileRemoveReadonly(const std::filesystem::path& name);
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_PLATFORM_SYSFILEIO_H_ */
|
#endif /* KYTY_COMMON_PLATFORM_SYSFILEIO_H_ */
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <execinfo.h>
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -17,56 +16,6 @@
|
|||||||
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
|
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Avoid unwinding a guest-owned stack.
|
|
||||||
static bool OnOwnStack() {
|
|
||||||
const char* probe = reinterpret_cast<const char*>(&probe);
|
|
||||||
|
|
||||||
pthread_attr_t attr {};
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
const auto* top = static_cast<const char*>(pthread_get_stackaddr_np(pthread_self()));
|
|
||||||
const auto size = pthread_get_stacksize_np(pthread_self());
|
|
||||||
(void)attr;
|
|
||||||
return top != nullptr && size != 0 && probe < top && probe >= top - size;
|
|
||||||
#else
|
|
||||||
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void* base = nullptr;
|
|
||||||
size_t size = 0;
|
|
||||||
const bool ok = pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
|
|
||||||
pthread_attr_destroy(&attr);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto* low = static_cast<const char*>(base);
|
|
||||||
return probe >= low && probe < low + size;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void SysStackWalk(void** stack, int* depth) {
|
|
||||||
if (stack == nullptr || depth == nullptr || *depth <= 0) {
|
|
||||||
if (depth != nullptr) {
|
|
||||||
*depth = 0;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!OnOwnStack()) {
|
|
||||||
*depth = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int n = ::backtrace(stack, *depth);
|
|
||||||
*depth = (n < 0 ? 0 : n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
|
|
||||||
printf("stack: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.commited_addr),
|
|
||||||
static_cast<uint64_t>(stack.commited_size));
|
|
||||||
printf("code: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.code_addr),
|
|
||||||
static_cast<uint64_t>(stack.code_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||||
pid_t pid = getpid();
|
pid_t pid = getpid();
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -53,7 +54,7 @@ struct sys_file_t {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
|
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
|
||||||
return name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
return name.empty() || name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass access-pattern hints to the host.
|
// Pass access-pattern hints to the host.
|
||||||
@@ -151,46 +152,29 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
static sys_file_t* OpenHostFile(const std::filesystem::path& file_name, const char* mode,
|
||||||
SysFileWrite(&n, 4, f);
|
sys_file_cache_type_t cache_type) {
|
||||||
|
auto* ret = new sys_file_t;
|
||||||
|
ret->f = fopen(get_internal_name(file_name).c_str(), mode);
|
||||||
|
ret->type = ret->f == nullptr ? SYS_FILE_ERROR : SYS_FILE_FILE;
|
||||||
|
if (ret->f != nullptr) {
|
||||||
|
// Independent handles must observe writes and truncation immediately.
|
||||||
|
setvbuf(ret->f, nullptr, _IONBF, 0);
|
||||||
|
apply_cache_hint(ret->f, cache_type);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||||
auto* ret = new sys_file_t;
|
auto* ret = OpenHostFile(file_name, "w+", SYS_FILE_CACHE_AUTO);
|
||||||
|
|
||||||
auto real_name = get_internal_name(file_name);
|
|
||||||
auto real_name_str = real_name.string();
|
|
||||||
|
|
||||||
ret->f = fopen(real_name_str.c_str(), "w+");
|
|
||||||
|
|
||||||
if (ret->f == nullptr) {
|
if (ret->f == nullptr) {
|
||||||
printf("can't create file: %s\n", real_name_str.c_str());
|
printf("can't create file: %s\n", file_name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ret->type = SYS_FILE_FILE;
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||||
auto* ret = new sys_file_t;
|
return OpenHostFile(file_name, "r", cache_type);
|
||||||
|
|
||||||
ret->type = SYS_FILE_FILE;
|
|
||||||
|
|
||||||
auto internal_name = get_internal_name(file_name);
|
|
||||||
auto internal_name_str = internal_name.string();
|
|
||||||
|
|
||||||
FILE* f = fopen(internal_name_str.c_str(), "r");
|
|
||||||
|
|
||||||
if (f == nullptr) {
|
|
||||||
ret->type = SYS_FILE_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_cache_hint(f, cache_type);
|
|
||||||
|
|
||||||
ret->f = f;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
||||||
@@ -218,46 +202,12 @@ sys_file_t* SysFileCreate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||||
auto* ret = new sys_file_t;
|
return OpenHostFile(file_name, "r+", cache_type);
|
||||||
|
|
||||||
auto real_name = get_internal_name(file_name);
|
|
||||||
auto real_name_str = real_name.string();
|
|
||||||
|
|
||||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
|
||||||
|
|
||||||
if (f == nullptr) {
|
|
||||||
ret->type = SYS_FILE_ERROR;
|
|
||||||
} else {
|
|
||||||
ret->type = SYS_FILE_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_cache_hint(f, cache_type);
|
|
||||||
|
|
||||||
ret->f = f;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||||
sys_file_cache_type_t cache_type) {
|
sys_file_cache_type_t cache_type) {
|
||||||
auto* ret = new sys_file_t;
|
return OpenHostFile(file_name, "r+", cache_type);
|
||||||
|
|
||||||
auto real_name = get_internal_name(file_name);
|
|
||||||
auto real_name_str = real_name.string();
|
|
||||||
|
|
||||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
|
||||||
|
|
||||||
if (f == nullptr) {
|
|
||||||
ret->type = SYS_FILE_ERROR;
|
|
||||||
} else {
|
|
||||||
ret->type = SYS_FILE_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_cache_hint(f, cache_type);
|
|
||||||
|
|
||||||
ret->f = f;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SysFileClose(sys_file_t* f) {
|
void SysFileClose(sys_file_t* f) {
|
||||||
@@ -300,10 +250,21 @@ uint64_t SysFileSize(sys_file_t& f) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size) {
|
||||||
|
const auto path = get_internal_name(name);
|
||||||
|
struct stat info {};
|
||||||
|
if (stat(path.c_str(), &info) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*is_file = !S_ISDIR(info.st_mode);
|
||||||
|
*size = *is_file ? static_cast<uint64_t>(info.st_size) : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||||
sys_file_t* f = SysFileOpenR(file_name);
|
bool is_file;
|
||||||
uint64_t size = SysFileSize(*f);
|
uint64_t size = 0;
|
||||||
SysFileClose(f);
|
SysFileGetInfo(file_name, &is_file, &size);
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,29 +317,15 @@ bool SysFileIsError(sys_file_t& f) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||||
auto real_name = get_internal_name(path);
|
bool is_file;
|
||||||
auto real_name_str = real_name.string();
|
uint64_t size;
|
||||||
|
return SysFileGetInfo(path, &is_file, &size) && !is_file;
|
||||||
struct stat s {};
|
|
||||||
|
|
||||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return S_ISDIR(s.st_mode); // NOLINT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||||
auto real_name = get_internal_name(name);
|
bool is_file;
|
||||||
auto real_name_str = real_name.string();
|
uint64_t size;
|
||||||
|
return SysFileGetInfo(name, &is_file, &size) && is_file;
|
||||||
struct stat s {};
|
|
||||||
|
|
||||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !S_ISDIR(s.st_mode); // NOLINT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||||
@@ -613,51 +560,6 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively collect regular files.
|
|
||||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
|
||||||
auto real_path = get_internal_name(path);
|
|
||||||
|
|
||||||
DIR* dir = opendir(real_path.string().c_str());
|
|
||||||
if (dir == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
|
|
||||||
const std::string file_name(entry->d_name);
|
|
||||||
|
|
||||||
if (file_name == "." || file_name == "..") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto child = real_path / file_name;
|
|
||||||
struct stat s {};
|
|
||||||
|
|
||||||
// lstat, so a symlink is never followed into a cycle during the recursive walk.
|
|
||||||
if (0 != lstat(child.string().c_str(), &s)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (S_ISDIR(s.st_mode)) {
|
|
||||||
SysFileFindFiles(child, out);
|
|
||||||
} else if (S_ISREG(s.st_mode)) {
|
|
||||||
sys_file_find_t r {};
|
|
||||||
|
|
||||||
r.path_with_name = child;
|
|
||||||
r.size = static_cast<uint64_t>(s.st_size);
|
|
||||||
r.last_access_time.is_invalid = false;
|
|
||||||
r.last_access_time.time = s.st_atime;
|
|
||||||
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
|
|
||||||
r.last_write_time.is_invalid = false;
|
|
||||||
r.last_write_time.time = s.st_mtime;
|
|
||||||
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
|
|
||||||
|
|
||||||
out.push_back(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closedir(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep "." and ".." to match FindFirstFileW.
|
// Keep "." and ".." to match FindFirstFileW.
|
||||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||||
auto real_path = get_internal_name(path);
|
auto real_path = get_internal_name(path);
|
||||||
@@ -693,7 +595,7 @@ bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::pa
|
|||||||
!error;
|
!error;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||||
auto real_src = get_internal_name(src);
|
auto real_src = get_internal_name(src);
|
||||||
auto real_dst = get_internal_name(dst);
|
auto real_dst = get_internal_name(dst);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#else
|
#else
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/platform/sysVirtual.h"
|
|
||||||
#include "common/virtualMemory.h"
|
#include "common/virtualMemory.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -28,13 +27,12 @@
|
|||||||
#define KYTY_FIXED_NOREPLACE
|
#define KYTY_FIXED_NOREPLACE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Common {
|
namespace Common::VirtualMemory {
|
||||||
|
|
||||||
static pthread_mutex_t g_virtual_mutex {};
|
static pthread_mutex_t g_virtual_mutex {};
|
||||||
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
|
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
|
||||||
static std::map<uintptr_t, int>* g_protects = nullptr;
|
|
||||||
|
|
||||||
void SysVirtualInit() {
|
void Init() {
|
||||||
pthread_mutexattr_t attr {};
|
pthread_mutexattr_t attr {};
|
||||||
|
|
||||||
pthread_mutexattr_init(&attr);
|
pthread_mutexattr_init(&attr);
|
||||||
@@ -46,43 +44,27 @@ void SysVirtualInit() {
|
|||||||
pthread_mutex_init(&g_virtual_mutex, &attr);
|
pthread_mutex_init(&g_virtual_mutex, &attr);
|
||||||
pthread_mutexattr_destroy(&attr);
|
pthread_mutexattr_destroy(&attr);
|
||||||
|
|
||||||
g_allocs = new std::map<uintptr_t, size_t>;
|
g_allocs = new std::map<uintptr_t, size_t>;
|
||||||
g_protects = new std::map<uintptr_t, int>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_protection_flag(VirtualMemory::Mode mode) {
|
static int get_protection_flag(Mode mode) {
|
||||||
int protect = PROT_NONE;
|
int protect = PROT_NONE;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case VirtualMemory::Mode::Read: protect = PROT_READ; break;
|
case Mode::Read: protect = PROT_READ; break;
|
||||||
case VirtualMemory::Mode::Write:
|
case Mode::Write:
|
||||||
case VirtualMemory::Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
case Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
||||||
case VirtualMemory::Mode::Execute: protect = PROT_EXEC; break;
|
case Mode::Execute: protect = PROT_EXEC; break;
|
||||||
case VirtualMemory::Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
case Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
||||||
case VirtualMemory::Mode::ExecuteWrite:
|
case Mode::ExecuteWrite:
|
||||||
case VirtualMemory::Mode::ExecuteReadWrite:
|
case Mode::ExecuteReadWrite:
|
||||||
protect = PROT_EXEC | PROT_WRITE | PROT_READ;
|
protect = PROT_EXEC | PROT_WRITE | PROT_READ;
|
||||||
break; // NOLINT
|
break; // NOLINT
|
||||||
case VirtualMemory::Mode::NoAccess:
|
case Mode::NoAccess:
|
||||||
default: protect = PROT_NONE; break;
|
default: protect = PROT_NONE; break;
|
||||||
}
|
}
|
||||||
return protect;
|
return protect;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VirtualMemory::Mode get_protection_flag(int mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case PROT_NONE: return VirtualMemory::Mode::NoAccess;
|
|
||||||
case PROT_READ: return VirtualMemory::Mode::Read;
|
|
||||||
case PROT_WRITE: return VirtualMemory::Mode::Write;
|
|
||||||
case PROT_READ | PROT_WRITE: return VirtualMemory::Mode::ReadWrite; // NOLINT
|
|
||||||
case PROT_EXEC: return VirtualMemory::Mode::Execute;
|
|
||||||
case PROT_EXEC | PROT_WRITE: return VirtualMemory::Mode::ExecuteWrite; // NOLINT
|
|
||||||
case PROT_EXEC | PROT_READ: return VirtualMemory::Mode::ExecuteRead; // NOLINT
|
|
||||||
case PROT_EXEC | PROT_WRITE | PROT_READ:
|
|
||||||
return VirtualMemory::Mode::ExecuteReadWrite; // NOLINT
|
|
||||||
default: return VirtualMemory::Mode::NoAccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep automatic mappings inside the guest and GPU-addressable low window.
|
// Keep automatic mappings inside the guest and GPU-addressable low window.
|
||||||
#ifdef KYTY_FIXED_NOREPLACE
|
#ifdef KYTY_FIXED_NOREPLACE
|
||||||
static constexpr uintptr_t LOW_ARENA_LIMIT = 0x000000FC00000000ULL; // libc mspace window ceiling
|
static constexpr uintptr_t LOW_ARENA_LIMIT = 0x000000FC00000000ULL; // libc mspace window ceiling
|
||||||
@@ -147,7 +129,7 @@ static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags)
|
|||||||
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
|
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||||
EXIT_IF(g_allocs == nullptr);
|
EXIT_IF(g_allocs == nullptr);
|
||||||
|
|
||||||
auto addr = static_cast<uintptr_t>(address);
|
auto addr = static_cast<uintptr_t>(address);
|
||||||
@@ -161,11 +143,6 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
|
|||||||
if (ptr != MAP_FAILED) {
|
if (ptr != MAP_FAILED) {
|
||||||
pthread_mutex_lock(&g_virtual_mutex);
|
pthread_mutex_lock(&g_virtual_mutex);
|
||||||
record_alloc(ret_addr, size);
|
record_alloc(ret_addr, size);
|
||||||
uintptr_t page_start = ret_addr >> 12u;
|
|
||||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
|
||||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
|
||||||
(*g_protects)[page] = protect;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_virtual_mutex);
|
pthread_mutex_unlock(&g_virtual_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,8 +153,7 @@ static uintptr_t align_up(uintptr_t addr, uint64_t alignment) {
|
|||||||
return (addr + alignment - 1) & ~(alignment - 1);
|
return (addr + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
|
||||||
uint64_t alignment) {
|
|
||||||
if (alignment == 0) {
|
if (alignment == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -246,16 +222,11 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ptr == MAP_FAILED) {
|
if (ptr == MAP_FAILED) {
|
||||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
return AllocAligned(address, size, mode, alignment << 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&g_virtual_mutex);
|
pthread_mutex_lock(&g_virtual_mutex);
|
||||||
record_alloc(ret_addr, size);
|
record_alloc(ret_addr, size);
|
||||||
uintptr_t page_start = ret_addr >> 12u;
|
|
||||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
|
||||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
|
||||||
(*g_protects)[page] = protect;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_virtual_mutex);
|
pthread_mutex_unlock(&g_virtual_mutex);
|
||||||
|
|
||||||
return ret_addr;
|
return ret_addr;
|
||||||
@@ -308,7 +279,7 @@ static bool is_mapped(void* ptr, size_t length) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||||
EXIT_IF(g_allocs == nullptr);
|
EXIT_IF(g_allocs == nullptr);
|
||||||
|
|
||||||
auto addr = static_cast<uintptr_t>(address);
|
auto addr = static_cast<uintptr_t>(address);
|
||||||
@@ -337,11 +308,6 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
|
|||||||
if (ptr != MAP_FAILED) {
|
if (ptr != MAP_FAILED) {
|
||||||
pthread_mutex_lock(&g_virtual_mutex);
|
pthread_mutex_lock(&g_virtual_mutex);
|
||||||
record_alloc(ret_addr, size);
|
record_alloc(ret_addr, size);
|
||||||
uintptr_t page_start = ret_addr >> 12u;
|
|
||||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
|
||||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
|
||||||
(*g_protects)[page] = protect;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_virtual_mutex);
|
pthread_mutex_unlock(&g_virtual_mutex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -350,15 +316,15 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||||
return SysVirtualProtect(address, size, mode);
|
return Protect(address, size, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||||
return SysVirtualReserveAligned(address, size, 1);
|
return ReserveAligned(address, size, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||||
if (alignment == 0) {
|
if (alignment == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -427,7 +393,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ptr == MAP_FAILED) {
|
if (ptr == MAP_FAILED) {
|
||||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
return ReserveAligned(address, size, alignment << 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&g_virtual_mutex);
|
pthread_mutex_lock(&g_virtual_mutex);
|
||||||
@@ -437,7 +403,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
|||||||
return ret_addr;
|
return ret_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||||
EXIT_IF(g_allocs == nullptr);
|
EXIT_IF(g_allocs == nullptr);
|
||||||
|
|
||||||
auto addr = static_cast<uintptr_t>(address);
|
auto addr = static_cast<uintptr_t>(address);
|
||||||
@@ -473,9 +439,9 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
bool Decommit(uint64_t address, uint64_t size) {
|
||||||
// Drop physical pages while preserving the reservation.
|
// Drop physical pages while preserving the reservation.
|
||||||
if (!SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess)) {
|
if (!Protect(address, size, Mode::NoAccess)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,7 +465,7 @@ bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualFree(uint64_t address) {
|
bool Free(uint64_t address) {
|
||||||
EXIT_IF(g_allocs == nullptr);
|
EXIT_IF(g_allocs == nullptr);
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
|
|
||||||
@@ -516,21 +482,10 @@ bool SysVirtualFree(uint64_t address) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (munmap(reinterpret_cast<void*>(addr), size) == 0) {
|
return munmap(reinterpret_cast<void*>(addr), size) == 0;
|
||||||
uintptr_t page_start = addr >> 12u;
|
|
||||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
|
||||||
pthread_mutex_lock(&g_virtual_mutex);
|
|
||||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
|
||||||
g_protects->erase(page);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_virtual_mutex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
bool FreeRange(uint64_t address, uint64_t size) {
|
||||||
EXIT_IF(g_allocs == nullptr);
|
EXIT_IF(g_allocs == nullptr);
|
||||||
if (size == 0 || (address & 0xfffu) != 0 || (size & 0xfffu) != 0) {
|
if (size == 0 || (address & 0xfffu) != 0 || (size & 0xfffu) != 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -582,46 +537,22 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
|||||||
if (end < alloc_end) {
|
if (end < alloc_end) {
|
||||||
(*g_allocs)[end] = alloc_end - end;
|
(*g_allocs)[end] = alloc_end - end;
|
||||||
}
|
}
|
||||||
for (uintptr_t page = addr >> 12u; page <= (end - 1u) >> 12u; page++) {
|
|
||||||
g_protects->erase(page);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_virtual_mutex);
|
pthread_mutex_unlock(&g_virtual_mutex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
bool Protect(uint64_t address, uint64_t size, Mode mode) {
|
||||||
VirtualMemory::Mode* old_mode) {
|
const auto addr = static_cast<uintptr_t>(address);
|
||||||
auto addr = static_cast<uintptr_t>(address);
|
const auto page_start = addr >> 12u;
|
||||||
|
const auto page_end = (addr + size - 1) >> 12u;
|
||||||
pthread_mutex_lock(&g_virtual_mutex);
|
return mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
|
||||||
if (old_mode != nullptr) {
|
get_protection_flag(mode)) == 0;
|
||||||
if (auto s = g_protects->find(addr >> 12u); s != g_protects->end()) {
|
|
||||||
*old_mode = get_protection_flag(s->second);
|
|
||||||
} else {
|
|
||||||
*old_mode = VirtualMemory::Mode::NoAccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_virtual_mutex);
|
|
||||||
|
|
||||||
uintptr_t page_start = addr >> 12u;
|
|
||||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
|
||||||
if (mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
|
|
||||||
get_protection_flag(mode)) == 0) {
|
|
||||||
pthread_mutex_lock(&g_virtual_mutex);
|
|
||||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
|
||||||
(*g_protects)[page] = get_protection_flag(mode);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_virtual_mutex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualFlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
bool FlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common::VirtualMemory
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#ifndef KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
|
||||||
#define KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
|
||||||
|
|
||||||
#include "common/common.h"
|
|
||||||
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
inline uint16_t SwapByteOrder16(uint16_t value) {
|
|
||||||
uint16_t hi = value << 8u;
|
|
||||||
uint16_t lo = value >> 8u;
|
|
||||||
return hi | lo;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint32_t SwapByteOrder32(uint32_t value) {
|
|
||||||
return __builtin_bswap32(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint64_t SwapByteOrder64(uint64_t value) {
|
|
||||||
return __builtin_bswap64(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
inline void SwapByteOrder(T& x) {
|
|
||||||
if (sizeof(x) == 2) {
|
|
||||||
if (std::is_signed_v<T>) {
|
|
||||||
x = static_cast<std::make_signed_t<T>>(
|
|
||||||
SwapByteOrder16(static_cast<std::make_unsigned_t<uint16_t>>(x)));
|
|
||||||
} else {
|
|
||||||
x = SwapByteOrder16(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sizeof(x) == 4) {
|
|
||||||
if (std::is_signed_v<T>) {
|
|
||||||
x = static_cast<std::make_signed_t<T>>(
|
|
||||||
SwapByteOrder32(static_cast<std::make_unsigned_t<uint32_t>>(x)));
|
|
||||||
} else {
|
|
||||||
x = SwapByteOrder32(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sizeof(x) == 8) {
|
|
||||||
if (std::is_signed_v<T>) {
|
|
||||||
x = static_cast<std::make_signed_t<T>>(
|
|
||||||
SwapByteOrder64(static_cast<std::make_unsigned_t<uint64_t>>(x)));
|
|
||||||
} else {
|
|
||||||
x = SwapByteOrder64(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
inline void NoSwapByteOrder(T& x) {}
|
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_ */
|
|
||||||