Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c01401862 | ||
|
|
a833e1b735 | ||
|
|
1be1c6c4f9 | ||
|
|
69fdb03dc2 | ||
|
|
2efd31443f | ||
|
|
9eef2e1434 | ||
|
|
39faa62ebd | ||
|
|
7ff8aff5e6 | ||
|
|
64a554dea7 | ||
|
|
4b0282e343 | ||
|
|
ffc81377b2 | ||
|
|
0685bffb2f | ||
|
|
a5b3673a39 | ||
|
|
09753bf8ff | ||
|
|
9d8c458535 | ||
|
|
358333125d | ||
|
|
fd6b28c4b2 | ||
|
|
6cee2dc0bd | ||
|
|
a526a8b9c9 | ||
|
|
1ec69f8e7d | ||
|
|
2463e3aaf0 | ||
|
|
556538bed5 | ||
|
|
770438cee0 | ||
|
|
db12f0c5fb | ||
|
|
3f9666acbb | ||
|
|
7eb8abb279 | ||
|
|
91a5ff6cb7 | ||
|
|
95a507a8ff | ||
|
|
b2053c2861 | ||
|
|
9801fd862b | ||
|
|
78adb9293d | ||
|
|
0a72641678 | ||
|
|
99f4eba223 | ||
|
|
04bfd9070c | ||
|
|
ffa3992c1a | ||
|
|
1b70938052 | ||
|
|
914a3bb4b8 | ||
|
|
ffb6ad6cab | ||
|
|
ff5d16eefc | ||
|
|
d118afbeae | ||
|
|
a3ac6d0d32 | ||
|
|
f297c0bfe1 | ||
|
|
20bb268a48 | ||
|
|
d3768c00e9 | ||
|
|
dfcf1c0b03 | ||
|
|
de3ed905c8 | ||
|
|
0a140911ad | ||
|
|
2483573651 | ||
|
|
986721f5bc | ||
|
|
f90a8b1589 | ||
|
|
17a0f53d39 | ||
|
|
943bc19f46 | ||
|
|
5429888d4c | ||
|
|
025d1f7edf | ||
|
|
3c62269751 | ||
|
|
ceb154d9e4 | ||
|
|
26ca609236 | ||
|
|
4ebae87a1b | ||
|
|
b4209dde1b | ||
|
|
72d7da7266 | ||
|
|
ce5e57aa36 | ||
|
|
6357740fa1 | ||
|
|
be1c84f4d7 | ||
|
|
560756f34c | ||
|
|
52c80be266 | ||
|
|
22a9392d23 | ||
|
|
95bbc9c65d | ||
|
|
c5f7644751 | ||
|
|
4db225008b | ||
|
|
dc3bd68dee | ||
|
|
c1dfb309a1 | ||
|
|
ba7f9969ef | ||
|
|
7524e8f79e | ||
|
|
153dadd0b3 | ||
|
|
463a096249 | ||
|
|
597137e8b1 | ||
|
|
b2c9ec7609 | ||
|
|
99044c7c49 | ||
|
|
83f8bfd3c9 | ||
|
|
dd5bc26c64 | ||
|
|
72903bfce6 | ||
|
|
59d8256b36 | ||
|
|
9a20c6f738 | ||
|
|
67c89e0915 | ||
|
|
5d8381e0f3 | ||
|
|
e864cb20bd | ||
|
|
40aaab482e | ||
|
|
ec8b75b244 | ||
|
|
9210264d47 | ||
|
|
66e21a6245 | ||
|
|
87e49282b8 | ||
|
|
1816004357 | ||
|
|
bfc377627a | ||
|
|
4b651f2ba7 | ||
|
|
8c01d8e849 | ||
|
|
e0c5ae090c | ||
|
|
906d25568f | ||
|
|
8291cfeb04 | ||
|
|
dbc7b8b2ff | ||
|
|
269e411ef2 | ||
|
|
170c107779 | ||
|
|
8dc0d56b7e | ||
|
|
d7856753e0 | ||
|
|
bdf48dcf3b | ||
|
|
f356fa3730 | ||
|
|
07f6420273 | ||
|
|
6aaa533296 | ||
|
|
dc40478e7b | ||
|
|
56a82bcd41 | ||
|
|
bf5dd62eea | ||
|
|
227f12ee4b | ||
|
|
246d511568 | ||
|
|
ba0e93daac | ||
|
|
77a8f30758 | ||
|
|
1ffaaa96b1 | ||
|
|
706aa030ad | ||
|
|
ecc30a1f2d | ||
|
|
3688412dca | ||
|
|
45f53236e8 | ||
|
|
9dcff95e80 | ||
|
|
719c44310a | ||
|
|
7bb618d41a | ||
|
|
87e815b250 | ||
|
|
f9e14084d6 | ||
|
|
38c884e8c1 | ||
|
|
27dc7c281f | ||
|
|
79837b423d | ||
|
|
aafe014d0c | ||
|
|
a358b847c1 | ||
|
|
a951a91703 | ||
|
|
a875d1b205 | ||
|
|
f5f4e7d48a | ||
|
|
86fed10b81 | ||
|
|
949de19bb0 | ||
|
|
c513a4a13a | ||
|
|
b54057942d | ||
|
|
b6495a5b67 | ||
|
|
5b315a9ea7 | ||
|
|
5f5d6effa4 | ||
|
|
c5016d9455 | ||
|
|
753f285198 | ||
|
|
d7c1896647 | ||
|
|
8502d8a77d | ||
|
|
cb8042c5aa | ||
|
|
b46eeb7866 | ||
|
|
05694abad5 | ||
|
|
919fac13d0 | ||
|
|
a52d1ed4e2 | ||
|
|
2b37dee2b7 | ||
|
|
a3aece2762 | ||
|
|
64186404a3 | ||
|
|
11465a5e3c | ||
|
|
09a9654458 | ||
|
|
6123419437 | ||
|
|
8177969f33 | ||
|
|
6ee2ef8715 | ||
|
|
9e4234cdbe | ||
|
|
ce54aa8d14 | ||
|
|
2d00e13bf3 | ||
|
|
3bb09ed2a4 | ||
|
|
1e4ac1371d | ||
|
|
9c39062d3e | ||
|
|
93071ebdf8 | ||
|
|
3a842e1a6b | ||
|
|
da19501889 | ||
|
|
0f7d112545 | ||
|
|
ae47ac26e7 | ||
|
|
330d373dc6 | ||
|
|
7c40dbc36c | ||
|
|
0cdc5c1224 | ||
|
|
fa32940d14 | ||
|
|
2862ad5b25 | ||
|
|
aacf15fd0b | ||
|
|
10a852c2ac | ||
|
|
4a1440197f | ||
|
|
6e5167598e | ||
|
|
c571d8ed5a | ||
|
|
286481889f | ||
|
|
c07ad621f2 | ||
|
|
80932f8da1 | ||
|
|
f0e55803ba | ||
|
|
ca6a7b88ce | ||
|
|
13b291f998 | ||
|
|
2944d126b3 | ||
|
|
7b7294aa03 | ||
|
|
4644c52927 | ||
|
|
4bb4be50b9 | ||
|
|
be9e13915d | ||
|
|
68d0ba5a81 | ||
|
|
70668fbd98 | ||
|
|
ea1b032c59 | ||
|
|
3c4124dafa | ||
|
|
8cc5f5364a | ||
|
|
1ce663b5a9 | ||
|
|
66f488a17f | ||
|
|
7b7f9a70c6 | ||
|
|
6ad393775f | ||
|
|
f492027be2 | ||
|
|
8843291506 | ||
|
|
63ca9c0b80 | ||
|
|
0caf4a6aa0 | ||
|
|
c715891242 | ||
|
|
f3e01aaf1e | ||
|
|
0957f088ad | ||
|
|
4a2961cdf9 | ||
|
|
f0e462a8a2 | ||
|
|
feed075a9f | ||
|
|
18e6747dbf | ||
|
|
6b6274fa5a | ||
|
|
1a053f9e13 | ||
|
|
12ca1599cf | ||
|
|
f186b35706 | ||
|
|
992a83003d | ||
|
|
fa8216c4d9 | ||
|
|
742fe17173 | ||
|
|
0cad4672e9 | ||
|
|
cd4eab9d65 | ||
|
|
fde0b98e69 | ||
|
|
d4465faa79 | ||
|
|
e5f0ee7fc2 | ||
|
|
5f29374d5b | ||
|
|
2c730e58ea | ||
|
|
e87fb8c859 | ||
|
|
440245f616 | ||
|
|
05d25ef28c | ||
|
|
cad6aafdc5 | ||
|
|
defb2f11d3 | ||
|
|
75d7a4311d | ||
|
|
bb505a3ae8 | ||
|
|
ab8566591f | ||
|
|
b2d3ce6dfc | ||
|
|
09c993dfe0 | ||
|
|
de81300b28 | ||
|
|
ac3711ce7c | ||
|
|
8b51370ca1 | ||
|
|
e9bcc77e0b | ||
|
|
8639e0de81 | ||
|
|
7675579e79 | ||
|
|
831862a50b | ||
|
|
099de8dea6 | ||
|
|
f4bdb52922 | ||
|
|
22ef16a044 | ||
|
|
37df7f3278 | ||
|
|
33208a545a | ||
|
|
4059179e9d | ||
|
|
fe3857a171 | ||
|
|
c3e7ec8285 | ||
|
|
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 |
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
|
||||||
@@ -6,7 +6,12 @@ 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:
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
|
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
|
||||||
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
|
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
|
||||||
|
|
||||||
@@ -27,7 +32,7 @@ jobs:
|
|||||||
windows:
|
windows:
|
||||||
name: Build KytyPS5 (Windows)
|
name: Build KytyPS5 (Windows)
|
||||||
needs: metadata
|
needs: metadata
|
||||||
runs-on: windows-2022
|
runs-on: windows-2025
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -38,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
|
||||||
@@ -78,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: |
|
||||||
@@ -100,17 +105,23 @@ jobs:
|
|||||||
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
|
-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 ime_dialog_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|ime_dialog|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
|
||||||
@@ -145,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
|
||||||
@@ -156,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: |
|
||||||
@@ -180,20 +194,25 @@ jobs:
|
|||||||
-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 ime_dialog_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|ime_dialog|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
|
||||||
@@ -238,6 +257,7 @@ jobs:
|
|||||||
"$install_dir/libMoltenVK.dylib" \
|
"$install_dir/libMoltenVK.dylib" \
|
||||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||||
|
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||||
file "$binary"
|
file "$binary"
|
||||||
lipo "$binary" -verify_arch x86_64
|
lipo "$binary" -verify_arch x86_64
|
||||||
@@ -247,6 +267,7 @@ jobs:
|
|||||||
"$install_dir/libMoltenVK.dylib" \
|
"$install_dir/libMoltenVK.dylib" \
|
||||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||||
|
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||||
codesign --verify --strict "$binary"
|
codesign --verify --strict "$binary"
|
||||||
done
|
done
|
||||||
@@ -302,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
|
||||||
@@ -315,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: |
|
||||||
@@ -339,10 +364,12 @@ jobs:
|
|||||||
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
|
-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
|
||||||
@@ -361,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 ime_dialog_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|ime_dialog|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
|
||||||
@@ -378,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"
|
||||||
@@ -391,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
|
||||||
|
|
||||||
@@ -467,8 +497,9 @@ jobs:
|
|||||||
linux_dir="artifacts/KytyPS5-Linux-x86_64"
|
linux_dir="artifacts/KytyPS5-Linux-x86_64"
|
||||||
test -d "$linux_dir"
|
test -d "$linux_dir"
|
||||||
chmod a+x \
|
chmod a+x \
|
||||||
"$linux_dir/launcher" \
|
"$linux_dir/kyty_launcher" \
|
||||||
"$linux_dir/kyty_emulator"
|
"$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" .
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ _Build/vscode-clang/
|
|||||||
_Build/
|
_Build/
|
||||||
cmake-build-debug/
|
cmake-build-debug/
|
||||||
_PipelineCache/
|
_PipelineCache/
|
||||||
|
CMakeUserPresets.json
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"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",
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"externalConsole": false,
|
"externalConsole": false,
|
||||||
"linux": {
|
"linux": {
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"program": "${workspaceFolder}/_Build/vscode-clang/launcher/launcher",
|
"program": "${workspaceFolder}/_Build/vscode-clang/launcher/kyty_launcher",
|
||||||
"cwd": "${workspaceFolder}/_Build/vscode-clang/launcher",
|
"cwd": "${workspaceFolder}/_Build/vscode-clang/launcher",
|
||||||
"MIMode": "gdb",
|
"MIMode": "gdb",
|
||||||
"miDebuggerPath": "/usr/bin/gdb"
|
"miDebuggerPath": "/usr/bin/gdb"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"--game", "D:/gamesps5/PPSA07429",
|
"--game", "D:/gamesps5/PPSA07429",
|
||||||
"--printf-direction", "File",
|
"--printf-direction", "File",
|
||||||
"--graphics-debug-dump", "True"
|
"--graphics-debug-dump"
|
||||||
],
|
],
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
|
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
|
||||||
|
|||||||
@@ -2,15 +2,17 @@
|
|||||||
"cmake.sourceDirectory": "${workspaceFolder}",
|
"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,10 +2,70 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
|||||||
|
|
||||||
add_subdirectory(cpuinfo)
|
add_subdirectory(cpuinfo)
|
||||||
|
|
||||||
# The Windows guest red-zone patcher needs a decoder/encoder and a small
|
|
||||||
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
|
||||||
include(FetchContent)
|
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")
|
||||||
|
|
||||||
|
# Headsets are driven through OpenXR. Only the static loader is built; the runtime that serves it
|
||||||
|
# (for example SteamVR, Monado or Virtual Desktop) comes from the user's system.
|
||||||
|
set(DYNAMIC_LOADER OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_WITH_SYSTEM_JSONCPP OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_Declare(openxr
|
||||||
|
GIT_REPOSITORY https://github.com/KhronosGroup/OpenXR-SDK.git
|
||||||
|
GIT_TAG f2448a8797c85814aa892efc1ab8707900fbcc78 # release-1.1.63
|
||||||
|
GIT_SHALLOW FALSE)
|
||||||
|
FetchContent_MakeAvailable(openxr)
|
||||||
|
# Keep the loader's headers and library out of the installed package.
|
||||||
|
set_property(DIRECTORY "${openxr_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
|
||||||
|
# The loader uses exceptions internally.
|
||||||
|
if(NOT KYTY_CLANG_CL)
|
||||||
|
target_compile_options(openxr_loader PRIVATE -fexceptions)
|
||||||
|
endif()
|
||||||
|
install(FILES "${openxr_SOURCE_DIR}/LICENSE" DESTINATION "licenses/openxr")
|
||||||
|
|
||||||
|
# Noto Sans v2.015 (SIL Open Font License 1.1) is libFont's fallback font.
|
||||||
|
set(KYTY_NOTO_SANS_REPOSITORY "https://raw.githubusercontent.com/notofonts/notofonts.github.io")
|
||||||
|
set(KYTY_NOTO_SANS_COMMIT 42af67ccd12bc0e31ed85384bf73e39248265374)
|
||||||
|
set(KYTY_NOTO_SANS_BASE_URL "${KYTY_NOTO_SANS_REPOSITORY}/${KYTY_NOTO_SANS_COMMIT}/fonts")
|
||||||
|
set(KYTY_NOTO_SANS_DIR "${FETCHCONTENT_BASE_DIR}/noto-sans")
|
||||||
|
function(kyty_fetch_noto_sans path sha256)
|
||||||
|
get_filename_component(name "${path}" NAME)
|
||||||
|
file(DOWNLOAD "${KYTY_NOTO_SANS_BASE_URL}/${path}" "${KYTY_NOTO_SANS_DIR}/${name}"
|
||||||
|
EXPECTED_HASH SHA256=${sha256})
|
||||||
|
endfunction()
|
||||||
|
kyty_fetch_noto_sans(LICENSE f2095b08bed08b23a6fe26112fcd679a2bee3f002eef077eb05d215ed1051bd8)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Regular.ttf
|
||||||
|
478c558ea716033cd60c03438f628dfa75694dcf6b5f6d505a2f05fd2b4f3823)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Bold.ttf
|
||||||
|
1df075a380fc7cb898acf64c1f7b3b4dd780de3caa860178bf929de35817a913)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Italic.ttf
|
||||||
|
467e3f89eeca4108bb8710a2b9e0cf2281ac56d5b0609211a83776d0505eecb5)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-BoldItalic.ttf
|
||||||
|
1b602a9d6353be42c91df097a4857b69fa2696f26703d7a33b54a15d87c2622c)
|
||||||
|
install(FILES "${KYTY_NOTO_SANS_DIR}/LICENSE" DESTINATION "licenses/noto-sans")
|
||||||
|
set(KYTY_NOTO_SANS_DIR "${KYTY_NOTO_SANS_DIR}" PARENT_SCOPE)
|
||||||
|
|
||||||
|
# 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)
|
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
xbyak
|
xbyak
|
||||||
@@ -27,9 +87,57 @@ FetchContent_Declare(
|
|||||||
)
|
)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(xbyak zydis)
|
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)
|
||||||
@@ -86,6 +194,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)
|
||||||
@@ -117,6 +228,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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.12...4.3)
|
cmake_minimum_required(VERSION 3.19...4.3)
|
||||||
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
||||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
|
set(CMAKE_POLICY_VERSION_MINIMUM 3.10 CACHE STRING "Minimum policy version for bundled dependencies")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
get_property(KYTY_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
get_property(KYTY_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
@@ -12,6 +12,22 @@ endif()
|
|||||||
|
|
||||||
project(Kyty)
|
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_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||||
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
||||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
|
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
|
||||||
@@ -24,6 +40,11 @@ if (NOT (WIN32 OR LINUX OR APPLE))
|
|||||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||||
endif()
|
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 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
@@ -228,7 +249,7 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
|||||||
"${KYTY_SOURCE_DIR}/loader/*.h"
|
"${KYTY_SOURCE_DIR}/loader/*.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
|
find_program(KYTY_GLSLANG_VALIDATOR NAMES glslangValidator glslang REQUIRED)
|
||||||
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
|
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_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
||||||
set(gpu_tiler_shader_names
|
set(gpu_tiler_shader_names
|
||||||
@@ -243,7 +264,8 @@ set(gpu_tiler_shader_names
|
|||||||
depth
|
depth
|
||||||
promote_d16
|
promote_d16
|
||||||
demote_d16
|
demote_d16
|
||||||
swap_bgra16
|
color_transform
|
||||||
|
color_clear
|
||||||
)
|
)
|
||||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||||
@@ -282,27 +304,27 @@ list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
|
|||||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||||
|
|
||||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||||
set(gpu_blit_shader_sources
|
function(add_gpu_blit_shader shader_name shader_file)
|
||||||
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
|
set(shader_source "${gpu_tiler_shader_dir}/${shader_file}")
|
||||||
"${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_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
||||||
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
||||||
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT "${shader_header}"
|
OUTPUT "${shader_header}"
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os ${ARGN}
|
||||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||||
DEPENDS "${shader_source}"
|
DEPENDS "${shader_source}"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
list(APPEND gpu_blit_shader_headers "${shader_header}")
|
set(gpu_blit_shader_headers ${gpu_blit_shader_headers} "${shader_header}" PARENT_SCOPE)
|
||||||
endforeach()
|
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 ${gpu_blit_shader_headers})
|
||||||
|
|
||||||
list(APPEND kyty_emulator_src
|
list(APPEND kyty_emulator_src
|
||||||
@@ -315,35 +337,21 @@ if (KYTY_CLANG_CL)
|
|||||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
|
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 OpenXR::openxr_loader libzstd_static)
|
||||||
|
|
||||||
# Linux system libraries required by the static FFmpeg archive.
|
# Linux system libraries.
|
||||||
if(LINUX)
|
if(LINUX)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
|
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()
|
endif()
|
||||||
|
|
||||||
set(inc_headers
|
set(inc_headers
|
||||||
${KYTY_SOURCE_DIR}
|
${KYTY_SOURCE_DIR}
|
||||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
|
||||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/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}/fmt/include
|
||||||
${KYTY_THIRD_PARTY_DIR}/xxhash
|
${KYTY_THIRD_PARTY_DIR}/xxHash
|
||||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||||
${KYTY_THIRD_PARTY_DIR}/renderdoc
|
${KYTY_THIRD_PARTY_DIR}/renderdoc
|
||||||
${KYTY_THIRD_PARTY_DIR}/stb
|
${KYTY_THIRD_PARTY_DIR}/stb
|
||||||
@@ -390,13 +398,17 @@ add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.c
|
|||||||
|
|
||||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
|
"${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/host_gpu/hostMemory.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.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"
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||||
)
|
)
|
||||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(build_flags_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/BuildFlagsTests.cpp")
|
||||||
|
|
||||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||||
@@ -404,11 +416,48 @@ add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
|||||||
target_link_libraries(page_manager_tests common)
|
target_link_libraries(page_manager_tests common)
|
||||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
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
|
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
|
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
|
||||||
)
|
)
|
||||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
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(slot_vector_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/SlotVectorTests.cpp"
|
||||||
|
)
|
||||||
|
target_include_directories(slot_vector_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
|
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
|
||||||
@@ -438,7 +487,7 @@ add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
|
|||||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||||
)
|
)
|
||||||
target_link_libraries(resource_materialization_tests fmt::fmt)
|
target_link_libraries(resource_materialization_tests fmt::fmt xbyak::xbyak)
|
||||||
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
|
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||||
@@ -450,9 +499,8 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
|||||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.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/ResourceMaterialization.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
|
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/BindingLayout.cpp"
|
|
||||||
)
|
)
|
||||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
target_link_libraries(resource_tracking_tests fmt::fmt xbyak::xbyak)
|
||||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||||
@@ -463,6 +511,70 @@ add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
|||||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
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(jpeg_dec_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/JpegDecTests.cpp")
|
||||||
|
target_link_libraries(jpeg_dec_tests common fmt::fmt)
|
||||||
|
target_include_directories(jpeg_dec_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(jpeg_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/JpegEncTests.cpp")
|
||||||
|
target_link_libraries(jpeg_enc_tests common fmt::fmt)
|
||||||
|
target_include_directories(jpeg_enc_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(occlusion_queries_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/OcclusionQueriesTests.cpp")
|
||||||
|
target_link_libraries(occlusion_queries_tests common fmt::fmt Vulkan::Headers)
|
||||||
|
target_include_directories(occlusion_queries_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
|
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||||
@@ -473,6 +585,7 @@ target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
|||||||
|
|
||||||
add_kyty_full_emulator_test(kernel_file_system_tests
|
add_kyty_full_emulator_test(kernel_file_system_tests
|
||||||
"${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp")
|
"${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp")
|
||||||
|
target_link_libraries(kernel_file_system_tests ZArchive::zarchive)
|
||||||
|
|
||||||
add_executable(sync_on_address_tests EXCLUDE_FROM_ALL
|
add_executable(sync_on_address_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
|
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
|
||||||
@@ -539,7 +652,13 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_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 memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||||
|
add_test(NAME build_flags COMMAND $<TARGET_FILE:build_flags_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 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 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_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
|
||||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||||
@@ -547,9 +666,30 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_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 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_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 jpeg_dec COMMAND $<TARGET_FILE:jpeg_dec_tests>)
|
||||||
|
add_test(NAME jpeg_enc COMMAND $<TARGET_FILE:jpeg_enc_tests>)
|
||||||
|
add_test(NAME occlusion_queries COMMAND $<TARGET_FILE:occlusion_queries_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_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||||
|
add_test(NAME shader_recompiler_tessellation
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
|
||||||
|
add_test(NAME shader_recompiler_float_min_max
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only)
|
||||||
add_test(NAME shader_recompiler_alignbyte
|
add_test(NAME shader_recompiler_alignbyte
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
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
|
add_test(NAME virtual_memory_allocation
|
||||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||||
add_test(NAME guest_red_zone_patcher
|
add_test(NAME guest_red_zone_patcher
|
||||||
@@ -560,10 +700,18 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||||
add_test(NAME gpu_command_lane
|
add_test(NAME gpu_command_lane
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||||
|
add_test(NAME pm4_flip
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
||||||
|
add_test(NAME depth_bits
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-bits-only)
|
||||||
|
add_test(NAME msaa_storage
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --msaa-storage-only)
|
||||||
add_test(NAME pm4_context_state
|
add_test(NAME pm4_context_state
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||||
add_test(NAME gpu_tiler
|
add_test(NAME gpu_tiler
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
|
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
|
add_test(NAME texture_cache_layered_image
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
||||||
@@ -579,16 +727,15 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
|
||||||
add_test(NAME buffer_cache_dirty_gc
|
add_test(NAME buffer_cache_dirty_gc
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
|
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)
|
||||||
|
|
||||||
if(WIN32)
|
add_test(NAME texture_cache_image_overlap
|
||||||
# These tests still depend on the Windows multisample-depth path.
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||||
add_test(NAME texture_cache_image_overlap
|
add_test(NAME texture_cache_htile_clear
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||||
add_test(NAME texture_cache_htile_clear
|
add_test(NAME buffer_cache_ranges
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-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
|
add_custom_target(kyty_tests DEPENDS
|
||||||
ime_dialog_tests
|
ime_dialog_tests
|
||||||
@@ -597,7 +744,13 @@ if(BUILD_TESTING)
|
|||||||
image_page_table_tests
|
image_page_table_tests
|
||||||
memory_tracker_tests
|
memory_tracker_tests
|
||||||
page_manager_tests
|
page_manager_tests
|
||||||
|
build_flags_tests
|
||||||
|
archive_file_tests
|
||||||
|
trophy_system_tests
|
||||||
|
avplayer_file_tests
|
||||||
|
vdecsw_tests
|
||||||
bit_array_tests
|
bit_array_tests
|
||||||
|
lru_cache_tests
|
||||||
shader_vertex_metadata_tests
|
shader_vertex_metadata_tests
|
||||||
resource_materialization_tests
|
resource_materialization_tests
|
||||||
resource_tracking_tests
|
resource_tracking_tests
|
||||||
@@ -607,13 +760,72 @@ if(BUILD_TESTING)
|
|||||||
shader_recompiler_compute_tests
|
shader_recompiler_compute_tests
|
||||||
virtual_memory_allocation_tests
|
virtual_memory_allocation_tests
|
||||||
audio_out2_port_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
|
||||||
|
jpeg_dec_tests
|
||||||
|
jpeg_enc_tests
|
||||||
|
occlusion_queries_tests
|
||||||
|
ces_tests
|
||||||
|
save_data_dialog_tests
|
||||||
|
http_uri_parse_tests
|
||||||
)
|
)
|
||||||
endif()
|
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})
|
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
|
||||||
configure_macos_guest_address_space(kyty_emulator)
|
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)
|
||||||
|
|
||||||
|
set(KYTY_NOTO_SANS_FONTS
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Regular.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Bold.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Italic.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-BoldItalic.ttf")
|
||||||
|
add_custom_target(kyty_noto_sans_fonts
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${KYTY_NOTO_SANS_FONTS}
|
||||||
|
"$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||||
|
VERBATIM)
|
||||||
|
add_dependencies(kyty_emulator kyty_noto_sans_fonts)
|
||||||
|
install(FILES ${KYTY_NOTO_SANS_FONTS} DESTINATION assets/fonts)
|
||||||
|
|
||||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(kyty_emulator opengl32)
|
target_link_libraries(kyty_emulator opengl32)
|
||||||
@@ -649,7 +861,16 @@ if(KYTY_CLANG_CL)
|
|||||||
)
|
)
|
||||||
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)
|
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)
|
elseif(WIN32 OR LINUX)
|
||||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
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()
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
@@ -667,6 +888,10 @@ endif()
|
|||||||
install(TARGETS kyty_emulator DESTINATION .)
|
install(TARGETS kyty_emulator DESTINATION .)
|
||||||
if(KYTY_BUILD_LAUNCHER)
|
if(KYTY_BUILD_LAUNCHER)
|
||||||
add_subdirectory("${KYTY_SOURCE_DIR}/launcher" "${CMAKE_CURRENT_BINARY_DIR}/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()
|
endif()
|
||||||
if(KYTY_CLANG_CL)
|
if(KYTY_CLANG_CL)
|
||||||
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
||||||
|
|||||||
@@ -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,13 +23,13 @@ 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
|
||||||
@@ -36,20 +40,30 @@ Community game test results are available in the
|
|||||||
|
|
||||||
## 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>Cyberpunk 2077</strong><br>
|
||||||
<img src="docs/screenshots/ps5-01.png" width="300" alt="Disgaea 6 running in KytyPS5">
|
<img src="docs/screenshots/ps5-07.png" width="300" alt="Cyberpunk 2077 running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<strong>Dreaming Sarah</strong><br>
|
<strong>MLB The Show 23</strong><br>
|
||||||
<img src="docs/screenshots/ps5-03.png" width="300" alt="Dreaming Sarah running in KytyPS5">
|
<img src="docs/screenshots/ps5-08.png" width="300" alt="MLB The Show 23 running in KytyPS5">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<strong>Astro Bot</strong><br>
|
||||||
|
<img src="docs/screenshots/ps5-01.png" width="300" alt="Astro Bot running in KytyPS5">
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<strong>Saros</strong><br>
|
||||||
|
<img src="docs/screenshots/ps5-03.png" width="300" alt="Saros running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -64,12 +78,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="Hellboy 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="Paleo Pines running in KytyPS5">
|
<img src="docs/screenshots/ps5-06.jpg" width="300" alt="UFC 6 running in KytyPS5">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -79,19 +93,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
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -126,11 +152,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`.
|
||||||
|
|
||||||
@@ -159,20 +187,20 @@ 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
|
||||||
@@ -186,13 +214,87 @@ 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.
|
||||||
|
|
||||||
The CMake source root is 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
|
||||||
|
|
||||||
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
|
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
|
||||||
@@ -203,8 +305,8 @@ 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
|
||||||
@@ -220,14 +322,14 @@ 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. When the launcher is built, the install
|
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.
|
also produces `_Build/macos/install/KytyPS5.app` — double-click to launch the GUI.
|
||||||
A flat `kyty_emulator` is kept for CLI usage.
|
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 the flat `kyty_emulator`
|
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to the flat `kyty_emulator`
|
||||||
(and, for the bundle, into `KytyPS5.app/Contents/Frameworks/`) and ad-hoc sign it:
|
(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
|
||||||
@@ -253,7 +355,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:
|
||||||
@@ -273,11 +375,11 @@ 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
|
```bash
|
||||||
@@ -285,17 +387,29 @@ 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, the adjacent flat or app-bundled `libMoltenVK.dylib` is found automatically; no
|
On macOS, the adjacent flat or app-bundled `libMoltenVK.dylib` is found automatically; no
|
||||||
@@ -339,5 +453,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,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs>
|
||||||
|
<linearGradient id="body" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(256 268) scale(1.16) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
|
||||||
|
<path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z" fill="url(#body)"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z" fill="url(#body)"/>
|
||||||
|
<path d="M128 164 L137 128 L174 156 Z" fill="#0b0d12"/><path d="M384 164 L375 128 L338 156 Z" fill="#0b0d12"/>
|
||||||
|
<g fill="none" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
|
||||||
|
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)"/>
|
||||||
|
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="none" stroke="#0b0d12" stroke-width="16"/>
|
||||||
|
<g fill="none" stroke="#0b0d12" stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
|
||||||
|
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" fill="none" stroke="#0b0d12" stroke-width="7"/>
|
||||||
|
<path d="M168 230 Q194 202 220 230" fill="none" stroke="#0b0d12" stroke-width="15"/>
|
||||||
|
<circle cx="322" cy="220" r="22" fill="#0b0d12"/>
|
||||||
|
<path d="M238 252 L274 252 L256 276 Z" fill="#0b0d12" stroke="#0b0d12" stroke-width="8"/>
|
||||||
|
</g></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -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 |
|
After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.0 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; }
|
||||||
@@ -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 ${KYTY_SOURCE_DIR}
|
PUBLIC ${KYTY_SOURCE_DIR}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +40,13 @@ target_link_libraries(common
|
|||||||
cpuinfo
|
cpuinfo
|
||||||
kyty_git_version
|
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,131 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AddContentEntry> ParseAddContent(std::string_view value) {
|
||||||
|
static const std::regex label_pattern("^[A-Z0-9]{16}$");
|
||||||
|
const auto eq = value.find('=');
|
||||||
|
const auto label = value.substr(0, eq);
|
||||||
|
if (!std::regex_match(label.begin(), label.end(), label_pattern)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddContentEntry entry {{}, std::string(label), {}, {}};
|
||||||
|
if (eq != std::string_view::npos) {
|
||||||
|
entry.mount_dir = PathFromUtf8(FixFilenameSlash(value.substr(eq + 1)));
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::is_directory(entry.mount_dir, ec)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||||
|
#define KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
struct AddContentEntry {
|
||||||
|
std::string content_id;
|
||||||
|
std::string entitlement_label;
|
||||||
|
std::filesystem::path mount_dir; // Empty for an add-on without extra data.
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Parses a user add-on: "LABEL" without extra data, or "LABEL=DIR" with its data in DIR.
|
||||||
|
std::optional<AddContentEntry> ParseAddContent(std::string_view value);
|
||||||
|
|
||||||
|
} // 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,57 @@
|
|||||||
|
#ifndef KYTY_COMMON_ARCHIVE_H_
|
||||||
|
#define KYTY_COMMON_ARCHIVE_H_
|
||||||
|
|
||||||
|
#include "common/file.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
#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);
|
||||||
|
|
||||||
|
// Packs a host directory into a new ZArchive outside it. progress gets 0 < done <= total and
|
||||||
|
// returns false to cancel. A failed or cancelled pack removes the partial archive.
|
||||||
|
[[nodiscard]] bool PackArchive(const std::filesystem::path& directory,
|
||||||
|
const std::filesystem::path& archive,
|
||||||
|
const std::function<bool(uint64_t done, uint64_t total)>& progress);
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
||||||
|
#endif /* KYTY_COMMON_ARCHIVE_H_ */
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// Kept out of archive.cpp so the statically linked emulator does not pull in the writer and the
|
||||||
|
// zstd compressor.
|
||||||
|
#include "common/archive.h"
|
||||||
|
|
||||||
|
#include "common/stringUtils.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <zarchive/zarchivewriter.h>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
bool PackArchive(const std::filesystem::path& directory, const std::filesystem::path& archive,
|
||||||
|
const std::function<bool(uint64_t done, uint64_t total)>& progress) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
struct Output {
|
||||||
|
File file;
|
||||||
|
bool ok = true;
|
||||||
|
} output;
|
||||||
|
// Create the archive before walking, so a re-run started while a cancelled pack is still
|
||||||
|
// walking sees it exists instead of losing its archive to that pack's cleanup.
|
||||||
|
if (!output.file.Create(archive)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct Entry {
|
||||||
|
fs::path path;
|
||||||
|
uint64_t size;
|
||||||
|
bool is_file;
|
||||||
|
};
|
||||||
|
std::vector<Entry> entries;
|
||||||
|
uint64_t total = 0;
|
||||||
|
std::error_code error;
|
||||||
|
// Follow directory links, as the emulator does when it runs the folder.
|
||||||
|
fs::recursive_directory_iterator it(directory, fs::directory_options::follow_directory_symlink,
|
||||||
|
error);
|
||||||
|
for (; !error && it != fs::end(it); it.increment(error)) {
|
||||||
|
const bool is_file = !it->is_directory(error);
|
||||||
|
const auto size = is_file && !error ? it->file_size(error) : 0;
|
||||||
|
if (error) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
entries.push_back({it->path(), size, is_file});
|
||||||
|
total += size;
|
||||||
|
}
|
||||||
|
output.ok = !error;
|
||||||
|
ZArchiveWriter writer(
|
||||||
|
[](int32_t, void*) {},
|
||||||
|
[](const void* data, size_t size, void* opaque) {
|
||||||
|
auto& out = *static_cast<Output*>(opaque);
|
||||||
|
uint32_t written = 0;
|
||||||
|
out.file.Write(data, static_cast<uint32_t>(size), &written);
|
||||||
|
out.ok = out.ok && written == size;
|
||||||
|
},
|
||||||
|
&output);
|
||||||
|
std::vector<uint8_t> buffer(1u << 20u);
|
||||||
|
uint64_t done = 0;
|
||||||
|
for (size_t index = 0; output.ok && index < entries.size(); index++) {
|
||||||
|
const auto& entry = entries[index];
|
||||||
|
const auto name = PathToGenericString(entry.path.lexically_relative(directory));
|
||||||
|
File input;
|
||||||
|
output.ok = entry.is_file ? writer.StartNewFile(name.c_str()) &&
|
||||||
|
input.Open(entry.path, File::Mode::Read)
|
||||||
|
: writer.MakeDir(name.c_str());
|
||||||
|
for (uint64_t left = entry.size; output.ok && left != 0;) {
|
||||||
|
uint32_t read = 0;
|
||||||
|
input.Read(buffer.data(), static_cast<uint32_t>(std::min<uint64_t>(left, buffer.size())),
|
||||||
|
&read);
|
||||||
|
writer.AppendData(buffer.data(), read);
|
||||||
|
done += read;
|
||||||
|
left -= read;
|
||||||
|
output.ok = output.ok && read != 0 && progress(done, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (output.ok) {
|
||||||
|
writer.Finalize();
|
||||||
|
}
|
||||||
|
output.ok = output.ok && output.file.Flush();
|
||||||
|
output.file.Close();
|
||||||
|
if (!output.ok) {
|
||||||
|
File::DeleteFile(archive);
|
||||||
|
}
|
||||||
|
return output.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#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 "kytyGitVersion.h"
|
||||||
@@ -12,25 +11,10 @@
|
|||||||
|
|
||||||
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 = "--- Build ---\n" KYTY_BUILD_LABEL "\n--- 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) {
|
||||||
|
|||||||
@@ -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,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,120 @@
|
|||||||
|
#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,
|
||||||
|
ColorMetadataReadbacks, // CPU blocked reading back GPU-written DCC/CMASK metadata.
|
||||||
|
ColorMetadataReadbackNs,
|
||||||
|
ColorMetadataChecks, // GPU clear-key checks of DCC/CMASK metadata.
|
||||||
|
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>
|
||||||
@@ -16,6 +17,7 @@ void Initialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown() {
|
void Shutdown() {
|
||||||
|
Common::DebugCounters::SetEnabled(false);
|
||||||
g_config.reset();
|
g_config.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +25,11 @@ 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(cfg.user_name.empty() || cfg.user_name.size() > MAX_USER_NAME_LENGTH);
|
||||||
EXIT_IF(!IsConfiguredUserIdValid(cfg.user_id));
|
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() {
|
||||||
@@ -43,6 +48,43 @@ int32_t GetUserId() {
|
|||||||
return g_config->user_id;
|
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() {
|
PresentMode GetPresentMode() {
|
||||||
return g_config->present_mode;
|
return g_config->present_mode;
|
||||||
}
|
}
|
||||||
@@ -55,6 +97,26 @@ bool FullscreenEnabled() {
|
|||||||
return g_config->fullscreen_enabled;
|
return g_config->fullscreen_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HdrEnabled() {
|
||||||
|
return g_config->hdr_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HideCursorEnabled() {
|
||||||
|
return g_config->hide_cursor_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StretchEnabled() {
|
||||||
|
return g_config->stretch_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);
|
||||||
}
|
}
|
||||||
@@ -67,15 +129,23 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShaderCacheEnabled() {
|
||||||
|
return g_config->shader_cache_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
ShaderOptimizationType GetShaderOptimizationType() {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +165,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +173,8 @@ 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() {
|
||||||
@@ -123,10 +193,38 @@ bool ReadbackLinearImagesEnabled() {
|
|||||||
return g_config->readback_linear_images;
|
return g_config->readback_linear_images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ReadbackTiledImagesEnabled() {
|
||||||
|
return g_config->readback_tiled_images;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SyncRawImageBuffersEnabled() {
|
||||||
|
return g_config->sync_raw_image_buffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TessellationEnabled() {
|
||||||
|
return g_config->tessellation_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OcclusionQueriesEnabled() {
|
||||||
|
return g_config->occlusion_queries_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrophyEnabled() {
|
||||||
|
return g_config->trophy_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetTrophyDurationSeconds() {
|
||||||
|
return std::clamp(g_config->trophy_duration_seconds, 5u, 10u);
|
||||||
|
}
|
||||||
|
|
||||||
bool PlayGoHackEnabled() {
|
bool PlayGoHackEnabled() {
|
||||||
return g_config->playgo_hack_enabled;
|
return g_config->playgo_hack_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SkipNoticeScreen() {
|
||||||
|
return g_config->skip_notice_screen;
|
||||||
|
}
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
bool RedZoneProtectionEnabled() {
|
bool RedZoneProtectionEnabled() {
|
||||||
return g_config->red_zone_protection_enabled;
|
return g_config->red_zone_protection_enabled;
|
||||||
@@ -137,4 +235,8 @@ const Keymap& GetKeymap() {
|
|||||||
return g_config->keymap;
|
return g_config->keymap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& GetGuestArguments() {
|
||||||
|
return g_config->guest_arguments;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Config
|
} // namespace Config
|
||||||
@@ -4,7 +4,10 @@
|
|||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -21,25 +24,28 @@ struct Lifecycle {
|
|||||||
|
|
||||||
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 OutputDirection { Silent, Console, File };
|
|
||||||
|
|
||||||
enum class PresentMode { Fifo, Mailbox, Immediate };
|
enum class PresentMode { Fifo, Mailbox, Immediate };
|
||||||
|
|
||||||
using Keymap = std::vector<std::string>;
|
using Keymap = std::vector<std::string>;
|
||||||
|
using ControllerColor = std::array<uint8_t, 3>;
|
||||||
|
|
||||||
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||||
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||||
constexpr std::size_t MAX_USER_NAME_LENGTH = 16;
|
constexpr std::size_t MAX_USER_NAME_LENGTH = 16;
|
||||||
constexpr int32_t DEFAULT_USER_ID = 1000;
|
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 bool IsConfiguredUserIdValid(int32_t user_id) {
|
||||||
constexpr int32_t USER_ID_EVERYONE = 0xfe;
|
constexpr int32_t USER_ID_EVERYONE = 0xfe;
|
||||||
constexpr int32_t USER_ID_SYSTEM = 0xff;
|
constexpr int32_t USER_ID_SYSTEM = 0xff;
|
||||||
return user_id >= 0 && user_id != USER_ID_EVERYONE && user_id != USER_ID_SYSTEM;
|
return user_id >= 0 && user_id <= MAX_USER_ID && user_id != USER_ID_EVERYONE &&
|
||||||
|
user_id != USER_ID_SYSTEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConfigOptions {
|
struct ConfigOptions {
|
||||||
@@ -47,31 +53,55 @@ struct ConfigOptions {
|
|||||||
uint32_t screen_height = 720;
|
uint32_t screen_height = 720;
|
||||||
std::string user_name = "Kyty";
|
std::string user_name = "Kyty";
|
||||||
int32_t user_id = DEFAULT_USER_ID;
|
int32_t user_id = DEFAULT_USER_ID;
|
||||||
PresentMode present_mode = PresentMode::Fifo;
|
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;
|
int32_t gpu_index = -1;
|
||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
|
bool hdr_enabled = false;
|
||||||
|
bool hide_cursor_enabled = false;
|
||||||
|
bool stretch_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;
|
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;
|
||||||
|
bool shader_cache_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::Silent;
|
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 gpu_assisted_validation_enabled = false;
|
||||||
bool renderdoc_enabled = false;
|
bool renderdoc_enabled = false;
|
||||||
bool readback_linear_images = false;
|
bool readback_linear_images = false;
|
||||||
|
bool readback_tiled_images = false;
|
||||||
|
bool sync_raw_image_buffers = false;
|
||||||
|
bool tessellation_enabled = false;
|
||||||
|
bool occlusion_queries_enabled = false;
|
||||||
|
bool trophy_enabled = true;
|
||||||
|
uint32_t trophy_duration_seconds = 6;
|
||||||
|
bool perf_overlay_enabled = false;
|
||||||
bool playgo_hack_enabled = false;
|
bool playgo_hack_enabled = false;
|
||||||
|
bool skip_notice_screen = false;
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
bool red_zone_protection_enabled = false;
|
bool red_zone_protection_enabled = false;
|
||||||
#endif
|
#endif
|
||||||
Keymap keymap;
|
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);
|
||||||
@@ -80,16 +110,34 @@ uint32_t GetScreenWidth();
|
|||||||
uint32_t GetScreenHeight();
|
uint32_t GetScreenHeight();
|
||||||
const std::string& GetUserName();
|
const std::string& GetUserName();
|
||||||
int32_t GetUserId();
|
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();
|
PresentMode GetPresentMode();
|
||||||
int32_t GetGpuIndex();
|
int32_t GetGpuIndex();
|
||||||
bool FullscreenEnabled();
|
bool FullscreenEnabled();
|
||||||
|
bool HdrEnabled();
|
||||||
|
bool HideCursorEnabled();
|
||||||
|
bool StretchEnabled();
|
||||||
|
bool VrEnabled();
|
||||||
|
bool AmdCpuEnabled();
|
||||||
|
bool IsNetworkConnected();
|
||||||
uint32_t GetVblankFrequency();
|
uint32_t GetVblankFrequency();
|
||||||
uint32_t GetConsoleLanguage();
|
uint32_t GetConsoleLanguage();
|
||||||
bool VulkanValidationEnabled();
|
bool VulkanValidationEnabled();
|
||||||
|
|
||||||
bool ShaderValidationEnabled();
|
bool ShaderValidationEnabled();
|
||||||
|
bool ShaderCacheEnabled();
|
||||||
ShaderOptimizationType GetShaderOptimizationType();
|
ShaderOptimizationType GetShaderOptimizationType();
|
||||||
ShaderLogDirection GetShaderLogDirection();
|
LogDirection GetShaderLogDirection();
|
||||||
std::filesystem::path GetShaderLogFolder();
|
std::filesystem::path GetShaderLogFolder();
|
||||||
|
|
||||||
bool CommandBufferDumpEnabled();
|
bool CommandBufferDumpEnabled();
|
||||||
@@ -97,10 +145,10 @@ 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();
|
||||||
|
|
||||||
@@ -108,12 +156,20 @@ bool GpuAssistedValidationEnabled();
|
|||||||
|
|
||||||
bool RenderDocEnabled();
|
bool RenderDocEnabled();
|
||||||
bool ReadbackLinearImagesEnabled();
|
bool ReadbackLinearImagesEnabled();
|
||||||
|
bool ReadbackTiledImagesEnabled();
|
||||||
|
bool SyncRawImageBuffersEnabled();
|
||||||
|
bool TessellationEnabled();
|
||||||
|
bool OcclusionQueriesEnabled();
|
||||||
|
bool TrophyEnabled();
|
||||||
|
uint32_t GetTrophyDurationSeconds();
|
||||||
bool PlayGoHackEnabled();
|
bool PlayGoHackEnabled();
|
||||||
|
bool SkipNoticeScreen();
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
bool RedZoneProtectionEnabled();
|
bool RedZoneProtectionEnabled();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const Keymap& GetKeymap();
|
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"
|
||||||
@@ -61,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>()) {
|
||||||
@@ -85,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;
|
||||||
|
|
||||||
@@ -104,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;
|
||||||
@@ -124,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);
|
||||||
|
|
||||||
@@ -136,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();
|
||||||
|
|
||||||
@@ -154,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;
|
||||||
@@ -161,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;
|
||||||
@@ -171,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);
|
||||||
@@ -213,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);
|
||||||
}
|
}
|
||||||
@@ -249,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);
|
||||||
|
|
||||||
@@ -353,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);
|
||||||
|
|
||||||
@@ -384,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);
|
||||||
|
|
||||||
@@ -398,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 {};
|
||||||
@@ -424,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();
|
||||||
@@ -454,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);
|
||||||
@@ -523,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;
|
||||||
@@ -538,12 +577,18 @@ 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::RenameFile(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")
|
||||||
}
|
}
|
||||||
@@ -552,6 +597,9 @@ bool File::RenameFile(const std::filesystem::path& src,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
|
|
||||||
@@ -30,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);
|
||||||
@@ -51,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();
|
||||||
@@ -73,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);
|
||||||
@@ -101,7 +96,6 @@ 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 RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
static bool RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||||
|
|||||||
@@ -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_ */
|
||||||
@@ -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);
|
||||||
@@ -152,9 +160,9 @@ void WriteFatal(fmt::text_style style, std::string_view text) {
|
|||||||
void Initialize() {
|
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 {});
|
||||||
|
|||||||
@@ -21,10 +21,15 @@ struct Lifecycle {
|
|||||||
|
|
||||||
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();
|
||||||
@@ -46,20 +51,26 @@ inline constexpr auto BrightWhite = fmt::fg(fmt::terminal_color::bright_white)
|
|||||||
|
|
||||||
} // namespace Color
|
} // namespace Color
|
||||||
|
|
||||||
|
// Out of line, so callers on small guest stacks (fibers) do not reserve the formatting frame.
|
||||||
|
template <typename... Args>
|
||||||
|
[[gnu::noinline]] void Printf(fmt::text_style style, const char* format, const Args&... args) {
|
||||||
|
Write(style, fmt::sprintf(format, args...));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Log
|
} // namespace Log
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||||
#define LOGF(...) \
|
#define LOGF(...) \
|
||||||
do { \
|
do { \
|
||||||
if (!::Log::IsSilent()) { \
|
if (!::Log::IsSilent()) { \
|
||||||
::Log::Write(::fmt::sprintf(__VA_ARGS__)); \
|
::Log::Printf({}, __VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||||
#define LOGF_COLOR(style, ...) \
|
#define LOGF_COLOR(style, ...) \
|
||||||
do { \
|
do { \
|
||||||
if (!::Log::IsSilent()) { \
|
if (!::Log::IsSilent()) { \
|
||||||
::Log::Write((style), ::fmt::sprintf(__VA_ARGS__)); \
|
::Log::Printf((style), __VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
|
|||||||
@@ -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,8 +68,6 @@ 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 SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -420,7 +367,7 @@ bool SysFileDeleteFile(const std::filesystem::path& name) {
|
|||||||
|
|
||||||
bool SysFileFlush(sys_file_t& f) {
|
bool SysFileFlush(sys_file_t& f) {
|
||||||
if (f.type == SYS_FILE_FILE && f.f != nullptr) {
|
if (f.type == SYS_FILE_FILE && f.f != nullptr) {
|
||||||
return (fflush(f.f) == 0);
|
return fflush(f.f) == 0 && fsync(fileno(f.f)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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_ */
|
|
||||||
@@ -151,10 +151,6 @@ inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
|
|||||||
SysFileToSystemTimeUtc(ft, s);
|
SysFileToSystemTimeUtc(ft, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline time_t SysTimegm(struct tm* tm) {
|
|
||||||
return timegm(tm);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
|
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
|
||||||
struct tm i {};
|
struct tm i {};
|
||||||
|
|
||||||
@@ -165,7 +161,7 @@ inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t)
|
|||||||
i.tm_min = f.Minute;
|
i.tm_min = f.Minute;
|
||||||
i.tm_sec = f.Second;
|
i.tm_sec = f.Second;
|
||||||
|
|
||||||
t.is_invalid = (f.is_invalid || (t.time = SysTimegm(&i)) == static_cast<time_t>(-1));
|
t.is_invalid = (f.is_invalid || (t.time = ::timegm(&i)) == static_cast<time_t>(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves the current local date and time.
|
// Retrieves the current local date and time.
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
#ifndef KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
|
||||||
#define KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
|
||||||
|
|
||||||
#include "common/common.h"
|
|
||||||
#include "common/virtualMemory.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
void SysVirtualInit();
|
|
||||||
|
|
||||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
|
||||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
|
||||||
uint64_t alignment);
|
|
||||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
|
||||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
|
||||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size);
|
|
||||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment);
|
|
||||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size);
|
|
||||||
bool SysVirtualDecommit(uint64_t address, uint64_t size);
|
|
||||||
bool SysVirtualFree(uint64_t address);
|
|
||||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size);
|
|
||||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
|
||||||
VirtualMemory::Mode* old_mode = nullptr);
|
|
||||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size);
|
|
||||||
|
|
||||||
} // namespace Common
|
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_ */
|
|
||||||
@@ -15,18 +15,6 @@
|
|||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/platform/sysDbg.h"
|
#include "common/platform/sysDbg.h"
|
||||||
|
|
||||||
void SysStackWalk(void** stack, int* depth) {
|
|
||||||
*depth = static_cast<int>(CaptureStackBackTrace(0, static_cast<DWORD>(*depth), stack, nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
|
|
||||||
printf("stack: (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64
|
|
||||||
", %" PRIu64 ")\n",
|
|
||||||
static_cast<uint64_t>(stack.reserved_addr), static_cast<uint64_t>(stack.reserved_size),
|
|
||||||
static_cast<uint64_t>(stack.guard_addr), static_cast<uint64_t>(stack.guard_size),
|
|
||||||
static_cast<uint64_t>(stack.commited_addr), static_cast<uint64_t>(stack.commited_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||||
MEMORY_BASIC_INFORMATION mbi {};
|
MEMORY_BASIC_INFORMATION mbi {};
|
||||||
[[maybe_unused]] size_t ss = VirtualQuery(&mbi, &mbi, sizeof(mbi));
|
[[maybe_unused]] size_t ss = VirtualQuery(&mbi, &mbi, sizeof(mbi));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "common/stringUtils.h"
|
#include "common/stringUtils.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
@@ -56,10 +57,10 @@ static DWORD GetCacheAccessType(sys_file_cache_type_t t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (t == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
|
if (t == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
|
||||||
return SYS_FILE_CACHE_SEQUENTIAL_SCAN;
|
return FILE_FLAG_SEQUENTIAL_SCAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SYS_FILE_CACHE_AUTO;
|
return FILE_ATTRIBUTE_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
||||||
@@ -135,10 +136,6 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
|
||||||
SysFileWrite(&n, 4, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = new sys_file_t;
|
||||||
|
|
||||||
@@ -269,19 +266,27 @@ uint64_t SysFileSize(sys_file_t& f) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size) {
|
||||||
LARGE_INTEGER s;
|
const bool directory_path = !name.empty() && !name.has_filename() && name != name.root_path();
|
||||||
WIN32_FILE_ATTRIBUTE_DATA a;
|
WIN32_FILE_ATTRIBUTE_DATA info {};
|
||||||
|
if (GetFileAttributesExW(directory_path ? name.parent_path().c_str() : name.c_str(),
|
||||||
auto wide = file_name.wstring();
|
GetFileExInfoStandard, &info) == 0) {
|
||||||
if (GetFileAttributesExW(wide.c_str(), GetFileExInfoStandard, &a) == 0) {
|
return false;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
const bool file = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
||||||
|
if (directory_path && file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*is_file = file;
|
||||||
|
*size = *is_file ? ((static_cast<uint64_t>(info.nFileSizeHigh) << 32u) | info.nFileSizeLow) : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
s.HighPart = static_cast<LONG>(a.nFileSizeHigh);
|
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||||
s.LowPart = a.nFileSizeLow;
|
bool is_file;
|
||||||
|
uint64_t size = 0;
|
||||||
return s.QuadPart;
|
SysFileGetInfo(file_name, &is_file, &size);
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
|
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
|
||||||
@@ -349,17 +354,15 @@ bool SysFileIsError(sys_file_t& f) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||||
auto wide = path.wstring();
|
bool is_file;
|
||||||
DWORD a = GetFileAttributesW(wide.c_str());
|
uint64_t size;
|
||||||
return a != INVALID_FILE_ATTRIBUTES &&
|
return SysFileGetInfo(path, &is_file, &size) && !is_file;
|
||||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||||
auto wide = name.wstring();
|
bool is_file;
|
||||||
DWORD a = GetFileAttributesW(wide.c_str());
|
uint64_t size;
|
||||||
return a != INVALID_FILE_ATTRIBUTES &&
|
return SysFileGetInfo(name, &is_file, &size) && is_file;
|
||||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||||
@@ -479,66 +482,11 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
|
||||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
|
||||||
if (!Common::EndsWith(real_path, "/")) {
|
|
||||||
real_path += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string pattern = real_path + "*";
|
|
||||||
|
|
||||||
HANDLE h = nullptr;
|
|
||||||
WIN32_FIND_DATAW data;
|
|
||||||
|
|
||||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
|
||||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
|
||||||
|
|
||||||
if (h == INVALID_HANDLE_VALUE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
std::filesystem::path file_name(data.cFileName);
|
|
||||||
auto file_name_str = Common::PathToString(file_name);
|
|
||||||
|
|
||||||
if (file_name_str == "." || file_name_str == "..") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
|
|
||||||
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
|
|
||||||
} else {
|
|
||||||
sys_file_find_t r {};
|
|
||||||
|
|
||||||
r.path_with_name = std::filesystem::path(real_path) / file_name;
|
|
||||||
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
|
|
||||||
static_cast<uint64_t>(data.nFileSizeLow);
|
|
||||||
r.last_access_time.is_invalid = false;
|
|
||||||
r.last_access_time.time = data.ftLastAccessTime;
|
|
||||||
r.last_write_time.is_invalid = false;
|
|
||||||
r.last_write_time.time = data.ftLastWriteTime;
|
|
||||||
|
|
||||||
out.push_back(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
} while (FindNextFileW(h, &data) != 0);
|
|
||||||
|
|
||||||
FindClose(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
const auto pattern = path / L"*";
|
||||||
if (!Common::EndsWith(real_path, "/")) {
|
|
||||||
real_path += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string pattern = real_path + "*";
|
WIN32_FIND_DATAW data {};
|
||||||
|
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
|
||||||
HANDLE h = nullptr;
|
|
||||||
WIN32_FIND_DATAW data;
|
|
||||||
|
|
||||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
|
||||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
|
||||||
|
|
||||||
if (h == INVALID_HANDLE_VALUE) {
|
if (h == INVALID_HANDLE_VALUE) {
|
||||||
return;
|
return;
|
||||||
@@ -552,7 +500,7 @@ void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entr
|
|||||||
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||||
r.name = Common::PathToString(file_name);
|
r.name = Common::PathToString(file_name);
|
||||||
|
|
||||||
out.push_back(r);
|
out.push_back(std::move(r));
|
||||||
|
|
||||||
} while (FindNextFileW(h, &data) != 0);
|
} while (FindNextFileW(h, &data) != 0);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include <windows.h> // IWYU pragma: keep
|
#include <windows.h> // IWYU pragma: keep
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/platform/sysVirtual.h"
|
|
||||||
#include "common/virtualMemory.h"
|
#include "common/virtualMemory.h"
|
||||||
|
|
||||||
// IWYU pragma: no_include <basetsd.h>
|
// IWYU pragma: no_include <basetsd.h>
|
||||||
@@ -22,45 +21,33 @@
|
|||||||
// IWYU pragma: no_include <winerror.h>
|
// IWYU pragma: no_include <winerror.h>
|
||||||
// IWYU pragma: no_include <wtypes.h>
|
// IWYU pragma: no_include <wtypes.h>
|
||||||
|
|
||||||
namespace Common {
|
namespace Common::VirtualMemory {
|
||||||
|
|
||||||
static DWORD GetProtectionFlag(VirtualMemory::Mode mode) {
|
static DWORD GetProtectionFlag(Mode mode) {
|
||||||
DWORD protect = PAGE_NOACCESS;
|
DWORD protect = PAGE_NOACCESS;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case VirtualMemory::Mode::Read: protect = PAGE_READONLY; break;
|
case Mode::Read: protect = PAGE_READONLY; break;
|
||||||
|
|
||||||
case VirtualMemory::Mode::Write:
|
case Mode::Write:
|
||||||
case VirtualMemory::Mode::ReadWrite: protect = PAGE_READWRITE; break;
|
case Mode::ReadWrite: protect = PAGE_READWRITE; break;
|
||||||
|
|
||||||
case VirtualMemory::Mode::Execute: protect = PAGE_EXECUTE; break;
|
case Mode::Execute: protect = PAGE_EXECUTE; break;
|
||||||
|
|
||||||
case VirtualMemory::Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
|
case Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
|
||||||
|
|
||||||
case VirtualMemory::Mode::ExecuteWrite:
|
case Mode::ExecuteWrite:
|
||||||
case VirtualMemory::Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
|
case Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
|
||||||
|
|
||||||
case VirtualMemory::Mode::NoAccess:
|
case Mode::NoAccess:
|
||||||
default: protect = PAGE_NOACCESS; break;
|
default: protect = PAGE_NOACCESS; break;
|
||||||
}
|
}
|
||||||
return protect;
|
return protect;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VirtualMemory::Mode GetProtectionFlag(DWORD mode) {
|
void Init() {}
|
||||||
switch (mode) {
|
|
||||||
case PAGE_NOACCESS: return VirtualMemory::Mode::NoAccess;
|
|
||||||
case PAGE_READONLY: return VirtualMemory::Mode::Read;
|
|
||||||
case PAGE_READWRITE: return VirtualMemory::Mode::ReadWrite;
|
|
||||||
case PAGE_EXECUTE: return VirtualMemory::Mode::Execute;
|
|
||||||
case PAGE_EXECUTE_READ: return VirtualMemory::Mode::ExecuteRead;
|
|
||||||
case PAGE_EXECUTE_READWRITE: return VirtualMemory::Mode::ExecuteReadWrite;
|
|
||||||
default: return VirtualMemory::Mode::NoAccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SysVirtualInit() {}
|
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||||
|
auto ptr = (address == 0 ? AllocAligned(address, size, mode, 1)
|
||||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
|
||||||
auto ptr = (address == 0 ? SysVirtualAllocAligned(address, size, mode, 1)
|
|
||||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||||
@@ -71,7 +58,7 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
|
|||||||
if (err != ERROR_INVALID_ADDRESS) {
|
if (err != ERROR_INVALID_ADDRESS) {
|
||||||
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
|
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
|
||||||
} else {
|
} else {
|
||||||
return SysVirtualAllocAligned(address, size, mode, 1);
|
return AllocAligned(address, size, mode, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ptr;
|
return ptr;
|
||||||
@@ -92,8 +79,7 @@ static uint64_t AlignUp(uint64_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) {
|
||||||
printf("VirtualAlloc2 failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
printf("VirtualAlloc2 failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||||
return 0;
|
return 0;
|
||||||
@@ -147,13 +133,13 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
|||||||
printf("VirtualAlloc2(alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32 "\n",
|
printf("VirtualAlloc2(alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32 "\n",
|
||||||
alignment, err);
|
alignment, err);
|
||||||
} else {
|
} else {
|
||||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
return AllocAligned(address, size, mode, alignment << 1u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||||
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(
|
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), GetProtectionFlag(mode)));
|
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), GetProtectionFlag(mode)));
|
||||||
@@ -173,7 +159,7 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||||
auto ptr = reinterpret_cast<uintptr_t>(
|
auto ptr = reinterpret_cast<uintptr_t>(
|
||||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||||
static_cast<DWORD>(MEM_COMMIT), GetProtectionFlag(mode)));
|
static_cast<DWORD>(MEM_COMMIT), GetProtectionFlag(mode)));
|
||||||
@@ -191,8 +177,8 @@ bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||||
auto ptr = (address == 0 ? SysVirtualReserveAligned(address, size, 1)
|
auto ptr = (address == 0 ? ReserveAligned(address, size, 1)
|
||||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS)));
|
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS)));
|
||||||
@@ -202,13 +188,13 @@ uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
|||||||
if (err != ERROR_INVALID_ADDRESS) {
|
if (err != ERROR_INVALID_ADDRESS) {
|
||||||
printf("VirtualAlloc(MEM_RESERVE) failed: 0x%08" PRIx32 "\n", err);
|
printf("VirtualAlloc(MEM_RESERVE) failed: 0x%08" PRIx32 "\n", err);
|
||||||
} else {
|
} else {
|
||||||
return SysVirtualReserveAligned(address, size, 1);
|
return ReserveAligned(address, size, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
printf("VirtualAlloc2(MEM_RESERVE) failed: 0x%08" PRIx32 "\n",
|
printf("VirtualAlloc2(MEM_RESERVE) failed: 0x%08" PRIx32 "\n",
|
||||||
static_cast<uint32_t>(GetLastError()));
|
static_cast<uint32_t>(GetLastError()));
|
||||||
@@ -262,13 +248,13 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
|||||||
"\n",
|
"\n",
|
||||||
alignment, err);
|
alignment, err);
|
||||||
} else {
|
} else {
|
||||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
return ReserveAligned(address, size, alignment << 1u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||||
auto ptr = reinterpret_cast<uintptr_t>(
|
auto ptr = reinterpret_cast<uintptr_t>(
|
||||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS));
|
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS));
|
||||||
@@ -288,7 +274,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
bool Decommit(uint64_t address, uint64_t size) {
|
||||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||||
MEM_DECOMMIT) == 0) {
|
MEM_DECOMMIT) == 0) {
|
||||||
printf("VirtualFree(MEM_DECOMMIT) failed: 0x%08" PRIx32 "\n",
|
printf("VirtualFree(MEM_DECOMMIT) failed: 0x%08" PRIx32 "\n",
|
||||||
@@ -298,7 +284,7 @@ bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualFree(uint64_t address) {
|
bool Free(uint64_t address) {
|
||||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), 0, MEM_RELEASE) ==
|
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), 0, MEM_RELEASE) ==
|
||||||
0) {
|
0) {
|
||||||
printf("VirtualFree() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
printf("VirtualFree() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||||
@@ -307,7 +293,7 @@ bool SysVirtualFree(uint64_t address) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
bool FreeRange(uint64_t address, uint64_t size) {
|
||||||
if (address == 0 || size == 0) {
|
if (address == 0 || size == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -325,24 +311,20 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
|||||||
}
|
}
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
return current - address == size && SysVirtualFree(address);
|
return current - address == size && Free(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
DWORD old_protect = 0;
|
DWORD old_protect = 0;
|
||||||
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||||
GetProtectionFlag(mode), &old_protect) == 0) {
|
GetProtectionFlag(mode), &old_protect) == 0) {
|
||||||
printf("VirtualProtect() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
printf("VirtualProtect() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (old_mode != nullptr) {
|
|
||||||
*old_mode = GetProtectionFlag(old_protect);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
|
bool FlushInstructionCache(uint64_t address, uint64_t size) {
|
||||||
if (::FlushInstructionCache(GetCurrentProcess(),
|
if (::FlushInstructionCache(GetCurrentProcess(),
|
||||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)),
|
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)),
|
||||||
size) == 0) {
|
size) == 0) {
|
||||||
@@ -353,6 +335,6 @@ bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common::VirtualMemory
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -2,52 +2,13 @@
|
|||||||
|
|
||||||
#include "common/emulatorConfig.h"
|
#include "common/emulatorConfig.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <common/TracyProtocol.hpp>
|
#include <common/TracyProtocol.hpp>
|
||||||
#include <common/TracyVersion.hpp>
|
#include <common/TracyVersion.hpp>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
thread_local std::vector<Profiler::ScopedBlock*> g_block_stack;
|
|
||||||
|
|
||||||
void RemoveBlock(Profiler::ScopedBlock* block) {
|
|
||||||
auto block_it = std::find(g_block_stack.rbegin(), g_block_stack.rend(), block);
|
|
||||||
if (block_it != g_block_stack.rend()) {
|
|
||||||
g_block_stack.erase(std::next(block_it).base());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Profiler {
|
namespace Profiler {
|
||||||
|
|
||||||
ScopedBlock::ScopedBlock(const tracy::SourceLocationData* source_location) {
|
|
||||||
if (tracy::ProfilerAvailable()) {
|
|
||||||
m_zone.emplace(source_location, TRACY_CALLSTACK, true);
|
|
||||||
g_block_stack.push_back(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedBlock::~ScopedBlock() {
|
|
||||||
End();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScopedBlock::End() {
|
|
||||||
if (m_zone.has_value()) {
|
|
||||||
m_zone.reset();
|
|
||||||
RemoveBlock(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndBlock() {
|
|
||||||
if (!g_block_stack.empty()) {
|
|
||||||
g_block_stack.back()->End();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetThreadName(const char* name) {
|
void SetThreadName(const char* name) {
|
||||||
if (tracy::ProfilerAvailable() && name != nullptr) {
|
if (tracy::ProfilerAvailable() && name != nullptr) {
|
||||||
tracy::SetThreadName(name);
|
tracy::SetThreadName(name);
|
||||||
@@ -55,19 +16,13 @@ void SetThreadName(const char* name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Initialize() {
|
void Initialize() {
|
||||||
switch (Config::GetProfilerDirection()) {
|
if (Config::ProfilerEnabled() && !tracy::ProfilerAvailable()) {
|
||||||
case Config::ProfilerDirection::Network:
|
tracy::StartupProfiler();
|
||||||
if (!tracy::ProfilerAvailable()) {
|
TracySetProgramName("KytyPS5");
|
||||||
tracy::StartupProfiler();
|
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
|
||||||
TracySetProgramName("KytyPS5");
|
"broadcast %u, connect to 127.0.0.1:8086\n",
|
||||||
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
|
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
|
||||||
"broadcast %u, connect to 127.0.0.1:8086\n",
|
tracy::ProtocolVersion, tracy::BroadcastVersion);
|
||||||
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
|
|
||||||
tracy::ProtocolVersion, tracy::BroadcastVersion);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Config::ProfilerDirection::None:
|
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <optional>
|
|
||||||
#include <tracy/Tracy.hpp> // IWYU pragma: export
|
#include <tracy/Tracy.hpp> // IWYU pragma: export
|
||||||
|
|
||||||
namespace profiler::colors {
|
namespace profiler::colors {
|
||||||
@@ -23,22 +22,6 @@ inline constexpr uint32_t DeepOrangeA200 = 0xff6e40;
|
|||||||
|
|
||||||
namespace Profiler {
|
namespace Profiler {
|
||||||
|
|
||||||
class ScopedBlock {
|
|
||||||
public:
|
|
||||||
explicit ScopedBlock(const tracy::SourceLocationData* source_location);
|
|
||||||
ScopedBlock(const ScopedBlock&) = delete;
|
|
||||||
ScopedBlock& operator=(const ScopedBlock&) = delete;
|
|
||||||
ScopedBlock(ScopedBlock&&) = delete;
|
|
||||||
ScopedBlock& operator=(ScopedBlock&&) = delete;
|
|
||||||
~ScopedBlock();
|
|
||||||
|
|
||||||
void End();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::optional<tracy::ScopedZone> m_zone;
|
|
||||||
};
|
|
||||||
|
|
||||||
void EndBlock();
|
|
||||||
void SetThreadName(const char* name);
|
void SetThreadName(const char* name);
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
@@ -66,19 +49,11 @@ struct Lifecycle {
|
|||||||
kyty_profiler_source_location_, line) {name, TracyFunction, TracyFile, \
|
kyty_profiler_source_location_, line) {name, TracyFunction, TracyFile, \
|
||||||
static_cast<uint32_t>(line), \
|
static_cast<uint32_t>(line), \
|
||||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
||||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
tracy::ScopedZone KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line), \
|
||||||
|
TRACY_CALLSTACK, tracy::ProfilerAvailable())
|
||||||
|
|
||||||
#define KYTY_PROFILER_FUNCTION(...) KYTY_PROFILER_FUNCTION_IMPL(__LINE__ __VA_OPT__(, ) __VA_ARGS__)
|
#define KYTY_PROFILER_FUNCTION(...) KYTY_PROFILER_BLOCK(nullptr __VA_OPT__(, ) __VA_ARGS__)
|
||||||
#define KYTY_PROFILER_FUNCTION_IMPL(line, ...) \
|
|
||||||
static constexpr tracy::SourceLocationData KYTY_PROFILER_CONCAT( \
|
|
||||||
kyty_profiler_source_location_, line) {nullptr, TracyFunction, TracyFile, \
|
|
||||||
static_cast<uint32_t>(line), \
|
|
||||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
|
||||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
|
||||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
|
||||||
|
|
||||||
#define KYTY_PROFILER_END_BLOCK Profiler::EndBlock()
|
|
||||||
|
|
||||||
#define KYTY_PROFILER_THREAD(name) Profiler::SetThreadName(name)
|
#define KYTY_PROFILER_THREAD(name) Profiler::SetThreadName(name)
|
||||||
|
|
||||||
|
|||||||