Compare commits
1263 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf944df166 | ||
|
|
2b06301dbf | ||
|
|
13d6593753 | ||
|
|
3b678b9e8e | ||
|
|
dde5dce736 | ||
|
|
762fcaf5de | ||
|
|
b0129489ea | ||
|
|
206f2e3436 | ||
|
|
d43dad001e | ||
|
|
e93fa7f2cc | ||
|
|
29f31356d8 | ||
|
|
e649db8c6b | ||
|
|
2beda7c2b3 | ||
|
|
59b04c0df6 | ||
|
|
40e63ede6d | ||
|
|
64806a8397 | ||
|
|
b4e050e6c4 | ||
|
|
b45e5c2399 | ||
|
|
555d76d065 | ||
|
|
26de4bb521 | ||
|
|
c1d54f4aea | ||
|
|
b6d61abd78 | ||
|
|
40e78b9a89 | ||
|
|
ef9433411d | ||
|
|
00ba704a7f | ||
|
|
4c3c608d59 | ||
|
|
4b84d5bcec | ||
|
|
9e48ca23b2 | ||
|
|
49d817134a | ||
|
|
61ed68f3d0 | ||
|
|
291ccf7257 | ||
|
|
52da0ce399 | ||
|
|
db340f6402 | ||
|
|
684fc2c320 | ||
|
|
29b6afb82f | ||
|
|
4de18e054b | ||
|
|
bae1822aed | ||
|
|
e79e967151 | ||
|
|
fe64e1d38e | ||
|
|
d0bd01146e | ||
|
|
d94a173877 | ||
|
|
c4e0c3d76c | ||
|
|
ce5ad45278 | ||
|
|
6058c84b79 | ||
|
|
fac0e42b2f | ||
|
|
9a50a4f2cc | ||
|
|
42a4c6b79e | ||
|
|
cec9e9b811 | ||
|
|
6b32e24161 | ||
|
|
d92e8ab062 | ||
|
|
f2a03468b1 | ||
|
|
c6db1c390b | ||
|
|
7469e26e5e | ||
|
|
a03c644aed | ||
|
|
c1c397d37c | ||
|
|
2bc4ab3958 | ||
|
|
89ebef6571 | ||
|
|
bf45092c61 | ||
|
|
42c5171322 | ||
|
|
5233040ab4 | ||
|
|
0f2ac928f2 | ||
|
|
746d7d4d28 | ||
|
|
9bb8720289 | ||
|
|
16b2fd9fc8 | ||
|
|
200c95db8a | ||
|
|
48acb764a4 | ||
|
|
f77cfab516 | ||
|
|
208a457909 | ||
|
|
e39294c267 | ||
|
|
d109279543 | ||
|
|
99a1d7440d | ||
|
|
0d8fcab136 | ||
|
|
703663d761 | ||
|
|
13cdf1f159 | ||
|
|
0497bb5528 | ||
|
|
187d8e215f | ||
|
|
9d59b96ef9 | ||
|
|
a9c921a41d | ||
|
|
03149d3e4a | ||
|
|
150527ec19 | ||
|
|
cc8234fa89 | ||
|
|
239a3113e4 | ||
|
|
22342487e8 | ||
|
|
83c3ae8be8 | ||
|
|
c54a10cb4f | ||
|
|
df5b75694f | ||
|
|
d2ad279a32 | ||
|
|
49e198b20d | ||
|
|
309384da88 | ||
|
|
ff2c1b0a94 | ||
|
|
98af269415 | ||
|
|
ca84b530a3 | ||
|
|
f5efac3442 | ||
|
|
bba63b33a1 | ||
|
|
da07faebfe | ||
|
|
0a2b219a31 | ||
|
|
3575c076cb | ||
|
|
fd020ad52a | ||
|
|
3a2581cc7d | ||
|
|
369f6e58aa | ||
|
|
1ced7bbea5 | ||
|
|
a2304fad16 | ||
|
|
b79362b9da | ||
|
|
86491da0d6 | ||
|
|
7da8f15461 | ||
|
|
268eeeb406 | ||
|
|
f08c0520a4 | ||
|
|
9d6aa7bff7 | ||
|
|
444a01afa6 | ||
|
|
e373027a73 | ||
|
|
bf9c62bc76 | ||
|
|
f437c11caf | ||
|
|
9ef227e09d | ||
|
|
8191273a3d | ||
|
|
e119e17d18 | ||
|
|
2482aca7c3 | ||
|
|
420f8fb29e | ||
|
|
0e8a2c7222 | ||
|
|
eaf66b4c9f | ||
|
|
5f59815f39 | ||
|
|
a483e5e28d | ||
|
|
05ee92a357 | ||
|
|
e6b08b2209 | ||
|
|
043db620c6 | ||
|
|
99b5aa273c | ||
|
|
884a4e1e19 | ||
|
|
1e873eea36 | ||
|
|
45d1438530 | ||
|
|
8b8637978d | ||
|
|
a6f886418a | ||
|
|
f8094c2617 | ||
|
|
c5fa3560a6 | ||
|
|
2c4bb11015 | ||
|
|
fd797e2424 | ||
|
|
1670c4421f | ||
|
|
6e5f83ee24 | ||
|
|
c87f198201 | ||
|
|
ff510157d8 | ||
|
|
38a1113674 | ||
|
|
b97739029b | ||
|
|
e8674f1f09 | ||
|
|
57c4d7aa00 | ||
|
|
6ea416091e | ||
|
|
7ce6858086 | ||
|
|
ca7655be3a | ||
|
|
458fdda700 | ||
|
|
d00dcdb1be | ||
|
|
e1d66ea794 | ||
|
|
f1c519f2cb | ||
|
|
2a742229ee | ||
|
|
ab756fd068 | ||
|
|
abb4894188 | ||
|
|
906d785c73 | ||
|
|
7931cc0ceb | ||
|
|
0191a1e526 | ||
|
|
2128ab2d21 | ||
|
|
833ebbb626 | ||
|
|
d2749ef0ed | ||
|
|
f46bfdd77d | ||
|
|
50dadc33e3 | ||
|
|
04d144aa40 | ||
|
|
ea8dd8b650 | ||
|
|
c2c543e8f7 | ||
|
|
dc4e5f9159 | ||
|
|
e247bd577c | ||
|
|
f49248437e | ||
|
|
1121622dc1 | ||
|
|
7550c2c866 | ||
|
|
82cb5f030d | ||
|
|
ec2b7cdb1d | ||
|
|
f1fbb476df | ||
|
|
8440b814bc | ||
|
|
41d7b020a8 | ||
|
|
e4191b624c | ||
|
|
40eb243f86 | ||
|
|
e013fdc2b2 | ||
|
|
5a27f4e4cc | ||
|
|
18c2c96927 | ||
|
|
b28af1f6c9 | ||
|
|
f85cff0f48 | ||
|
|
0cd843151f | ||
|
|
6f05a57712 | ||
|
|
9d2a1c6f12 | ||
|
|
7a2acc86e6 | ||
|
|
f916611e32 | ||
|
|
8781beaf0d | ||
|
|
57cd80c410 | ||
|
|
31642ae2ee | ||
|
|
3f5dfc4d96 | ||
|
|
165e7645e1 | ||
|
|
1958d07d7d | ||
|
|
8ac52c6b56 | ||
|
|
0e05f98be2 | ||
|
|
3fb124961e | ||
|
|
91d86df920 | ||
|
|
6f4d3d8163 | ||
|
|
8650be1020 | ||
|
|
7458e71f24 | ||
|
|
821f2c03cb | ||
|
|
b0adb9a3d9 | ||
|
|
851089b482 | ||
|
|
4672a01cbf | ||
|
|
d245610939 | ||
|
|
19b5ae5a25 | ||
|
|
c88382517c | ||
|
|
f9951352f6 | ||
|
|
a2cd07d094 | ||
|
|
85ed42a1d2 | ||
|
|
c664f8a257 | ||
|
|
2474340684 | ||
|
|
4175712976 | ||
|
|
1e4935c3a0 | ||
|
|
d3fd0351a7 | ||
|
|
3c1cb3b11e | ||
|
|
657bd113e3 | ||
|
|
52cb5dcf0f | ||
|
|
5c42cadbc9 | ||
|
|
f445464baf | ||
|
|
c5b838aeef | ||
|
|
ee8123bf13 | ||
|
|
0686183c3e | ||
|
|
af787744ab | ||
|
|
704824d50a | ||
|
|
184324c2f0 | ||
|
|
abc5b3347d | ||
|
|
a6ea6febc9 | ||
|
|
e0106a7d68 | ||
|
|
daf2504d31 | ||
|
|
f747a7e35d | ||
|
|
4f574201ea | ||
|
|
340771ccd7 | ||
|
|
5d4ad999cf | ||
|
|
ea0bc8c002 | ||
|
|
933c9ee853 | ||
|
|
8f2c4191ab | ||
|
|
4cc1e180ec | ||
|
|
0162f8b3a7 | ||
|
|
16139ed53b | ||
|
|
b5eb3905cd | ||
|
|
bf608f125e | ||
|
|
d71e19fd75 | ||
|
|
4cf2185e81 | ||
|
|
07c2d057bd | ||
|
|
9539a1eadd | ||
|
|
77daef44b0 | ||
|
|
c73410bf2c | ||
|
|
8e8e906432 | ||
|
|
316c994f55 | ||
|
|
23e85b6b9f | ||
|
|
1cbf7ac6ea | ||
|
|
0f830d08f1 | ||
|
|
d092ea0870 | ||
|
|
b70f757913 | ||
|
|
54af9c206a | ||
|
|
a61124a9e7 | ||
|
|
44646e2ea0 | ||
|
|
b8459d2778 | ||
|
|
9d2cacdc9e | ||
|
|
63c605c04a | ||
|
|
76f191ce36 | ||
|
|
2f029577c7 | ||
|
|
bc5b65a1b0 | ||
|
|
69c45ce71c | ||
|
|
6b3e54621f | ||
|
|
ccc42702b5 | ||
|
|
7d124ec82b | ||
|
|
1d755abce4 | ||
|
|
1ff3bea6c7 | ||
|
|
2ff86f5765 | ||
|
|
92304181d5 | ||
|
|
8b83adfed6 | ||
|
|
42b5158c96 | ||
|
|
47ac369180 | ||
|
|
c2b4ff5d48 | ||
|
|
59cb258409 | ||
|
|
22fd3f0026 | ||
|
|
33e2033af5 | ||
|
|
2a1daf8f83 | ||
|
|
f6657bc8d7 | ||
|
|
25635907a2 | ||
|
|
84cc5dd35d | ||
|
|
1ce5e04be8 | ||
|
|
10dd03dec5 | ||
|
|
9505283989 | ||
|
|
f6f6f3811e | ||
|
|
52ec1840f5 | ||
|
|
e0b6771e25 | ||
|
|
a27c0099ed | ||
|
|
3a19c1098d | ||
|
|
bcc184acfa | ||
|
|
89db8c2171 | ||
|
|
c4322ce87e | ||
|
|
81aa02424b | ||
|
|
e12c84d5c5 | ||
|
|
db48ebb9c9 | ||
|
|
a147fa5825 | ||
|
|
184c516182 | ||
|
|
1b4d0ac20e | ||
|
|
2b497e5830 | ||
|
|
ac8133b9ee | ||
|
|
1c16700372 | ||
|
|
07e5319d55 | ||
|
|
d787873b3b | ||
|
|
3b88ce3dcb | ||
|
|
1432912ae8 | ||
|
|
344a0c91f2 | ||
|
|
cbe841c9c9 | ||
|
|
1f3889a290 | ||
|
|
e85308cd90 | ||
|
|
7138b99f21 | ||
|
|
e8f641a52d | ||
|
|
a85366a40c | ||
|
|
22f448b632 | ||
|
|
37aeecd29f | ||
|
|
e85a528bb9 | ||
|
|
9d33122197 | ||
|
|
26a157cd31 | ||
|
|
cd46b267f5 | ||
|
|
a4b2af7382 | ||
|
|
c994cdc532 | ||
|
|
5ee4c49c30 | ||
|
|
7841447cf0 | ||
|
|
3618a68f93 | ||
|
|
ba2fb83d60 | ||
|
|
5fc99553d2 | ||
|
|
3e2b32a3ee | ||
|
|
7dde4b7a77 | ||
|
|
0b1c2e5505 | ||
|
|
0797657bc0 | ||
|
|
148a5bef7e | ||
|
|
0081252d31 | ||
|
|
398444e676 | ||
|
|
258a5cee84 | ||
|
|
af4bde8cd1 | ||
|
|
2d563ec8d5 | ||
|
|
ef163c1a15 | ||
|
|
f5a2944ab6 | ||
|
|
c43eaa94f3 | ||
|
|
4cd5df95d6 | ||
|
|
c5de0a67a8 | ||
|
|
0ba7fe4ab1 | ||
|
|
964154ce44 | ||
|
|
63fbf9a7d3 | ||
|
|
4301f0b539 | ||
|
|
cd47391c2d | ||
|
|
d8c60029d6 | ||
|
|
5287991a36 | ||
|
|
53a219f163 | ||
|
|
3ac736c003 | ||
|
|
f5e87f4ce1 | ||
|
|
9533875eeb | ||
|
|
d95a1a3742 | ||
|
|
5c49e56d41 | ||
|
|
d66b43dadf | ||
|
|
34d6a1349c | ||
|
|
41660c8923 | ||
|
|
973fdce79b | ||
|
|
0f20fa5a1e | ||
|
|
de7cb91995 | ||
|
|
867ba1ceee | ||
|
|
1c7c1347d8 | ||
|
|
ff8754f921 | ||
|
|
89cc8c1617 | ||
|
|
552aac7e6c | ||
|
|
fe2498a650 | ||
|
|
3d938b8c60 | ||
|
|
1519ce7eab | ||
|
|
d85cfc94e2 | ||
|
|
459e158340 | ||
|
|
b46c0ed1fa | ||
|
|
ec71915ede | ||
|
|
d36e327ba6 | ||
|
|
2b91386e15 | ||
|
|
3e0727df1b | ||
|
|
894b0de0f2 | ||
|
|
dd09439fee | ||
|
|
05231d8b08 | ||
|
|
966874e357 | ||
|
|
d84eb9dac6 | ||
|
|
8afc21f175 | ||
|
|
d4104c72aa | ||
|
|
ae09adfcb3 | ||
|
|
d5bc9aef4e | ||
|
|
863579736c | ||
|
|
bb960c8cb4 | ||
|
|
2b7d862366 | ||
|
|
3acd6fa086 | ||
|
|
48733744bb | ||
|
|
a8bb1eb39f | ||
|
|
a44475207c | ||
|
|
3268321f4b | ||
|
|
6279c2dcf4 | ||
|
|
474ec2ee5f | ||
|
|
f63ccbd936 | ||
|
|
7a1a860abe | ||
|
|
457d1b4490 | ||
|
|
dffd154d6d | ||
|
|
0a0b3c4b9f | ||
|
|
c1c9ab31e8 | ||
|
|
8346541901 | ||
|
|
29f49bd3c1 | ||
|
|
ffbd51e207 | ||
|
|
701c7cb85c | ||
|
|
fbc2bcd4a9 | ||
|
|
741cae1e1d | ||
|
|
a1805ceb0b | ||
|
|
86d1649b32 | ||
|
|
204d707ce7 | ||
|
|
9c7321fe6d | ||
|
|
6a9cd17227 | ||
|
|
0faa13baeb | ||
|
|
e6b3d3a9ea | ||
|
|
8874d0e657 | ||
|
|
0e9d58e82a | ||
|
|
f36affdbe3 | ||
|
|
ce66a188d0 | ||
|
|
40c9c5a55c | ||
|
|
c20cea118b | ||
|
|
8b08f82dc7 | ||
|
|
dbfe82773d | ||
|
|
bbd6429ecb | ||
|
|
364b950515 | ||
|
|
a25c5b982a | ||
|
|
f26866ff6a | ||
|
|
c2121cb059 | ||
|
|
1bdb67440b | ||
|
|
d3cfaf95c8 | ||
|
|
0b13ce1435 | ||
|
|
af08034c71 | ||
|
|
2aeb3355e4 | ||
|
|
c6352ffc58 | ||
|
|
ec468c990d | ||
|
|
f43d8ea523 | ||
|
|
2194308245 | ||
|
|
b5c77313de | ||
|
|
dd0446ff43 | ||
|
|
31413f0d2f | ||
|
|
05549e45c5 | ||
|
|
50e2777724 | ||
|
|
b102815f1f | ||
|
|
7244671137 | ||
|
|
ff500a7b68 | ||
|
|
eb9b55eafe | ||
|
|
78dd1cd441 | ||
|
|
df001e83b1 | ||
|
|
b879fb84a2 | ||
|
|
68c1ffdd1c | ||
|
|
7eace8f512 | ||
|
|
38b35e752b | ||
|
|
c945226973 | ||
|
|
a37a47448d | ||
|
|
95103a1b7b | ||
|
|
427fc4ac6b | ||
|
|
e91ba6c057 | ||
|
|
d6c7a05239 | ||
|
|
1034bcc742 | ||
|
|
e5c916a27c | ||
|
|
50d08beed2 | ||
|
|
bbc31ba6af | ||
|
|
130a02f330 | ||
|
|
8176ab3a07 | ||
|
|
9b22f856c2 | ||
|
|
c061c2bf3c | ||
|
|
f3daecafeb | ||
|
|
04f7a7036a | ||
|
|
cbf43225a9 | ||
|
|
f1d7486eac | ||
|
|
b0334af05b | ||
|
|
1bf7ae79c8 | ||
|
|
dc35c3f9d7 | ||
|
|
af2698dcea | ||
|
|
6c1ba02e0c | ||
|
|
3e9b79e088 | ||
|
|
758c357868 | ||
|
|
87053fb3b8 | ||
|
|
5da4c78c6a | ||
|
|
abbf038191 | ||
|
|
2cc0ef83cf | ||
|
|
f317080f40 | ||
|
|
910ad2e110 | ||
|
|
6be342118a | ||
|
|
d6e9b96e2f | ||
|
|
5c47ea1a4e | ||
|
|
0a868641fa | ||
|
|
1edf4dd7ef | ||
|
|
9128271292 | ||
|
|
e0b8a35937 | ||
|
|
ef03d0178a | ||
|
|
25f997097d | ||
|
|
63e64f0131 | ||
|
|
cd4fca8447 | ||
|
|
33fbcb45a7 | ||
|
|
368e1d25be | ||
|
|
85421f3406 | ||
|
|
88ba94e8a2 | ||
|
|
9abc5763b6 | ||
|
|
cf30c4be22 | ||
|
|
4790bb907d | ||
|
|
87a9bb392b | ||
|
|
522bd5b736 | ||
|
|
0b566f43a1 | ||
|
|
5e626c774f | ||
|
|
1371e2fb6a | ||
|
|
b10905c8ae | ||
|
|
55ab369043 | ||
|
|
1831b5ef62 | ||
|
|
e3a30ccc7c | ||
|
|
90ce935f3d | ||
|
|
3f93279047 | ||
|
|
49b0966003 | ||
|
|
89f0acfd36 | ||
|
|
d883d3b64f | ||
|
|
2975f7820e | ||
|
|
b496a9eefe | ||
|
|
29aff8d5ab | ||
|
|
72207577b2 | ||
|
|
2cd3141c30 | ||
|
|
296e68fd43 | ||
|
|
93cba6f699 | ||
|
|
46458e7284 | ||
|
|
10d2ab8098 | ||
|
|
3a4841e403 | ||
|
|
f5d7706ca1 | ||
|
|
079be8032d | ||
|
|
924c473bb3 | ||
|
|
d17e172d92 | ||
|
|
c337272ca9 | ||
|
|
f2f368014e | ||
|
|
3d1e8f750c | ||
|
|
e3da9fc367 | ||
|
|
24a55bba42 | ||
|
|
b87a71b3fe | ||
|
|
c65a8fafa0 | ||
|
|
0aebe6b3d5 | ||
|
|
7c3cc08957 | ||
|
|
3575d367a4 | ||
|
|
f4b98a857b | ||
|
|
c8f3fc9a4b | ||
|
|
f4c69149f9 | ||
|
|
6a03badcbc | ||
|
|
0f148548f3 | ||
|
|
c5803e30d3 | ||
|
|
e3fadb9616 | ||
|
|
4cd52a34b9 | ||
|
|
03c2d049d4 | ||
|
|
49e5de9f03 | ||
|
|
0d1a99edf6 | ||
|
|
c3dd456d51 | ||
|
|
8e28af6f89 | ||
|
|
3d3b10adc7 | ||
|
|
69bfe075b5 | ||
|
|
88a3140c9b | ||
|
|
519035db3d | ||
|
|
5f119bed56 | ||
|
|
170e19d4ea | ||
|
|
3b885691a1 | ||
|
|
56cc1c11ec | ||
|
|
f00ca69a81 | ||
|
|
0722adb471 | ||
|
|
068668780c | ||
|
|
04b9cde4f5 | ||
|
|
7d209b3c9f | ||
|
|
497b81558e | ||
|
|
98762e9601 | ||
|
|
3a96670f2d | ||
|
|
aaec0b7e70 | ||
|
|
f8ab956189 | ||
|
|
3145114190 | ||
|
|
e21190f47f | ||
|
|
2cb3fdca86 | ||
|
|
c324a378ac | ||
|
|
fd1f5c5414 | ||
|
|
6daebaaa57 | ||
|
|
b07f4d6afb | ||
|
|
ad0166a982 | ||
|
|
7e5e4f8d7a | ||
|
|
a7d6c0d6ea | ||
|
|
81739a5448 | ||
|
|
05cb10530f | ||
|
|
b37354cca8 | ||
|
|
9fc0d1d701 | ||
|
|
e066bc75b9 | ||
|
|
8aeff9cf8e | ||
|
|
c4015cd93a | ||
|
|
ce23ae3ede | ||
|
|
64b5e5d5d9 | ||
|
|
52636f67cc | ||
|
|
8bd8d1e3da | ||
|
|
6642011706 | ||
|
|
49c0c081c4 | ||
|
|
4757ffdcce | ||
|
|
274d1fb0fc | ||
|
|
3ff21345b4 | ||
|
|
c1ae841f47 | ||
|
|
316b933a31 | ||
|
|
0cad310e12 | ||
|
|
4f41ffdd41 | ||
|
|
7c7b2b8285 | ||
|
|
b89fb430c7 | ||
|
|
b30c5370b1 | ||
|
|
020d005d8c | ||
|
|
706892de7d | ||
|
|
3d68f6ba6c | ||
|
|
7230ceb584 | ||
|
|
afb26b190f | ||
|
|
e2037821b6 | ||
|
|
12a6996262 | ||
|
|
379a935016 | ||
|
|
04524e76c2 | ||
|
|
3e966be6fc | ||
|
|
0944bfe3cb | ||
|
|
aec90ca506 | ||
|
|
ef2c955db5 | ||
|
|
dacc89b38b | ||
|
|
51a3e93f8e | ||
|
|
0d51cfe2f5 | ||
|
|
eb6cbfdbd8 | ||
|
|
1b3dd30ba8 | ||
|
|
854f474f52 | ||
|
|
639346bcfb | ||
|
|
d990f2355b | ||
|
|
f89b47fdf7 | ||
|
|
913896cbd9 | ||
|
|
3417f46dd5 | ||
|
|
4633dd9505 | ||
|
|
f2c2383c8b | ||
|
|
476e0fae4c | ||
|
|
b8384c0c91 | ||
|
|
9df698fa9c | ||
|
|
70a6691e3b | ||
|
|
e197476344 | ||
|
|
650c89bbbc | ||
|
|
bebe09a1aa | ||
|
|
77c684c114 | ||
|
|
c9aadff9a9 | ||
|
|
51bd76a5fd | ||
|
|
9f6a5660e8 | ||
|
|
762bf6a522 | ||
|
|
637f9d780a | ||
|
|
956b5db52e | ||
|
|
8b815877a6 | ||
|
|
1b0a74e23f | ||
|
|
9a3c0b161e | ||
|
|
b0c92b80b1 | ||
|
|
d800a02b4b | ||
|
|
77cfe4f027 | ||
|
|
ce39ae3e57 | ||
|
|
4bda9693be | ||
|
|
c42b818cf9 | ||
|
|
53a55bd751 | ||
|
|
2355460d7c | ||
|
|
016e357c75 | ||
|
|
c1bebdef5e | ||
|
|
81a44d38ee | ||
|
|
5a9df3c675 | ||
|
|
c996787d84 | ||
|
|
4030f600dc | ||
|
|
78443a7f29 | ||
|
|
c1811ed3d1 | ||
|
|
be51120d23 | ||
|
|
827bb08c91 | ||
|
|
c164f02c48 | ||
|
|
9da1552417 | ||
|
|
6ff20dc6a7 | ||
|
|
3dab0e284b | ||
|
|
15e68cdbaa | ||
|
|
e3ca561ea0 | ||
|
|
ddb767f1b6 | ||
|
|
5410b4659d | ||
|
|
a9cacd03f6 | ||
|
|
6e0eba9917 | ||
|
|
65c664560c | ||
|
|
76b475faf7 | ||
|
|
6269a01b4e | ||
|
|
0d46f0df12 | ||
|
|
638956aa81 | ||
|
|
92c7135065 | ||
|
|
a6d4903aaf | ||
|
|
6e4e0b2b41 | ||
|
|
055f1546d7 | ||
|
|
79167fc989 | ||
|
|
9685dd5840 | ||
|
|
18c8ae7750 | ||
|
|
d480b63e0d | ||
|
|
c1f55c32c8 | ||
|
|
0f929762b3 | ||
|
|
4c59105adf | ||
|
|
fca3d1cc65 | ||
|
|
cc73bad293 | ||
|
|
3d41fdfbba | ||
|
|
ca633a5a3c | ||
|
|
80c5e8ae99 | ||
|
|
e9d147349b | ||
|
|
6c0c81dfdc | ||
|
|
a093feca62 | ||
|
|
066d6184d4 | ||
|
|
b611d852db | ||
|
|
85a60e2044 | ||
|
|
f33e406ff2 | ||
|
|
c0e2d52758 | ||
|
|
b11072d54a | ||
|
|
c96da97630 | ||
|
|
50ef2beb58 | ||
|
|
c18425ef98 | ||
|
|
da2bdbc0d7 | ||
|
|
7fa9177830 | ||
|
|
1dd754590f | ||
|
|
8af1ae46aa | ||
|
|
c7c379bd19 | ||
|
|
6a28a66832 | ||
|
|
1bbbd26563 | ||
|
|
3f9f047375 | ||
|
|
ff6785f3e8 | ||
|
|
9f2f819bb6 | ||
|
|
5f57ab1b2a | ||
|
|
84cadf9918 | ||
|
|
10422f3c18 | ||
|
|
dfac394e60 | ||
|
|
73de9bab1a | ||
|
|
0399d98cd9 | ||
|
|
8447d20a11 | ||
|
|
20b58bab9c | ||
|
|
41b3725d28 | ||
|
|
2981408722 | ||
|
|
1669911b1d | ||
|
|
36dedae842 | ||
|
|
1da0ee57fd | ||
|
|
ad39bab271 | ||
|
|
c9e821e93e | ||
|
|
11fb17054e | ||
|
|
23dc36ed71 | ||
|
|
838724c588 | ||
|
|
0b831dd2ba | ||
|
|
81f24f5685 | ||
|
|
ea1880f47c | ||
|
|
6d7941042b | ||
|
|
52a78228dd | ||
|
|
a27befe456 | ||
|
|
067ac434ba | ||
|
|
5f8aa02584 | ||
|
|
08d454e30d | ||
|
|
b7162c32a4 | ||
|
|
dc70a87af1 | ||
|
|
63f26d5c40 | ||
|
|
8f8fe62a19 | ||
|
|
62bd1299ea | ||
|
|
4f81bc4e1b | ||
|
|
9d71ce88ce | ||
|
|
c06d6b27f3 | ||
|
|
7e191dccc1 | ||
|
|
c3e95086b6 | ||
|
|
a3d82ef5d9 | ||
|
|
be1f5dedfb | ||
|
|
7a0bb406d5 | ||
|
|
0d8ae773f1 | ||
|
|
1ab133d7fa | ||
|
|
38989bef43 | ||
|
|
eab7457c00 | ||
|
|
5f57a70a7d | ||
|
|
0e13d9cb7b | ||
|
|
c11cfaa705 | ||
|
|
4ac4b308e4 | ||
|
|
ea080501fb | ||
|
|
bf4e2b2f0b | ||
|
|
7c7f4a9be2 | ||
|
|
61779fa072 | ||
|
|
d2277b825e | ||
|
|
fe906fff36 | ||
|
|
f9af74201c | ||
|
|
afdd657d30 | ||
|
|
5673ce39c7 | ||
|
|
3c43ea5c68 | ||
|
|
d383043e07 | ||
|
|
fb5bd0920d | ||
|
|
46cbb6b090 | ||
|
|
55c49d5bf4 | ||
|
|
61f9d9c4ab | ||
|
|
acc8fe5a2a | ||
|
|
f969ddb54e | ||
|
|
9f8fbce35b | ||
|
|
94d27b1717 | ||
|
|
ac88d3e89f | ||
|
|
a353322b58 | ||
|
|
50153a1cb2 | ||
|
|
17f3590d59 | ||
|
|
7786f41cc0 | ||
|
|
019d7208c8 | ||
|
|
2015a1b180 | ||
|
|
db0497b808 | ||
|
|
987a170665 | ||
|
|
33dbf24b56 | ||
|
|
2dc8b5c224 | ||
|
|
5f3d6c85db | ||
|
|
2f9c0e7c7e | ||
|
|
09b8a16414 | ||
|
|
004b1b3830 | ||
|
|
281fd881a0 | ||
|
|
2a7653142d | ||
|
|
b366b885a1 | ||
|
|
3cb753eeb1 | ||
|
|
d81aaa3ed3 | ||
|
|
e2176dc7ce | ||
|
|
174c22e5f6 | ||
|
|
5440b9c634 | ||
|
|
abec5f82e2 | ||
|
|
bbc4f369ed | ||
|
|
79e9c2e237 | ||
|
|
83517cb53a | ||
|
|
9949e4d508 | ||
|
|
c116b220e9 | ||
|
|
c011b6f67e | ||
|
|
c712dafaee | ||
|
|
a931cf9e8b | ||
|
|
a941a94148 | ||
|
|
8d9534d830 | ||
|
|
47dc5e0dab | ||
|
|
f3885845fc | ||
|
|
c0d3e2da4e | ||
|
|
517112f549 | ||
|
|
6324d86c71 | ||
|
|
5aff2d38a9 | ||
|
|
ee318d4015 | ||
|
|
86146ef819 | ||
|
|
a2efb1dd48 | ||
|
|
ee1eb8cfdf | ||
|
|
0639e03055 | ||
|
|
930487c7fb | ||
|
|
92209f905f | ||
|
|
f22e090b86 | ||
|
|
218a08df93 | ||
|
|
0cb7ce71e0 | ||
|
|
9f21f20d7c | ||
|
|
128aeba0f3 | ||
|
|
03f877919d | ||
|
|
37f50c8773 | ||
|
|
4732e1f064 | ||
|
|
00c830405b | ||
|
|
4b114e1b8a | ||
|
|
0a49c46353 | ||
|
|
47629c89a8 | ||
|
|
89e81a9be2 | ||
|
|
0ff2929644 | ||
|
|
cfc9effa6c | ||
|
|
4669f15f8b | ||
|
|
4112aa68a6 | ||
|
|
6e386a334b | ||
|
|
dbfc39d214 | ||
|
|
61fbf5c8e6 | ||
|
|
be09dfeed9 | ||
|
|
2f842a86fe | ||
|
|
ce026332a5 | ||
|
|
fa220dd709 | ||
|
|
a776464a55 | ||
|
|
9a85277d83 | ||
|
|
05dc93399b | ||
|
|
39fb3e362c | ||
|
|
566f97b580 | ||
|
|
bf0543af23 | ||
|
|
c5684411a0 | ||
|
|
2abe5e39fc | ||
|
|
adf47cd59a | ||
|
|
c531a92eda | ||
|
|
14afc704d4 | ||
|
|
8d70d1ea45 | ||
|
|
5fb99e6a16 | ||
|
|
5c3d5d0849 | ||
|
|
79de0f8fe8 | ||
|
|
6e8e1a4110 | ||
|
|
9232fbdf34 | ||
|
|
38eb33f150 | ||
|
|
b54a72afc0 | ||
|
|
62cd19e4ae | ||
|
|
7e3d746b06 | ||
|
|
e7dfcdde74 | ||
|
|
4b89348c00 | ||
|
|
8c99dd055c | ||
|
|
799e632ccb | ||
|
|
c23c30c76f | ||
|
|
00749f5ab3 | ||
|
|
6ea1576513 | ||
|
|
81a16c073a | ||
|
|
23b1e6eded | ||
|
|
438a9b70cc | ||
|
|
e8bfff7b4b | ||
|
|
f564822e78 | ||
|
|
6cf6fa2842 | ||
|
|
d27279092f | ||
|
|
37fd4e6d9b | ||
|
|
cdd92dc692 | ||
|
|
38d25a4cb2 | ||
|
|
2933521a08 | ||
|
|
f6679ce422 | ||
|
|
5d55403f94 | ||
|
|
0a0233f39f | ||
|
|
9936d1b9e2 | ||
|
|
4fad069870 | ||
|
|
0c688b421c | ||
|
|
cb47abecc6 | ||
|
|
fbef849c04 | ||
|
|
0641950f9a | ||
|
|
b7c64f0ded | ||
|
|
90cddf1996 | ||
|
|
7c181fd4f4 | ||
|
|
d16f83fda3 | ||
|
|
5c82400ef8 | ||
|
|
bb081dd1d2 | ||
|
|
019778707d | ||
|
|
afdd2f4cad | ||
|
|
df4336a85e | ||
|
|
51d8a2c322 | ||
|
|
049ce242a4 | ||
|
|
b481d8a00d | ||
|
|
06c72b4fcf | ||
|
|
876b805e50 | ||
|
|
2dcb98226b | ||
|
|
9fedfbe141 | ||
|
|
d73c22bf4d | ||
|
|
ba117854f9 | ||
|
|
527c098ff6 | ||
|
|
d57333406d | ||
|
|
1efcba346a | ||
|
|
bb9d39b8fe | ||
|
|
27c0f9e02d | ||
|
|
41faeeeb03 | ||
|
|
63270e588b | ||
|
|
e54ea773fc | ||
|
|
0d64ddc6dd | ||
|
|
9cd87a6352 | ||
|
|
99f9d47d16 | ||
|
|
bbbe34429e | ||
|
|
11568c2ea3 | ||
|
|
888eb345c0 | ||
|
|
4c727d0ba8 | ||
|
|
bdd68fc210 | ||
|
|
f1bded1270 | ||
|
|
49309b5848 | ||
|
|
c02d7c8ce7 | ||
|
|
3957b0c34e | ||
|
|
ca5a4a704b | ||
|
|
15086a22be | ||
|
|
94fecef137 | ||
|
|
d1f9c750a6 | ||
|
|
99f12b05fa | ||
|
|
8df011a57f | ||
|
|
9a273bb23b | ||
|
|
6fcc7e9c36 | ||
|
|
c8e1383fa9 | ||
|
|
68937a662d | ||
|
|
734106dcb9 | ||
|
|
6306655665 | ||
|
|
0658973a4e | ||
|
|
0d843eaba6 | ||
|
|
5a763e8a5a | ||
|
|
220d4672df | ||
|
|
7757cc1a7f | ||
|
|
2abf39ea4a | ||
|
|
d809f65827 | ||
|
|
7f155ba713 | ||
|
|
7029daa32e | ||
|
|
15c388e0d6 | ||
|
|
9e30f5574f | ||
|
|
7ddc872b52 | ||
|
|
6138075df0 | ||
|
|
0d681f7a7a | ||
|
|
c23ce3365d | ||
|
|
ee53688ca7 | ||
|
|
b16e5c6a81 | ||
|
|
87f21657f8 | ||
|
|
e6df4b37db | ||
|
|
aee356bd10 | ||
|
|
cd7665218d | ||
|
|
e2cdf54177 | ||
|
|
e3a92b09ba | ||
|
|
e2db7a83f6 | ||
|
|
126270d963 | ||
|
|
e6a87428ae | ||
|
|
55e6296e71 | ||
|
|
1ce7942dc2 | ||
|
|
6b6287dda0 | ||
|
|
c74d24f841 | ||
|
|
4cb92b776c | ||
|
|
a55f112cb1 | ||
|
|
5cdc277dd2 | ||
|
|
74efdd6928 | ||
|
|
3825b703fa | ||
|
|
1efb81a61d | ||
|
|
3c26b7179d | ||
|
|
8c648b59cd | ||
|
|
f217d6c66f | ||
|
|
58d9078742 | ||
|
|
58857b9f46 | ||
|
|
c6eaf0b2cf | ||
|
|
693f78e6c2 | ||
|
|
898f0fa029 | ||
|
|
ff54287a73 | ||
|
|
882111c4f2 | ||
|
|
6486544e09 | ||
|
|
2dbfcd32d7 | ||
|
|
8440cef223 | ||
|
|
fd500d3da6 | ||
|
|
525492428d | ||
|
|
72b5c448cf | ||
|
|
03388c3071 | ||
|
|
353e1dd7e4 | ||
|
|
a215f63235 | ||
|
|
dc26601860 | ||
|
|
2a35a36251 | ||
|
|
c74f2555b6 | ||
|
|
fab3dd98fe | ||
|
|
c50393e066 | ||
|
|
a056d5ad8c | ||
|
|
98b143c2d6 | ||
|
|
370ab5df9b | ||
|
|
21959ddfef | ||
|
|
abe79b2724 | ||
|
|
536cfb13e6 | ||
|
|
e35cfc1b03 | ||
|
|
fd86cdb2e2 | ||
|
|
0984e9d601 | ||
|
|
1b5c02fc37 | ||
|
|
e07218906d | ||
|
|
e7b0e8a3cc | ||
|
|
811dae12f9 | ||
|
|
46ec9a9bc9 | ||
|
|
edc52250b8 | ||
|
|
fbd7afefaa | ||
|
|
91af2f94e8 | ||
|
|
e6671190a5 | ||
|
|
4822765fef | ||
|
|
8aa5d25f82 | ||
|
|
d6e3cd9a17 | ||
|
|
6ea8b3ef60 | ||
|
|
1c36f2a798 | ||
|
|
6a890023e9 | ||
|
|
5c0421ebd8 | ||
|
|
9bf2a428f9 | ||
|
|
cba69fdcd4 | ||
|
|
a434fdcb10 | ||
|
|
9776ff9179 | ||
|
|
5590245930 | ||
|
|
5e9c547952 | ||
|
|
266703b50e | ||
|
|
9eccb5de9d | ||
|
|
8c665d6752 | ||
|
|
732a77d0e8 | ||
|
|
9f3641755e | ||
|
|
1147db9dd1 | ||
|
|
b1a8e5914b | ||
|
|
902182f80c | ||
|
|
7c9644646f | ||
|
|
fadab1d5f3 | ||
|
|
acc10c7ee2 | ||
|
|
8262aeeac8 | ||
|
|
ff2f0d980a | ||
|
|
0c8b7c00e8 | ||
|
|
f362cf46ee | ||
|
|
0197e28cc9 | ||
|
|
81a0082f6b | ||
|
|
225ff1130f | ||
|
|
b3962e7d1e | ||
|
|
3abba08080 | ||
|
|
e8bbafb746 | ||
|
|
40d2dcabd7 | ||
|
|
f41eb95e13 | ||
|
|
08b8fcbe6d | ||
|
|
316327f487 | ||
|
|
c7ce472eeb | ||
|
|
869075867b | ||
|
|
da32c648bf | ||
|
|
a71346cd7c | ||
|
|
6c464a2a4a | ||
|
|
49d92aa661 | ||
|
|
334e859ab1 | ||
|
|
17b16cf6f6 | ||
|
|
faa431b274 | ||
|
|
f87ea8fa8b | ||
|
|
0c01c34eff | ||
|
|
e73927cfc2 | ||
|
|
c691fa4074 | ||
|
|
f2dcb39049 | ||
|
|
a7b5ab4d9a | ||
|
|
3d9126ba87 | ||
|
|
7fd54fed92 | ||
|
|
5d9ee12b1a | ||
|
|
bad00085ca | ||
|
|
e56e2a1528 | ||
|
|
99ac33de20 | ||
|
|
d43c49264f | ||
|
|
6b365f7703 | ||
|
|
41dde2394b | ||
|
|
5a579f66a0 | ||
|
|
16198f979e | ||
|
|
843dd62c81 | ||
|
|
e6242ab5e6 | ||
|
|
acede1f1d3 | ||
|
|
8475496630 | ||
|
|
3c40496409 | ||
|
|
abc23416e8 | ||
|
|
4f120a9ec0 | ||
|
|
18f8012233 | ||
|
|
3e0ec3dbd7 | ||
|
|
3cfe77ae75 | ||
|
|
3062eb52f4 | ||
|
|
376f6397c6 | ||
|
|
6a3d59fdc1 | ||
|
|
bc43946140 | ||
|
|
7391741a20 | ||
|
|
f1f7f2cba9 | ||
|
|
4ac9b47dca | ||
|
|
c33755e2b9 | ||
|
|
87a92ef062 | ||
|
|
c9d7abe9c9 | ||
|
|
37fa9a15cd | ||
|
|
3dd3cdeafd | ||
|
|
623d772476 | ||
|
|
4f281b3829 | ||
|
|
08da0b7acc | ||
|
|
f81b915fd8 | ||
|
|
dd6c67c627 | ||
|
|
42d43ea741 | ||
|
|
d0825c9519 | ||
|
|
1913cf4783 | ||
|
|
40dee76c57 | ||
|
|
23d68a07dc | ||
|
|
60746e4e52 | ||
|
|
20d86d8a36 | ||
|
|
e9ad8e9185 | ||
|
|
1740aa5444 | ||
|
|
5ab597041f | ||
|
|
1dd4861d38 | ||
|
|
a6da2b93c1 | ||
|
|
378c881427 | ||
|
|
b1109931b9 | ||
|
|
c16cfbbc6c | ||
|
|
a994446b6e | ||
|
|
e2f2a49d2d | ||
|
|
3f78a61f09 | ||
|
|
5aafc83cc9 | ||
|
|
6d00780045 | ||
|
|
59dae03dbe | ||
|
|
22420612db | ||
|
|
b7551e457b | ||
|
|
2ba4e2263c | ||
|
|
0369ee7248 | ||
|
|
ea3151f475 | ||
|
|
6c9ca8cbca | ||
|
|
c30cd898fc | ||
|
|
f1a4a004fb | ||
|
|
0a023cfb4f | ||
|
|
9022d926eb | ||
|
|
fbb3cd110c | ||
|
|
bc0f1896fc | ||
|
|
4415e00181 | ||
|
|
cc2e14ec2a | ||
|
|
10c6d89119 | ||
|
|
239ac8abe2 | ||
|
|
9e11a76e92 | ||
|
|
e8c2bb24b2 | ||
|
|
b7953d2ebf | ||
|
|
d08cfb55fe | ||
|
|
88eb612718 | ||
|
|
bd9c2aa51f | ||
|
|
bfe49edb2a | ||
|
|
82413a6c89 | ||
|
|
62c69f4a1e | ||
|
|
2a3f3bf977 | ||
|
|
32ece18bb6 | ||
|
|
13f9cf2bd0 | ||
|
|
72b497e876 | ||
|
|
285d8d8b7d | ||
|
|
022fc59dcd | ||
|
|
47054327c2 | ||
|
|
28b92db7fd | ||
|
|
dabfd90dfe | ||
|
|
9cd7485cd7 | ||
|
|
8fc4003dab | ||
|
|
b5b613ea29 | ||
|
|
299f943202 | ||
|
|
d1b23b2b51 | ||
|
|
c6a740d7c2 | ||
|
|
8d32bf9a96 | ||
|
|
d652e41365 | ||
|
|
e74dbfc572 | ||
|
|
5483c08b44 | ||
|
|
f85d880ac6 | ||
|
|
7495142688 | ||
|
|
a0179e5ca5 | ||
|
|
07dc0bbf3e | ||
|
|
f208953585 | ||
|
|
0214351f4f | ||
|
|
bf25299272 | ||
|
|
d4f87e9af4 | ||
|
|
3967f9c6ef | ||
|
|
55d0b0609d | ||
|
|
9531a29283 | ||
|
|
46572d027d | ||
|
|
0d6eafe11a | ||
|
|
e4bd0bddea | ||
|
|
e862c50a70 | ||
|
|
f823c1d599 | ||
|
|
010227e149 | ||
|
|
27650499bc | ||
|
|
df669bc540 | ||
|
|
f3137d3bc1 | ||
|
|
ebb8e06df0 | ||
|
|
c079cf4eec | ||
|
|
62937798a0 | ||
|
|
f48d5e4c4c | ||
|
|
f8764bb5d3 | ||
|
|
f8a037ead4 | ||
|
|
d08fd7e86d | ||
|
|
8b28dc55e6 | ||
|
|
e1630c4d43 | ||
|
|
9f6d305eab | ||
|
|
8ac3a3f45e | ||
|
|
634d9ee18b | ||
|
|
ba6f3e8f9f | ||
|
|
17a0ef1e1e | ||
|
|
c3a8ea76f1 | ||
|
|
0a5e01b710 | ||
|
|
a70ed9c8ae | ||
|
|
013778aa21 | ||
|
|
be155f4d9d | ||
|
|
5fdfbfe25a | ||
|
|
b18ccf9399 | ||
|
|
e81a2080eb | ||
|
|
1723b4d8d4 | ||
|
|
2e7ce96b1d | ||
|
|
eafdcc1b8a | ||
|
|
ab71997b2c | ||
|
|
7db0b8d74f | ||
|
|
659a612368 | ||
|
|
bec05db746 | ||
|
|
1df3a7710e | ||
|
|
d03fc77475 | ||
|
|
2e0a9f66a0 | ||
|
|
326b044c19 | ||
|
|
87f89ac82d | ||
|
|
fae2dd0344 | ||
|
|
a904d70afe | ||
|
|
b11f6f90e7 | ||
|
|
4d96997447 | ||
|
|
b5c204ac6f | ||
|
|
701dd649e6 | ||
|
|
79c1ed80e9 | ||
|
|
cb267093bb | ||
|
|
b2febaff2f | ||
|
|
956e200f12 | ||
|
|
0eba5911f2 | ||
|
|
b134e6afcf | ||
|
|
902fc61ef8 | ||
|
|
16ffecd8fb | ||
|
|
e8e5041955 | ||
|
|
ccca5e7c28 | ||
|
|
2c8afe1140 | ||
|
|
2ef04f69b2 | ||
|
|
14bf88a777 | ||
|
|
3990da488b | ||
|
|
80982748c8 | ||
|
|
e61a4dd485 | ||
|
|
b05f8ea5b5 | ||
|
|
3841ec4200 | ||
|
|
5922f2c46d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ src/common/scm_rev.cpp
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# *nix related
|
||||
# Common convention for backup or temporary files
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -7,6 +7,9 @@
|
||||
[submodule "catch"]
|
||||
path = externals/catch
|
||||
url = https://github.com/philsquared/Catch.git
|
||||
[submodule "cubeb"]
|
||||
path = externals/cubeb
|
||||
url = https://github.com/kinetiknz/cubeb.git
|
||||
[submodule "dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://github.com/MerryMage/dynarmic.git
|
||||
@@ -22,3 +25,9 @@
|
||||
[submodule "unicorn"]
|
||||
path = externals/unicorn
|
||||
url = https://github.com/yuzu-emu/unicorn
|
||||
[submodule "mbedtls"]
|
||||
path = externals/mbedtls
|
||||
url = https://github.com/DarkLordZach/mbedtls
|
||||
[submodule "opus"]
|
||||
path = externals/opus
|
||||
url = https://github.com/ogniK5377/opus.git
|
||||
|
||||
@@ -23,7 +23,7 @@ matrix:
|
||||
- os: osx
|
||||
env: NAME="macos build"
|
||||
sudo: false
|
||||
osx_image: xcode9.2
|
||||
osx_image: xcode9.3
|
||||
install: "./.travis/macos/deps.sh"
|
||||
script: "./.travis/macos/build.sh"
|
||||
after_success: "./.travis/macos/upload.sh"
|
||||
@@ -42,3 +42,7 @@ notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://api.yuzu-emu.org/code/travis/notify
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.ccache
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
docker run -v $(pwd):/yuzu ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
docker run -e CCACHE_DIR=/ccache -v $HOME/.ccache:/ccache -v $(pwd):/yuzu ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
apt-get update
|
||||
apt-get install -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget
|
||||
|
||||
# Get a recent version of CMake
|
||||
wget https://cmake.org/files/v3.10/cmake-3.10.1-Linux-x86_64.sh
|
||||
sh cmake-3.10.1-Linux-x86_64.sh --exclude-subdir --prefix=/ --skip-license
|
||||
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget cmake ninja-build ccache
|
||||
|
||||
cd /yuzu
|
||||
|
||||
export PATH=/usr/lib/ccache:$PATH
|
||||
ln -sf /usr/bin/ccache /usr/lib/ccache/cc
|
||||
ln -sf /usr/bin/ccache /usr/lib/ccache/c++
|
||||
mkdir build && cd build
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release
|
||||
make -j4
|
||||
ccache --show-stats > ccache_before
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
ninja
|
||||
ccache --show-stats > ccache_after
|
||||
diff -U100 ccache_before ccache_after || true
|
||||
|
||||
ctest -VV -C Release
|
||||
|
||||
@@ -7,8 +7,12 @@ export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export UNICORNDIR=$(pwd)/externals/unicorn
|
||||
|
||||
mkdir build && cd build
|
||||
export PATH=/usr/local/opt/ccache/libexec:$PATH
|
||||
ccache --show-stats > ccache_before
|
||||
cmake --version
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release
|
||||
make -j4
|
||||
ccache --show-stats > ccache_after
|
||||
diff -U100 ccache_before ccache_after || true
|
||||
|
||||
ctest -VV -C Release
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
brew update
|
||||
brew install dylibbundler p7zip qt5 sdl2
|
||||
brew install dylibbundler p7zip qt5 sdl2 ccache
|
||||
brew outdated cmake || brew upgrade cmake
|
||||
|
||||
@@ -17,6 +17,8 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "EN
|
||||
|
||||
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
message(STATUS "Copying pre-commit hook")
|
||||
file(COPY hooks/pre-commit
|
||||
|
||||
@@ -3,7 +3,9 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
|
||||
set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin")
|
||||
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
|
||||
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
|
||||
set(PLATFORMS ${DLL_DEST}platforms/)
|
||||
set(STYLES ${DLL_DEST}styles/)
|
||||
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
|
||||
icudt*.dll
|
||||
icuin*.dll
|
||||
@@ -14,4 +16,5 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
Qt5Widgets$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
||||
endfunction(copy_yuzu_Qt5_deps)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Reporting Issues
|
||||
|
||||
**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/yuzu-emu/yuzu/wiki/FAQ) and then either visit our Discord server, [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
|
||||
**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/yuzu-emu/yuzu/wiki/FAQ) and then either visit our [Discord server](https://discordapp.com/invite/u77vRWY), [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
|
||||
|
||||
If you believe you have a valid issue report, please post text or a screenshot from the log (the console window that opens alongside yuzu) and build version (hex string visible in the titlebar and zip filename), as well as your hardware and software information if applicable.
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ after_build:
|
||||
|
||||
mkdir $RELEASE_DIST
|
||||
mkdir $RELEASE_DIST/platforms
|
||||
mkdir $RELEASE_DIST/styles
|
||||
|
||||
# copy the compiled binaries and other release files to the release folder
|
||||
Get-ChildItem "$CMAKE_BINARY_DIR" -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
@@ -136,6 +137,9 @@ after_build:
|
||||
# copy the qt windows plugin dll to platforms
|
||||
Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/platforms/qwindows.dll" -force -destination "$RELEASE_DIST/platforms"
|
||||
|
||||
# copy the qt windows vista style dll to platforms
|
||||
Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/styles/qwindowsvistastyle.dll" -force -destination "$RELEASE_DIST/styles"
|
||||
|
||||
7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\*
|
||||
7z a $MINGW_SEVENZIP $RELEASE_DIST
|
||||
}
|
||||
|
||||
183
dist/qt_themes/qdarkstyle/LICENSE.md
vendored
Normal file
183
dist/qt_themes/qdarkstyle/LICENSE.md
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
# License
|
||||
|
||||
## The MIT License (MIT) - Code
|
||||
|
||||
Copyright (c) 2013-2018 Colin Duquesnoy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
## Creative Commons Attribution International 4.0 - Images
|
||||
|
||||
QDarkStyle (c) 2013-2018 Colin Duquesnoy
|
||||
|
||||
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
|
||||
|
||||
### Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
|
||||
|
||||
* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).
|
||||
|
||||
* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).
|
||||
|
||||
## Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
|
||||
|
||||
### Section 1 – Definitions
|
||||
|
||||
a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
|
||||
|
||||
b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
|
||||
|
||||
d. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
|
||||
|
||||
e. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
|
||||
|
||||
f. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
|
||||
|
||||
g. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.
|
||||
|
||||
i. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
|
||||
|
||||
j. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
|
||||
|
||||
k. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
|
||||
|
||||
### Section 2 – Scope
|
||||
|
||||
a. ___License grant.___
|
||||
|
||||
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
A. reproduce and Share the Licensed Material, in whole or in part; and
|
||||
|
||||
B. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
|
||||
|
||||
3. __Term.__ The term of this Public License is specified in Section 6(a).
|
||||
|
||||
4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
|
||||
|
||||
5. __Downstream recipients.__
|
||||
|
||||
A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
|
||||
|
||||
B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
|
||||
|
||||
6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. ___Other rights.___
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
|
||||
|
||||
### Section 3 – License Conditions
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
|
||||
|
||||
a. ___Attribution.___
|
||||
|
||||
1. If You Share the Licensed Material (including in modified form), You must:
|
||||
|
||||
A. retain the following if it is supplied by the Licensor with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
|
||||
|
||||
B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
|
||||
|
||||
C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
|
||||
|
||||
### Section 4 – Sui Generis Database Rights
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
|
||||
|
||||
### Section 5 – Disclaimer of Warranties and Limitation of Liability
|
||||
|
||||
a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__
|
||||
|
||||
b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
|
||||
|
||||
### Section 6 – Term and Termination
|
||||
|
||||
a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
|
||||
|
||||
### Section 7 – Other Terms and Conditions
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
|
||||
|
||||
### Section 8 – Interpretation
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
|
||||
|
||||
> Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
|
||||
>
|
||||
> Creative Commons may be contacted at creativecommons.org
|
||||
609
dist/qt_themes/qdarkstyle/style.qss
vendored
609
dist/qt_themes/qdarkstyle/style.qss
vendored
File diff suppressed because it is too large
Load Diff
14
externals/CMakeLists.txt
vendored
14
externals/CMakeLists.txt
vendored
@@ -35,6 +35,10 @@ set(LZ4_BUNDLED_MODE ON)
|
||||
add_subdirectory(lz4/contrib/cmake_unofficial)
|
||||
target_include_directories(lz4_static INTERFACE ./lz4/lib)
|
||||
|
||||
# mbedtls
|
||||
add_subdirectory(mbedtls)
|
||||
target_include_directories(mbedtls PUBLIC ./mbedtls/include)
|
||||
|
||||
# MicroProfile
|
||||
add_library(microprofile INTERFACE)
|
||||
target_include_directories(microprofile INTERFACE ./microprofile)
|
||||
@@ -50,3 +54,13 @@ if (ARCHITECTURE_x86_64)
|
||||
target_include_directories(xbyak INTERFACE ./xbyak/xbyak)
|
||||
target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
|
||||
endif()
|
||||
|
||||
# Opus
|
||||
add_subdirectory(opus)
|
||||
target_include_directories(opus INTERFACE ./opus/include)
|
||||
|
||||
# Cubeb
|
||||
if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(cubeb)
|
||||
endif()
|
||||
|
||||
2
externals/catch
vendored
2
externals/catch
vendored
Submodule externals/catch updated: cd76f5730c...d2a130f243
@@ -1,4 +1,4 @@
|
||||
# Copyright 2016 Citra Emulator Project
|
||||
# Copyright 2018 Yuzu Emulator Project
|
||||
# Licensed under GPLv2 or any later version
|
||||
# Refer to the license.txt file included.
|
||||
|
||||
@@ -22,7 +22,7 @@ function(windows_copy_files TARGET SOURCE_DIR DEST_DIR)
|
||||
# cmake adds an extra check for command success which doesn't work too well with robocopy
|
||||
# so trick it into thinking the command was successful with the || cmd /c "exit /b 0"
|
||||
add_custom_command(TARGET ${TARGET} POST_BUILD
|
||||
COMMAND if not exist ${DEST_DIR} mkdir ${DEST_DIR} 2> nul
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_DIR}
|
||||
COMMAND robocopy ${SOURCE_DIR} ${DEST_DIR} ${ARGN} /NJH /NJS /NDL /NFL /NC /NS /NP || cmd /c "exit /b 0"
|
||||
)
|
||||
endfunction()
|
||||
endfunction()
|
||||
|
||||
1
externals/cubeb
vendored
Submodule
1
externals/cubeb
vendored
Submodule
Submodule externals/cubeb added at 12b78c0edf
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 9cc12d80b9...73d3efc3e0
2
externals/fmt
vendored
2
externals/fmt
vendored
Submodule externals/fmt updated: 5859e58ba1...c2ce7e4f07
16
externals/glad/include/KHR/khrplatform.h
vendored
16
externals/glad/include/KHR/khrplatform.h
vendored
@@ -2,7 +2,7 @@
|
||||
#define __khrplatform_h_
|
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2009 The Khronos Group Inc.
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||
** copy of this software and/or associated documentation files (the
|
||||
@@ -26,18 +26,16 @@
|
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
*
|
||||
* $Revision: 32517 $ on $Date: 2016-03-11 02:41:19 -0800 (Fri, 11 Mar 2016) $
|
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID:
|
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||
*
|
||||
* Adopters may modify this file to suit their platform. Adopters are
|
||||
* encouraged to submit platform specific modifications to the Khronos
|
||||
* group so that they can be included in future versions of this file.
|
||||
* Please submit changes by sending them to the public Khronos Bugzilla
|
||||
* (http://khronos.org/bugzilla) by filing a bug against product
|
||||
* "Khronos (general)" component "Registry".
|
||||
*
|
||||
* A predefined template which fills in some of the bug fields can be
|
||||
* reached using http://tinyurl.com/khrplatform-h-bugreport, but you
|
||||
* must create a Bugzilla login first.
|
||||
* Please submit changes by filing pull requests or issues on
|
||||
* the EGL Registry repository linked above.
|
||||
*
|
||||
*
|
||||
* See the Implementer's Guidelines for information about where this file
|
||||
|
||||
56
externals/glad/include/glad/glad.h
vendored
56
externals/glad/include/glad/glad.h
vendored
File diff suppressed because one or more lines are too long
1962
externals/glad/src/glad.c
vendored
1962
externals/glad/src/glad.c
vendored
File diff suppressed because one or more lines are too long
1
externals/mbedtls
vendored
Submodule
1
externals/mbedtls
vendored
Submodule
Submodule externals/mbedtls added at 06442b840e
1
externals/opus
vendored
Submodule
1
externals/opus
vendored
Submodule
Submodule externals/opus added at b2871922a1
2
externals/xbyak
vendored
2
externals/xbyak
vendored
Submodule externals/xbyak updated: 2794cde79e...71b75f653f
@@ -3,6 +3,7 @@ include_directories(.)
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(audio_core)
|
||||
add_subdirectory(video_core)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(tests)
|
||||
|
||||
23
src/audio_core/CMakeLists.txt
Normal file
23
src/audio_core/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
add_library(audio_core STATIC
|
||||
audio_out.cpp
|
||||
audio_out.h
|
||||
buffer.h
|
||||
cubeb_sink.cpp
|
||||
cubeb_sink.h
|
||||
null_sink.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
sink.h
|
||||
sink_details.cpp
|
||||
sink_details.h
|
||||
sink_stream.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
|
||||
target_link_libraries(audio_core PUBLIC common core)
|
||||
|
||||
if(ENABLE_CUBEB)
|
||||
target_link_libraries(audio_core PRIVATE cubeb)
|
||||
target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
|
||||
endif()
|
||||
58
src/audio_core/audio_out.cpp
Normal file
58
src/audio_core/audio_out.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/// Returns the stream format from the specified number of channels
|
||||
static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
return Stream::Format::Mono16;
|
||||
case 2:
|
||||
return Stream::Format::Stereo16;
|
||||
case 6:
|
||||
return Stream::Format::Multi51Channel16;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Audio, "Unimplemented num_channels={}", num_channels);
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels,
|
||||
Stream::ReleaseCallback&& release_callback) {
|
||||
if (!sink) {
|
||||
const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id);
|
||||
sink = sink_details.factory(Settings::values.audio_device_id);
|
||||
}
|
||||
|
||||
return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
|
||||
std::move(release_callback),
|
||||
sink->AcquireSinkStream(sample_rate, num_channels));
|
||||
}
|
||||
|
||||
std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
|
||||
return stream->GetTagsAndReleaseBuffers(max_count);
|
||||
}
|
||||
|
||||
void AudioOut::StartStream(StreamPtr stream) {
|
||||
stream->Play();
|
||||
}
|
||||
|
||||
void AudioOut::StopStream(StreamPtr stream) {
|
||||
stream->Stop();
|
||||
}
|
||||
|
||||
bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data) {
|
||||
return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data)));
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
42
src/audio_core/audio_out.h
Normal file
42
src/audio_core/audio_out.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/buffer.h"
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/**
|
||||
* Represents an audio playback interface, used to open and play audio streams
|
||||
*/
|
||||
class AudioOut {
|
||||
public:
|
||||
/// Opens a new audio stream
|
||||
StreamPtr OpenStream(u32 sample_rate, u32 num_channels,
|
||||
Stream::ReleaseCallback&& release_callback);
|
||||
|
||||
/// Returns a vector of recently released buffers specified by tag for the specified stream
|
||||
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count);
|
||||
|
||||
/// Starts an audio stream for playback
|
||||
void StartStream(StreamPtr stream);
|
||||
|
||||
/// Stops an audio stream that is currently playing
|
||||
void StopStream(StreamPtr stream);
|
||||
|
||||
/// Queues a buffer into the specified audio stream, returns true on success
|
||||
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
|
||||
|
||||
private:
|
||||
SinkPtr sink;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
40
src/audio_core/buffer.h
Normal file
40
src/audio_core/buffer.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/**
|
||||
* Represents a buffer of audio samples to be played in an audio stream
|
||||
*/
|
||||
class Buffer {
|
||||
public:
|
||||
using Tag = u64;
|
||||
|
||||
Buffer(Tag tag, std::vector<u8>&& data) : tag{tag}, data{std::move(data)} {}
|
||||
|
||||
/// Returns the raw audio data for the buffer
|
||||
const std::vector<u8>& GetData() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Returns the buffer tag, this is provided by the game to the audout service
|
||||
Tag GetTag() const {
|
||||
return tag;
|
||||
}
|
||||
|
||||
private:
|
||||
Tag tag;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
using BufferPtr = std::shared_ptr<Buffer>;
|
||||
|
||||
} // namespace AudioCore
|
||||
190
src/audio_core/cubeb_sink.cpp
Normal file
190
src/audio_core/cubeb_sink.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class SinkStreamImpl final : public SinkStream {
|
||||
public:
|
||||
SinkStreamImpl(cubeb* ctx, cubeb_devid output_device) : ctx{ctx} {
|
||||
cubeb_stream_params params;
|
||||
params.rate = 48000;
|
||||
params.channels = GetNumChannels();
|
||||
params.format = CUBEB_SAMPLE_S16NE;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
u32 minimum_latency = 0;
|
||||
if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
|
||||
}
|
||||
|
||||
if (cubeb_stream_init(ctx, &stream_backend, "yuzu Audio Output", nullptr, nullptr,
|
||||
output_device, ¶ms, std::max(512u, minimum_latency),
|
||||
&SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback,
|
||||
this) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~SinkStreamImpl() {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
|
||||
}
|
||||
|
||||
cubeb_stream_destroy(stream_backend);
|
||||
}
|
||||
|
||||
void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) override {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
queue.reserve(queue.size() + sample_count * GetNumChannels());
|
||||
|
||||
if (num_channels == 2) {
|
||||
// Copy as-is
|
||||
std::copy(samples, samples + sample_count * GetNumChannels(),
|
||||
std::back_inserter(queue));
|
||||
} else if (num_channels == 6) {
|
||||
// Downsample 6 channels to 2
|
||||
const size_t sample_count_copy_size = sample_count * num_channels * 2;
|
||||
queue.reserve(sample_count_copy_size);
|
||||
for (size_t i = 0; i < sample_count * num_channels; i += num_channels) {
|
||||
queue.push_back(samples[i]);
|
||||
queue.push_back(samples[i + 1]);
|
||||
}
|
||||
} else {
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetNumChannels() const {
|
||||
// Only support 2-channel stereo output for now
|
||||
return 2;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> device_list;
|
||||
|
||||
cubeb* ctx{};
|
||||
cubeb_stream* stream_backend{};
|
||||
|
||||
std::vector<s16> queue;
|
||||
|
||||
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* output_buffer, long num_frames);
|
||||
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
|
||||
};
|
||||
|
||||
CubebSink::CubebSink(std::string target_device_name) {
|
||||
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_device_name != auto_device_name && !target_device_name.empty()) {
|
||||
cubeb_device_collection collection;
|
||||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
|
||||
} else {
|
||||
const auto collection_end{collection.device + collection.count};
|
||||
const auto device{std::find_if(collection.device, collection_end,
|
||||
[&](const cubeb_device_info& device) {
|
||||
return target_device_name == device.friendly_name;
|
||||
})};
|
||||
if (device != collection_end) {
|
||||
output_device = device->devid;
|
||||
}
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CubebSink::~CubebSink() {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& sink_stream : sink_streams) {
|
||||
sink_stream.reset();
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
}
|
||||
|
||||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels) {
|
||||
sink_streams.push_back(std::make_unique<SinkStreamImpl>(ctx, output_device));
|
||||
return *sink_streams.back();
|
||||
}
|
||||
|
||||
long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* output_buffer, long num_frames) {
|
||||
SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data);
|
||||
u8* buffer = reinterpret_cast<u8*>(output_buffer);
|
||||
|
||||
if (!impl) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const size_t frames_to_write{
|
||||
std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))};
|
||||
|
||||
memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels());
|
||||
impl->queue.erase(impl->queue.begin(),
|
||||
impl->queue.begin() + frames_to_write * impl->GetNumChannels());
|
||||
|
||||
if (frames_to_write < num_frames) {
|
||||
// Fill the rest of the frames with silence
|
||||
memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0,
|
||||
(num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels());
|
||||
}
|
||||
|
||||
return num_frames;
|
||||
}
|
||||
|
||||
void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
|
||||
|
||||
std::vector<std::string> ListCubebSinkDevices() {
|
||||
std::vector<std::string> device_list;
|
||||
cubeb* ctx;
|
||||
|
||||
if (cubeb_init(&ctx, "Citra Device Enumerator", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
cubeb_device_collection collection;
|
||||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
|
||||
} else {
|
||||
for (size_t i = 0; i < collection.count; i++) {
|
||||
const cubeb_device_info& device = collection.device[i];
|
||||
if (device.friendly_name) {
|
||||
device_list.emplace_back(device.friendly_name);
|
||||
}
|
||||
}
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
return device_list;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
31
src/audio_core/cubeb_sink.h
Normal file
31
src/audio_core/cubeb_sink.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cubeb/cubeb.h>
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class CubebSink final : public Sink {
|
||||
public:
|
||||
explicit CubebSink(std::string device_id);
|
||||
~CubebSink() override;
|
||||
|
||||
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override;
|
||||
|
||||
private:
|
||||
cubeb* ctx{};
|
||||
cubeb_devid output_device{};
|
||||
std::vector<SinkStreamPtr> sink_streams;
|
||||
};
|
||||
|
||||
std::vector<std::string> ListCubebSinkDevices();
|
||||
|
||||
} // namespace AudioCore
|
||||
27
src/audio_core/null_sink.h
Normal file
27
src/audio_core/null_sink.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class NullSink final : public Sink {
|
||||
public:
|
||||
explicit NullSink(std::string){};
|
||||
~NullSink() override = default;
|
||||
|
||||
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/) override {
|
||||
return null_sink_stream;
|
||||
}
|
||||
|
||||
private:
|
||||
struct NullSinkStreamImpl final : SinkStream {
|
||||
void EnqueueSamples(u32 /*num_channels*/, const s16* /*samples*/,
|
||||
size_t /*sample_count*/) override {}
|
||||
} null_sink_stream;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
29
src/audio_core/sink.h
Normal file
29
src/audio_core/sink.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "audio_core/sink_stream.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr char auto_device_name[] = "auto";
|
||||
|
||||
/**
|
||||
* This class is an interface for an audio sink. An audio sink accepts samples in stereo signed
|
||||
* PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate.
|
||||
* They are dumb outputs.
|
||||
*/
|
||||
class Sink {
|
||||
public:
|
||||
virtual ~Sink() = default;
|
||||
virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) = 0;
|
||||
};
|
||||
|
||||
using SinkPtr = std::unique_ptr<Sink>;
|
||||
|
||||
} // namespace AudioCore
|
||||
44
src/audio_core/sink_details.cpp
Normal file
44
src/audio_core/sink_details.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "audio_core/null_sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#endif
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
// g_sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||
const std::vector<SinkDetails> g_sink_details = {
|
||||
#ifdef HAVE_CUBEB
|
||||
SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
|
||||
#endif
|
||||
SinkDetails{"null", &std::make_unique<NullSink, std::string>,
|
||||
[] { return std::vector<std::string>{"null"}; }},
|
||||
};
|
||||
|
||||
const SinkDetails& GetSinkDetails(std::string sink_id) {
|
||||
auto iter =
|
||||
std::find_if(g_sink_details.begin(), g_sink_details.end(),
|
||||
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
||||
|
||||
if (sink_id == "auto" || iter == g_sink_details.end()) {
|
||||
if (sink_id != "auto") {
|
||||
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
|
||||
}
|
||||
// Auto-select.
|
||||
// g_sink_details is ordered in terms of desirability, with the best choice at the front.
|
||||
iter = g_sink_details.begin();
|
||||
}
|
||||
|
||||
return *iter;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
35
src/audio_core/sink_details.h
Normal file
35
src/audio_core/sink_details.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class Sink;
|
||||
|
||||
struct SinkDetails {
|
||||
using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>;
|
||||
using ListDevicesFn = std::function<std::vector<std::string>()>;
|
||||
|
||||
SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_)
|
||||
: id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {}
|
||||
|
||||
/// Name for this sink.
|
||||
const char* id;
|
||||
/// A method to call to construct an instance of this type of sink.
|
||||
FactoryFn factory;
|
||||
/// A method to call to list available devices.
|
||||
ListDevicesFn list_devices;
|
||||
};
|
||||
|
||||
extern const std::vector<SinkDetails> g_sink_details;
|
||||
|
||||
const SinkDetails& GetSinkDetails(std::string sink_id);
|
||||
|
||||
} // namespace AudioCore
|
||||
32
src/audio_core/sink_stream.h
Normal file
32
src/audio_core/sink_stream.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/**
|
||||
* Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and
|
||||
* expect the correct sample rate. They are dumb outputs.
|
||||
*/
|
||||
class SinkStream {
|
||||
public:
|
||||
virtual ~SinkStream() = default;
|
||||
|
||||
/**
|
||||
* Feed stereo samples to sink.
|
||||
* @param num_channels Number of channels used.
|
||||
* @param samples Samples in interleaved stereo PCM16 format.
|
||||
* @param sample_count Number of samples.
|
||||
*/
|
||||
virtual void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) = 0;
|
||||
};
|
||||
|
||||
using SinkStreamPtr = std::unique_ptr<SinkStream>;
|
||||
|
||||
} // namespace AudioCore
|
||||
135
src/audio_core/stream.cpp
Normal file
135
src/audio_core/stream.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr size_t MaxAudioBufferCount{32};
|
||||
|
||||
u32 Stream::GetNumChannels() const {
|
||||
switch (format) {
|
||||
case Format::Mono16:
|
||||
return 1;
|
||||
case Format::Stereo16:
|
||||
return 2;
|
||||
case Format::Multi51Channel16:
|
||||
return 6;
|
||||
}
|
||||
LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
u32 Stream::GetSampleSize() const {
|
||||
return GetNumChannels() * 2;
|
||||
}
|
||||
|
||||
Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream)
|
||||
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
|
||||
sink_stream{sink_stream} {
|
||||
|
||||
release_event = CoreTiming::RegisterEvent(
|
||||
"Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
|
||||
}
|
||||
|
||||
void Stream::Play() {
|
||||
state = State::Playing;
|
||||
PlayNextBuffer();
|
||||
}
|
||||
|
||||
void Stream::Stop() {
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
}
|
||||
|
||||
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
|
||||
const size_t num_samples{buffer.GetData().size() / GetSampleSize()};
|
||||
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
}
|
||||
|
||||
static std::vector<s16> GetVolumeAdjustedSamples(const std::vector<u8>& data) {
|
||||
std::vector<s16> samples(data.size() / sizeof(s16));
|
||||
std::memcpy(samples.data(), data.data(), data.size());
|
||||
const float volume{std::clamp(Settings::values.volume, 0.0f, 1.0f)};
|
||||
|
||||
if (volume == 1.0f) {
|
||||
return samples;
|
||||
}
|
||||
|
||||
// Implementation of a volume slider with a dynamic range of 60 dB
|
||||
const float volume_scale_factor{std::exp(6.90775f * volume) * 0.001f};
|
||||
for (auto& sample : samples) {
|
||||
sample = static_cast<s16>(sample * volume_scale_factor);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
void Stream::PlayNextBuffer() {
|
||||
if (!IsPlaying()) {
|
||||
// Ensure we are in playing state before playing the next buffer
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_buffer) {
|
||||
// Do not queue a new buffer if we are already playing a buffer
|
||||
return;
|
||||
}
|
||||
|
||||
if (queued_buffers.empty()) {
|
||||
// No queued buffers - we are effectively paused
|
||||
return;
|
||||
}
|
||||
|
||||
active_buffer = queued_buffers.front();
|
||||
queued_buffers.pop();
|
||||
|
||||
const size_t sample_count{active_buffer->GetData().size() / GetSampleSize()};
|
||||
sink_stream.EnqueueSamples(
|
||||
GetNumChannels(), GetVolumeAdjustedSamples(active_buffer->GetData()).data(), sample_count);
|
||||
|
||||
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
}
|
||||
|
||||
void Stream::ReleaseActiveBuffer() {
|
||||
released_buffers.push(std::move(active_buffer));
|
||||
release_callback();
|
||||
PlayNextBuffer();
|
||||
}
|
||||
|
||||
bool Stream::QueueBuffer(BufferPtr&& buffer) {
|
||||
if (queued_buffers.size() < MaxAudioBufferCount) {
|
||||
queued_buffers.push(std::move(buffer));
|
||||
PlayNextBuffer();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Stream::ContainsBuffer(Buffer::Tag tag) const {
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(size_t max_count) {
|
||||
std::vector<Buffer::Tag> tags;
|
||||
for (size_t count = 0; count < max_count && !released_buffers.empty(); ++count) {
|
||||
tags.push_back(released_buffers.front()->GetTag());
|
||||
released_buffers.pop();
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
103
src/audio_core/stream.h
Normal file
103
src/audio_core/stream.h
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include "audio_core/buffer.h"
|
||||
#include "audio_core/sink_stream.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/**
|
||||
* Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
|
||||
*/
|
||||
class Stream {
|
||||
public:
|
||||
/// Audio format of the stream
|
||||
enum class Format {
|
||||
Mono16,
|
||||
Stereo16,
|
||||
Multi51Channel16,
|
||||
};
|
||||
|
||||
/// Callback function type, used to change guest state on a buffer being released
|
||||
using ReleaseCallback = std::function<void()>;
|
||||
|
||||
Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream);
|
||||
|
||||
/// Plays the audio stream
|
||||
void Play();
|
||||
|
||||
/// Stops the audio stream
|
||||
void Stop();
|
||||
|
||||
/// Queues a buffer into the audio stream, returns true on success
|
||||
bool QueueBuffer(BufferPtr&& buffer);
|
||||
|
||||
/// Returns true if the audio stream contains a buffer with the specified tag
|
||||
bool ContainsBuffer(Buffer::Tag tag) const;
|
||||
|
||||
/// Returns a vector of recently released buffers specified by tag
|
||||
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(size_t max_count);
|
||||
|
||||
/// Returns true if the stream is currently playing
|
||||
bool IsPlaying() const {
|
||||
return state == State::Playing;
|
||||
}
|
||||
|
||||
/// Returns the number of queued buffers
|
||||
size_t GetQueueSize() const {
|
||||
return queued_buffers.size();
|
||||
}
|
||||
|
||||
/// Gets the sample rate
|
||||
u32 GetSampleRate() const {
|
||||
return sample_rate;
|
||||
}
|
||||
|
||||
/// Gets the number of channels
|
||||
u32 GetNumChannels() const;
|
||||
|
||||
/// Gets the sample size in bytes
|
||||
u32 GetSampleSize() const;
|
||||
|
||||
private:
|
||||
/// Current state of the stream
|
||||
enum class State {
|
||||
Stopped,
|
||||
Playing,
|
||||
};
|
||||
|
||||
/// Plays the next queued buffer in the audio stream, starting playback if necessary
|
||||
void PlayNextBuffer();
|
||||
|
||||
/// Releases the actively playing buffer, signalling that it has been completed
|
||||
void ReleaseActiveBuffer();
|
||||
|
||||
/// Gets the number of core cycles when the specified buffer will be released
|
||||
s64 GetBufferReleaseCycles(const Buffer& buffer) const;
|
||||
|
||||
u32 sample_rate; ///< Sample rate of the stream
|
||||
Format format; ///< Format of the stream
|
||||
ReleaseCallback release_callback; ///< Buffer release callback for the stream
|
||||
State state{State::Stopped}; ///< Playback state of the stream
|
||||
CoreTiming::EventType* release_event{}; ///< Core timing release event for the stream
|
||||
BufferPtr active_buffer; ///< Actively playing buffer in the stream
|
||||
std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
|
||||
std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
|
||||
SinkStream& sink_stream; ///< Output sink for the stream
|
||||
};
|
||||
|
||||
using StreamPtr = std::shared_ptr<Stream>;
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -31,10 +31,8 @@ add_library(common STATIC
|
||||
bit_set.h
|
||||
break_points.cpp
|
||||
break_points.h
|
||||
chunk_file.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
code_block.h
|
||||
color.h
|
||||
common_funcs.h
|
||||
common_paths.h
|
||||
@@ -42,7 +40,6 @@ add_library(common STATIC
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
hash.h
|
||||
linear_disk_cache.h
|
||||
logging/backend.cpp
|
||||
logging/backend.h
|
||||
logging/filter.cpp
|
||||
@@ -66,7 +63,6 @@ add_library(common STATIC
|
||||
string_util.cpp
|
||||
string_util.h
|
||||
swap.h
|
||||
synchronized_wrapper.h
|
||||
telemetry.cpp
|
||||
telemetry.h
|
||||
thread.cpp
|
||||
|
||||
@@ -52,5 +52,5 @@ __declspec(noinline, noreturn)
|
||||
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...)
|
||||
#endif
|
||||
|
||||
#define UNIMPLEMENTED() DEBUG_ASSERT_MSG(false, "Unimplemented code!")
|
||||
#define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!")
|
||||
#define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
|
||||
|
||||
@@ -1,623 +0,0 @@
|
||||
// Copyright (C) 2003 Dolphin Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official SVN repository and contact information can be found at
|
||||
// http://code.google.com/p/dolphin-emu/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Extremely simple serialization framework.
|
||||
|
||||
// (mis)-features:
|
||||
// + Super fast
|
||||
// + Very simple
|
||||
// + Same code is used for serialization and deserializaition (in most cases)
|
||||
// - Zero backwards/forwards compatibility
|
||||
// - Serialization code for anything complex has to be manually written.
|
||||
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
template <class T>
|
||||
struct LinkedListItem : public T {
|
||||
LinkedListItem<T>* next;
|
||||
};
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
class PointerWrapSection {
|
||||
public:
|
||||
PointerWrapSection(PointerWrap& p, int ver, const char* title)
|
||||
: p_(p), ver_(ver), title_(title) {}
|
||||
~PointerWrapSection();
|
||||
|
||||
bool operator==(const int& v) const {
|
||||
return ver_ == v;
|
||||
}
|
||||
bool operator!=(const int& v) const {
|
||||
return ver_ != v;
|
||||
}
|
||||
bool operator<=(const int& v) const {
|
||||
return ver_ <= v;
|
||||
}
|
||||
bool operator>=(const int& v) const {
|
||||
return ver_ >= v;
|
||||
}
|
||||
bool operator<(const int& v) const {
|
||||
return ver_ < v;
|
||||
}
|
||||
bool operator>(const int& v) const {
|
||||
return ver_ > v;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return ver_ > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
PointerWrap& p_;
|
||||
int ver_;
|
||||
const char* title_;
|
||||
};
|
||||
|
||||
// Wrapper class
|
||||
class PointerWrap {
|
||||
// This makes it a compile error if you forget to define DoState() on non-POD.
|
||||
// Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason...
|
||||
#ifdef _MSC_VER
|
||||
template <typename T, bool isPOD = std::is_pod<T>::value,
|
||||
bool isPointer = std::is_pointer<T>::value>
|
||||
#else
|
||||
template <typename T, bool isPOD = __is_pod(T), bool isPointer = std::is_pointer<T>::value>
|
||||
#endif
|
||||
struct DoHelper {
|
||||
static void DoArray(PointerWrap* p, T* x, int count) {
|
||||
for (int i = 0; i < count; ++i)
|
||||
p->Do(x[i]);
|
||||
}
|
||||
|
||||
static void Do(PointerWrap* p, T& x) {
|
||||
p->DoClass(x);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct DoHelper<T, true, false> {
|
||||
static void DoArray(PointerWrap* p, T* x, int count) {
|
||||
p->DoVoid((void*)x, sizeof(T) * count);
|
||||
}
|
||||
|
||||
static void Do(PointerWrap* p, T& x) {
|
||||
p->DoVoid((void*)&x, sizeof(x));
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
enum Mode {
|
||||
MODE_READ = 1, // load
|
||||
MODE_WRITE, // save
|
||||
MODE_MEASURE, // calculate size
|
||||
MODE_VERIFY, // compare
|
||||
};
|
||||
|
||||
enum Error {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_WARNING = 1,
|
||||
ERROR_FAILURE = 2,
|
||||
};
|
||||
|
||||
u8** ptr;
|
||||
Mode mode;
|
||||
Error error;
|
||||
|
||||
public:
|
||||
PointerWrap(u8** ptr_, Mode mode_) : ptr(ptr_), mode(mode_), error(ERROR_NONE) {}
|
||||
PointerWrap(unsigned char** ptr_, int mode_)
|
||||
: ptr((u8**)ptr_), mode((Mode)mode_), error(ERROR_NONE) {}
|
||||
|
||||
PointerWrapSection Section(const char* title, int ver) {
|
||||
return Section(title, ver, ver);
|
||||
}
|
||||
|
||||
// The returned object can be compared against the version that was loaded.
|
||||
// This can be used to support versions as old as minVer.
|
||||
// Version = 0 means the section was not found.
|
||||
PointerWrapSection Section(const char* title, int minVer, int ver) {
|
||||
char marker[16] = {0};
|
||||
int foundVersion = ver;
|
||||
|
||||
strncpy(marker, title, sizeof(marker));
|
||||
if (!ExpectVoid(marker, sizeof(marker))) {
|
||||
// Might be before we added name markers for safety.
|
||||
if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion)))
|
||||
DoMarker(title);
|
||||
// Wasn't found, but maybe we can still load the state.
|
||||
else
|
||||
foundVersion = 0;
|
||||
} else
|
||||
Do(foundVersion);
|
||||
|
||||
if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) {
|
||||
LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion,
|
||||
title);
|
||||
SetError(ERROR_FAILURE);
|
||||
return PointerWrapSection(*this, -1, title);
|
||||
}
|
||||
return PointerWrapSection(*this, foundVersion, title);
|
||||
}
|
||||
|
||||
void SetMode(Mode mode_) {
|
||||
mode = mode_;
|
||||
}
|
||||
Mode GetMode() const {
|
||||
return mode;
|
||||
}
|
||||
u8** GetPPtr() {
|
||||
return ptr;
|
||||
}
|
||||
void SetError(Error error_) {
|
||||
if (error < error_)
|
||||
error = error_;
|
||||
if (error > ERROR_WARNING)
|
||||
mode = PointerWrap::MODE_MEASURE;
|
||||
}
|
||||
|
||||
bool ExpectVoid(void* data, int size) {
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
if (memcmp(data, *ptr, size) != 0)
|
||||
return false;
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, data, size);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break; // MODE_MEASURE - don't need to do anything
|
||||
case MODE_VERIFY:
|
||||
for (int i = 0; i < size; i++) {
|
||||
DEBUG_ASSERT_MSG(
|
||||
((u8*)data)[i] == (*ptr)[i],
|
||||
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
||||
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
|
||||
&(*ptr)[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break; // throw an error?
|
||||
}
|
||||
(*ptr) += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DoVoid(void* data, int size) {
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
memcpy(data, *ptr, size);
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, data, size);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break; // MODE_MEASURE - don't need to do anything
|
||||
case MODE_VERIFY:
|
||||
for (int i = 0; i < size; i++) {
|
||||
DEBUG_ASSERT_MSG(
|
||||
((u8*)data)[i] == (*ptr)[i],
|
||||
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
||||
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
|
||||
&(*ptr)[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break; // throw an error?
|
||||
}
|
||||
(*ptr) += size;
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::map<K, T*>& x) {
|
||||
if (mode == MODE_READ) {
|
||||
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||
if (it->second != nullptr)
|
||||
delete it->second;
|
||||
}
|
||||
}
|
||||
T* dv = nullptr;
|
||||
DoMap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::map<K, T>& x) {
|
||||
T dv = T();
|
||||
DoMap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void DoMap(std::map<K, T>& x, T& default_val) {
|
||||
unsigned int number = (unsigned int)x.size();
|
||||
Do(number);
|
||||
switch (mode) {
|
||||
case MODE_READ: {
|
||||
x.clear();
|
||||
while (number > 0) {
|
||||
K first = K();
|
||||
Do(first);
|
||||
T second = default_val;
|
||||
Do(second);
|
||||
x[first] = second;
|
||||
--number;
|
||||
}
|
||||
} break;
|
||||
case MODE_WRITE:
|
||||
case MODE_MEASURE:
|
||||
case MODE_VERIFY: {
|
||||
typename std::map<K, T>::iterator itr = x.begin();
|
||||
while (number > 0) {
|
||||
K first = itr->first;
|
||||
Do(first);
|
||||
Do(itr->second);
|
||||
--number;
|
||||
++itr;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::multimap<K, T*>& x) {
|
||||
if (mode == MODE_READ) {
|
||||
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||
if (it->second != nullptr)
|
||||
delete it->second;
|
||||
}
|
||||
}
|
||||
T* dv = nullptr;
|
||||
DoMultimap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::multimap<K, T>& x) {
|
||||
T dv = T();
|
||||
DoMultimap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void DoMultimap(std::multimap<K, T>& x, T& default_val) {
|
||||
unsigned int number = (unsigned int)x.size();
|
||||
Do(number);
|
||||
switch (mode) {
|
||||
case MODE_READ: {
|
||||
x.clear();
|
||||
while (number > 0) {
|
||||
K first = K();
|
||||
Do(first);
|
||||
T second = default_val;
|
||||
Do(second);
|
||||
x.insert(std::make_pair(first, second));
|
||||
--number;
|
||||
}
|
||||
} break;
|
||||
case MODE_WRITE:
|
||||
case MODE_MEASURE:
|
||||
case MODE_VERIFY: {
|
||||
typename std::multimap<K, T>::iterator itr = x.begin();
|
||||
while (number > 0) {
|
||||
Do(itr->first);
|
||||
Do(itr->second);
|
||||
--number;
|
||||
++itr;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Store vectors.
|
||||
template <class T>
|
||||
void Do(std::vector<T*>& x) {
|
||||
T* dv = nullptr;
|
||||
DoVector(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::vector<T>& x) {
|
||||
T dv = T();
|
||||
DoVector(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoPOD(std::vector<T>& x) {
|
||||
T dv = T();
|
||||
DoVectorPOD(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::vector<T>& x, T& default_val) {
|
||||
DoVector(x, default_val);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoVector(std::vector<T>& x, T& default_val) {
|
||||
u32 vec_size = (u32)x.size();
|
||||
Do(vec_size);
|
||||
x.resize(vec_size, default_val);
|
||||
if (vec_size > 0)
|
||||
DoArray(&x[0], vec_size);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoVectorPOD(std::vector<T>& x, T& default_val) {
|
||||
u32 vec_size = (u32)x.size();
|
||||
Do(vec_size);
|
||||
x.resize(vec_size, default_val);
|
||||
if (vec_size > 0)
|
||||
DoArray(&x[0], vec_size);
|
||||
}
|
||||
|
||||
// Store deques.
|
||||
template <class T>
|
||||
void Do(std::deque<T*>& x) {
|
||||
T* dv = nullptr;
|
||||
DoDeque(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::deque<T>& x) {
|
||||
T dv = T();
|
||||
DoDeque(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoDeque(std::deque<T>& x, T& default_val) {
|
||||
u32 deq_size = (u32)x.size();
|
||||
Do(deq_size);
|
||||
x.resize(deq_size, default_val);
|
||||
u32 i;
|
||||
for (i = 0; i < deq_size; i++)
|
||||
Do(x[i]);
|
||||
}
|
||||
|
||||
// Store STL lists.
|
||||
template <class T>
|
||||
void Do(std::list<T*>& x) {
|
||||
T* dv = nullptr;
|
||||
Do(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::list<T>& x) {
|
||||
T dv = T();
|
||||
DoList(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::list<T>& x, T& default_val) {
|
||||
DoList(x, default_val);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoList(std::list<T>& x, T& default_val) {
|
||||
u32 list_size = (u32)x.size();
|
||||
Do(list_size);
|
||||
x.resize(list_size, default_val);
|
||||
|
||||
typename std::list<T>::iterator itr, end;
|
||||
for (itr = x.begin(), end = x.end(); itr != end; ++itr)
|
||||
Do(*itr);
|
||||
}
|
||||
|
||||
// Store STL sets.
|
||||
template <class T>
|
||||
void Do(std::set<T*>& x) {
|
||||
if (mode == MODE_READ) {
|
||||
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||
if (*it != nullptr)
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
DoSet(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::set<T>& x) {
|
||||
DoSet(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoSet(std::set<T>& x) {
|
||||
unsigned int number = (unsigned int)x.size();
|
||||
Do(number);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_READ: {
|
||||
x.clear();
|
||||
while (number-- > 0) {
|
||||
T it = T();
|
||||
Do(it);
|
||||
x.insert(it);
|
||||
}
|
||||
} break;
|
||||
case MODE_WRITE:
|
||||
case MODE_MEASURE:
|
||||
case MODE_VERIFY: {
|
||||
typename std::set<T>::iterator itr = x.begin();
|
||||
while (number-- > 0)
|
||||
Do(*itr++);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Store strings.
|
||||
void Do(std::string& x) {
|
||||
int stringLen = (int)x.length() + 1;
|
||||
Do(stringLen);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
x = (char*)*ptr;
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, x.c_str(), stringLen);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break;
|
||||
case MODE_VERIFY:
|
||||
DEBUG_ASSERT_MSG((x == (char*)*ptr),
|
||||
"Savestate verification failure: \"%s\" != \"%s\" (at %p).\n",
|
||||
x.c_str(), (char*)*ptr, ptr);
|
||||
break;
|
||||
}
|
||||
(*ptr) += stringLen;
|
||||
}
|
||||
|
||||
void Do(std::wstring& x) {
|
||||
int stringLen = sizeof(wchar_t) * ((int)x.length() + 1);
|
||||
Do(stringLen);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
x = (wchar_t*)*ptr;
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, x.c_str(), stringLen);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break;
|
||||
case MODE_VERIFY:
|
||||
DEBUG_ASSERT_MSG((x == (wchar_t*)*ptr),
|
||||
"Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n",
|
||||
x.c_str(), (wchar_t*)*ptr, ptr);
|
||||
break;
|
||||
}
|
||||
(*ptr) += stringLen;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoClass(T& x) {
|
||||
x.DoState(*this);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoClass(T*& x) {
|
||||
if (mode == MODE_READ) {
|
||||
if (x != nullptr)
|
||||
delete x;
|
||||
x = new T();
|
||||
}
|
||||
x->DoState(*this);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoArray(T* x, int count) {
|
||||
DoHelper<T>::DoArray(this, x, count);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(T& x) {
|
||||
DoHelper<T>::Do(this, x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoPOD(T& x) {
|
||||
DoHelper<T>::Do(this, x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoPointer(T*& x, T* const base) {
|
||||
// pointers can be more than 2^31 apart, but you're using this function wrong if you need
|
||||
// that much range
|
||||
s32 offset = x - base;
|
||||
Do(offset);
|
||||
if (mode == MODE_READ)
|
||||
x = base + offset;
|
||||
}
|
||||
|
||||
template <class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*),
|
||||
void (*TDo)(PointerWrap&, T*)>
|
||||
void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = nullptr) {
|
||||
LinkedListItem<T>* list_cur = list_start;
|
||||
LinkedListItem<T>* prev = nullptr;
|
||||
|
||||
while (true) {
|
||||
u8 shouldExist = (list_cur ? 1 : 0);
|
||||
Do(shouldExist);
|
||||
if (shouldExist == 1) {
|
||||
LinkedListItem<T>* cur = list_cur ? list_cur : TNew();
|
||||
TDo(*this, (T*)cur);
|
||||
if (!list_cur) {
|
||||
if (mode == MODE_READ) {
|
||||
cur->next = nullptr;
|
||||
list_cur = cur;
|
||||
if (prev)
|
||||
prev->next = cur;
|
||||
else
|
||||
list_start = cur;
|
||||
} else {
|
||||
TFree(cur);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mode == MODE_READ) {
|
||||
if (prev)
|
||||
prev->next = nullptr;
|
||||
if (list_end)
|
||||
*list_end = prev;
|
||||
if (list_cur) {
|
||||
if (list_start == list_cur)
|
||||
list_start = nullptr;
|
||||
do {
|
||||
LinkedListItem<T>* next = list_cur->next;
|
||||
TFree(list_cur);
|
||||
list_cur = next;
|
||||
} while (list_cur);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev = list_cur;
|
||||
list_cur = list_cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
void DoMarker(const char* prevName, u32 arbitraryNumber = 0x42) {
|
||||
u32 cookie = arbitraryNumber;
|
||||
Do(cookie);
|
||||
if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) {
|
||||
LOG_ERROR(Common,
|
||||
"After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). "
|
||||
"Aborting savestate load...",
|
||||
prevName, cookie, cookie, arbitraryNumber, arbitraryNumber);
|
||||
SetError(ERROR_FAILURE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline PointerWrapSection::~PointerWrapSection() {
|
||||
if (ver_ > 0) {
|
||||
p_.DoMarker(title_);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_util.h"
|
||||
|
||||
// Everything that needs to generate code should inherit from this.
|
||||
// You get memory management for free, plus, you can use all emitter functions without
|
||||
// having to prefix them with gen-> or something similar.
|
||||
// Example implementation:
|
||||
// class JIT : public CodeBlock<ARMXEmitter> {}
|
||||
template <class T>
|
||||
class CodeBlock : public T, NonCopyable {
|
||||
private:
|
||||
// A privately used function to set the executable RAM space to something invalid.
|
||||
// For debugging usefulness it should be used to set the RAM to a host specific breakpoint
|
||||
// instruction
|
||||
virtual void PoisonMemory() = 0;
|
||||
|
||||
protected:
|
||||
u8* region;
|
||||
size_t region_size;
|
||||
|
||||
public:
|
||||
CodeBlock() : region(nullptr), region_size(0) {}
|
||||
virtual ~CodeBlock() {
|
||||
if (region)
|
||||
FreeCodeSpace();
|
||||
}
|
||||
|
||||
// Call this before you generate any code.
|
||||
void AllocCodeSpace(int size) {
|
||||
region_size = size;
|
||||
region = (u8*)AllocateExecutableMemory(region_size);
|
||||
T::SetCodePtr(region);
|
||||
}
|
||||
|
||||
// Always clear code space with breakpoints, so that if someone accidentally executes
|
||||
// uninitialized, it just breaks into the debugger.
|
||||
void ClearCodeSpace() {
|
||||
PoisonMemory();
|
||||
ResetCodePtr();
|
||||
}
|
||||
|
||||
// Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
|
||||
void FreeCodeSpace() {
|
||||
#ifdef __SYMBIAN32__
|
||||
ResetExecutableMemory(region);
|
||||
#else
|
||||
FreeMemoryPages(region, region_size);
|
||||
#endif
|
||||
region = nullptr;
|
||||
region_size = 0;
|
||||
}
|
||||
|
||||
bool IsInSpace(const u8* ptr) {
|
||||
return (ptr >= region) && (ptr < (region + region_size));
|
||||
}
|
||||
|
||||
// Cannot currently be undone. Will write protect the entire code region.
|
||||
// Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()).
|
||||
void WriteProtect() {
|
||||
WriteProtectMemory(region, region_size, true);
|
||||
}
|
||||
|
||||
void ResetCodePtr() {
|
||||
T::SetCodePtr(region);
|
||||
}
|
||||
|
||||
size_t GetSpaceLeft() const {
|
||||
return region_size - (T::GetCodePtr() - region);
|
||||
}
|
||||
|
||||
u8* GetBasePtr() {
|
||||
return region;
|
||||
}
|
||||
|
||||
size_t GetOffset(const u8* ptr) const {
|
||||
return ptr - region;
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(ARCHITECTURE_x86_64) && !defined(_M_ARM)
|
||||
#include <string>
|
||||
|
||||
#if !defined(ARCHITECTURE_x86_64)
|
||||
#include <cstdlib> // for exit
|
||||
#endif
|
||||
#include "common/common_types.h"
|
||||
@@ -30,53 +32,12 @@
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#define Crash() __asm__ __volatile__("int $3")
|
||||
#elif defined(_M_ARM)
|
||||
#define Crash() __asm__ __volatile__("trap")
|
||||
#else
|
||||
#define Crash() exit(1)
|
||||
#endif
|
||||
|
||||
// GCC 4.8 defines all the rotate functions now
|
||||
// Small issue with GCC's lrotl/lrotr intrinsics is they are still 32bit while we require 64bit
|
||||
#ifdef _rotl
|
||||
#define rotl _rotl
|
||||
#else
|
||||
inline u32 rotl(u32 x, int shift) {
|
||||
shift &= 31;
|
||||
if (!shift)
|
||||
return x;
|
||||
return (x << shift) | (x >> (32 - shift));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _rotr
|
||||
#define rotr _rotr
|
||||
#else
|
||||
inline u32 rotr(u32 x, int shift) {
|
||||
shift &= 31;
|
||||
if (!shift)
|
||||
return x;
|
||||
return (x >> shift) | (x << (32 - shift));
|
||||
}
|
||||
#endif
|
||||
|
||||
inline u64 _rotl64(u64 x, unsigned int shift) {
|
||||
unsigned int n = shift % 64;
|
||||
return (x << n) | (x >> (64 - n));
|
||||
}
|
||||
|
||||
inline u64 _rotr64(u64 x, unsigned int shift) {
|
||||
unsigned int n = shift % 64;
|
||||
return (x >> n) | (x << (64 - n));
|
||||
}
|
||||
|
||||
#else // _MSC_VER
|
||||
|
||||
#if (_MSC_VER < 1900)
|
||||
// Function Cross-Compatibility
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
// Locale Cross-Compatibility
|
||||
#define locale_t _locale_t
|
||||
|
||||
@@ -85,17 +46,13 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
}
|
||||
#define Crash() DebugBreak()
|
||||
|
||||
// cstdlib provides these on MSVC
|
||||
#define rotr _rotr
|
||||
#define rotl _rotl
|
||||
|
||||
#endif // _MSC_VER ndef
|
||||
|
||||
// Generic function to get last error message.
|
||||
// Call directly after the command or use the error num.
|
||||
// This function might change the error code.
|
||||
// Defined in Misc.cpp.
|
||||
const char* GetLastErrorMsg();
|
||||
std::string GetLastErrorMsg();
|
||||
|
||||
namespace Common {
|
||||
|
||||
|
||||
@@ -26,18 +26,22 @@
|
||||
#define USA_DIR "USA"
|
||||
#define JAP_DIR "JAP"
|
||||
|
||||
// Subdirs in the User dir returned by GetUserPath(D_USER_IDX)
|
||||
// Subdirs in the User dir returned by GetUserPath(UserPath::UserDir)
|
||||
#define CONFIG_DIR "config"
|
||||
#define CACHE_DIR "cache"
|
||||
#define SDMC_DIR "sdmc"
|
||||
#define NAND_DIR "nand"
|
||||
#define SYSDATA_DIR "sysdata"
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOG_DIR "log"
|
||||
|
||||
// Filenames
|
||||
// Files in the directory returned by GetUserPath(D_CONFIG_IDX)
|
||||
// Files in the directory returned by GetUserPath(UserPath::ConfigDir)
|
||||
#define EMU_CONFIG "emu.ini"
|
||||
#define DEBUGGER_CONFIG "debugger.ini"
|
||||
#define LOGGER_CONFIG "logger.ini"
|
||||
// Files in the directory returned by GetUserPath(UserPath::LogDir)
|
||||
#define LOG_FILE "yuzu_log.txt"
|
||||
|
||||
// Sys files
|
||||
#define SHARED_FONT "shared_font.bin"
|
||||
|
||||
@@ -27,29 +27,23 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifndef __func__
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
#endif
|
||||
using u8 = std::uint8_t; ///< 8-bit unsigned byte
|
||||
using u16 = std::uint16_t; ///< 16-bit unsigned short
|
||||
using u32 = std::uint32_t; ///< 32-bit unsigned word
|
||||
using u64 = std::uint64_t; ///< 64-bit unsigned int
|
||||
|
||||
typedef std::uint8_t u8; ///< 8-bit unsigned byte
|
||||
typedef std::uint16_t u16; ///< 16-bit unsigned short
|
||||
typedef std::uint32_t u32; ///< 32-bit unsigned word
|
||||
typedef std::uint64_t u64; ///< 64-bit unsigned int
|
||||
using s8 = std::int8_t; ///< 8-bit signed byte
|
||||
using s16 = std::int16_t; ///< 16-bit signed short
|
||||
using s32 = std::int32_t; ///< 32-bit signed word
|
||||
using s64 = std::int64_t; ///< 64-bit signed int
|
||||
|
||||
typedef std::int8_t s8; ///< 8-bit signed byte
|
||||
typedef std::int16_t s16; ///< 16-bit signed short
|
||||
typedef std::int32_t s32; ///< 32-bit signed word
|
||||
typedef std::int64_t s64; ///< 64-bit signed int
|
||||
|
||||
typedef float f32; ///< 32-bit floating point
|
||||
typedef double f64; ///< 64-bit floating point
|
||||
using f32 = float; ///< 32-bit floating point
|
||||
using f64 = double; ///< 64-bit floating point
|
||||
|
||||
// TODO: It would be nice to eventually replace these with strong types that prevent accidental
|
||||
// conversion between each other.
|
||||
typedef u64 VAddr; ///< Represents a pointer in the userspace virtual address space.
|
||||
typedef u64 PAddr; ///< Represents a pointer in the ARM11 physical address space.
|
||||
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
|
||||
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
|
||||
|
||||
using u128 = std::array<std::uint64_t, 2>;
|
||||
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
@@ -118,7 +122,7 @@ bool IsDirectory(const std::string& filename) {
|
||||
#endif
|
||||
|
||||
if (result < 0) {
|
||||
LOG_DEBUG(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -128,31 +132,29 @@ bool IsDirectory(const std::string& filename) {
|
||||
// Deletes a given filename, return true on success
|
||||
// Doesn't supports deleting a directory
|
||||
bool Delete(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "file %s", filename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "file {}", filename);
|
||||
|
||||
// Return true because we care about the file no
|
||||
// being there, not the actual delete.
|
||||
if (!Exists(filename)) {
|
||||
LOG_DEBUG(Common_Filesystem, "%s does not exist", filename.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can't delete a directory
|
||||
if (IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
|
||||
LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s", filename.c_str(),
|
||||
GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (unlink(filename.c_str()) == -1) {
|
||||
LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s", filename.c_str(),
|
||||
GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
@@ -162,16 +164,16 @@ bool Delete(const std::string& filename) {
|
||||
|
||||
// Returns true if successful, or path already exists.
|
||||
bool CreateDir(const std::string& path) {
|
||||
LOG_TRACE(Common_Filesystem, "directory %s", path.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", path);
|
||||
#ifdef _WIN32
|
||||
if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
|
||||
return true;
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_ALREADY_EXISTS) {
|
||||
LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
|
||||
return true;
|
||||
}
|
||||
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error);
|
||||
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
|
||||
return false;
|
||||
#else
|
||||
if (mkdir(path.c_str(), 0755) == 0)
|
||||
@@ -180,11 +182,11 @@ bool CreateDir(const std::string& path) {
|
||||
int err = errno;
|
||||
|
||||
if (err == EEXIST) {
|
||||
LOG_DEBUG(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "mkdir failed on %s: %s", path.c_str(), strerror(err));
|
||||
LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -192,10 +194,10 @@ bool CreateDir(const std::string& path) {
|
||||
// Creates the full path of fullPath returns true on success
|
||||
bool CreateFullPath(const std::string& fullPath) {
|
||||
int panicCounter = 100;
|
||||
LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
|
||||
|
||||
if (FileUtil::Exists(fullPath)) {
|
||||
LOG_DEBUG(Common_Filesystem, "path exists %s", fullPath.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -227,11 +229,11 @@ bool CreateFullPath(const std::string& fullPath) {
|
||||
|
||||
// Deletes a directory filename, returns true on success
|
||||
bool DeleteDir(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "directory %s", filename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", filename);
|
||||
|
||||
// check if a directory
|
||||
if (!FileUtil::IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -242,14 +244,14 @@ bool DeleteDir(const std::string& filename) {
|
||||
if (rmdir(filename.c_str()) == 0)
|
||||
return true;
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// renames file srcFilename to destFilename, returns true on success
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
||||
LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
||||
#ifdef _WIN32
|
||||
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
||||
@@ -258,88 +260,76 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
||||
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
||||
return true;
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
// copies file srcFilename to destFilename, returns true on success
|
||||
bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
||||
#ifdef _WIN32
|
||||
if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
|
||||
return true;
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
#else
|
||||
|
||||
// buffer size
|
||||
#define BSIZE 1024
|
||||
|
||||
char buffer[BSIZE];
|
||||
using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
|
||||
|
||||
// Open input file
|
||||
FILE* input = fopen(srcFilename.c_str(), "rb");
|
||||
CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
|
||||
if (!input) {
|
||||
LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s", srcFilename.c_str(),
|
||||
destFilename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
// open output file
|
||||
FILE* output = fopen(destFilename.c_str(), "wb");
|
||||
CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose};
|
||||
if (!output) {
|
||||
fclose(input);
|
||||
LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s", srcFilename.c_str(),
|
||||
destFilename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy loop
|
||||
while (!feof(input)) {
|
||||
std::array<char, 1024> buffer;
|
||||
while (!feof(input.get())) {
|
||||
// read input
|
||||
size_t rnum = fread(buffer, sizeof(char), BSIZE, input);
|
||||
if (rnum != BSIZE) {
|
||||
if (ferror(input) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s",
|
||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
||||
goto bail;
|
||||
size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
|
||||
if (rnum != buffer.size()) {
|
||||
if (ferror(input.get()) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
|
||||
srcFilename, destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// write output
|
||||
size_t wnum = fwrite(buffer, sizeof(char), rnum, output);
|
||||
size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
|
||||
if (wnum != rnum) {
|
||||
LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s",
|
||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
||||
goto bail;
|
||||
LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// close files
|
||||
fclose(input);
|
||||
fclose(output);
|
||||
|
||||
return true;
|
||||
bail:
|
||||
if (input)
|
||||
fclose(input);
|
||||
if (output)
|
||||
fclose(output);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns the size of filename (64bit)
|
||||
u64 GetSize(const std::string& filename) {
|
||||
if (!Exists(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -350,11 +340,11 @@ u64 GetSize(const std::string& filename) {
|
||||
if (stat(filename.c_str(), &buf) == 0)
|
||||
#endif
|
||||
{
|
||||
LOG_TRACE(Common_Filesystem, "%s: %lld", filename.c_str(), (long long)buf.st_size);
|
||||
LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
|
||||
return buf.st_size;
|
||||
}
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "Stat failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -362,7 +352,7 @@ u64 GetSize(const std::string& filename) {
|
||||
u64 GetSize(const int fd) {
|
||||
struct stat buf;
|
||||
if (fstat(fd, &buf) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s", fd, GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
return buf.st_size;
|
||||
@@ -373,12 +363,12 @@ u64 GetSize(FILE* f) {
|
||||
// can't use off_t here because it can be 32-bit
|
||||
u64 pos = ftello(f);
|
||||
if (fseeko(f, 0, SEEK_END) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
u64 size = ftello(f);
|
||||
if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
return size;
|
||||
@@ -386,22 +376,22 @@ u64 GetSize(FILE* f) {
|
||||
|
||||
// creates an empty file filename, returns true on success
|
||||
bool CreateEmptyFile(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "%s", filename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "{}", filename);
|
||||
|
||||
if (!FileUtil::IOFile(filename, "wb")) {
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
if (!FileUtil::IOFile(filename, "wb").IsOpen()) {
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directory,
|
||||
bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
||||
DirectoryEntryCallable callback) {
|
||||
LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", directory);
|
||||
|
||||
// How many files + directories we found
|
||||
unsigned found_entries = 0;
|
||||
u64 found_entries = 0;
|
||||
|
||||
// Save the status of callback function
|
||||
bool callback_error = false;
|
||||
@@ -431,7 +421,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directo
|
||||
if (virtual_name == "." || virtual_name == "..")
|
||||
continue;
|
||||
|
||||
unsigned ret_entries = 0;
|
||||
u64 ret_entries = 0;
|
||||
if (!callback(&ret_entries, directory, virtual_name)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
@@ -455,9 +445,9 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directo
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
unsigned int recursion) {
|
||||
const auto callback = [recursion, &parent_entry](unsigned* num_entries_out,
|
||||
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
unsigned int recursion) {
|
||||
const auto callback = [recursion, &parent_entry](u64* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
FSTEntry entry;
|
||||
@@ -469,7 +459,7 @@ unsigned ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
// is a directory, lets go inside if we didn't recurse to often
|
||||
if (recursion > 0) {
|
||||
entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
|
||||
*num_entries_out += (int)entry.size;
|
||||
*num_entries_out += entry.size;
|
||||
} else {
|
||||
entry.size = 0;
|
||||
}
|
||||
@@ -480,16 +470,16 @@ unsigned ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
(*num_entries_out)++;
|
||||
|
||||
// Push into the tree
|
||||
parent_entry.children.push_back(entry);
|
||||
parent_entry.children.push_back(std::move(entry));
|
||||
return true;
|
||||
};
|
||||
|
||||
unsigned num_entries;
|
||||
u64 num_entries;
|
||||
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
|
||||
}
|
||||
|
||||
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
|
||||
const auto callback = [recursion](unsigned* num_entries_out, const std::string& directory,
|
||||
const auto callback = [recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string new_path = directory + DIR_SEP_CHR + virtual_name;
|
||||
|
||||
@@ -556,7 +546,7 @@ std::string GetCurrentDir() {
|
||||
char* dir;
|
||||
if (!(dir = getcwd(nullptr, 0))) {
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
@@ -593,7 +583,7 @@ std::string GetBundleDirectory() {
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string& GetExeDirectory() {
|
||||
const std::string& GetExeDirectory() {
|
||||
static std::string exe_path;
|
||||
if (exe_path.empty()) {
|
||||
wchar_t wchar_exe_path[2048];
|
||||
@@ -653,12 +643,12 @@ static const std::string GetUserDirectory(const std::string& envvar) {
|
||||
else if (envvar == "XDG_CACHE_HOME")
|
||||
subdirectory = DIR_SEP ".cache";
|
||||
else
|
||||
ASSERT_MSG(false, "Unknown XDG variable %s.", envvar.c_str());
|
||||
ASSERT_MSG(false, "Unknown XDG variable {}.", envvar);
|
||||
user_dir = GetHomeDirectory() + subdirectory;
|
||||
}
|
||||
|
||||
ASSERT_MSG(!user_dir.empty(), "User directory %s musn’t be empty.", envvar.c_str());
|
||||
ASSERT_MSG(user_dir[0] == '/', "User directory %s must be absolute.", envvar.c_str());
|
||||
ASSERT_MSG(!user_dir.empty(), "User directory {} mustn’t be empty.", envvar);
|
||||
ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar);
|
||||
|
||||
return user_dir;
|
||||
}
|
||||
@@ -676,71 +666,88 @@ std::string GetSysDirectory() {
|
||||
#endif
|
||||
sysDir += DIR_SEP;
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "Setting to %s:", sysDir.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
|
||||
return sysDir;
|
||||
}
|
||||
|
||||
// Returns a string with a Citra data dir or file in the user's home
|
||||
// Returns a string with a yuzu data dir or file in the user's home
|
||||
// directory. To be used in "multi-user" mode (that is, installed).
|
||||
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath) {
|
||||
static std::string paths[NUM_PATH_INDICES];
|
||||
const std::string& GetUserPath(UserPath path, const std::string& new_path) {
|
||||
static std::unordered_map<UserPath, std::string> paths;
|
||||
auto& user_path = paths[UserPath::UserDir];
|
||||
|
||||
// Set up all paths and files on the first run
|
||||
if (paths[D_USER_IDX].empty()) {
|
||||
if (user_path.empty()) {
|
||||
#ifdef _WIN32
|
||||
paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
if (!FileUtil::IsDirectory(paths[D_USER_IDX])) {
|
||||
paths[D_USER_IDX] = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
|
||||
user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
if (!FileUtil::IsDirectory(user_path)) {
|
||||
user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Using the local user directory");
|
||||
}
|
||||
|
||||
paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
|
||||
paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP;
|
||||
paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
#else
|
||||
if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
|
||||
paths[D_USER_IDX] = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
|
||||
paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP;
|
||||
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
} else {
|
||||
std::string data_dir = GetUserDirectory("XDG_DATA_HOME");
|
||||
std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME");
|
||||
std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME");
|
||||
|
||||
paths[D_USER_IDX] = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP;
|
||||
paths[D_CONFIG_IDX] = config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP;
|
||||
paths[D_CACHE_IDX] = cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP;
|
||||
user_path = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP;
|
||||
paths.emplace(UserPath::ConfigDir, config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::CacheDir, cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP);
|
||||
}
|
||||
#endif
|
||||
paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP;
|
||||
paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP;
|
||||
paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP;
|
||||
paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
|
||||
// TODO: Put the logs in a better location for each OS
|
||||
paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP);
|
||||
}
|
||||
|
||||
if (!newPath.empty()) {
|
||||
if (!FileUtil::IsDirectory(newPath)) {
|
||||
LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str());
|
||||
return paths[DirIDX];
|
||||
if (!new_path.empty()) {
|
||||
if (!FileUtil::IsDirectory(new_path)) {
|
||||
LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path);
|
||||
return paths[path];
|
||||
} else {
|
||||
paths[DirIDX] = newPath;
|
||||
paths[path] = new_path;
|
||||
}
|
||||
|
||||
switch (DirIDX) {
|
||||
case D_ROOT_IDX:
|
||||
paths[D_USER_IDX] = paths[D_ROOT_IDX] + DIR_SEP;
|
||||
switch (path) {
|
||||
case UserPath::RootDir:
|
||||
user_path = paths[UserPath::RootDir] + DIR_SEP;
|
||||
break;
|
||||
|
||||
case D_USER_IDX:
|
||||
paths[D_USER_IDX] = paths[D_ROOT_IDX] + DIR_SEP;
|
||||
paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
|
||||
paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP;
|
||||
paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP;
|
||||
paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP;
|
||||
case UserPath::UserDir:
|
||||
user_path = paths[UserPath::RootDir] + DIR_SEP;
|
||||
paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP;
|
||||
paths[UserPath::CacheDir] = user_path + CACHE_DIR DIR_SEP;
|
||||
paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP;
|
||||
paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return paths[DirIDX];
|
||||
return paths[path];
|
||||
}
|
||||
|
||||
std::string GetHactoolConfigurationPath() {
|
||||
#ifdef _WIN32
|
||||
PWSTR pw_local_path = nullptr;
|
||||
if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK)
|
||||
return "";
|
||||
std::string local_path = Common::UTF16ToUTF8(pw_local_path);
|
||||
CoTaskMemFree(pw_local_path);
|
||||
return local_path + "\\.switch";
|
||||
#else
|
||||
return GetHomeDirectory() + "/.switch";
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
|
||||
@@ -750,7 +757,7 @@ size_t WriteStringToFile(bool text_file, const std::string& str, const char* fil
|
||||
size_t ReadFileToString(bool text_file, const char* filename, std::string& str) {
|
||||
IOFile file(filename, text_file ? "r" : "rb");
|
||||
|
||||
if (!file)
|
||||
if (!file.IsOpen())
|
||||
return false;
|
||||
|
||||
str.resize(static_cast<u32>(file.GetSize()));
|
||||
@@ -799,49 +806,139 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitPathComponents(std::string_view filename) {
|
||||
std::string copy(filename);
|
||||
std::replace(copy.begin(), copy.end(), '\\', '/');
|
||||
std::vector<std::string> out;
|
||||
|
||||
std::stringstream stream(copy);
|
||||
std::string item;
|
||||
while (std::getline(stream, item, '/')) {
|
||||
out.push_back(std::move(item));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string_view GetParentPath(std::string_view path) {
|
||||
const auto name_bck_index = path.rfind('\\');
|
||||
const auto name_fwd_index = path.rfind('/');
|
||||
size_t name_index;
|
||||
|
||||
if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) {
|
||||
name_index = std::min(name_bck_index, name_fwd_index);
|
||||
} else {
|
||||
name_index = std::max(name_bck_index, name_fwd_index);
|
||||
}
|
||||
|
||||
return path.substr(0, name_index);
|
||||
}
|
||||
|
||||
std::string_view GetPathWithoutTop(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
while (path[0] == '\\' || path[0] == '/') {
|
||||
path.remove_prefix(1);
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
const auto name_bck_index = path.find('\\');
|
||||
const auto name_fwd_index = path.find('/');
|
||||
return path.substr(std::min(name_bck_index, name_fwd_index) + 1);
|
||||
}
|
||||
|
||||
std::string_view GetFilename(std::string_view path) {
|
||||
const auto name_index = path.find_last_of("\\/");
|
||||
|
||||
if (name_index == std::string_view::npos) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return path.substr(name_index + 1);
|
||||
}
|
||||
|
||||
std::string_view GetExtensionFromFilename(std::string_view name) {
|
||||
const size_t index = name.rfind('.');
|
||||
|
||||
if (index == std::string_view::npos) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return name.substr(index + 1);
|
||||
}
|
||||
|
||||
std::string_view RemoveTrailingSlash(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.back() == '\\' || path.back() == '/') {
|
||||
path.remove_suffix(1);
|
||||
return path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string SanitizePath(std::string_view path_) {
|
||||
std::string path(path_);
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
path.erase(std::unique(path.begin(), path.end(),
|
||||
[](char c1, char c2) { return c1 == '/' && c2 == '/'; }),
|
||||
path.end());
|
||||
return std::string(RemoveTrailingSlash(path));
|
||||
}
|
||||
|
||||
IOFile::IOFile() {}
|
||||
|
||||
IOFile::IOFile(const std::string& filename, const char openmode[]) {
|
||||
Open(filename, openmode);
|
||||
IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
|
||||
Open(filename, openmode, flags);
|
||||
}
|
||||
|
||||
IOFile::~IOFile() {
|
||||
Close();
|
||||
}
|
||||
|
||||
IOFile::IOFile(IOFile&& other) {
|
||||
IOFile::IOFile(IOFile&& other) noexcept {
|
||||
Swap(other);
|
||||
}
|
||||
|
||||
IOFile& IOFile::operator=(IOFile&& other) {
|
||||
IOFile& IOFile::operator=(IOFile&& other) noexcept {
|
||||
Swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void IOFile::Swap(IOFile& other) {
|
||||
void IOFile::Swap(IOFile& other) noexcept {
|
||||
std::swap(m_file, other.m_file);
|
||||
std::swap(m_good, other.m_good);
|
||||
}
|
||||
|
||||
bool IOFile::Open(const std::string& filename, const char openmode[]) {
|
||||
bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
|
||||
Close();
|
||||
#ifdef _WIN32
|
||||
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str());
|
||||
if (flags != 0) {
|
||||
m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str(), flags);
|
||||
} else {
|
||||
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str());
|
||||
}
|
||||
#else
|
||||
m_file = fopen(filename.c_str(), openmode);
|
||||
#endif
|
||||
|
||||
m_good = IsOpen();
|
||||
return m_good;
|
||||
return IsOpen();
|
||||
}
|
||||
|
||||
bool IOFile::Close() {
|
||||
if (!IsOpen() || 0 != std::fclose(m_file))
|
||||
m_good = false;
|
||||
return false;
|
||||
|
||||
m_file = nullptr;
|
||||
return m_good;
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 IOFile::GetSize() const {
|
||||
@@ -851,11 +948,8 @@ u64 IOFile::GetSize() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IOFile::Seek(s64 off, int origin) {
|
||||
if (!IsOpen() || 0 != fseeko(m_file, off, origin))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
bool IOFile::Seek(s64 off, int origin) const {
|
||||
return IsOpen() && 0 == fseeko(m_file, off, origin);
|
||||
}
|
||||
|
||||
u64 IOFile::Tell() const {
|
||||
@@ -866,26 +960,20 @@ u64 IOFile::Tell() const {
|
||||
}
|
||||
|
||||
bool IOFile::Flush() {
|
||||
if (!IsOpen() || 0 != std::fflush(m_file))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
return IsOpen() && 0 == std::fflush(m_file);
|
||||
}
|
||||
|
||||
bool IOFile::Resize(u64 size) {
|
||||
if (!IsOpen() || 0 !=
|
||||
return IsOpen() && 0 ==
|
||||
#ifdef _WIN32
|
||||
// ector: _chsize sucks, not 64-bit safe
|
||||
// F|RES: changed to _chsize_s. i think it is 64-bit safe
|
||||
_chsize_s(_fileno(m_file), size)
|
||||
// ector: _chsize sucks, not 64-bit safe
|
||||
// F|RES: changed to _chsize_s. i think it is 64-bit safe
|
||||
_chsize_s(_fileno(m_file), size)
|
||||
#else
|
||||
// TODO: handle 64bit and growing
|
||||
ftruncate(fileno(m_file), size)
|
||||
// TODO: handle 64bit and growing
|
||||
ftruncate(fileno(m_file), size)
|
||||
#endif
|
||||
)
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
;
|
||||
}
|
||||
|
||||
} // namespace FileUtil
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
@@ -16,21 +17,21 @@
|
||||
#include "common/string_util.h"
|
||||
#endif
|
||||
|
||||
// User directory indices for GetUserPath
|
||||
enum {
|
||||
D_USER_IDX,
|
||||
D_ROOT_IDX,
|
||||
D_CONFIG_IDX,
|
||||
D_CACHE_IDX,
|
||||
D_SDMC_IDX,
|
||||
D_NAND_IDX,
|
||||
D_SYSDATA_IDX,
|
||||
D_LOGS_IDX,
|
||||
NUM_PATH_INDICES
|
||||
};
|
||||
|
||||
namespace FileUtil {
|
||||
|
||||
// User paths for GetUserPath
|
||||
enum class UserPath {
|
||||
CacheDir,
|
||||
ConfigDir,
|
||||
KeysDir,
|
||||
LogDir,
|
||||
NANDDir,
|
||||
RootDir,
|
||||
SDMCDir,
|
||||
SysDataDir,
|
||||
UserDir,
|
||||
};
|
||||
|
||||
// FileSystem tree node/
|
||||
struct FSTEntry {
|
||||
bool isDirectory;
|
||||
@@ -85,7 +86,7 @@ bool CreateEmptyFile(const std::string& filename);
|
||||
* @return whether handling the entry succeeded
|
||||
*/
|
||||
using DirectoryEntryCallable = std::function<bool(
|
||||
unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name)>;
|
||||
u64* num_entries_out, const std::string& directory, const std::string& virtual_name)>;
|
||||
|
||||
/**
|
||||
* Scans a directory, calling the callback for each file/directory contained within.
|
||||
@@ -96,7 +97,7 @@ using DirectoryEntryCallable = std::function<bool(
|
||||
* @param callback The callback which will be called for each entry
|
||||
* @return whether scanning the directory succeeded
|
||||
*/
|
||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directory,
|
||||
bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
||||
DirectoryEntryCallable callback);
|
||||
|
||||
/**
|
||||
@@ -106,8 +107,8 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directo
|
||||
* @param recursion Number of children directories to read before giving up.
|
||||
* @return the total number of files/directories found
|
||||
*/
|
||||
unsigned ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
unsigned int recursion = 0);
|
||||
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
unsigned int recursion = 0);
|
||||
|
||||
// deletes the given directory and anything under it. Returns true on success.
|
||||
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
|
||||
@@ -123,7 +124,9 @@ bool SetCurrentDir(const std::string& directory);
|
||||
|
||||
// Returns a pointer to a string with a yuzu data dir in the user's home
|
||||
// directory. To be used in "multi-user" mode (that is, installed).
|
||||
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = "");
|
||||
const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
|
||||
|
||||
std::string GetHactoolConfigurationPath();
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
std::string GetSysDirectory();
|
||||
@@ -133,7 +136,7 @@ std::string GetBundleDirectory();
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string& GetExeDirectory();
|
||||
const std::string& GetExeDirectory();
|
||||
std::string AppDataRoamingDirectory();
|
||||
#endif
|
||||
|
||||
@@ -150,71 +153,87 @@ size_t ReadFileToString(bool text_file, const char* filename, std::string& str);
|
||||
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
|
||||
std::array<char, 4>& extension);
|
||||
|
||||
// Splits the path on '/' or '\' and put the components into a vector
|
||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||
std::vector<std::string> SplitPathComponents(std::string_view filename);
|
||||
|
||||
// Gets all of the text up to the last '/' or '\' in the path.
|
||||
std::string_view GetParentPath(std::string_view path);
|
||||
|
||||
// Gets all of the text after the first '/' or '\' in the path.
|
||||
std::string_view GetPathWithoutTop(std::string_view path);
|
||||
|
||||
// Gets the filename of the path
|
||||
std::string_view GetFilename(std::string_view path);
|
||||
|
||||
// Gets the extension of the filename
|
||||
std::string_view GetExtensionFromFilename(std::string_view name);
|
||||
|
||||
// Removes the final '/' or '\' if one exists
|
||||
std::string_view RemoveTrailingSlash(std::string_view path);
|
||||
|
||||
// Creates a new vector containing indices [first, last) from the original.
|
||||
template <typename T>
|
||||
std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t last) {
|
||||
if (first >= last)
|
||||
return {};
|
||||
last = std::min<size_t>(last, vector.size());
|
||||
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
|
||||
}
|
||||
|
||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'.
|
||||
std::string SanitizePath(std::string_view path);
|
||||
|
||||
// simple wrapper for cstdlib file functions to
|
||||
// hopefully will make error checking easier
|
||||
// and make forgetting an fclose() harder
|
||||
class IOFile : public NonCopyable {
|
||||
public:
|
||||
IOFile();
|
||||
IOFile(const std::string& filename, const char openmode[]);
|
||||
// flags is used for windows specific file open mode flags, which
|
||||
// allows yuzu to open the logs in shared write mode, so that the file
|
||||
// isn't considered "locked" while yuzu is open and people can open the log file and view it
|
||||
IOFile(const std::string& filename, const char openmode[], int flags = 0);
|
||||
|
||||
~IOFile();
|
||||
|
||||
IOFile(IOFile&& other);
|
||||
IOFile& operator=(IOFile&& other);
|
||||
IOFile(IOFile&& other) noexcept;
|
||||
IOFile& operator=(IOFile&& other) noexcept;
|
||||
|
||||
void Swap(IOFile& other);
|
||||
void Swap(IOFile& other) noexcept;
|
||||
|
||||
bool Open(const std::string& filename, const char openmode[]);
|
||||
bool Open(const std::string& filename, const char openmode[], int flags = 0);
|
||||
bool Close();
|
||||
|
||||
template <typename T>
|
||||
size_t ReadArray(T* data, size_t length) {
|
||||
static_assert(std::is_standard_layout<T>(),
|
||||
"Given array does not consist of standard layout objects");
|
||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||
size_t ReadArray(T* data, size_t length) const {
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
if (!IsOpen())
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t items_read = std::fread(data, sizeof(T), length, m_file);
|
||||
if (items_read != length)
|
||||
m_good = false;
|
||||
|
||||
return items_read;
|
||||
return std::fread(data, sizeof(T), length, m_file);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t WriteArray(const T* data, size_t length) {
|
||||
static_assert(std::is_standard_layout<T>(),
|
||||
"Given array does not consist of standard layout objects");
|
||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
if (!IsOpen())
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t items_written = std::fwrite(data, sizeof(T), length, m_file);
|
||||
if (items_written != length)
|
||||
m_good = false;
|
||||
|
||||
return items_written;
|
||||
return std::fwrite(data, sizeof(T), length, m_file);
|
||||
}
|
||||
|
||||
size_t ReadBytes(void* data, size_t length) {
|
||||
template <typename T>
|
||||
size_t ReadBytes(T* data, size_t length) const {
|
||||
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
|
||||
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||
}
|
||||
|
||||
size_t WriteBytes(const void* data, size_t length) {
|
||||
template <typename T>
|
||||
size_t WriteBytes(const T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
|
||||
return WriteArray(reinterpret_cast<const char*>(data), length);
|
||||
}
|
||||
|
||||
@@ -224,19 +243,15 @@ public:
|
||||
return WriteArray(&object, 1);
|
||||
}
|
||||
|
||||
size_t WriteString(const std::string& str) {
|
||||
return WriteArray(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
bool IsOpen() const {
|
||||
return nullptr != m_file;
|
||||
}
|
||||
|
||||
// m_good is set to false when a read, write or other function fails
|
||||
bool IsGood() const {
|
||||
return m_good;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return IsGood();
|
||||
}
|
||||
|
||||
bool Seek(s64 off, int origin);
|
||||
bool Seek(s64 off, int origin) const;
|
||||
u64 Tell() const;
|
||||
u64 GetSize() const;
|
||||
bool Resize(u64 size);
|
||||
@@ -244,13 +259,11 @@ public:
|
||||
|
||||
// clear error state
|
||||
void Clear() {
|
||||
m_good = true;
|
||||
std::clearerr(m_file);
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* m_file = nullptr;
|
||||
bool m_good = true;
|
||||
};
|
||||
|
||||
} // namespace FileUtil
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include "common/common_types.h"
|
||||
|
||||
// defined in Version.cpp
|
||||
extern const char* scm_rev_git_str;
|
||||
|
||||
// On disk format:
|
||||
// header{
|
||||
// u32 'DCAC';
|
||||
// u32 version; // svn_rev
|
||||
// u16 sizeof(key_type);
|
||||
// u16 sizeof(value_type);
|
||||
//}
|
||||
|
||||
// key_value_pair{
|
||||
// u32 value_size;
|
||||
// key_type key;
|
||||
// value_type[value_size] value;
|
||||
//}
|
||||
|
||||
template <typename K, typename V>
|
||||
class LinearDiskCacheReader {
|
||||
public:
|
||||
virtual void Read(const K& key, const V* value, u32 value_size) = 0;
|
||||
};
|
||||
|
||||
// Dead simple unsorted key-value store with append functionality.
|
||||
// No random read functionality, all reading is done in OpenAndRead.
|
||||
// Keys and values can contain any characters, including \0.
|
||||
//
|
||||
// Suitable for caching generated shader bytecode between executions.
|
||||
// Not tuned for extreme performance but should be reasonably fast.
|
||||
// Does not support keys or values larger than 2GB, which should be reasonable.
|
||||
// Keys must have non-zero length; values can have zero length.
|
||||
|
||||
// K and V are some POD type
|
||||
// K : the key type
|
||||
// V : value array type
|
||||
template <typename K, typename V>
|
||||
class LinearDiskCache {
|
||||
public:
|
||||
// return number of read entries
|
||||
u32 OpenAndRead(const char* filename, LinearDiskCacheReader<K, V>& reader) {
|
||||
using std::ios_base;
|
||||
|
||||
// close any currently opened file
|
||||
Close();
|
||||
m_num_entries = 0;
|
||||
|
||||
// try opening for reading/writing
|
||||
OpenFStream(m_file, filename, ios_base::in | ios_base::out | ios_base::binary);
|
||||
|
||||
m_file.seekg(0, std::ios::end);
|
||||
std::fstream::pos_type end_pos = m_file.tellg();
|
||||
m_file.seekg(0, std::ios::beg);
|
||||
std::fstream::pos_type start_pos = m_file.tellg();
|
||||
std::streamoff file_size = end_pos - start_pos;
|
||||
|
||||
if (m_file.is_open() && ValidateHeader()) {
|
||||
// good header, read some key/value pairs
|
||||
K key;
|
||||
|
||||
V* value = nullptr;
|
||||
u32 value_size;
|
||||
u32 entry_number;
|
||||
|
||||
std::fstream::pos_type last_pos = m_file.tellg();
|
||||
|
||||
while (Read(&value_size)) {
|
||||
std::streamoff next_extent =
|
||||
(last_pos - start_pos) + sizeof(value_size) + value_size;
|
||||
if (next_extent > file_size)
|
||||
break;
|
||||
|
||||
delete[] value;
|
||||
value = new V[value_size];
|
||||
|
||||
// read key/value and pass to reader
|
||||
if (Read(&key) && Read(value, value_size) && Read(&entry_number) &&
|
||||
entry_number == m_num_entries + 1) {
|
||||
reader.Read(key, value, value_size);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
m_num_entries++;
|
||||
last_pos = m_file.tellg();
|
||||
}
|
||||
m_file.seekp(last_pos);
|
||||
m_file.clear();
|
||||
|
||||
delete[] value;
|
||||
return m_num_entries;
|
||||
}
|
||||
|
||||
// failed to open file for reading or bad header
|
||||
// close and recreate file
|
||||
Close();
|
||||
m_file.open(filename, ios_base::out | ios_base::trunc | ios_base::binary);
|
||||
WriteHeader();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
m_file.flush();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
if (m_file.is_open())
|
||||
m_file.close();
|
||||
// clear any error flags
|
||||
m_file.clear();
|
||||
}
|
||||
|
||||
// Appends a key-value pair to the store.
|
||||
void Append(const K& key, const V* value, u32 value_size) {
|
||||
// TODO: Should do a check that we don't already have "key"? (I think each caller does that
|
||||
// already.)
|
||||
Write(&value_size);
|
||||
Write(&key);
|
||||
Write(value, value_size);
|
||||
m_num_entries++;
|
||||
Write(&m_num_entries);
|
||||
}
|
||||
|
||||
private:
|
||||
void WriteHeader() {
|
||||
Write(&m_header);
|
||||
}
|
||||
|
||||
bool ValidateHeader() {
|
||||
char file_header[sizeof(Header)];
|
||||
|
||||
return (Read(file_header, sizeof(Header)) &&
|
||||
!memcmp((const char*)&m_header, file_header, sizeof(Header)));
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
bool Write(const D* data, u32 count = 1) {
|
||||
return m_file.write((const char*)data, count * sizeof(D)).good();
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
bool Read(const D* data, u32 count = 1) {
|
||||
return m_file.read((char*)data, count * sizeof(D)).good();
|
||||
}
|
||||
|
||||
struct Header {
|
||||
Header() : id(*(u32*)"DCAC"), key_t_size(sizeof(K)), value_t_size(sizeof(V)) {
|
||||
memcpy(ver, scm_rev_git_str, 40);
|
||||
}
|
||||
|
||||
const u32 id;
|
||||
const u16 key_t_size, value_t_size;
|
||||
char ver[40];
|
||||
|
||||
} m_header;
|
||||
|
||||
std::fstream m_file;
|
||||
u32 m_num_entries;
|
||||
};
|
||||
@@ -3,18 +3,148 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <share.h> // For _SH_DENYWR
|
||||
#else
|
||||
#define _SH_DENYWR 0
|
||||
#endif
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h" // snprintf compatibility define
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
/**
|
||||
* Static state as a singleton.
|
||||
*/
|
||||
class Impl {
|
||||
public:
|
||||
static Impl& Instance() {
|
||||
static Impl backend;
|
||||
return backend;
|
||||
}
|
||||
|
||||
Impl(Impl const&) = delete;
|
||||
const Impl& operator=(Impl const&) = delete;
|
||||
|
||||
void PushEntry(Entry e) {
|
||||
std::lock_guard<std::mutex> lock(message_mutex);
|
||||
message_queue.Push(std::move(e));
|
||||
message_cv.notify_one();
|
||||
}
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
backends.push_back(std::move(backend));
|
||||
}
|
||||
|
||||
void RemoveBackend(std::string_view backend_name) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
const auto it =
|
||||
std::remove_if(backends.begin(), backends.end(),
|
||||
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
|
||||
backends.erase(it, backends.end());
|
||||
}
|
||||
|
||||
const Filter& GetGlobalFilter() const {
|
||||
return filter;
|
||||
}
|
||||
|
||||
void SetGlobalFilter(const Filter& f) {
|
||||
filter = f;
|
||||
}
|
||||
|
||||
Backend* GetBackend(std::string_view backend_name) {
|
||||
const auto it =
|
||||
std::find_if(backends.begin(), backends.end(),
|
||||
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
|
||||
if (it == backends.end())
|
||||
return nullptr;
|
||||
return it->get();
|
||||
}
|
||||
|
||||
private:
|
||||
Impl() {
|
||||
backend_thread = std::thread([&] {
|
||||
Entry entry;
|
||||
auto write_logs = [&](Entry& e) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
for (const auto& backend : backends) {
|
||||
backend->Write(e);
|
||||
}
|
||||
};
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(message_mutex);
|
||||
message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
|
||||
}
|
||||
if (!running) {
|
||||
break;
|
||||
}
|
||||
write_logs(entry);
|
||||
}
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
|
||||
// where a system is repeatedly spamming logs even on close.
|
||||
const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
|
||||
int logs_written = 0;
|
||||
while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
|
||||
write_logs(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
running = false;
|
||||
message_cv.notify_one();
|
||||
backend_thread.join();
|
||||
}
|
||||
|
||||
std::atomic_bool running{true};
|
||||
std::mutex message_mutex, writing_mutex;
|
||||
std::condition_variable message_cv;
|
||||
std::thread backend_thread;
|
||||
std::vector<std::unique_ptr<Backend>> backends;
|
||||
Common::MPSCQueue<Log::Entry> message_queue;
|
||||
Filter filter;
|
||||
};
|
||||
|
||||
void ConsoleBackend::Write(const Entry& entry) {
|
||||
PrintMessage(entry);
|
||||
}
|
||||
|
||||
void ColorConsoleBackend::Write(const Entry& entry) {
|
||||
PrintColoredMessage(entry);
|
||||
}
|
||||
|
||||
// _SH_DENYWR allows read only access to the file for other programs.
|
||||
// It is #defined to 0 on other platforms
|
||||
FileBackend::FileBackend(const std::string& filename)
|
||||
: file(filename, "w", _SH_DENYWR), bytes_written(0) {}
|
||||
|
||||
void FileBackend::Write(const Entry& entry) {
|
||||
// prevent logs from going over the maximum size (in case its spamming and the user doesn't
|
||||
// know)
|
||||
constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
|
||||
if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
|
||||
return;
|
||||
}
|
||||
bytes_written += file.WriteString(FormatLogMessage(entry) + '\n');
|
||||
if (entry.log_level >= Level::Error) {
|
||||
file.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||
#define ALL_LOG_CLASSES() \
|
||||
CLS(Log) \
|
||||
@@ -38,22 +168,39 @@ namespace Log {
|
||||
SUB(Service, AM) \
|
||||
SUB(Service, AOC) \
|
||||
SUB(Service, APM) \
|
||||
SUB(Service, BCAT) \
|
||||
SUB(Service, BPC) \
|
||||
SUB(Service, BTM) \
|
||||
SUB(Service, Capture) \
|
||||
SUB(Service, Fatal) \
|
||||
SUB(Service, FGM) \
|
||||
SUB(Service, Friend) \
|
||||
SUB(Service, FS) \
|
||||
SUB(Service, HID) \
|
||||
SUB(Service, LBL) \
|
||||
SUB(Service, LDN) \
|
||||
SUB(Service, LM) \
|
||||
SUB(Service, Migration) \
|
||||
SUB(Service, Mii) \
|
||||
SUB(Service, MM) \
|
||||
SUB(Service, NCM) \
|
||||
SUB(Service, NFC) \
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NS) \
|
||||
SUB(Service, NVDRV) \
|
||||
SUB(Service, PCIE) \
|
||||
SUB(Service, PCTL) \
|
||||
SUB(Service, PCV) \
|
||||
SUB(Service, PREPO) \
|
||||
SUB(Service, PSC) \
|
||||
SUB(Service, SET) \
|
||||
SUB(Service, SM) \
|
||||
SUB(Service, SPL) \
|
||||
SUB(Service, SSL) \
|
||||
SUB(Service, Time) \
|
||||
SUB(Service, VI) \
|
||||
SUB(Service, WLAN) \
|
||||
CLS(HW) \
|
||||
SUB(HW, Memory) \
|
||||
SUB(HW, LCD) \
|
||||
@@ -70,6 +217,7 @@ namespace Log {
|
||||
CLS(Input) \
|
||||
CLS(Network) \
|
||||
CLS(Loader) \
|
||||
CLS(Crypto) \
|
||||
CLS(WebService)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
@@ -125,35 +273,32 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign
|
||||
return entry;
|
||||
}
|
||||
|
||||
static Filter* filter = nullptr;
|
||||
|
||||
void SetFilter(Filter* new_filter) {
|
||||
filter = new_filter;
|
||||
void SetGlobalFilter(const Filter& filter) {
|
||||
Impl::Instance().SetGlobalFilter(filter);
|
||||
}
|
||||
|
||||
void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, const char* format, ...) {
|
||||
if (filter && !filter->CheckMessage(log_class, log_level))
|
||||
return;
|
||||
std::array<char, 4 * 1024> formatting_buffer;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args);
|
||||
va_end(args);
|
||||
Entry entry = CreateEntry(log_class, log_level, filename, line_num, function,
|
||||
std::string(formatting_buffer.data()));
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
Impl::Instance().AddBackend(std::move(backend));
|
||||
}
|
||||
|
||||
PrintColoredMessage(entry);
|
||||
void RemoveBackend(std::string_view backend_name) {
|
||||
Impl::Instance().RemoveBackend(backend_name);
|
||||
}
|
||||
|
||||
Backend* GetBackend(std::string_view backend_name) {
|
||||
return Impl::Instance().GetBackend(backend_name);
|
||||
}
|
||||
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
const fmt::format_args& args) {
|
||||
if (filter && !filter->CheckMessage(log_class, log_level))
|
||||
auto filter = Impl::Instance().GetGlobalFilter();
|
||||
if (!filter.CheckMessage(log_class, log_level))
|
||||
return;
|
||||
|
||||
Entry entry =
|
||||
CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
|
||||
|
||||
PrintColoredMessage(entry);
|
||||
Impl::Instance().PushEntry(std::move(entry));
|
||||
}
|
||||
} // namespace Log
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <string_view>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Log {
|
||||
@@ -34,6 +35,80 @@ struct Entry {
|
||||
Entry& operator=(const Entry& o) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for logging backends. As loggers can be created and removed at runtime, this can be
|
||||
* used by a frontend for adding a custom logging backend as needed
|
||||
*/
|
||||
class Backend {
|
||||
public:
|
||||
virtual ~Backend() = default;
|
||||
virtual void SetFilter(const Filter& new_filter) {
|
||||
filter = new_filter;
|
||||
}
|
||||
virtual const char* GetName() const = 0;
|
||||
virtual void Write(const Entry& entry) = 0;
|
||||
|
||||
private:
|
||||
Filter filter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to stderr without any color commands
|
||||
*/
|
||||
class ConsoleBackend : public Backend {
|
||||
public:
|
||||
static const char* Name() {
|
||||
return "console";
|
||||
}
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to stderr and with color
|
||||
*/
|
||||
class ColorConsoleBackend : public Backend {
|
||||
public:
|
||||
static const char* Name() {
|
||||
return "color_console";
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to a file passed into the constructor
|
||||
*/
|
||||
class FileBackend : public Backend {
|
||||
public:
|
||||
explicit FileBackend(const std::string& filename);
|
||||
|
||||
static const char* Name() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
|
||||
void Write(const Entry& entry) override;
|
||||
|
||||
private:
|
||||
FileUtil::IOFile file;
|
||||
size_t bytes_written;
|
||||
};
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend);
|
||||
|
||||
void RemoveBackend(std::string_view backend_name);
|
||||
|
||||
Backend* GetBackend(std::string_view backend_name);
|
||||
|
||||
/**
|
||||
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
||||
* instead of underscores as in the enumeration.
|
||||
@@ -49,5 +124,10 @@ const char* GetLevelName(Level log_level);
|
||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
||||
const char* function, std::string message);
|
||||
|
||||
void SetFilter(Filter* filter);
|
||||
} // namespace Log
|
||||
/**
|
||||
* The global filter will prevent any messages from even being processed if they are filtered. Each
|
||||
* backend can have a filter, but if the level is lower than the global filter, the backend will
|
||||
* never get the message
|
||||
*/
|
||||
void SetGlobalFilter(const Filter& filter);
|
||||
} // namespace Log
|
||||
@@ -8,6 +8,59 @@
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace Log {
|
||||
namespace {
|
||||
template <typename It>
|
||||
Level GetLevelByName(const It begin, const It end) {
|
||||
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
|
||||
const char* level_name = GetLevelName(static_cast<Level>(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||
return static_cast<Level>(i);
|
||||
}
|
||||
}
|
||||
return Level::Count;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
Class GetClassByName(const It begin, const It end) {
|
||||
for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) {
|
||||
const char* level_name = GetLogClassName(static_cast<Class>(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||
return static_cast<Class>(i);
|
||||
}
|
||||
}
|
||||
return Class::Count;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
auto level_separator = std::find(begin, end, ':');
|
||||
if (level_separator == end) {
|
||||
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}",
|
||||
std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Common::ComparePartialString(begin, level_separator, "*")) {
|
||||
instance.ResetAll(level);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Class log_class = GetClassByName(begin, level_separator);
|
||||
if (log_class == Class::Count) {
|
||||
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
instance.SetClassLevel(log_class, level);
|
||||
return true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
Filter::Filter(Level default_level) {
|
||||
ResetAll(default_level);
|
||||
@@ -21,17 +74,17 @@ void Filter::SetClassLevel(Class log_class, Level level) {
|
||||
class_levels[static_cast<size_t>(log_class)] = level;
|
||||
}
|
||||
|
||||
void Filter::ParseFilterString(const std::string& filter_str) {
|
||||
auto clause_begin = filter_str.cbegin();
|
||||
while (clause_begin != filter_str.cend()) {
|
||||
auto clause_end = std::find(clause_begin, filter_str.cend(), ' ');
|
||||
void Filter::ParseFilterString(std::string_view filter_view) {
|
||||
auto clause_begin = filter_view.cbegin();
|
||||
while (clause_begin != filter_view.cend()) {
|
||||
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
|
||||
|
||||
// If clause isn't empty
|
||||
if (clause_end != clause_begin) {
|
||||
ParseFilterRule(clause_begin, clause_end);
|
||||
ParseFilterRule(*this, clause_begin, clause_end);
|
||||
}
|
||||
|
||||
if (clause_end != filter_str.cend()) {
|
||||
if (clause_end != filter_view.cend()) {
|
||||
// Skip over the whitespace
|
||||
++clause_end;
|
||||
}
|
||||
@@ -39,59 +92,14 @@ void Filter::ParseFilterString(const std::string& filter_str) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
static Level GetLevelByName(const It begin, const It end) {
|
||||
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
|
||||
const char* level_name = GetLevelName(static_cast<Level>(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||
return static_cast<Level>(i);
|
||||
}
|
||||
}
|
||||
return Level::Count;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
static Class GetClassByName(const It begin, const It end) {
|
||||
for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) {
|
||||
const char* level_name = GetLogClassName(static_cast<Class>(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||
return static_cast<Class>(i);
|
||||
}
|
||||
}
|
||||
return Class::Count;
|
||||
}
|
||||
|
||||
bool Filter::ParseFilterRule(const std::string::const_iterator begin,
|
||||
const std::string::const_iterator end) {
|
||||
auto level_separator = std::find(begin, end, ':');
|
||||
if (level_separator == end) {
|
||||
NGLOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
|
||||
std::string(begin, end).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
NGLOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Common::ComparePartialString(begin, level_separator, "*")) {
|
||||
ResetAll(level);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Class log_class = GetClassByName(begin, level_separator);
|
||||
if (log_class == Class::Count) {
|
||||
NGLOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
SetClassLevel(log_class, level);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Filter::CheckMessage(Class log_class, Level level) const {
|
||||
return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]);
|
||||
}
|
||||
|
||||
bool Filter::IsDebug() const {
|
||||
return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) {
|
||||
return static_cast<u8>(l) <= static_cast<u8>(Level::Debug);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Log
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Log {
|
||||
@@ -40,13 +40,14 @@ public:
|
||||
* - `Service:Info` -- Sets the level of Service to Info.
|
||||
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
|
||||
*/
|
||||
void ParseFilterString(const std::string& filter_str);
|
||||
bool ParseFilterRule(const std::string::const_iterator start,
|
||||
const std::string::const_iterator end);
|
||||
void ParseFilterString(std::string_view filter_view);
|
||||
|
||||
/// Matches class/level combination against the filter, returning true if it passed.
|
||||
bool CheckMessage(Class log_class, Level level) const;
|
||||
|
||||
/// Returns true if any logging classes are set to debug
|
||||
bool IsDebug() const;
|
||||
|
||||
private:
|
||||
std::array<Level, (size_t)Class::Count> class_levels;
|
||||
};
|
||||
|
||||
@@ -55,22 +55,39 @@ enum class Class : ClassType {
|
||||
Service_AOC, ///< The AOC (AddOn Content) service
|
||||
Service_APM, ///< The APM (Performance) service
|
||||
Service_Audio, ///< The Audio (Audio control) service
|
||||
Service_BCAT, ///< The BCAT service
|
||||
Service_BPC, ///< The BPC service
|
||||
Service_BTM, ///< The BTM service
|
||||
Service_Capture, ///< The capture service
|
||||
Service_Fatal, ///< The Fatal service
|
||||
Service_FGM, ///< The FGM service
|
||||
Service_Friend, ///< The friend service
|
||||
Service_FS, ///< The FS (Filesystem) service
|
||||
Service_HID, ///< The HID (Human interface device) service
|
||||
Service_LBL, ///< The LBL (LCD backlight) service
|
||||
Service_LDN, ///< The LDN (Local domain network) service
|
||||
Service_LM, ///< The LM (Logger) service
|
||||
Service_Migration, ///< The migration service
|
||||
Service_Mii, ///< The Mii service
|
||||
Service_MM, ///< The MM (Multimedia) service
|
||||
Service_NCM, ///< The NCM service
|
||||
Service_NFC, ///< The NFC (Near-field communication) service
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NS, ///< The NS services
|
||||
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
||||
Service_PCIE, ///< The PCIe service
|
||||
Service_PCTL, ///< The PCTL (Parental control) service
|
||||
Service_PCV, ///< The PCV service
|
||||
Service_PREPO, ///< The PREPO (Play report) service
|
||||
Service_PSC, ///< The PSC service
|
||||
Service_SET, ///< The SET (Settings) service
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
Service_SSL, ///< The SSL service
|
||||
Service_Time, ///< The time service
|
||||
Service_VI, ///< The VI (Video interface) service
|
||||
Service_WLAN, ///< The WLAN (Wireless local area network) service
|
||||
HW, ///< Low-level hardware emulation
|
||||
HW_Memory, ///< Memory-map and address translation
|
||||
HW_LCD, ///< LCD register emulation
|
||||
@@ -85,25 +102,13 @@ enum class Class : ClassType {
|
||||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
Loader, ///< ROM loader
|
||||
Crypto, ///< Cryptographic engine/functions
|
||||
Input, ///< Input emulation
|
||||
Network, ///< Network emulation
|
||||
WebService, ///< Interface to yuzu Web Services
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
/// Logs a message to the global logger.
|
||||
void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function,
|
||||
#ifdef _MSC_VER
|
||||
_Printf_format_string_
|
||||
#endif
|
||||
const char* format,
|
||||
...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 6, 7)))
|
||||
#endif
|
||||
;
|
||||
|
||||
/// Logs a message to the global logger, using fmt
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
@@ -113,53 +118,31 @@ template <typename... Args>
|
||||
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, const char* format, const Args&... args) {
|
||||
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
|
||||
fmt::make_args(args...));
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
} // namespace Log
|
||||
|
||||
#define LOG_GENERIC(log_class, log_level, ...) \
|
||||
::Log::LogMessage(log_class, log_level, __FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define LOG_TRACE(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Trace, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_TRACE(log_class, ...) (void(0))
|
||||
#endif
|
||||
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Debug, __VA_ARGS__)
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Info, __VA_ARGS__)
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Warning, __VA_ARGS__)
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Error, __VA_ARGS__)
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Critical, __VA_ARGS__)
|
||||
|
||||
// Define the fmt lib macros
|
||||
#ifdef _DEBUG
|
||||
#define NGLOG_TRACE(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#else
|
||||
#define NGLOG_TRACE(log_class, fmt, ...) (void(0))
|
||||
#define LOG_TRACE(log_class, fmt, ...) (void(0))
|
||||
#endif
|
||||
|
||||
#define NGLOG_DEBUG(log_class, ...) \
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_INFO(log_class, ...) \
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_WARNING(log_class, ...) \
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_ERROR(log_class, ...) \
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_CRITICAL(log_class, ...) \
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace Log {
|
||||
|
||||
|
||||
@@ -17,19 +17,14 @@ inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start
|
||||
return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T Clamp(const T val, const T& min, const T& max) {
|
||||
return std::max(min, std::min(max, val));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct Rectangle {
|
||||
T left;
|
||||
T top;
|
||||
T right;
|
||||
T bottom;
|
||||
T left{};
|
||||
T top{};
|
||||
T right{};
|
||||
T bottom{};
|
||||
|
||||
Rectangle() {}
|
||||
Rectangle() = default;
|
||||
|
||||
Rectangle(T left, T top, T right, T bottom)
|
||||
: left(left), top(top), right(right), bottom(bottom) {}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
#include <unistd.h>
|
||||
#define PAGE_MASK (getpagesize() - 1)
|
||||
#define round_page(x) ((((unsigned long)(x)) + PAGE_MASK) & ~(PAGE_MASK))
|
||||
@@ -30,7 +30,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
#else
|
||||
static char* map_hint = nullptr;
|
||||
#if defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
// This OS has no flag to enforce allocation below the 4 GB boundary,
|
||||
// but if we hint that we want a low address it is very likely we will
|
||||
// get one.
|
||||
@@ -42,7 +42,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
#endif
|
||||
void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_ANON | MAP_PRIVATE
|
||||
#if defined(ARCHITECTURE_X64) && defined(MAP_32BIT)
|
||||
#if defined(ARCHITECTURE_x86_64) && defined(MAP_32BIT)
|
||||
| (low ? MAP_32BIT : 0)
|
||||
#endif
|
||||
,
|
||||
@@ -57,7 +57,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
#endif
|
||||
LOG_ERROR(Common_Memory, "Failed to allocate executable memory");
|
||||
}
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
else {
|
||||
if (low) {
|
||||
map_hint += size;
|
||||
@@ -113,7 +113,7 @@ void FreeMemoryPages(void* ptr, size_t size) {
|
||||
if (ptr) {
|
||||
#ifdef _WIN32
|
||||
if (!VirtualFree(ptr, 0, MEM_RELEASE))
|
||||
LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n%s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
munmap(ptr, size);
|
||||
#endif
|
||||
@@ -134,7 +134,7 @@ void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) {
|
||||
#ifdef _WIN32
|
||||
DWORD oldValue;
|
||||
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue))
|
||||
LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n%s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ);
|
||||
#endif
|
||||
@@ -145,7 +145,7 @@ void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) {
|
||||
DWORD oldValue;
|
||||
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE,
|
||||
&oldValue))
|
||||
LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n%s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
mprotect(ptr, size,
|
||||
allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ);
|
||||
@@ -167,8 +167,7 @@ std::string MemUsage() {
|
||||
return "MemUsage Error";
|
||||
|
||||
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
|
||||
Ret = Common::StringFromFormat(
|
||||
"%s K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7).c_str());
|
||||
Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7));
|
||||
|
||||
CloseHandle(hProcess);
|
||||
return Ret;
|
||||
|
||||
@@ -4,34 +4,28 @@
|
||||
|
||||
#include <cstddef>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
// Neither Android nor OS X support TLS
|
||||
#if defined(__APPLE__) || (ANDROID && __clang__)
|
||||
#define __thread
|
||||
#endif
|
||||
#include "common/common_funcs.h"
|
||||
|
||||
// Generic function to get last error message.
|
||||
// Call directly after the command or use the error num.
|
||||
// This function might change the error code.
|
||||
const char* GetLastErrorMsg() {
|
||||
std::string GetLastErrorMsg() {
|
||||
static const size_t buff_size = 255;
|
||||
char err_str[buff_size];
|
||||
|
||||
#ifdef _WIN32
|
||||
static __declspec(thread) char err_str[buff_size] = {};
|
||||
|
||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr);
|
||||
#else
|
||||
static __thread char err_str[buff_size] = {};
|
||||
|
||||
// Thread safe (XSI-compliant)
|
||||
strerror_r(errno, err_str, buff_size);
|
||||
#endif
|
||||
|
||||
return err_str;
|
||||
return std::string(err_str, buff_size);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -12,10 +14,11 @@ namespace Common {
|
||||
|
||||
constexpr char KEY_VALUE_SEPARATOR = ':';
|
||||
constexpr char PARAM_SEPARATOR = ',';
|
||||
|
||||
constexpr char ESCAPE_CHARACTER = '$';
|
||||
const std::string KEY_VALUE_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '0'};
|
||||
const std::string PARAM_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '1'};
|
||||
const std::string ESCAPE_CHARACTER_ESCAPE{ESCAPE_CHARACTER, '2'};
|
||||
constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
|
||||
constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
|
||||
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
|
||||
|
||||
ParamPackage::ParamPackage(const std::string& serialized) {
|
||||
std::vector<std::string> pairs;
|
||||
@@ -25,7 +28,7 @@ ParamPackage::ParamPackage(const std::string& serialized) {
|
||||
std::vector<std::string> key_value;
|
||||
Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value);
|
||||
if (key_value.size() != 2) {
|
||||
LOG_ERROR(Common, "invalid key pair %s", pair.c_str());
|
||||
LOG_ERROR(Common, "invalid key pair {}", pair);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -35,7 +38,7 @@ ParamPackage::ParamPackage(const std::string& serialized) {
|
||||
part = Common::ReplaceAll(part, ESCAPE_CHARACTER_ESCAPE, {ESCAPE_CHARACTER});
|
||||
}
|
||||
|
||||
Set(key_value[0], key_value[1]);
|
||||
Set(key_value[0], std::move(key_value[1]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +67,7 @@ std::string ParamPackage::Serialize() const {
|
||||
std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const {
|
||||
auto pair = data.find(key);
|
||||
if (pair == data.end()) {
|
||||
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||
LOG_DEBUG(Common, "key '{}' not found", key);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
@@ -74,14 +77,14 @@ std::string ParamPackage::Get(const std::string& key, const std::string& default
|
||||
int ParamPackage::Get(const std::string& key, int default_value) const {
|
||||
auto pair = data.find(key);
|
||||
if (pair == data.end()) {
|
||||
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||
LOG_DEBUG(Common, "key '{}' not found", key);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stoi(pair->second);
|
||||
} catch (const std::logic_error&) {
|
||||
LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str());
|
||||
LOG_ERROR(Common, "failed to convert {} to int", pair->second);
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
@@ -89,28 +92,28 @@ int ParamPackage::Get(const std::string& key, int default_value) const {
|
||||
float ParamPackage::Get(const std::string& key, float default_value) const {
|
||||
auto pair = data.find(key);
|
||||
if (pair == data.end()) {
|
||||
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||
LOG_DEBUG(Common, "key {} not found", key);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stof(pair->second);
|
||||
} catch (const std::logic_error&) {
|
||||
LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str());
|
||||
LOG_ERROR(Common, "failed to convert {} to float", pair->second);
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
void ParamPackage::Set(const std::string& key, const std::string& value) {
|
||||
data[key] = value;
|
||||
void ParamPackage::Set(const std::string& key, std::string value) {
|
||||
data.insert_or_assign(key, std::move(value));
|
||||
}
|
||||
|
||||
void ParamPackage::Set(const std::string& key, int value) {
|
||||
data[key] = std::to_string(value);
|
||||
data.insert_or_assign(key, std::to_string(value));
|
||||
}
|
||||
|
||||
void ParamPackage::Set(const std::string& key, float value) {
|
||||
data[key] = std::to_string(value);
|
||||
data.insert_or_assign(key, std::to_string(value));
|
||||
}
|
||||
|
||||
bool ParamPackage::Has(const std::string& key) const {
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
std::string Get(const std::string& key, const std::string& default_value) const;
|
||||
int Get(const std::string& key, int default_value) const;
|
||||
float Get(const std::string& key, float default_value) const;
|
||||
void Set(const std::string& key, const std::string& value);
|
||||
void Set(const std::string& key, std::string value);
|
||||
void Set(const std::string& key, int value);
|
||||
void Set(const std::string& key, float value);
|
||||
bool Has(const std::string& key) const;
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <boost/range/algorithm/transform.hpp>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -24,98 +24,18 @@ namespace Common {
|
||||
|
||||
/// Make a string lowercase
|
||||
std::string ToLower(std::string str) {
|
||||
boost::transform(str, str.begin(), ::tolower);
|
||||
std::transform(str.begin(), str.end(), str.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Make a string uppercase
|
||||
std::string ToUpper(std::string str) {
|
||||
boost::transform(str, str.begin(), ::toupper);
|
||||
std::transform(str.begin(), str.end(), str.begin(),
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
return str;
|
||||
}
|
||||
|
||||
// faster than sscanf
|
||||
bool AsciiToHex(const char* _szValue, u32& result) {
|
||||
char* endptr = nullptr;
|
||||
const u32 value = strtoul(_szValue, &endptr, 16);
|
||||
|
||||
if (!endptr || *endptr)
|
||||
return false;
|
||||
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args) {
|
||||
int writtenCount;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// You would think *printf are simple, right? Iterate on each character,
|
||||
// if it's a format specifier handle it properly, etc.
|
||||
//
|
||||
// Nooooo. Not according to the C standard.
|
||||
//
|
||||
// According to the C99 standard (7.19.6.1 "The fprintf function")
|
||||
// The format shall be a multibyte character sequence
|
||||
//
|
||||
// Because some character encodings might have '%' signs in the middle of
|
||||
// a multibyte sequence (SJIS for example only specifies that the first
|
||||
// byte of a 2 byte sequence is "high", the second byte can be anything),
|
||||
// printf functions have to decode the multibyte sequences and try their
|
||||
// best to not screw up.
|
||||
//
|
||||
// Unfortunately, on Windows, the locale for most languages is not UTF-8
|
||||
// as we would need. Notably, for zh_TW, Windows chooses EUC-CN as the
|
||||
// locale, and completely fails when trying to decode UTF-8 as EUC-CN.
|
||||
//
|
||||
// On the other hand, the fix is simple: because we use UTF-8, no such
|
||||
// multibyte handling is required as we can simply assume that no '%' char
|
||||
// will be present in the middle of a multibyte sequence.
|
||||
//
|
||||
// This is why we lookup an ANSI (cp1252) locale here and use _vsnprintf_l.
|
||||
static locale_t c_locale = nullptr;
|
||||
if (!c_locale)
|
||||
c_locale = _create_locale(LC_ALL, ".1252");
|
||||
writtenCount = _vsnprintf_l(out, outsize, format, c_locale, args);
|
||||
#else
|
||||
writtenCount = vsnprintf(out, outsize, format, args);
|
||||
#endif
|
||||
|
||||
if (writtenCount > 0 && writtenCount < outsize) {
|
||||
out[writtenCount] = '\0';
|
||||
return true;
|
||||
} else {
|
||||
out[outsize - 1] = '\0';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string StringFromFormat(const char* format, ...) {
|
||||
va_list args;
|
||||
char* buf = nullptr;
|
||||
#ifdef _WIN32
|
||||
int required = 0;
|
||||
|
||||
va_start(args, format);
|
||||
required = _vscprintf(format, args);
|
||||
buf = new char[required + 1];
|
||||
CharArrayFromFormatV(buf, required + 1, format, args);
|
||||
va_end(args);
|
||||
|
||||
std::string temp = buf;
|
||||
delete[] buf;
|
||||
#else
|
||||
va_start(args, format);
|
||||
if (vasprintf(&buf, format, args) < 0)
|
||||
LOG_ERROR(Common, "Unable to allocate memory for string");
|
||||
va_end(args);
|
||||
|
||||
std::string temp = buf;
|
||||
free(buf);
|
||||
#endif
|
||||
return temp;
|
||||
}
|
||||
|
||||
// For Debugging. Read out an u8 array.
|
||||
std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces) {
|
||||
std::ostringstream oss;
|
||||
@@ -134,6 +54,10 @@ std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string StringFromBuffer(const std::vector<u8>& data) {
|
||||
return std::string(data.begin(), std::find(data.begin(), data.end(), '\0'));
|
||||
}
|
||||
|
||||
// Turns " hej " into "hej". Also handles tabs.
|
||||
std::string StripSpaces(const std::string& str) {
|
||||
const size_t s = str.find_first_not_of(" \t\r\n");
|
||||
@@ -200,7 +124,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
||||
size_t dir_end = full_path.find_last_of("/"
|
||||
// windows needs the : included for something like just "C:" to be considered a directory
|
||||
#ifdef _WIN32
|
||||
":"
|
||||
"\\:"
|
||||
#endif
|
||||
);
|
||||
if (std::string::npos == dir_end)
|
||||
@@ -240,21 +164,21 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri
|
||||
std::istringstream iss(str);
|
||||
output.resize(1);
|
||||
|
||||
while (std::getline(iss, *output.rbegin(), delim))
|
||||
output.push_back("");
|
||||
while (std::getline(iss, *output.rbegin(), delim)) {
|
||||
output.emplace_back();
|
||||
}
|
||||
|
||||
output.pop_back();
|
||||
}
|
||||
|
||||
std::string TabsToSpaces(int tab_size, const std::string& in) {
|
||||
const std::string spaces(tab_size, ' ');
|
||||
std::string out(in);
|
||||
|
||||
std::string TabsToSpaces(int tab_size, std::string in) {
|
||||
size_t i = 0;
|
||||
while (out.npos != (i = out.find('\t')))
|
||||
out.replace(i, 1, spaces);
|
||||
|
||||
return out;
|
||||
while ((i = in.find('\t')) != std::string::npos) {
|
||||
in.replace(i, 1, tab_size, ' ');
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) {
|
||||
@@ -298,31 +222,37 @@ std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
}
|
||||
|
||||
static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
|
||||
auto const size =
|
||||
const auto size =
|
||||
MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
|
||||
|
||||
std::wstring output;
|
||||
output.resize(size);
|
||||
if (size == 0) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
if (size == 0 ||
|
||||
size != MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()),
|
||||
&output[0], static_cast<int>(output.size())))
|
||||
std::wstring output(size, L'\0');
|
||||
|
||||
if (size != MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()),
|
||||
&output[0], static_cast<int>(output.size()))) {
|
||||
output.clear();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string UTF16ToUTF8(const std::wstring& input) {
|
||||
auto const size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
|
||||
const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
|
||||
nullptr, 0, nullptr, nullptr);
|
||||
if (size == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string output;
|
||||
output.resize(size);
|
||||
std::string output(size, '\0');
|
||||
|
||||
if (size == 0 ||
|
||||
size != WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
|
||||
&output[0], static_cast<int>(output.size()), nullptr, nullptr))
|
||||
if (size != WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
|
||||
&output[0], static_cast<int>(output.size()), nullptr,
|
||||
nullptr)) {
|
||||
output.clear();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -343,11 +273,9 @@ std::string CP1252ToUTF8(const std::string& input) {
|
||||
|
||||
template <typename T>
|
||||
static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& input) {
|
||||
std::string result;
|
||||
|
||||
iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno));
|
||||
LOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
@@ -356,8 +284,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
|
||||
// Multiply by 4, which is the max number of bytes to encode a codepoint
|
||||
const size_t out_buffer_size = 4 * in_bytes;
|
||||
|
||||
std::string out_buffer;
|
||||
out_buffer.resize(out_buffer_size);
|
||||
std::string out_buffer(out_buffer_size, '\0');
|
||||
|
||||
auto src_buffer = &input[0];
|
||||
size_t src_bytes = in_bytes;
|
||||
@@ -376,12 +303,13 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [%s]: %s", fromcode, strerror(errno));
|
||||
LOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string result;
|
||||
out_buffer.resize(out_buffer_size - dst_bytes);
|
||||
out_buffer.swap(result);
|
||||
|
||||
@@ -391,11 +319,9 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
|
||||
}
|
||||
|
||||
std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
std::u16string result;
|
||||
|
||||
iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: %s", strerror(errno));
|
||||
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
@@ -404,8 +330,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
// Multiply by 4, which is the max number of bytes to encode a codepoint
|
||||
const size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes;
|
||||
|
||||
std::u16string out_buffer;
|
||||
out_buffer.resize(out_buffer_size);
|
||||
std::u16string out_buffer(out_buffer_size, char16_t{});
|
||||
|
||||
char* src_buffer = const_cast<char*>(&input[0]);
|
||||
size_t src_bytes = in_bytes;
|
||||
@@ -424,12 +349,13 @@ std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [UTF-8]: %s", strerror(errno));
|
||||
LOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string result;
|
||||
out_buffer.resize(out_buffer_size - dst_bytes);
|
||||
out_buffer.swap(result);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
@@ -20,21 +19,10 @@ std::string ToLower(std::string str);
|
||||
/// Make a string uppercase
|
||||
std::string ToUpper(std::string str);
|
||||
|
||||
std::string StringFromFormat(const char* format, ...);
|
||||
// Cheap!
|
||||
bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args);
|
||||
|
||||
template <size_t Count>
|
||||
inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
CharArrayFromFormatV(out, Count, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// Good
|
||||
std::string ArrayToString(const u8* data, size_t size, int line_len = 20, bool spaces = true);
|
||||
|
||||
std::string StringFromBuffer(const std::vector<u8>& data);
|
||||
|
||||
std::string StripSpaces(const std::string& s);
|
||||
std::string StripQuotes(const std::string& s);
|
||||
|
||||
@@ -69,10 +57,7 @@ static bool TryParse(const std::string& str, N* const output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: kill this
|
||||
bool AsciiToHex(const char* _szValue, u32& result);
|
||||
|
||||
std::string TabsToSpaces(int tab_size, const std::string& in);
|
||||
std::string TabsToSpaces(int tab_size, std::string in);
|
||||
|
||||
void SplitString(const std::string& str, char delim, std::vector<std::string>& output);
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ inline u32 swap32(u32 _data) {
|
||||
inline u64 swap64(u64 _data) {
|
||||
return _byteswap_uint64(_data);
|
||||
}
|
||||
#elif _M_ARM
|
||||
#elif defined(ARCHITECTURE_ARM) && (__ARM_ARCH >= 6)
|
||||
inline u16 swap16(u16 _data) {
|
||||
u32 data = _data;
|
||||
__asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
|
||||
@@ -167,7 +167,7 @@ inline double swapd(double f) {
|
||||
|
||||
template <typename T, typename F>
|
||||
struct swap_struct_t {
|
||||
typedef swap_struct_t<T, F> swapped_t;
|
||||
using swapped_t = swap_struct_t;
|
||||
|
||||
protected:
|
||||
T value = T();
|
||||
@@ -177,7 +177,7 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
T const swap() const {
|
||||
T swap() const {
|
||||
return swap(value);
|
||||
}
|
||||
swap_struct_t() = default;
|
||||
@@ -185,39 +185,39 @@ public:
|
||||
|
||||
template <typename S>
|
||||
swapped_t& operator=(const S& source) {
|
||||
value = swap((T)source);
|
||||
value = swap(static_cast<T>(source));
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator s8() const {
|
||||
return (s8)swap();
|
||||
return static_cast<s8>(swap());
|
||||
}
|
||||
operator u8() const {
|
||||
return (u8)swap();
|
||||
return static_cast<u8>(swap());
|
||||
}
|
||||
operator s16() const {
|
||||
return (s16)swap();
|
||||
return static_cast<s16>(swap());
|
||||
}
|
||||
operator u16() const {
|
||||
return (u16)swap();
|
||||
return static_cast<u16>(swap());
|
||||
}
|
||||
operator s32() const {
|
||||
return (s32)swap();
|
||||
return static_cast<s32>(swap());
|
||||
}
|
||||
operator u32() const {
|
||||
return (u32)swap();
|
||||
return static_cast<u32>(swap());
|
||||
}
|
||||
operator s64() const {
|
||||
return (s64)swap();
|
||||
return static_cast<s64>(swap());
|
||||
}
|
||||
operator u64() const {
|
||||
return (u64)swap();
|
||||
return static_cast<u64>(swap());
|
||||
}
|
||||
operator float() const {
|
||||
return (float)swap();
|
||||
return static_cast<float>(swap());
|
||||
}
|
||||
operator double() const {
|
||||
return (double)swap();
|
||||
return static_cast<double>(swap());
|
||||
}
|
||||
|
||||
// +v
|
||||
@@ -253,7 +253,7 @@ public:
|
||||
}
|
||||
template <typename S>
|
||||
swapped_t operator+(const S& i) const {
|
||||
return swap() + (T)i;
|
||||
return swap() + static_cast<T>(i);
|
||||
}
|
||||
// v - 5
|
||||
swapped_t operator-(const swapped_t& i) const {
|
||||
@@ -261,7 +261,7 @@ public:
|
||||
}
|
||||
template <typename S>
|
||||
swapped_t operator-(const S& i) const {
|
||||
return swap() - (T)i;
|
||||
return swap() - static_cast<T>(i);
|
||||
}
|
||||
|
||||
// v += 5
|
||||
@@ -271,7 +271,7 @@ public:
|
||||
}
|
||||
template <typename S>
|
||||
swapped_t& operator+=(const S& i) {
|
||||
value = swap(swap() + (T)i);
|
||||
value = swap(swap() + static_cast<T>(i));
|
||||
return *this;
|
||||
}
|
||||
// v -= 5
|
||||
@@ -281,7 +281,7 @@ public:
|
||||
}
|
||||
template <typename S>
|
||||
swapped_t& operator-=(const S& i) {
|
||||
value = swap(swap() - (T)i);
|
||||
value = swap(swap() - static_cast<T>(i));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ S operator&(const S& i, const swap_struct_t<T, F> v) {
|
||||
|
||||
template <typename S, typename T, typename F>
|
||||
S operator&(const swap_struct_t<T, F> v, const S& i) {
|
||||
return (S)(v.swap() & i);
|
||||
return static_cast<S>(v.swap() & i);
|
||||
}
|
||||
|
||||
// Comparaison
|
||||
@@ -606,51 +606,51 @@ struct swap_double_t {
|
||||
};
|
||||
|
||||
#if COMMON_LITTLE_ENDIAN
|
||||
typedef u32 u32_le;
|
||||
typedef u16 u16_le;
|
||||
typedef u64 u64_le;
|
||||
using u16_le = u16;
|
||||
using u32_le = u32;
|
||||
using u64_le = u64;
|
||||
|
||||
typedef s32 s32_le;
|
||||
typedef s16 s16_le;
|
||||
typedef s64 s64_le;
|
||||
using s16_le = s16;
|
||||
using s32_le = s32;
|
||||
using s64_le = s64;
|
||||
|
||||
typedef float float_le;
|
||||
typedef double double_le;
|
||||
using float_le = float;
|
||||
using double_le = double;
|
||||
|
||||
typedef swap_struct_t<u64, swap_64_t<u64>> u64_be;
|
||||
typedef swap_struct_t<s64, swap_64_t<s64>> s64_be;
|
||||
using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
typedef swap_struct_t<u32, swap_32_t<u32>> u32_be;
|
||||
typedef swap_struct_t<s32, swap_32_t<s32>> s32_be;
|
||||
using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
typedef swap_struct_t<u16, swap_16_t<u16>> u16_be;
|
||||
typedef swap_struct_t<s16, swap_16_t<s16>> s16_be;
|
||||
using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
typedef swap_struct_t<float, swap_float_t<float>> float_be;
|
||||
typedef swap_struct_t<double, swap_double_t<double>> double_be;
|
||||
using float_be = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_be = swap_struct_t<double, swap_double_t<double>>;
|
||||
#else
|
||||
|
||||
typedef swap_struct_t<u64, swap_64_t<u64>> u64_le;
|
||||
typedef swap_struct_t<s64, swap_64_t<s64>> s64_le;
|
||||
using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
typedef swap_struct_t<u32, swap_32_t<u32>> u32_le;
|
||||
typedef swap_struct_t<s32, swap_32_t<s32>> s32_le;
|
||||
using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
typedef swap_struct_t<u16, swap_16_t<u16>> u16_le;
|
||||
typedef swap_struct_t<s16, swap_16_t<s16>> s16_le;
|
||||
using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
typedef swap_struct_t<float, swap_float_t<float>> float_le;
|
||||
typedef swap_struct_t<double, swap_double_t<double>> double_le;
|
||||
using float_le = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_le = swap_struct_t<double, swap_double_t<double>>;
|
||||
|
||||
typedef u32 u32_be;
|
||||
typedef u16 u16_be;
|
||||
typedef u64 u64_be;
|
||||
using u16_be = u16;
|
||||
using u32_be = u32;
|
||||
using u64_be = u64;
|
||||
|
||||
typedef s32 s32_be;
|
||||
typedef s16 s16_be;
|
||||
typedef s64 s64_be;
|
||||
using s16_be = s16;
|
||||
using s32_be = s32;
|
||||
using s64_be = s64;
|
||||
|
||||
typedef float float_be;
|
||||
typedef double double_be;
|
||||
using float_be = float;
|
||||
using double_be = double;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename T>
|
||||
class SynchronizedWrapper;
|
||||
|
||||
/**
|
||||
* Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This
|
||||
* greatly reduces the chance that someone will access the wrapped resource without locking the
|
||||
* mutex.
|
||||
*/
|
||||
template <typename T>
|
||||
class SynchronizedRef {
|
||||
public:
|
||||
SynchronizedRef(SynchronizedWrapper<T>& wrapper) : wrapper(&wrapper) {
|
||||
wrapper.mutex.lock();
|
||||
}
|
||||
|
||||
SynchronizedRef(SynchronizedRef&) = delete;
|
||||
SynchronizedRef(SynchronizedRef&& o) : wrapper(o.wrapper) {
|
||||
o.wrapper = nullptr;
|
||||
}
|
||||
|
||||
~SynchronizedRef() {
|
||||
if (wrapper)
|
||||
wrapper->mutex.unlock();
|
||||
}
|
||||
|
||||
SynchronizedRef& operator=(SynchronizedRef&) = delete;
|
||||
SynchronizedRef& operator=(SynchronizedRef&& o) {
|
||||
std::swap(wrapper, o.wrapper);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T& operator*() {
|
||||
return wrapper->data;
|
||||
}
|
||||
const T& operator*() const {
|
||||
return wrapper->data;
|
||||
}
|
||||
|
||||
T* operator->() {
|
||||
return &wrapper->data;
|
||||
}
|
||||
const T* operator->() const {
|
||||
return &wrapper->data;
|
||||
}
|
||||
|
||||
private:
|
||||
SynchronizedWrapper<T>* wrapper;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no
|
||||
* one forgets to lock a mutex before acessing an object. To access the wrapped object construct a
|
||||
* SyncronizedRef on this wrapper. Inspired by Rust's Mutex type
|
||||
* (http://doc.rust-lang.org/std/sync/struct.Mutex.html).
|
||||
*/
|
||||
template <typename T>
|
||||
class SynchronizedWrapper {
|
||||
public:
|
||||
template <typename... Args>
|
||||
SynchronizedWrapper(Args&&... args) : data(std::forward<Args>(args)...) {}
|
||||
|
||||
SynchronizedRef<T> Lock() {
|
||||
return {*this};
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename U>
|
||||
friend class SynchronizedRef;
|
||||
|
||||
std::mutex mutex;
|
||||
T data;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -52,27 +52,14 @@ public:
|
||||
template <typename T>
|
||||
class Field : public FieldInterface {
|
||||
public:
|
||||
Field(FieldType type, std::string name, const T& value)
|
||||
: name(std::move(name)), type(type), value(value) {}
|
||||
|
||||
Field(FieldType type, std::string name, T&& value)
|
||||
Field(FieldType type, std::string name, T value)
|
||||
: name(std::move(name)), type(type), value(std::move(value)) {}
|
||||
|
||||
Field(const Field& other) : Field(other.type, other.name, other.value) {}
|
||||
Field(const Field&) = default;
|
||||
Field& operator=(const Field&) = default;
|
||||
|
||||
Field& operator=(const Field& other) {
|
||||
type = other.type;
|
||||
name = other.name;
|
||||
value = other.value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Field& operator=(Field&& other) {
|
||||
type = other.type;
|
||||
name = std::move(other.name);
|
||||
value = std::move(other.value);
|
||||
return *this;
|
||||
}
|
||||
Field(Field&&) = default;
|
||||
Field& operator=(Field&& other) = default;
|
||||
|
||||
void Accept(VisitorInterface& visitor) const override;
|
||||
|
||||
@@ -94,11 +81,11 @@ public:
|
||||
return value;
|
||||
}
|
||||
|
||||
inline bool operator==(const Field<T>& other) {
|
||||
bool operator==(const Field& other) const {
|
||||
return (type == other.type) && (name == other.name) && (value == other.value);
|
||||
}
|
||||
|
||||
inline bool operator!=(const Field<T>& other) {
|
||||
bool operator!=(const Field& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,25 +11,6 @@
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
|
||||
// Support for C++11's thread_local keyword was surprisingly spotty in compilers until very
|
||||
// recently. Fortunately, thread local variables have been well supported for compilers for a while,
|
||||
// but with semantics supporting only POD types, so we can use a few defines to get some amount of
|
||||
// backwards compat support.
|
||||
// WARNING: This only works correctly with POD types.
|
||||
#if defined(__clang__)
|
||||
#if !__has_feature(cxx_thread_local)
|
||||
#define thread_local __thread
|
||||
#endif
|
||||
#elif defined(__GNUC__)
|
||||
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
|
||||
#define thread_local __thread
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
#if _MSC_VER < 1900
|
||||
#define thread_local __declspec(thread)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
int CurrentThreadId();
|
||||
|
||||
@@ -33,9 +33,11 @@ public:
|
||||
bool Empty() const {
|
||||
return !read_ptr->next.load();
|
||||
}
|
||||
|
||||
T& Front() const {
|
||||
return read_ptr->current;
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
void Push(Arg&& t) {
|
||||
// create the element, add it to the queue
|
||||
@@ -108,15 +110,41 @@ private:
|
||||
// single reader, multiple writer queue
|
||||
|
||||
template <typename T, bool NeedSize = true>
|
||||
class MPSCQueue : public SPSCQueue<T, NeedSize> {
|
||||
class MPSCQueue {
|
||||
public:
|
||||
u32 Size() const {
|
||||
return spsc_queue.Size();
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return spsc_queue.Empty();
|
||||
}
|
||||
|
||||
T& Front() const {
|
||||
return spsc_queue.Front();
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
void Push(Arg&& t) {
|
||||
std::lock_guard<std::mutex> lock(write_lock);
|
||||
SPSCQueue<T, NeedSize>::Push(t);
|
||||
spsc_queue.Push(t);
|
||||
}
|
||||
|
||||
void Pop() {
|
||||
return spsc_queue.Pop();
|
||||
}
|
||||
|
||||
bool Pop(T& t) {
|
||||
return spsc_queue.Pop(t);
|
||||
}
|
||||
|
||||
// not thread-safe
|
||||
void Clear() {
|
||||
spsc_queue.Clear();
|
||||
}
|
||||
|
||||
private:
|
||||
SPSCQueue<T, NeedSize> spsc_queue;
|
||||
std::mutex write_lock;
|
||||
};
|
||||
} // namespace Common
|
||||
|
||||
@@ -2,29 +2,17 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <time.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
// windows.h needs to be included before other windows headers
|
||||
#include <mmsystem.h>
|
||||
#include <sys/timeb.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#include <ctime>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
u32 Timer::GetTimeMs() {
|
||||
#ifdef _WIN32
|
||||
return timeGetTime();
|
||||
#else
|
||||
struct timeval t;
|
||||
(void)gettimeofday(&t, nullptr);
|
||||
return ((u32)(t.tv_sec * 1000 + t.tv_usec / 1000));
|
||||
#endif
|
||||
std::chrono::milliseconds Timer::GetTimeMs() {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
@@ -60,7 +48,7 @@ void Timer::Update() {
|
||||
// -------------------------------------
|
||||
|
||||
// Get the number of milliseconds since the last Update()
|
||||
u64 Timer::GetTimeDifference() {
|
||||
std::chrono::milliseconds Timer::GetTimeDifference() {
|
||||
return GetTimeMs() - m_LastTime;
|
||||
}
|
||||
|
||||
@@ -71,11 +59,11 @@ void Timer::AddTimeDifference() {
|
||||
}
|
||||
|
||||
// Get the time elapsed since the Start()
|
||||
u64 Timer::GetTimeElapsed() {
|
||||
std::chrono::milliseconds Timer::GetTimeElapsed() {
|
||||
// If we have not started yet, return 1 (because then I don't
|
||||
// have to change the FPS calculation in CoreRerecording.cpp .
|
||||
if (m_StartTime == 0)
|
||||
return 1;
|
||||
if (m_StartTime.count() == 0)
|
||||
return std::chrono::milliseconds(1);
|
||||
|
||||
// Return the final timer time if the timer is stopped
|
||||
if (!m_Running)
|
||||
@@ -87,49 +75,34 @@ u64 Timer::GetTimeElapsed() {
|
||||
// Get the formatted time elapsed since the Start()
|
||||
std::string Timer::GetTimeElapsedFormatted() const {
|
||||
// If we have not started yet, return zero
|
||||
if (m_StartTime == 0)
|
||||
if (m_StartTime.count() == 0)
|
||||
return "00:00:00:000";
|
||||
|
||||
// The number of milliseconds since the start.
|
||||
// Use a different value if the timer is stopped.
|
||||
u64 Milliseconds;
|
||||
std::chrono::milliseconds Milliseconds;
|
||||
if (m_Running)
|
||||
Milliseconds = GetTimeMs() - m_StartTime;
|
||||
else
|
||||
Milliseconds = m_LastTime - m_StartTime;
|
||||
// Seconds
|
||||
u32 Seconds = (u32)(Milliseconds / 1000);
|
||||
std::chrono::seconds Seconds = std::chrono::duration_cast<std::chrono::seconds>(Milliseconds);
|
||||
// Minutes
|
||||
u32 Minutes = Seconds / 60;
|
||||
std::chrono::minutes Minutes = std::chrono::duration_cast<std::chrono::minutes>(Milliseconds);
|
||||
// Hours
|
||||
u32 Hours = Minutes / 60;
|
||||
std::chrono::hours Hours = std::chrono::duration_cast<std::chrono::hours>(Milliseconds);
|
||||
|
||||
std::string TmpStr = StringFromFormat("%02i:%02i:%02i:%03i", Hours, Minutes % 60, Seconds % 60,
|
||||
Milliseconds % 1000);
|
||||
std::string TmpStr = fmt::format("{:02}:{:02}:{:02}:{:03}", Hours.count(), Minutes.count() % 60,
|
||||
Seconds.count() % 60, Milliseconds.count() % 1000);
|
||||
return TmpStr;
|
||||
}
|
||||
|
||||
// Get current time
|
||||
void Timer::IncreaseResolution() {
|
||||
#ifdef _WIN32
|
||||
timeBeginPeriod(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Timer::RestoreResolution() {
|
||||
#ifdef _WIN32
|
||||
timeEndPeriod(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Get the number of seconds since January 1 1970
|
||||
u64 Timer::GetTimeSinceJan1970() {
|
||||
time_t ltime;
|
||||
time(<ime);
|
||||
return ((u64)ltime);
|
||||
std::chrono::seconds Timer::GetTimeSinceJan1970() {
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(GetTimeMs());
|
||||
}
|
||||
|
||||
u64 Timer::GetLocalTimeSinceJan1970() {
|
||||
std::chrono::seconds Timer::GetLocalTimeSinceJan1970() {
|
||||
time_t sysTime, tzDiff, tzDST;
|
||||
struct tm* gmTime;
|
||||
|
||||
@@ -146,7 +119,7 @@ u64 Timer::GetLocalTimeSinceJan1970() {
|
||||
gmTime = gmtime(&sysTime);
|
||||
tzDiff = sysTime - mktime(gmTime);
|
||||
|
||||
return (u64)(sysTime + tzDiff + tzDST);
|
||||
return std::chrono::seconds(sysTime + tzDiff + tzDST);
|
||||
}
|
||||
|
||||
// Return the current time formatted as Minutes:Seconds:Milliseconds
|
||||
@@ -161,30 +134,16 @@ std::string Timer::GetTimeFormatted() {
|
||||
|
||||
strftime(tmp, 6, "%M:%S", gmTime);
|
||||
|
||||
// Now tack on the milliseconds
|
||||
#ifdef _WIN32
|
||||
struct timeb tp;
|
||||
(void)::ftime(&tp);
|
||||
return StringFromFormat("%s:%03i", tmp, tp.millitm);
|
||||
#else
|
||||
struct timeval t;
|
||||
(void)gettimeofday(&t, nullptr);
|
||||
return StringFromFormat("%s:%03d", tmp, (int)(t.tv_usec / 1000));
|
||||
#endif
|
||||
u64 milliseconds = static_cast<u64>(GetTimeMs().count()) % 1000;
|
||||
return fmt::format("{}:{:03}", tmp, milliseconds);
|
||||
}
|
||||
|
||||
// Returns a timestamp with decimals for precise time comparisons
|
||||
// ----------------
|
||||
double Timer::GetDoubleTime() {
|
||||
#ifdef _WIN32
|
||||
struct timeb tp;
|
||||
(void)::ftime(&tp);
|
||||
#else
|
||||
struct timeval t;
|
||||
(void)gettimeofday(&t, nullptr);
|
||||
#endif
|
||||
// Get continuous timestamp
|
||||
u64 TmpSeconds = Common::Timer::GetTimeSinceJan1970();
|
||||
u64 TmpSeconds = static_cast<u64>(Common::Timer::GetTimeSinceJan1970().count());
|
||||
double ms = static_cast<u64>(GetTimeMs().count()) % 1000;
|
||||
|
||||
// Remove a few years. We only really want enough seconds to make
|
||||
// sure that we are detecting actual actions, perhaps 60 seconds is
|
||||
@@ -193,12 +152,7 @@ double Timer::GetDoubleTime() {
|
||||
TmpSeconds = TmpSeconds - (38 * 365 * 24 * 60 * 60);
|
||||
|
||||
// Make a smaller integer that fits in the double
|
||||
u32 Seconds = (u32)TmpSeconds;
|
||||
#ifdef _WIN32
|
||||
double ms = tp.millitm / 1000.0 / 1000.0;
|
||||
#else
|
||||
double ms = t.tv_usec / 1000000.0;
|
||||
#endif
|
||||
u32 Seconds = static_cast<u32>(TmpSeconds);
|
||||
double TmpTime = Seconds + ms;
|
||||
|
||||
return TmpTime;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -18,24 +19,22 @@ public:
|
||||
|
||||
// The time difference is always returned in milliseconds, regardless of alternative internal
|
||||
// representation
|
||||
u64 GetTimeDifference();
|
||||
std::chrono::milliseconds GetTimeDifference();
|
||||
void AddTimeDifference();
|
||||
|
||||
static void IncreaseResolution();
|
||||
static void RestoreResolution();
|
||||
static u64 GetTimeSinceJan1970();
|
||||
static u64 GetLocalTimeSinceJan1970();
|
||||
static std::chrono::seconds GetTimeSinceJan1970();
|
||||
static std::chrono::seconds GetLocalTimeSinceJan1970();
|
||||
static double GetDoubleTime();
|
||||
|
||||
static std::string GetTimeFormatted();
|
||||
std::string GetTimeElapsedFormatted() const;
|
||||
u64 GetTimeElapsed();
|
||||
std::chrono::milliseconds GetTimeElapsed();
|
||||
|
||||
static u32 GetTimeMs();
|
||||
static std::chrono::milliseconds GetTimeMs();
|
||||
|
||||
private:
|
||||
u64 m_LastTime;
|
||||
u64 m_StartTime;
|
||||
std::chrono::milliseconds m_LastTime;
|
||||
std::chrono::milliseconds m_StartTime;
|
||||
bool m_Running;
|
||||
};
|
||||
|
||||
|
||||
@@ -52,12 +52,8 @@ static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w);
|
||||
template <typename T>
|
||||
class Vec2 {
|
||||
public:
|
||||
T x;
|
||||
T y;
|
||||
|
||||
T* AsArray() {
|
||||
return &x;
|
||||
}
|
||||
T x{};
|
||||
T y{};
|
||||
|
||||
Vec2() = default;
|
||||
Vec2(const T& _x, const T& _y) : x(_x), y(_y) {}
|
||||
@@ -71,11 +67,6 @@ public:
|
||||
return Vec2<T>(f, f);
|
||||
}
|
||||
|
||||
void Write(T a[2]) {
|
||||
a[0] = x;
|
||||
a[1] = y;
|
||||
}
|
||||
|
||||
Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
|
||||
return MakeVec(x + other.x, y + other.y);
|
||||
}
|
||||
@@ -201,13 +192,9 @@ inline float Vec2<float>::Normalize() {
|
||||
template <typename T>
|
||||
class Vec3 {
|
||||
public:
|
||||
T x;
|
||||
T y;
|
||||
T z;
|
||||
|
||||
T* AsArray() {
|
||||
return &x;
|
||||
}
|
||||
T x{};
|
||||
T y{};
|
||||
T z{};
|
||||
|
||||
Vec3() = default;
|
||||
Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {}
|
||||
@@ -225,12 +212,6 @@ public:
|
||||
return MakeVec(f, f, f);
|
||||
}
|
||||
|
||||
void Write(T a[3]) {
|
||||
a[0] = x;
|
||||
a[1] = y;
|
||||
a[2] = z;
|
||||
}
|
||||
|
||||
Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
|
||||
return MakeVec(x + other.x, y + other.y, z + other.z);
|
||||
}
|
||||
@@ -411,14 +392,10 @@ typedef Vec3<float> Vec3f;
|
||||
template <typename T>
|
||||
class Vec4 {
|
||||
public:
|
||||
T x;
|
||||
T y;
|
||||
T z;
|
||||
T w;
|
||||
|
||||
T* AsArray() {
|
||||
return &x;
|
||||
}
|
||||
T x{};
|
||||
T y{};
|
||||
T z{};
|
||||
T w{};
|
||||
|
||||
Vec4() = default;
|
||||
Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {}
|
||||
@@ -436,13 +413,6 @@ public:
|
||||
return Vec4<T>(f, f, f, f);
|
||||
}
|
||||
|
||||
void Write(T a[4]) {
|
||||
a[0] = x;
|
||||
a[1] = y;
|
||||
a[2] = z;
|
||||
a[3] = w;
|
||||
}
|
||||
|
||||
Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
|
||||
return MakeVec(x + other.x, y + other.y, z + other.z, w + other.w);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,54 @@
|
||||
add_library(core STATIC
|
||||
arm/arm_interface.h
|
||||
arm/exclusive_monitor.cpp
|
||||
arm/exclusive_monitor.h
|
||||
arm/unicorn/arm_unicorn.cpp
|
||||
arm/unicorn/arm_unicorn.h
|
||||
core.cpp
|
||||
core.h
|
||||
core_cpu.cpp
|
||||
core_cpu.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
core_timing_util.cpp
|
||||
core_timing_util.h
|
||||
crypto/aes_util.cpp
|
||||
crypto/aes_util.h
|
||||
crypto/encryption_layer.cpp
|
||||
crypto/encryption_layer.h
|
||||
crypto/key_manager.cpp
|
||||
crypto/key_manager.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
crypto/ctr_encryption_layer.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
file_sys/control_metadata.h
|
||||
file_sys/directory.h
|
||||
file_sys/disk_filesystem.cpp
|
||||
file_sys/disk_filesystem.h
|
||||
file_sys/errors.h
|
||||
file_sys/filesystem.cpp
|
||||
file_sys/filesystem.h
|
||||
file_sys/mode.h
|
||||
file_sys/partition_filesystem.cpp
|
||||
file_sys/partition_filesystem.h
|
||||
file_sys/path_parser.cpp
|
||||
file_sys/path_parser.h
|
||||
file_sys/program_metadata.cpp
|
||||
file_sys/program_metadata.h
|
||||
file_sys/romfs.cpp
|
||||
file_sys/romfs.h
|
||||
file_sys/romfs_factory.cpp
|
||||
file_sys/romfs_factory.h
|
||||
file_sys/romfs_filesystem.cpp
|
||||
file_sys/romfs_filesystem.h
|
||||
file_sys/savedata_factory.cpp
|
||||
file_sys/savedata_factory.h
|
||||
file_sys/sdmc_factory.cpp
|
||||
file_sys/sdmc_factory.h
|
||||
file_sys/storage.h
|
||||
file_sys/vfs.cpp
|
||||
file_sys/vfs.h
|
||||
file_sys/vfs_offset.cpp
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
file_sys/vfs_real.h
|
||||
file_sys/vfs_vector.cpp
|
||||
file_sys/vfs_vector.h
|
||||
frontend/emu_window.cpp
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
@@ -34,16 +56,14 @@ add_library(core STATIC
|
||||
frontend/input.h
|
||||
gdbstub/gdbstub.cpp
|
||||
gdbstub/gdbstub.h
|
||||
hle/config_mem.cpp
|
||||
hle/config_mem.h
|
||||
hle/ipc.h
|
||||
hle/ipc_helpers.h
|
||||
hle/kernel/address_arbiter.cpp
|
||||
hle/kernel/address_arbiter.h
|
||||
hle/kernel/client_port.cpp
|
||||
hle/kernel/client_port.h
|
||||
hle/kernel/client_session.cpp
|
||||
hle/kernel/client_session.h
|
||||
hle/kernel/condition_variable.cpp
|
||||
hle/kernel/condition_variable.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/event.cpp
|
||||
hle/kernel/event.h
|
||||
@@ -53,12 +73,10 @@ add_library(core STATIC
|
||||
hle/kernel/hle_ipc.h
|
||||
hle/kernel/kernel.cpp
|
||||
hle/kernel/kernel.h
|
||||
hle/kernel/memory.cpp
|
||||
hle/kernel/memory.h
|
||||
hle/kernel/mutex.cpp
|
||||
hle/kernel/mutex.h
|
||||
hle/kernel/object_address_table.cpp
|
||||
hle/kernel/object_address_table.h
|
||||
hle/kernel/object.cpp
|
||||
hle/kernel/object.h
|
||||
hle/kernel/process.cpp
|
||||
hle/kernel/process.h
|
||||
hle/kernel/resource_limit.cpp
|
||||
@@ -104,26 +122,62 @@ add_library(core STATIC
|
||||
hle/service/am/applet_ae.h
|
||||
hle/service/am/applet_oe.cpp
|
||||
hle/service/am/applet_oe.h
|
||||
hle/service/am/idle.cpp
|
||||
hle/service/am/idle.h
|
||||
hle/service/am/omm.cpp
|
||||
hle/service/am/omm.h
|
||||
hle/service/am/spsm.cpp
|
||||
hle/service/am/spsm.h
|
||||
hle/service/aoc/aoc_u.cpp
|
||||
hle/service/aoc/aoc_u.h
|
||||
hle/service/apm/apm.cpp
|
||||
hle/service/apm/apm.h
|
||||
hle/service/apm/interface.cpp
|
||||
hle/service/apm/interface.h
|
||||
hle/service/audio/audio.cpp
|
||||
hle/service/audio/audio.h
|
||||
hle/service/audio/audctl.cpp
|
||||
hle/service/audio/audctl.h
|
||||
hle/service/audio/auddbg.cpp
|
||||
hle/service/audio/auddbg.h
|
||||
hle/service/audio/audin_a.cpp
|
||||
hle/service/audio/audin_a.h
|
||||
hle/service/audio/audin_u.cpp
|
||||
hle/service/audio/audin_u.h
|
||||
hle/service/audio/audio.cpp
|
||||
hle/service/audio/audio.h
|
||||
hle/service/audio/audout_a.cpp
|
||||
hle/service/audio/audout_a.h
|
||||
hle/service/audio/audout_u.cpp
|
||||
hle/service/audio/audout_u.h
|
||||
hle/service/audio/audrec_a.cpp
|
||||
hle/service/audio/audrec_a.h
|
||||
hle/service/audio/audrec_u.cpp
|
||||
hle/service/audio/audrec_u.h
|
||||
hle/service/audio/audren_u.cpp
|
||||
hle/service/audio/audren_u.h
|
||||
hle/service/audio/audren_a.cpp
|
||||
hle/service/audio/audren_a.h
|
||||
hle/service/audio/audren_u.cpp
|
||||
hle/service/audio/audren_u.h
|
||||
hle/service/audio/codecctl.cpp
|
||||
hle/service/audio/codecctl.h
|
||||
hle/service/audio/hwopus.cpp
|
||||
hle/service/audio/hwopus.h
|
||||
hle/service/bcat/bcat.cpp
|
||||
hle/service/bcat/bcat.h
|
||||
hle/service/bcat/module.cpp
|
||||
hle/service/bcat/module.h
|
||||
hle/service/bpc/bpc.cpp
|
||||
hle/service/bpc/bpc.h
|
||||
hle/service/btdrv/btdrv.cpp
|
||||
hle/service/btdrv/btdrv.h
|
||||
hle/service/btm/btm.cpp
|
||||
hle/service/btm/btm.h
|
||||
hle/service/caps/caps.cpp
|
||||
hle/service/caps/caps.h
|
||||
hle/service/erpt/erpt.cpp
|
||||
hle/service/erpt/erpt.h
|
||||
hle/service/es/es.cpp
|
||||
hle/service/es/es.h
|
||||
hle/service/eupld/eupld.cpp
|
||||
hle/service/eupld/eupld.h
|
||||
hle/service/fatal/fatal.cpp
|
||||
hle/service/fatal/fatal.h
|
||||
hle/service/fatal/fatal_p.cpp
|
||||
@@ -132,30 +186,52 @@ add_library(core STATIC
|
||||
hle/service/fatal/fatal_u.h
|
||||
hle/service/filesystem/filesystem.cpp
|
||||
hle/service/filesystem/filesystem.h
|
||||
hle/service/filesystem/fsp_ldr.cpp
|
||||
hle/service/filesystem/fsp_ldr.h
|
||||
hle/service/filesystem/fsp_pr.cpp
|
||||
hle/service/filesystem/fsp_pr.h
|
||||
hle/service/filesystem/fsp_srv.cpp
|
||||
hle/service/filesystem/fsp_srv.h
|
||||
hle/service/fgm/fgm.cpp
|
||||
hle/service/fgm/fgm.h
|
||||
hle/service/friend/friend.cpp
|
||||
hle/service/friend/friend.h
|
||||
hle/service/friend/friend_a.cpp
|
||||
hle/service/friend/friend_a.h
|
||||
hle/service/friend/friend_u.cpp
|
||||
hle/service/friend/friend_u.h
|
||||
hle/service/friend/interface.cpp
|
||||
hle/service/friend/interface.h
|
||||
hle/service/grc/grc.cpp
|
||||
hle/service/grc/grc.h
|
||||
hle/service/hid/hid.cpp
|
||||
hle/service/hid/hid.h
|
||||
hle/service/hid/irs.cpp
|
||||
hle/service/hid/irs.h
|
||||
hle/service/hid/xcd.cpp
|
||||
hle/service/hid/xcd.h
|
||||
hle/service/lbl/lbl.cpp
|
||||
hle/service/lbl/lbl.h
|
||||
hle/service/ldn/ldn.cpp
|
||||
hle/service/ldn/ldn.h
|
||||
hle/service/ldr/ldr.cpp
|
||||
hle/service/ldr/ldr.h
|
||||
hle/service/lm/lm.cpp
|
||||
hle/service/lm/lm.h
|
||||
hle/service/nifm/nifm.cpp
|
||||
hle/service/nifm/nifm.h
|
||||
hle/service/nifm/nifm_a.cpp
|
||||
hle/service/nifm/nifm_a.h
|
||||
hle/service/nifm/nifm_s.cpp
|
||||
hle/service/nifm/nifm_s.h
|
||||
hle/service/nifm/nifm_u.cpp
|
||||
hle/service/nifm/nifm_u.h
|
||||
hle/service/mig/mig.cpp
|
||||
hle/service/mig/mig.h
|
||||
hle/service/mii/mii.cpp
|
||||
hle/service/mii/mii.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
hle/service/mm/mm_u.h
|
||||
hle/service/ncm/ncm.cpp
|
||||
hle/service/ncm/ncm.h
|
||||
hle/service/nfc/nfc.cpp
|
||||
hle/service/nfc/nfc.h
|
||||
hle/service/nfp/nfp.cpp
|
||||
hle/service/nfp/nfp.h
|
||||
hle/service/nfp/nfp_user.cpp
|
||||
hle/service/nfp/nfp_user.h
|
||||
hle/service/nifm/nifm.cpp
|
||||
hle/service/nifm/nifm.h
|
||||
hle/service/nim/nim.cpp
|
||||
hle/service/nim/nim.h
|
||||
hle/service/ns/ns.cpp
|
||||
hle/service/ns/ns.h
|
||||
hle/service/ns/pl_u.cpp
|
||||
@@ -171,6 +247,8 @@ add_library(core STATIC
|
||||
hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
|
||||
hle/service/nvdrv/devices/nvhost_gpu.cpp
|
||||
hle/service/nvdrv/devices/nvhost_gpu.h
|
||||
hle/service/nvdrv/devices/nvhost_nvdec.cpp
|
||||
hle/service/nvdrv/devices/nvhost_nvdec.h
|
||||
hle/service/nvdrv/devices/nvmap.cpp
|
||||
hle/service/nvdrv/devices/nvmap.h
|
||||
hle/service/nvdrv/interface.cpp
|
||||
@@ -183,10 +261,20 @@ add_library(core STATIC
|
||||
hle/service/nvflinger/buffer_queue.h
|
||||
hle/service/nvflinger/nvflinger.cpp
|
||||
hle/service/nvflinger/nvflinger.h
|
||||
hle/service/pcie/pcie.cpp
|
||||
hle/service/pcie/pcie.h
|
||||
hle/service/pctl/module.cpp
|
||||
hle/service/pctl/module.h
|
||||
hle/service/pctl/pctl.cpp
|
||||
hle/service/pctl/pctl.h
|
||||
hle/service/pctl/pctl_a.cpp
|
||||
hle/service/pctl/pctl_a.h
|
||||
hle/service/pcv/pcv.cpp
|
||||
hle/service/pcv/pcv.h
|
||||
hle/service/pm/pm.cpp
|
||||
hle/service/pm/pm.h
|
||||
hle/service/prepo/prepo.cpp
|
||||
hle/service/prepo/prepo.h
|
||||
hle/service/psc/psc.cpp
|
||||
hle/service/psc/psc.h
|
||||
hle/service/service.cpp
|
||||
hle/service/service.h
|
||||
hle/service/set/set.cpp
|
||||
@@ -205,6 +293,8 @@ add_library(core STATIC
|
||||
hle/service/sm/sm.h
|
||||
hle/service/sockets/bsd.cpp
|
||||
hle/service/sockets/bsd.h
|
||||
hle/service/sockets/ethc.cpp
|
||||
hle/service/sockets/ethc.h
|
||||
hle/service/sockets/nsd.cpp
|
||||
hle/service/sockets/nsd.h
|
||||
hle/service/sockets/sfdnsres.cpp
|
||||
@@ -219,12 +309,10 @@ add_library(core STATIC
|
||||
hle/service/spl/spl.h
|
||||
hle/service/ssl/ssl.cpp
|
||||
hle/service/ssl/ssl.h
|
||||
hle/service/time/interface.cpp
|
||||
hle/service/time/interface.h
|
||||
hle/service/time/time.cpp
|
||||
hle/service/time/time.h
|
||||
hle/service/time/time_s.cpp
|
||||
hle/service/time/time_s.h
|
||||
hle/service/time/time_u.cpp
|
||||
hle/service/time/time_u.h
|
||||
hle/service/vi/vi.cpp
|
||||
hle/service/vi/vi.h
|
||||
hle/service/vi/vi_m.cpp
|
||||
@@ -233,12 +321,8 @@ add_library(core STATIC
|
||||
hle/service/vi/vi_s.h
|
||||
hle/service/vi/vi_u.cpp
|
||||
hle/service/vi/vi_u.h
|
||||
hle/shared_page.cpp
|
||||
hle/shared_page.h
|
||||
hw/hw.cpp
|
||||
hw/hw.h
|
||||
hw/lcd.cpp
|
||||
hw/lcd.h
|
||||
hle/service/wlan/wlan.cpp
|
||||
hle/service/wlan/wlan.h
|
||||
loader/deconstructed_rom_directory.cpp
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/elf.cpp
|
||||
@@ -247,12 +331,17 @@ add_library(core STATIC
|
||||
loader/linker.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/nca.cpp
|
||||
loader/nca.h
|
||||
loader/nro.cpp
|
||||
loader/nro.h
|
||||
loader/nso.cpp
|
||||
loader/nso.h
|
||||
loader/xci.cpp
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
memory_setup.h
|
||||
perf_stats.cpp
|
||||
@@ -268,8 +357,8 @@ add_library(core STATIC
|
||||
|
||||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static unicorn)
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn)
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_sources(core PRIVATE
|
||||
|
||||
@@ -20,9 +20,6 @@ public:
|
||||
u64 cpsr;
|
||||
std::array<u128, 32> fpu_registers;
|
||||
u64 fpscr;
|
||||
|
||||
// TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
|
||||
VAddr tls_address;
|
||||
};
|
||||
|
||||
/// Runs the CPU until an event happens
|
||||
@@ -104,6 +101,10 @@ public:
|
||||
|
||||
virtual void SetTlsAddress(VAddr address) = 0;
|
||||
|
||||
virtual u64 GetTPIDR_EL0() const = 0;
|
||||
|
||||
virtual void SetTPIDR_EL0(u64 value) = 0;
|
||||
|
||||
/**
|
||||
* Saves the current CPU context
|
||||
* @param ctx Thread context to save
|
||||
@@ -116,6 +117,8 @@ public:
|
||||
*/
|
||||
virtual void LoadContext(const ThreadContext& ctx) = 0;
|
||||
|
||||
virtual void ClearExclusiveState() = 0;
|
||||
|
||||
/// Prepare core for thread reschedule (if needed to correctly handle state)
|
||||
virtual void PrepareReschedule() = 0;
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -55,8 +55,8 @@ public:
|
||||
}
|
||||
|
||||
void InterpreterFallback(u64 pc, size_t num_instructions) override {
|
||||
LOG_INFO(Core_ARM, "Unicorn fallback @ 0x%" PRIx64 " for %zu instructions (instr = %08x)",
|
||||
pc, num_instructions, MemoryReadCode(pc));
|
||||
LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc,
|
||||
num_instructions, MemoryReadCode(pc));
|
||||
|
||||
ARM_Interface::ThreadContext ctx;
|
||||
parent.SaveContext(ctx);
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
case Dynarmic::A64::Exception::Yield:
|
||||
return;
|
||||
default:
|
||||
ASSERT_MSG(false, "ExceptionRaised(exception = %zu, pc = %" PRIx64 ")",
|
||||
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})",
|
||||
static_cast<size_t>(exception), pc);
|
||||
}
|
||||
}
|
||||
@@ -102,18 +102,28 @@ public:
|
||||
u64 tpidr_el0 = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Dynarmic::A64::Jit> MakeJit(const std::unique_ptr<ARM_Dynarmic_Callbacks>& cb) {
|
||||
const auto page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data();
|
||||
std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
auto** const page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data();
|
||||
|
||||
Dynarmic::A64::UserConfig config;
|
||||
|
||||
// Callbacks
|
||||
config.callbacks = cb.get();
|
||||
|
||||
// Memory
|
||||
config.page_table = reinterpret_cast<void**>(page_table);
|
||||
config.page_table_address_space_bits = Memory::ADDRESS_SPACE_BITS;
|
||||
config.silently_mirror_page_table = false;
|
||||
|
||||
// Multi-process state
|
||||
config.processor_id = core_index;
|
||||
config.global_monitor = &exclusive_monitor->monitor;
|
||||
|
||||
// System registers
|
||||
config.tpidrro_el0 = &cb->tpidrro_el0;
|
||||
config.tpidr_el0 = &cb->tpidr_el0;
|
||||
config.dczid_el0 = 4;
|
||||
config.ctr_el0 = 0x8444c004;
|
||||
config.page_table = reinterpret_cast<void**>(page_table);
|
||||
config.page_table_address_space_bits = Memory::ADDRESS_SPACE_BITS;
|
||||
config.silently_mirror_page_table = false;
|
||||
|
||||
return std::make_unique<Dynarmic::A64::Jit>(config);
|
||||
}
|
||||
@@ -128,12 +138,13 @@ void ARM_Dynarmic::Step() {
|
||||
cb->InterpreterFallback(jit->GetPC(), 1);
|
||||
}
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic()
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), jit(MakeJit(cb)) {
|
||||
ARM_Interface::ThreadContext ctx;
|
||||
ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, size_t core_index)
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
|
||||
exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
|
||||
ThreadContext ctx;
|
||||
inner_unicorn.SaveContext(ctx);
|
||||
LoadContext(ctx);
|
||||
PageTableChanged();
|
||||
LoadContext(ctx);
|
||||
}
|
||||
|
||||
ARM_Dynarmic::~ARM_Dynarmic() = default;
|
||||
@@ -192,28 +203,34 @@ u64 ARM_Dynarmic::GetTlsAddress() const {
|
||||
return cb->tpidrro_el0;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetTlsAddress(u64 address) {
|
||||
void ARM_Dynarmic::SetTlsAddress(VAddr address) {
|
||||
cb->tpidrro_el0 = address;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
u64 ARM_Dynarmic::GetTPIDR_EL0() const {
|
||||
return cb->tpidr_el0;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetTPIDR_EL0(u64 value) {
|
||||
cb->tpidr_el0 = value;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SaveContext(ThreadContext& ctx) {
|
||||
ctx.cpu_registers = jit->GetRegisters();
|
||||
ctx.sp = jit->GetSP();
|
||||
ctx.pc = jit->GetPC();
|
||||
ctx.cpsr = jit->GetPstate();
|
||||
ctx.fpu_registers = jit->GetVectors();
|
||||
ctx.fpscr = jit->GetFpcr();
|
||||
ctx.tls_address = cb->tpidrro_el0;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) {
|
||||
void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) {
|
||||
jit->SetRegisters(ctx.cpu_registers);
|
||||
jit->SetSP(ctx.sp);
|
||||
jit->SetPC(ctx.pc);
|
||||
jit->SetPstate(static_cast<u32>(ctx.cpsr));
|
||||
jit->SetVectors(ctx.fpu_registers);
|
||||
jit->SetFpcr(static_cast<u32>(ctx.fpscr));
|
||||
cb->tpidrro_el0 = ctx.tls_address;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::PrepareReschedule() {
|
||||
@@ -226,7 +243,50 @@ void ARM_Dynarmic::ClearInstructionCache() {
|
||||
jit->ClearCache();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::ClearExclusiveState() {
|
||||
jit->ClearExclusiveState();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::PageTableChanged() {
|
||||
jit = MakeJit(cb);
|
||||
jit = MakeJit();
|
||||
current_page_table = Memory::GetCurrentPageTable();
|
||||
}
|
||||
|
||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(size_t core_count) : monitor(core_count) {}
|
||||
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
|
||||
|
||||
void DynarmicExclusiveMonitor::SetExclusive(size_t core_index, VAddr addr) {
|
||||
// Size doesn't actually matter.
|
||||
monitor.Mark(core_index, addr, 16);
|
||||
}
|
||||
|
||||
void DynarmicExclusiveMonitor::ClearExclusive() {
|
||||
monitor.Clear();
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite8(size_t core_index, VAddr vaddr, u8 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 1,
|
||||
[&] { Memory::Write8(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite16(size_t core_index, VAddr vaddr, u16 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 2,
|
||||
[&] { Memory::Write16(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite32(size_t core_index, VAddr vaddr, u32 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 4,
|
||||
[&] { Memory::Write32(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite64(size_t core_index, VAddr vaddr, u64 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 8,
|
||||
[&] { Memory::Write64(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite128(size_t core_index, VAddr vaddr, u128 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 16, [&] {
|
||||
Memory::Write64(vaddr, value[0]);
|
||||
Memory::Write64(vaddr, value[1]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
|
||||
#include <memory>
|
||||
#include <dynarmic/A64/a64.h>
|
||||
#include <dynarmic/A64/exclusive_monitor.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
class ARM_Dynarmic_Callbacks;
|
||||
class DynarmicExclusiveMonitor;
|
||||
|
||||
class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic();
|
||||
ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, size_t core_index);
|
||||
~ARM_Dynarmic();
|
||||
|
||||
void MapBackingMemory(VAddr address, size_t size, u8* memory,
|
||||
@@ -34,20 +37,47 @@ public:
|
||||
void SetCPSR(u32 cpsr) override;
|
||||
VAddr GetTlsAddress() const override;
|
||||
void SetTlsAddress(VAddr address) override;
|
||||
void SetTPIDR_EL0(u64 value) override;
|
||||
u64 GetTPIDR_EL0() const override;
|
||||
|
||||
void SaveContext(ThreadContext& ctx) override;
|
||||
void LoadContext(const ThreadContext& ctx) override;
|
||||
|
||||
void PrepareReschedule() override;
|
||||
void ClearExclusiveState() override;
|
||||
|
||||
void ClearInstructionCache() override;
|
||||
void PageTableChanged() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Dynarmic::A64::Jit> MakeJit() const;
|
||||
|
||||
friend class ARM_Dynarmic_Callbacks;
|
||||
std::unique_ptr<ARM_Dynarmic_Callbacks> cb;
|
||||
std::unique_ptr<Dynarmic::A64::Jit> jit;
|
||||
ARM_Unicorn inner_unicorn;
|
||||
|
||||
size_t core_index;
|
||||
std::shared_ptr<DynarmicExclusiveMonitor> exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
public:
|
||||
explicit DynarmicExclusiveMonitor(size_t core_count);
|
||||
~DynarmicExclusiveMonitor();
|
||||
|
||||
void SetExclusive(size_t core_index, VAddr addr) override;
|
||||
void ClearExclusive() override;
|
||||
|
||||
bool ExclusiveWrite8(size_t core_index, VAddr vaddr, u8 value) override;
|
||||
bool ExclusiveWrite16(size_t core_index, VAddr vaddr, u16 value) override;
|
||||
bool ExclusiveWrite32(size_t core_index, VAddr vaddr, u32 value) override;
|
||||
bool ExclusiveWrite64(size_t core_index, VAddr vaddr, u64 value) override;
|
||||
bool ExclusiveWrite128(size_t core_index, VAddr vaddr, u128 value) override;
|
||||
|
||||
private:
|
||||
friend class ARM_Dynarmic;
|
||||
Dynarmic::A64::ExclusiveMonitor monitor;
|
||||
};
|
||||
|
||||
7
src/core/arm/exclusive_monitor.cpp
Normal file
7
src/core/arm/exclusive_monitor.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
||||
ExclusiveMonitor::~ExclusiveMonitor() = default;
|
||||
21
src/core/arm/exclusive_monitor.h
Normal file
21
src/core/arm/exclusive_monitor.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
class ExclusiveMonitor {
|
||||
public:
|
||||
virtual ~ExclusiveMonitor();
|
||||
|
||||
virtual void SetExclusive(size_t core_index, VAddr addr) = 0;
|
||||
virtual void ClearExclusive() = 0;
|
||||
|
||||
virtual bool ExclusiveWrite8(size_t core_index, VAddr vaddr, u8 value) = 0;
|
||||
virtual bool ExclusiveWrite16(size_t core_index, VAddr vaddr, u16 value) = 0;
|
||||
virtual bool ExclusiveWrite32(size_t core_index, VAddr vaddr, u32 value) = 0;
|
||||
virtual bool ExclusiveWrite64(size_t core_index, VAddr vaddr, u64 value) = 0;
|
||||
virtual bool ExclusiveWrite128(size_t core_index, VAddr vaddr, u128 value) = 0;
|
||||
};
|
||||
@@ -30,11 +30,22 @@ LoadDll LoadDll::g_load_dll;
|
||||
#define CHECKED(expr) \
|
||||
do { \
|
||||
if (auto _cerr = (expr)) { \
|
||||
ASSERT_MSG(false, "Call " #expr " failed with error: %u (%s)\n", _cerr, \
|
||||
ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \
|
||||
uc_strerror(_cerr)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) {
|
||||
GDBStub::BreakpointAddress bkpt =
|
||||
GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute);
|
||||
if (GDBStub::IsMemoryBreak() ||
|
||||
(bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) {
|
||||
auto core = static_cast<ARM_Unicorn*>(user_data);
|
||||
core->RecordBreak(bkpt);
|
||||
uc_emu_stop(uc);
|
||||
}
|
||||
}
|
||||
|
||||
static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
|
||||
u32 esr{};
|
||||
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
|
||||
@@ -52,8 +63,8 @@ static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
|
||||
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
|
||||
void* user_data) {
|
||||
ARM_Interface::ThreadContext ctx{};
|
||||
Core::CPU().SaveContext(ctx);
|
||||
ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%lx, pc=0x%lx, lr=0x%lx", addr,
|
||||
Core::CurrentArmInterface().SaveContext(ctx);
|
||||
ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr,
|
||||
ctx.pc, ctx.cpu_registers[30]);
|
||||
return {};
|
||||
}
|
||||
@@ -67,6 +78,10 @@ ARM_Unicorn::ARM_Unicorn() {
|
||||
uc_hook hook{};
|
||||
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, -1));
|
||||
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, 0, -1));
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, -1));
|
||||
last_bkpt_hit = false;
|
||||
}
|
||||
}
|
||||
|
||||
ARM_Unicorn::~ARM_Unicorn() {
|
||||
@@ -154,8 +169,22 @@ void ARM_Unicorn::SetTlsAddress(VAddr base) {
|
||||
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
|
||||
}
|
||||
|
||||
u64 ARM_Unicorn::GetTPIDR_EL0() const {
|
||||
u64 value{};
|
||||
CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDR_EL0, &value));
|
||||
return value;
|
||||
}
|
||||
|
||||
void ARM_Unicorn::SetTPIDR_EL0(u64 value) {
|
||||
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value));
|
||||
}
|
||||
|
||||
void ARM_Unicorn::Run() {
|
||||
ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0));
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
ExecuteInstructions(std::max(4000000, 0));
|
||||
} else {
|
||||
ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Unicorn::Step() {
|
||||
@@ -168,6 +197,18 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
MICROPROFILE_SCOPE(ARM_Jit);
|
||||
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
|
||||
CoreTiming::AddTicks(num_instructions);
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
if (last_bkpt_hit) {
|
||||
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
|
||||
}
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
if (last_bkpt_hit || (num_instructions == 1)) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Unicorn::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
@@ -189,8 +230,6 @@ void ARM_Unicorn::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
|
||||
CHECKED(uc_reg_read_batch(uc, uregs, tregs, 31));
|
||||
|
||||
ctx.tls_address = GetTlsAddress();
|
||||
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
uregs[i] = UC_ARM64_REG_Q0 + i;
|
||||
tregs[i] = &ctx.fpu_registers[i];
|
||||
@@ -218,8 +257,6 @@ void ARM_Unicorn::LoadContext(const ARM_Interface::ThreadContext& ctx) {
|
||||
|
||||
CHECKED(uc_reg_write_batch(uc, uregs, tregs, 31));
|
||||
|
||||
SetTlsAddress(ctx.tls_address);
|
||||
|
||||
for (auto i = 0; i < 32; ++i) {
|
||||
uregs[i] = UC_ARM64_REG_Q0 + i;
|
||||
tregs[i] = (void*)&ctx.fpu_registers[i];
|
||||
@@ -232,4 +269,11 @@ void ARM_Unicorn::PrepareReschedule() {
|
||||
CHECKED(uc_emu_stop(uc));
|
||||
}
|
||||
|
||||
void ARM_Unicorn::ClearExclusiveState() {}
|
||||
|
||||
void ARM_Unicorn::ClearInstructionCache() {}
|
||||
|
||||
void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
||||
last_bkpt = bkpt;
|
||||
last_bkpt_hit = true;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <unicorn/unicorn.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
|
||||
class ARM_Unicorn final : public ARM_Interface {
|
||||
public:
|
||||
@@ -27,15 +28,21 @@ public:
|
||||
void SetCPSR(u32 cpsr) override;
|
||||
VAddr GetTlsAddress() const override;
|
||||
void SetTlsAddress(VAddr address) override;
|
||||
void SetTPIDR_EL0(u64 value) override;
|
||||
u64 GetTPIDR_EL0() const override;
|
||||
void SaveContext(ThreadContext& ctx) override;
|
||||
void LoadContext(const ThreadContext& ctx) override;
|
||||
void PrepareReschedule() override;
|
||||
void ClearExclusiveState() override;
|
||||
void ExecuteInstructions(int num_instructions);
|
||||
void Run() override;
|
||||
void Step() override;
|
||||
void ClearInstructionCache() override;
|
||||
void PageTableChanged() override{};
|
||||
void RecordBreak(GDBStub::BreakpointAddress bkpt);
|
||||
|
||||
private:
|
||||
uc_engine* uc{};
|
||||
GDBStub::BreakpointAddress last_bkpt{};
|
||||
bool last_bkpt_hit;
|
||||
};
|
||||
|
||||
@@ -5,32 +5,54 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "common/logging/log.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#endif
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "core/settings.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
/*static*/ System System::s_instance;
|
||||
|
||||
System::System() = default;
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
/// Runs a CPU core while the system is powered on
|
||||
static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
|
||||
while (Core::System::GetInstance().IsPoweredOn()) {
|
||||
cpu_state->RunLoop(true);
|
||||
}
|
||||
}
|
||||
|
||||
Cpu& System::CurrentCpuCore() {
|
||||
// If multicore is enabled, use host thread to figure out the current CPU core
|
||||
if (Settings::values.use_multi_core) {
|
||||
const auto& search = thread_to_cpu.find(std::this_thread::get_id());
|
||||
ASSERT(search != thread_to_cpu.end());
|
||||
ASSERT(search->second);
|
||||
return *search->second;
|
||||
}
|
||||
|
||||
// Otherwise, use single-threaded mode active_core variable
|
||||
return *cpu_cores[active_core];
|
||||
}
|
||||
|
||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
status = ResultStatus::Success;
|
||||
if (!cpu_core) {
|
||||
return ResultStatus::ErrorNotInitialized;
|
||||
}
|
||||
|
||||
// Update thread_to_cpu in case Core 0 is run from a different host thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::HandlePacket();
|
||||
@@ -47,25 +69,14 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (Kernel::GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core_ARM, "Idling");
|
||||
CoreTiming::Idle();
|
||||
CoreTiming::Advance();
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
CoreTiming::Advance();
|
||||
if (tight_loop) {
|
||||
cpu_core->Run();
|
||||
} else {
|
||||
cpu_core->Step();
|
||||
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
|
||||
cpu_cores[active_core]->RunLoop(tight_loop);
|
||||
if (Settings::values.use_multi_core) {
|
||||
// Cores 1-3 are run on other threads in this mode
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HW::Update();
|
||||
Reschedule();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -73,23 +84,25 @@ System::ResultStatus System::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(filepath);
|
||||
System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(filepath));
|
||||
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for %s!", filepath.c_str());
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
return ResultStatus::ErrorGetLoader;
|
||||
}
|
||||
std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode =
|
||||
app_loader->LoadKernelSystemMode();
|
||||
|
||||
if (system_mode.second != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error %i)!",
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
|
||||
static_cast<int>(system_mode.second));
|
||||
|
||||
switch (system_mode.second) {
|
||||
case Loader::ResultStatus::ErrorEncrypted:
|
||||
return ResultStatus::ErrorLoader_ErrorEncrypted;
|
||||
case Loader::ResultStatus::ErrorMissingKeys:
|
||||
return ResultStatus::ErrorLoader_ErrorMissingKeys;
|
||||
case Loader::ResultStatus::ErrorDecrypting:
|
||||
return ResultStatus::ErrorLoader_ErrorDecrypting;
|
||||
case Loader::ResultStatus::ErrorInvalidFormat:
|
||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||
case Loader::ResultStatus::ErrorUnsupportedArch:
|
||||
@@ -99,9 +112,9 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||
}
|
||||
}
|
||||
|
||||
ResultStatus init_result{Init(emu_window, system_mode.first.get())};
|
||||
ResultStatus init_result{Init(emu_window)};
|
||||
if (init_result != ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!",
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||
static_cast<int>(init_result));
|
||||
System::Shutdown();
|
||||
return init_result;
|
||||
@@ -109,12 +122,14 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||
|
||||
const Loader::ResultStatus load_result{app_loader->Load(current_process)};
|
||||
if (Loader::ResultStatus::Success != load_result) {
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", static_cast<int>(load_result));
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
||||
System::Shutdown();
|
||||
|
||||
switch (load_result) {
|
||||
case Loader::ResultStatus::ErrorEncrypted:
|
||||
return ResultStatus::ErrorLoader_ErrorEncrypted;
|
||||
case Loader::ResultStatus::ErrorMissingKeys:
|
||||
return ResultStatus::ErrorLoader_ErrorMissingKeys;
|
||||
case Loader::ResultStatus::ErrorDecrypting:
|
||||
return ResultStatus::ErrorLoader_ErrorDecrypting;
|
||||
case Loader::ResultStatus::ErrorInvalidFormat:
|
||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||
case Loader::ResultStatus::ErrorUnsupportedArch:
|
||||
@@ -128,55 +143,64 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||
}
|
||||
|
||||
void System::PrepareReschedule() {
|
||||
cpu_core->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
CurrentCpuCore().PrepareReschedule();
|
||||
}
|
||||
|
||||
PerfStats::Results System::GetAndResetPerfStats() {
|
||||
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
void System::Reschedule() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
Core::System::GetInstance().Scheduler().Reschedule();
|
||||
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return cpu_cores[core_index]->Scheduler();
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||
ARM_Interface& System::ArmInterface(size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return cpu_cores[core_index]->ArmInterface();
|
||||
}
|
||||
|
||||
Cpu& System::CpuCore(size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return *cpu_cores[core_index];
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(EmuWindow& emu_window) {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
CoreTiming::Init();
|
||||
|
||||
current_process = Kernel::Process::Create("main");
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
cpu_core = std::make_shared<ARM_Dynarmic>();
|
||||
#else
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
|
||||
for (size_t index = 0; index < cpu_cores.size(); ++index) {
|
||||
cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index);
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>();
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
HW::Init();
|
||||
Kernel::Init(system_mode);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get());
|
||||
Service::Init();
|
||||
Kernel::Init();
|
||||
Service::Init(service_manager);
|
||||
GDBStub::Init();
|
||||
|
||||
if (!VideoCore::Init(emu_window)) {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
// CPU core 0 is run on the main thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
if (Settings::values.use_multi_core) {
|
||||
for (size_t index = 0; index < cpu_core_threads.size(); ++index) {
|
||||
cpu_core_threads[index] =
|
||||
std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
|
||||
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
@@ -200,17 +224,40 @@ void System::Shutdown() {
|
||||
VideoCore::Shutdown();
|
||||
GDBStub::Shutdown();
|
||||
Service::Shutdown();
|
||||
scheduler = nullptr;
|
||||
Kernel::Shutdown();
|
||||
HW::Shutdown();
|
||||
telemetry_session = nullptr;
|
||||
gpu_core = nullptr;
|
||||
cpu_core = nullptr;
|
||||
service_manager.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
// Close all CPU/threading state
|
||||
cpu_barrier->NotifyEnd();
|
||||
if (Settings::values.use_multi_core) {
|
||||
for (auto& thread : cpu_core_threads) {
|
||||
thread->join();
|
||||
thread.reset();
|
||||
}
|
||||
}
|
||||
thread_to_cpu.clear();
|
||||
for (auto& cpu_core : cpu_cores) {
|
||||
cpu_core.reset();
|
||||
}
|
||||
cpu_barrier.reset();
|
||||
|
||||
// Close core timing
|
||||
CoreTiming::Shutdown();
|
||||
|
||||
app_loader = nullptr;
|
||||
// Close app loader
|
||||
app_loader.reset();
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& System::ServiceManager() {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
const Service::SM::ServiceManager& System::ServiceManager() const {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
107
src/core/core.h
107
src/core/core.h
@@ -4,10 +4,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
@@ -19,10 +23,16 @@
|
||||
class EmuWindow;
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System {
|
||||
public:
|
||||
~System();
|
||||
|
||||
/**
|
||||
* Gets the instance of the System singleton class.
|
||||
* @returns Reference to the instance of the System singleton class.
|
||||
@@ -33,12 +43,14 @@ public:
|
||||
|
||||
/// Enumeration representing the return values of the System Initialize and Load process.
|
||||
enum class ResultStatus : u32 {
|
||||
Success, ///< Succeeded
|
||||
ErrorNotInitialized, ///< Error trying to use core prior to initialization
|
||||
ErrorGetLoader, ///< Error finding the correct application loader
|
||||
ErrorSystemMode, ///< Error determining the system mode
|
||||
ErrorLoader, ///< Error loading the specified application
|
||||
ErrorLoader_ErrorEncrypted, ///< Error loading the specified application due to encryption
|
||||
Success, ///< Succeeded
|
||||
ErrorNotInitialized, ///< Error trying to use core prior to initialization
|
||||
ErrorGetLoader, ///< Error finding the correct application loader
|
||||
ErrorSystemMode, ///< Error determining the system mode
|
||||
ErrorLoader, ///< Error loading the specified application
|
||||
ErrorLoader_ErrorMissingKeys, ///< Error because the key/keys needed to run could not be
|
||||
///< found.
|
||||
ErrorLoader_ErrorDecrypting, ///< Error loading the specified application due to encryption
|
||||
ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an
|
||||
/// invalid format
|
||||
ErrorSystemFiles, ///< Error in finding system files
|
||||
@@ -71,11 +83,12 @@ public:
|
||||
|
||||
/**
|
||||
* Load an executable application.
|
||||
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
* input.
|
||||
* @param filepath String path to the executable application to load on the host file system.
|
||||
* @returns ResultStatus code, indicating if the operation succeeded.
|
||||
*/
|
||||
ResultStatus Load(EmuWindow* emu_window, const std::string& filepath);
|
||||
ResultStatus Load(EmuWindow& emu_window, const std::string& filepath);
|
||||
|
||||
/**
|
||||
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an
|
||||
@@ -83,7 +96,7 @@ public:
|
||||
* @returns True if the emulated system is powered on, otherwise false.
|
||||
*/
|
||||
bool IsPoweredOn() const {
|
||||
return cpu_core != nullptr;
|
||||
return cpu_barrier && cpu_barrier->IsAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,24 +110,44 @@ public:
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule();
|
||||
|
||||
/// Gets and resets core performance statistics
|
||||
PerfStats::Results GetAndResetPerfStats();
|
||||
|
||||
/**
|
||||
* Gets a reference to the emulated CPU.
|
||||
* @returns A reference to the emulated CPU.
|
||||
*/
|
||||
ARM_Interface& CPU() {
|
||||
return *cpu_core;
|
||||
/// Gets an ARM interface to the CPU core that is currently running
|
||||
ARM_Interface& CurrentArmInterface() {
|
||||
return CurrentCpuCore().ArmInterface();
|
||||
}
|
||||
|
||||
/// Gets the index of the currently running CPU core
|
||||
size_t CurrentCoreIndex() {
|
||||
return CurrentCpuCore().CoreIndex();
|
||||
}
|
||||
|
||||
/// Gets an ARM interface to the CPU core with the specified index
|
||||
ARM_Interface& ArmInterface(size_t core_index);
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
Cpu& CpuCore(size_t core_index);
|
||||
|
||||
/// Gets the GPU interface
|
||||
Tegra::GPU& GPU() {
|
||||
return *gpu_core;
|
||||
}
|
||||
|
||||
Kernel::Scheduler& Scheduler() {
|
||||
return *scheduler;
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
Kernel::Scheduler& CurrentScheduler() {
|
||||
return *CurrentCpuCore().Scheduler();
|
||||
}
|
||||
|
||||
/// Gets the exclusive monitor
|
||||
ExclusiveMonitor& Monitor() {
|
||||
return *cpu_exclusive_monitor;
|
||||
}
|
||||
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
|
||||
|
||||
/// Gets the current process
|
||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
|
||||
return current_process;
|
||||
}
|
||||
@@ -137,6 +170,9 @@ public:
|
||||
return *app_loader;
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& ServiceManager();
|
||||
const Service::SM::ServiceManager& ServiceManager() const;
|
||||
|
||||
void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
|
||||
debug_context = std::move(context);
|
||||
}
|
||||
@@ -146,30 +182,32 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
/// Returns the currently running CPU core
|
||||
Cpu& CurrentCpuCore();
|
||||
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
|
||||
* @param system_mode The system mode.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
* input.
|
||||
* @return ResultStatus code, indicating if the operation succeeded.
|
||||
*/
|
||||
ResultStatus Init(EmuWindow* emu_window, u32 system_mode);
|
||||
|
||||
/// Reschedule the core emulation
|
||||
void Reschedule();
|
||||
ResultStatus Init(EmuWindow& emu_window);
|
||||
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
|
||||
std::shared_ptr<ARM_Interface> cpu_core;
|
||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
|
||||
size_t active_core{}; ///< Active core, only used in single thread mode
|
||||
|
||||
/// When true, signals that a reschedule should happen
|
||||
bool reschedule_pending{};
|
||||
/// Service manager
|
||||
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
||||
|
||||
/// Telemetry session for this emulation session
|
||||
std::unique_ptr<Core::TelemetrySession> telemetry_session;
|
||||
@@ -178,10 +216,13 @@ private:
|
||||
|
||||
ResultStatus status = ResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
|
||||
/// Map of guest threads to CPU cores
|
||||
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
|
||||
};
|
||||
|
||||
inline ARM_Interface& CPU() {
|
||||
return System::GetInstance().CPU();
|
||||
inline ARM_Interface& CurrentArmInterface() {
|
||||
return System::GetInstance().CurrentArmInterface();
|
||||
}
|
||||
|
||||
inline TelemetrySession& Telemetry() {
|
||||
|
||||
131
src/core/core_cpu.cpp
Normal file
131
src/core/core_cpu.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#endif
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
void CpuBarrier::NotifyEnd() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
end = true;
|
||||
condition.notify_all();
|
||||
}
|
||||
|
||||
bool CpuBarrier::Rendezvous() {
|
||||
if (!Settings::values.use_multi_core) {
|
||||
// Meaningless when running in single-core mode
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!end) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
--cores_waiting;
|
||||
if (!cores_waiting) {
|
||||
cores_waiting = NUM_CPU_CORES;
|
||||
condition.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
condition.wait(lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index)
|
||||
: cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_shared<ARM_Dynarmic>(exclusive_monitor, core_index);
|
||||
#else
|
||||
arm_interface = std::make_shared<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
arm_interface = std::make_shared<ARM_Unicorn>();
|
||||
}
|
||||
|
||||
scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get());
|
||||
}
|
||||
|
||||
std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(size_t num_cores) {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
return std::make_shared<DynarmicExclusiveMonitor>(num_cores);
|
||||
#else
|
||||
return nullptr; // TODO(merry): Passthrough exclusive monitor
|
||||
#endif
|
||||
} else {
|
||||
return nullptr; // TODO(merry): Passthrough exclusive monitor
|
||||
}
|
||||
}
|
||||
|
||||
void Cpu::RunLoop(bool tight_loop) {
|
||||
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
|
||||
if (!cpu_barrier->Rendezvous()) {
|
||||
// If rendezvous failed, session has been killed
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (Kernel::GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core, "Core-{} idling", core_index);
|
||||
|
||||
if (IsMainCore()) {
|
||||
CoreTiming::Idle();
|
||||
CoreTiming::Advance();
|
||||
}
|
||||
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
if (IsMainCore()) {
|
||||
CoreTiming::Advance();
|
||||
}
|
||||
|
||||
if (tight_loop) {
|
||||
arm_interface->Run();
|
||||
} else {
|
||||
arm_interface->Step();
|
||||
}
|
||||
}
|
||||
|
||||
Reschedule();
|
||||
}
|
||||
|
||||
void Cpu::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
void Cpu::PrepareReschedule() {
|
||||
arm_interface->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
}
|
||||
|
||||
void Cpu::Reschedule() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
scheduler->Reschedule();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
86
src/core/core_cpu.h
Normal file
86
src/core/core_cpu.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Kernel {
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr unsigned NUM_CPU_CORES{4};
|
||||
|
||||
class CpuBarrier {
|
||||
public:
|
||||
bool IsAlive() const {
|
||||
return !end;
|
||||
}
|
||||
|
||||
void NotifyEnd();
|
||||
|
||||
bool Rendezvous();
|
||||
|
||||
private:
|
||||
unsigned cores_waiting{NUM_CPU_CORES};
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
std::atomic<bool> end{};
|
||||
};
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index);
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
|
||||
void SingleStep();
|
||||
|
||||
void PrepareReschedule();
|
||||
|
||||
ARM_Interface& ArmInterface() {
|
||||
return *arm_interface;
|
||||
}
|
||||
|
||||
const ARM_Interface& ArmInterface() const {
|
||||
return *arm_interface;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
bool IsMainCore() const {
|
||||
return core_index == 0;
|
||||
}
|
||||
|
||||
size_t CoreIndex() const {
|
||||
return core_index;
|
||||
}
|
||||
|
||||
static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(size_t num_cores);
|
||||
|
||||
private:
|
||||
void Reschedule();
|
||||
|
||||
std::shared_ptr<ARM_Interface> arm_interface;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::shared_ptr<Kernel::Scheduler> scheduler;
|
||||
|
||||
bool reschedule_pending{};
|
||||
size_t core_index;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -5,16 +5,15 @@
|
||||
#include "core/core_timing.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
|
||||
@@ -57,7 +56,7 @@ static u64 event_fifo_id;
|
||||
// to the event_queue by the emu thread
|
||||
static Common::MPSCQueue<Event, false> ts_queue;
|
||||
|
||||
static constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
|
||||
static s64 idled_cycles;
|
||||
|
||||
@@ -74,7 +73,7 @@ EventType* RegisterEvent(const std::string& name, TimedCallback callback) {
|
||||
// check for existing type with same name.
|
||||
// we want event type names to remain unique so that we can use them for serialization.
|
||||
ASSERT_MSG(event_types.find(name) == event_types.end(),
|
||||
"CoreTiming Event \"%s\" is already registered. Events should only be registered "
|
||||
"CoreTiming Event \"{}\" is already registered. Events should only be registered "
|
||||
"during Init to avoid breaking save states.",
|
||||
name.c_str());
|
||||
|
||||
|
||||
@@ -18,104 +18,8 @@
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(ms) / 1000;
|
||||
}
|
||||
|
||||
inline s64 msToCycles(float ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.001f) * ms);
|
||||
}
|
||||
|
||||
inline s64 msToCycles(double ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.001) * ms);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(float us) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.000001f) * us);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(int us) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(s64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * us) / 1000000;
|
||||
}
|
||||
|
||||
inline s64 usToCycles(u64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(float ns) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns);
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(int ns) {
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000;
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(s64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (ns / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * ns) / 1000000000;
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(u64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
inline u64 cyclesToNs(s64 cycles) {
|
||||
return cycles * 1000000000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
inline s64 cyclesToUs(s64 cycles) {
|
||||
return cycles * 1000000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
namespace CoreTiming {
|
||||
|
||||
|
||||
63
src/core/core_timing_util.cpp
Normal file
63
src/core/core_timing_util.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
|
||||
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
|
||||
|
||||
s64 usToCycles(s64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * us) / 1000000;
|
||||
}
|
||||
|
||||
s64 usToCycles(u64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(s64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (ns / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * ns) / 1000000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(u64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
} // namespace CoreTiming
|
||||
64
src/core/core_timing_util.h
Normal file
64
src/core/core_timing_util.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(ms) / 1000;
|
||||
}
|
||||
|
||||
inline s64 msToCycles(float ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.001f) * ms);
|
||||
}
|
||||
|
||||
inline s64 msToCycles(double ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.001) * ms);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(float us) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.000001f) * us);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(int us) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000);
|
||||
}
|
||||
|
||||
s64 usToCycles(s64 us);
|
||||
|
||||
s64 usToCycles(u64 us);
|
||||
|
||||
inline s64 nsToCycles(float ns) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns);
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(int ns) {
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(s64 ns);
|
||||
|
||||
s64 nsToCycles(u64 ns);
|
||||
|
||||
inline u64 cyclesToNs(s64 cycles) {
|
||||
return cycles * 1000000000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
inline s64 cyclesToUs(s64 cycles) {
|
||||
return cycles * 1000000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
} // namespace CoreTiming
|
||||
112
src/core/crypto/aes_util.cpp
Normal file
112
src/core/crypto/aes_util.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <mbedtls/cipher.h>
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR),
|
||||
"CTR has incorrect value.");
|
||||
static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB),
|
||||
"ECB has incorrect value.");
|
||||
static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS),
|
||||
"XTS has incorrect value.");
|
||||
|
||||
// Structure to hide mbedtls types from header file
|
||||
struct CipherContext {
|
||||
mbedtls_cipher_context_t encryption_context;
|
||||
mbedtls_cipher_context_t decryption_context;
|
||||
};
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
|
||||
: ctx(std::make_unique<CipherContext>()) {
|
||||
mbedtls_cipher_init(&ctx->encryption_context);
|
||||
mbedtls_cipher_init(&ctx->decryption_context);
|
||||
|
||||
ASSERT_MSG((mbedtls_cipher_setup(
|
||||
&ctx->encryption_context,
|
||||
mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
|
||||
mbedtls_cipher_setup(
|
||||
&ctx->decryption_context,
|
||||
mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
|
||||
"Failed to initialize mbedtls ciphers.");
|
||||
|
||||
ASSERT(
|
||||
!mbedtls_cipher_setkey(&ctx->encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
|
||||
ASSERT(
|
||||
!mbedtls_cipher_setkey(&ctx->decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT));
|
||||
//"Failed to set key on mbedtls ciphers.");
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
AESCipher<Key, KeySize>::~AESCipher() {
|
||||
mbedtls_cipher_free(&ctx->encryption_context);
|
||||
mbedtls_cipher_free(&ctx->decryption_context);
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) {
|
||||
ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) ||
|
||||
mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0,
|
||||
"Failed to set IV on mbedtls ciphers.");
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op) {
|
||||
size_t written = 0;
|
||||
|
||||
const auto context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context;
|
||||
|
||||
mbedtls_cipher_reset(context);
|
||||
|
||||
if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
|
||||
mbedtls_cipher_update(context, src, size, dest, &written);
|
||||
if (written != size)
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
size, written);
|
||||
} else {
|
||||
const auto block_size = mbedtls_cipher_get_block_size(context);
|
||||
|
||||
for (size_t offset = 0; offset < size; offset += block_size) {
|
||||
auto length = std::min<size_t>(block_size, size - offset);
|
||||
mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
|
||||
if (written != length)
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
length, written);
|
||||
}
|
||||
}
|
||||
|
||||
mbedtls_cipher_finish(context, nullptr, nullptr);
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id,
|
||||
size_t sector_size, Op op) {
|
||||
if (size % sector_size > 0) {
|
||||
LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; i += sector_size) {
|
||||
SetIV(CalculateNintendoTweak(sector_id++));
|
||||
Transcode<u8, u8>(src + i, sector_size, dest + i, op);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Key, size_t KeySize>
|
||||
std::vector<u8> AESCipher<Key, KeySize>::CalculateNintendoTweak(size_t sector_id) {
|
||||
std::vector<u8> out(0x10);
|
||||
for (size_t i = 0xF; i <= 0xF; --i) {
|
||||
out[i] = sector_id & 0xFF;
|
||||
sector_id >>= 8;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template class AESCipher<Key128>;
|
||||
template class AESCipher<Key256>;
|
||||
} // namespace Core::Crypto
|
||||
62
src/core/crypto/aes_util.h
Normal file
62
src/core/crypto/aes_util.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
struct CipherContext;
|
||||
|
||||
enum class Mode {
|
||||
CTR = 11,
|
||||
ECB = 2,
|
||||
XTS = 70,
|
||||
};
|
||||
|
||||
enum class Op {
|
||||
Encrypt,
|
||||
Decrypt,
|
||||
};
|
||||
|
||||
template <typename Key, size_t KeySize = sizeof(Key)>
|
||||
class AESCipher {
|
||||
static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8.");
|
||||
static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256.");
|
||||
|
||||
public:
|
||||
AESCipher(Key key, Mode mode);
|
||||
|
||||
~AESCipher();
|
||||
|
||||
void SetIV(std::vector<u8> iv);
|
||||
|
||||
template <typename Source, typename Dest>
|
||||
void Transcode(const Source* src, size_t size, Dest* dest, Op op) {
|
||||
Transcode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), op);
|
||||
}
|
||||
|
||||
void Transcode(const u8* src, size_t size, u8* dest, Op op);
|
||||
|
||||
template <typename Source, typename Dest>
|
||||
void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id,
|
||||
size_t sector_size, Op op) {
|
||||
XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id,
|
||||
sector_size, op);
|
||||
}
|
||||
|
||||
void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size,
|
||||
Op op);
|
||||
|
||||
private:
|
||||
std::unique_ptr<CipherContext> ctx;
|
||||
|
||||
static std::vector<u8> CalculateNintendoTweak(size_t sector_id);
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
56
src/core/crypto/ctr_encryption_layer.cpp
Normal file
56
src/core/crypto/ctr_encryption_layer.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset)
|
||||
: EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR),
|
||||
iv(16, 0) {}
|
||||
|
||||
size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
|
||||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
const auto sector_offset = offset & 0xF;
|
||||
if (sector_offset == 0) {
|
||||
UpdateIV(base_offset + offset);
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
if (raw.size() != length)
|
||||
return Read(data, raw.size(), offset);
|
||||
cipher.Transcode(raw.data(), length, data, Op::Decrypt);
|
||||
return length;
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x10)
|
||||
std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset);
|
||||
UpdateIV(base_offset + offset - sector_offset);
|
||||
cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt);
|
||||
size_t read = 0x10 - sector_offset;
|
||||
|
||||
if (length + sector_offset < 0x10) {
|
||||
std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
|
||||
return read;
|
||||
}
|
||||
std::memcpy(data, block.data() + sector_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
}
|
||||
|
||||
void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) {
|
||||
const auto length = std::min(iv_.size(), iv.size());
|
||||
iv.assign(iv_.cbegin(), iv_.cbegin() + length);
|
||||
}
|
||||
|
||||
void CTREncryptionLayer::UpdateIV(size_t offset) const {
|
||||
offset >>= 4;
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
iv[16 - i - 1] = offset & 0xFF;
|
||||
offset >>= 8;
|
||||
}
|
||||
cipher.SetIV(iv);
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
33
src/core/crypto/ctr_encryption_layer.h
Normal file
33
src/core/crypto/ctr_encryption_layer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
// Sits on top of a VirtualFile and provides CTR-mode AES decription.
|
||||
class CTREncryptionLayer : public EncryptionLayer {
|
||||
public:
|
||||
CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset);
|
||||
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
|
||||
void SetIV(const std::vector<u8>& iv);
|
||||
|
||||
private:
|
||||
size_t base_offset;
|
||||
|
||||
// Must be mutable as operations modify cipher contexts.
|
||||
mutable AESCipher<Key128> cipher;
|
||||
mutable std::vector<u8> iv;
|
||||
|
||||
void UpdateIV(size_t offset) const;
|
||||
};
|
||||
|
||||
} // namespace Core::Crypto
|
||||
42
src/core/crypto/encryption_layer.cpp
Normal file
42
src/core/crypto/encryption_layer.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {}
|
||||
|
||||
std::string EncryptionLayer::GetName() const {
|
||||
return base->GetName();
|
||||
}
|
||||
|
||||
size_t EncryptionLayer::GetSize() const {
|
||||
return base->GetSize();
|
||||
}
|
||||
|
||||
bool EncryptionLayer::Resize(size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::VfsDirectory> EncryptionLayer::GetContainingDirectory() const {
|
||||
return base->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool EncryptionLayer::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EncryptionLayer::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool EncryptionLayer::Rename(std::string_view name) {
|
||||
return base->Rename(name);
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
32
src/core/crypto/encryption_layer.h
Normal file
32
src/core/crypto/encryption_layer.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
// Basically non-functional class that implements all of the methods that are irrelevant to an
|
||||
// EncryptionLayer. Reduces duplicate code.
|
||||
class EncryptionLayer : public FileSys::VfsFile {
|
||||
public:
|
||||
explicit EncryptionLayer(FileSys::VirtualFile base);
|
||||
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override = 0;
|
||||
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
std::shared_ptr<FileSys::VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
protected:
|
||||
FileSys::VirtualFile base;
|
||||
};
|
||||
|
||||
} // namespace Core::Crypto
|
||||
215
src/core/crypto/key_manager.cpp
Normal file
215
src/core/crypto/key_manager.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/settings.h"
|
||||
#include "key_manager.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
static u8 ToHexNibble(char c1) {
|
||||
if (c1 >= 65 && c1 <= 70)
|
||||
return c1 - 55;
|
||||
if (c1 >= 97 && c1 <= 102)
|
||||
return c1 - 87;
|
||||
if (c1 >= 48 && c1 <= 57)
|
||||
return c1 - 48;
|
||||
throw std::logic_error("Invalid hex digit");
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
static std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
std::array<u8, Size> out{};
|
||||
for (size_t i = 0; i < 2 * Size; i += 2) {
|
||||
auto d1 = str[i];
|
||||
auto d2 = str[i + 1];
|
||||
out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
|
||||
if (len != 32)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<16>(str);
|
||||
}
|
||||
|
||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
|
||||
if (len != 64)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<32>(str);
|
||||
}
|
||||
|
||||
KeyManager::KeyManager() {
|
||||
// Initialize keys
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
|
||||
if (Settings::values.use_dev_keys) {
|
||||
dev_mode = true;
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
|
||||
} else {
|
||||
dev_mode = false;
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false);
|
||||
}
|
||||
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
|
||||
const auto filename = std::string(filename_);
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
std::vector<std::string> out;
|
||||
std::stringstream stream(line);
|
||||
std::string item;
|
||||
while (std::getline(stream, item, '='))
|
||||
out.push_back(std::move(item));
|
||||
|
||||
if (out.size() != 2)
|
||||
continue;
|
||||
|
||||
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
|
||||
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
|
||||
|
||||
if (is_title_keys) {
|
||||
auto rights_id_raw = HexStringToArray<16>(out[0]);
|
||||
u128 rights_id{};
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
|
||||
Key128 key = HexStringToArray<16>(out[1]);
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
} else {
|
||||
std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
|
||||
if (s128_file_id.find(out[0]) != s128_file_id.end()) {
|
||||
const auto index = s128_file_id.at(out[0]);
|
||||
Key128 key = HexStringToArray<16>(out[1]);
|
||||
SetKey(index.type, key, index.field1, index.field2);
|
||||
} else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
|
||||
const auto index = s256_file_id.at(out[0]);
|
||||
Key256 key = HexStringToArray<32>(out[1]);
|
||||
SetKey(index.type, key, index.field1, index.field2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::AttemptLoadKeyFile(std::string_view dir1_, std::string_view dir2_,
|
||||
std::string_view filename_, bool title) {
|
||||
const std::string dir1(dir1_);
|
||||
const std::string dir2(dir2_);
|
||||
const std::string filename(filename_);
|
||||
if (FileUtil::Exists(dir1 + DIR_SEP + filename))
|
||||
LoadFromFile(dir1 + DIR_SEP + filename, title);
|
||||
else if (FileUtil::Exists(dir2 + DIR_SEP + filename))
|
||||
LoadFromFile(dir2 + DIR_SEP + filename, title);
|
||||
}
|
||||
|
||||
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
|
||||
return s128_keys.find({id, field1, field2}) != s128_keys.end();
|
||||
}
|
||||
|
||||
bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const {
|
||||
return s256_keys.find({id, field1, field2}) != s256_keys.end();
|
||||
}
|
||||
|
||||
Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const {
|
||||
if (!HasKey(id, field1, field2))
|
||||
return {};
|
||||
return s128_keys.at({id, field1, field2});
|
||||
}
|
||||
|
||||
Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
|
||||
if (!HasKey(id, field1, field2))
|
||||
return {};
|
||||
return s256_keys.at({id, field1, field2});
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
s128_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||
s256_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
bool KeyManager::KeyFileExists(bool title) {
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
|
||||
if (title) {
|
||||
return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
|
||||
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
|
||||
}
|
||||
|
||||
if (Settings::values.use_dev_keys) {
|
||||
return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
|
||||
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
|
||||
}
|
||||
|
||||
return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
|
||||
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
|
||||
{"master_key_00", {S128KeyType::Master, 0, 0}},
|
||||
{"master_key_01", {S128KeyType::Master, 1, 0}},
|
||||
{"master_key_02", {S128KeyType::Master, 2, 0}},
|
||||
{"master_key_03", {S128KeyType::Master, 3, 0}},
|
||||
{"master_key_04", {S128KeyType::Master, 4, 0}},
|
||||
{"package1_key_00", {S128KeyType::Package1, 0, 0}},
|
||||
{"package1_key_01", {S128KeyType::Package1, 1, 0}},
|
||||
{"package1_key_02", {S128KeyType::Package1, 2, 0}},
|
||||
{"package1_key_03", {S128KeyType::Package1, 3, 0}},
|
||||
{"package1_key_04", {S128KeyType::Package1, 4, 0}},
|
||||
{"package2_key_00", {S128KeyType::Package2, 0, 0}},
|
||||
{"package2_key_01", {S128KeyType::Package2, 1, 0}},
|
||||
{"package2_key_02", {S128KeyType::Package2, 2, 0}},
|
||||
{"package2_key_03", {S128KeyType::Package2, 3, 0}},
|
||||
{"package2_key_04", {S128KeyType::Package2, 4, 0}},
|
||||
{"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
|
||||
{"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
|
||||
{"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
|
||||
{"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
|
||||
{"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
|
||||
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
|
||||
{"key_area_key_application_00",
|
||||
{S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_01",
|
||||
{S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_02",
|
||||
{S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_03",
|
||||
{S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_04",
|
||||
{S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
|
||||
{"header_key", {S256KeyType::Header, 0, 0}},
|
||||
{"sd_card_save_key", {S256KeyType::SDSave, 0, 0}},
|
||||
{"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}},
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
119
src/core/crypto/key_manager.h
Normal file
119
src/core/crypto/key_manager.h
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
using Key128 = std::array<u8, 0x10>;
|
||||
using Key256 = std::array<u8, 0x20>;
|
||||
using SHA256Hash = std::array<u8, 0x20>;
|
||||
|
||||
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
|
||||
|
||||
enum class S256KeyType : u64 {
|
||||
Header, //
|
||||
SDSave, //
|
||||
SDNCA, //
|
||||
};
|
||||
|
||||
enum class S128KeyType : u64 {
|
||||
Master, // f1=crypto revision
|
||||
Package1, // f1=crypto revision
|
||||
Package2, // f1=crypto revision
|
||||
Titlekek, // f1=crypto revision
|
||||
ETicketRSAKek, //
|
||||
KeyArea, // f1=crypto revision f2=type {app, ocean, system}
|
||||
SDSeed, //
|
||||
Titlekey, // f1=rights id LSB f2=rights id MSB
|
||||
};
|
||||
|
||||
enum class KeyAreaKeyType : u8 {
|
||||
Application,
|
||||
Ocean,
|
||||
System,
|
||||
};
|
||||
|
||||
template <typename KeyType>
|
||||
struct KeyIndex {
|
||||
KeyType type;
|
||||
u64 field1;
|
||||
u64 field2;
|
||||
|
||||
std::string DebugInfo() const {
|
||||
u8 key_size = 16;
|
||||
if constexpr (std::is_same_v<KeyType, S256KeyType>)
|
||||
key_size = 32;
|
||||
return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size,
|
||||
static_cast<u8>(type), field1, field2);
|
||||
}
|
||||
};
|
||||
|
||||
// The following two (== and hash) are so KeyIndex can be a key in unordered_map
|
||||
|
||||
template <typename KeyType>
|
||||
bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2);
|
||||
}
|
||||
|
||||
template <typename KeyType>
|
||||
bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
namespace std {
|
||||
template <typename KeyType>
|
||||
struct hash<Core::Crypto::KeyIndex<KeyType>> {
|
||||
size_t operator()(const Core::Crypto::KeyIndex<KeyType>& k) const {
|
||||
using std::hash;
|
||||
|
||||
return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^
|
||||
(hash<u64>()(k.field2) << 1);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
|
||||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
|
||||
|
||||
class KeyManager {
|
||||
public:
|
||||
KeyManager();
|
||||
|
||||
bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
|
||||
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
|
||||
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
|
||||
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
|
||||
|
||||
static bool KeyFileExists(bool title);
|
||||
|
||||
private:
|
||||
std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
|
||||
bool dev_mode;
|
||||
void LoadFromFile(std::string_view filename, bool is_title_keys);
|
||||
void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename,
|
||||
bool title);
|
||||
|
||||
static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
|
||||
static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
5
src/core/crypto/sha_util.cpp
Normal file
5
src/core/crypto/sha_util.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
namespace Crypto {} // namespace Crypto
|
||||
20
src/core/crypto/sha_util.h
Normal file
20
src/core/crypto/sha_util.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "key_manager.h"
|
||||
#include "mbedtls/cipher.h"
|
||||
|
||||
namespace Crypto {
|
||||
typedef std::array<u8, 0x20> SHA256Hash;
|
||||
|
||||
inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
|
||||
if (len != 0x40)
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Crypto
|
||||
149
src/core/file_sys/card_image.cpp
Normal file
149
src/core/file_sys/card_image.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <core/loader/loader.h>
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionFilesystem main_hfs(
|
||||
std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset));
|
||||
|
||||
if (main_hfs.GetStatus() != Loader::ResultStatus::Success) {
|
||||
status = main_hfs.GetStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure",
|
||||
"logo"};
|
||||
|
||||
for (XCIPartition partition :
|
||||
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
|
||||
auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]);
|
||||
if (raw != nullptr)
|
||||
partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
|
||||
}
|
||||
|
||||
auto result = AddNCAFromPartition(XCIPartition::Secure);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
|
||||
result = AddNCAFromPartition(XCIPartition::Update);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
|
||||
result = AddNCAFromPartition(XCIPartition::Normal);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetFormatVersion() >= 0x2) {
|
||||
result = AddNCAFromPartition(XCIPartition::Logo);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetPartition(XCIPartition partition) const {
|
||||
return partitions[static_cast<size_t>(partition)];
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetSecurePartition() const {
|
||||
return GetPartition(XCIPartition::Secure);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetNormalPartition() const {
|
||||
return GetPartition(XCIPartition::Normal);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetUpdatePartition() const {
|
||||
return GetPartition(XCIPartition::Update);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetLogoPartition() const {
|
||||
return GetPartition(XCIPartition::Logo);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
|
||||
const auto iter =
|
||||
std::find_if(ncas.begin(), ncas.end(),
|
||||
[type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; });
|
||||
return iter == ncas.end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
|
||||
auto nca = GetNCAByType(type);
|
||||
if (nca != nullptr)
|
||||
return nca->GetBaseFile();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const {
|
||||
return std::vector<std::shared_ptr<VfsDirectory>>();
|
||||
}
|
||||
|
||||
std::string XCI::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
if (partitions[static_cast<size_t>(part)] == nullptr) {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) {
|
||||
if (file->GetExtension() != "nca")
|
||||
continue;
|
||||
auto nca = std::make_shared<NCA>(file);
|
||||
if (nca->GetStatus() == Loader::ResultStatus::Success)
|
||||
ncas.push_back(std::move(nca));
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
u8 XCI::GetFormatVersion() const {
|
||||
return GetLogoPartition() == nullptr ? 0x1 : 0x2;
|
||||
}
|
||||
} // namespace FileSys
|
||||
96
src/core/file_sys/card_image.h
Normal file
96
src/core/file_sys/card_image.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class GamecardSize : u8 {
|
||||
S_1GB = 0xFA,
|
||||
S_2GB = 0xF8,
|
||||
S_4GB = 0xF0,
|
||||
S_8GB = 0xE0,
|
||||
S_16GB = 0xE1,
|
||||
S_32GB = 0xE2,
|
||||
};
|
||||
|
||||
struct GamecardInfo {
|
||||
std::array<u8, 0x70> data;
|
||||
};
|
||||
static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");
|
||||
|
||||
struct GamecardHeader {
|
||||
std::array<u8, 0x100> signature;
|
||||
u32_le magic;
|
||||
u32_le secure_area_start;
|
||||
u32_le backup_area_start;
|
||||
u8 kek_index;
|
||||
GamecardSize size;
|
||||
u8 header_version;
|
||||
u8 flags;
|
||||
u64_le package_id;
|
||||
u64_le valid_data_end;
|
||||
u128 info_iv;
|
||||
u64_le hfs_offset;
|
||||
u64_le hfs_size;
|
||||
std::array<u8, 0x20> hfs_header_hash;
|
||||
std::array<u8, 0x20> initial_data_hash;
|
||||
u32_le secure_mode_flag;
|
||||
u32_le title_key_flag;
|
||||
u32_le key_flag;
|
||||
u32_le normal_area_end;
|
||||
GamecardInfo info;
|
||||
};
|
||||
static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size.");
|
||||
|
||||
enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
|
||||
|
||||
class XCI : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit XCI(VirtualFile file);
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
u8 GetFormatVersion() const;
|
||||
|
||||
VirtualDir GetPartition(XCIPartition partition) const;
|
||||
VirtualDir GetSecurePartition() const;
|
||||
VirtualDir GetNormalPartition() const;
|
||||
VirtualDir GetUpdatePartition() const;
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
|
||||
VirtualFile GetNCAFileByType(NCAContentType type) const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
|
||||
|
||||
VirtualFile file;
|
||||
GamecardHeader header{};
|
||||
|
||||
Loader::ResultStatus status;
|
||||
|
||||
std::vector<VirtualDir> partitions;
|
||||
std::vector<std::shared_ptr<NCA>> ncas;
|
||||
};
|
||||
} // namespace FileSys
|
||||
276
src/core/file_sys/content_archive.cpp
Normal file
276
src/core/file_sys/content_archive.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
|
||||
constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
|
||||
|
||||
constexpr u64 SECTION_HEADER_SIZE = 0x200;
|
||||
constexpr u64 SECTION_HEADER_OFFSET = 0x400;
|
||||
|
||||
constexpr u32 IVFC_MAX_LEVEL = 6;
|
||||
|
||||
enum class NCASectionFilesystemType : u8 {
|
||||
PFS0 = 0x2,
|
||||
ROMFS = 0x3,
|
||||
};
|
||||
|
||||
struct NCASectionHeaderBlock {
|
||||
INSERT_PADDING_BYTES(3);
|
||||
NCASectionFilesystemType filesystem_type;
|
||||
NCASectionCryptoType crypto_type;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
|
||||
|
||||
struct NCASectionRaw {
|
||||
NCASectionHeaderBlock header;
|
||||
std::array<u8, 0x138> block_data;
|
||||
std::array<u8, 0x8> section_ctr;
|
||||
INSERT_PADDING_BYTES(0xB8);
|
||||
};
|
||||
static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
|
||||
|
||||
struct PFS0Superblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
std::array<u8, 0x20> hash;
|
||||
u32_le size;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u64_le hash_table_offset;
|
||||
u64_le hash_table_size;
|
||||
u64_le pfs0_header_offset;
|
||||
u64_le pfs0_size;
|
||||
INSERT_PADDING_BYTES(0x1B0);
|
||||
};
|
||||
static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
|
||||
|
||||
struct RomFSSuperblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
IVFCHeader ivfc;
|
||||
INSERT_PADDING_BYTES(0x118);
|
||||
};
|
||||
static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
|
||||
|
||||
union NCASectionHeader {
|
||||
NCASectionRaw raw;
|
||||
PFS0Superblock pfs0;
|
||||
RomFSSuperblock romfs;
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
|
||||
|
||||
bool IsValidNCA(const NCAHeader& header) {
|
||||
// TODO(DarkLordZach): Add NCA2/NCA0 support.
|
||||
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
||||
}
|
||||
|
||||
boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
|
||||
u8 master_key_id = header.crypto_type;
|
||||
if (header.crypto_type_2 > master_key_id)
|
||||
master_key_id = header.crypto_type_2;
|
||||
if (master_key_id > 0)
|
||||
--master_key_id;
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
|
||||
return boost::none;
|
||||
|
||||
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
|
||||
keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
|
||||
Core::Crypto::Mode::ECB);
|
||||
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
|
||||
|
||||
Core::Crypto::Key128 out;
|
||||
if (type == NCASectionCryptoType::XTS)
|
||||
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
|
||||
else if (type == NCASectionCryptoType::CTR)
|
||||
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
|
||||
else
|
||||
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
|
||||
static_cast<u8>(type));
|
||||
u128 out_128{};
|
||||
memcpy(out_128.data(), out.data(), 16);
|
||||
LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
|
||||
master_key_id, header.key_index, out_128[1], out_128[0]);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const {
|
||||
if (!encrypted)
|
||||
return in;
|
||||
|
||||
switch (header.raw.header.crypto_type) {
|
||||
case NCASectionCryptoType::NONE:
|
||||
LOG_DEBUG(Crypto, "called with mode=NONE");
|
||||
return in;
|
||||
case NCASectionCryptoType::CTR:
|
||||
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
|
||||
{
|
||||
const auto key = GetKeyAreaKey(NCASectionCryptoType::CTR);
|
||||
if (key == boost::none)
|
||||
return nullptr;
|
||||
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
|
||||
std::move(in), key.value(), starting_offset);
|
||||
std::vector<u8> iv(16);
|
||||
for (u8 i = 0; i < 8; ++i)
|
||||
iv[i] = header.raw.section_ctr[0x8 - i - 1];
|
||||
out->SetIV(iv);
|
||||
return std::static_pointer_cast<VfsFile>(out);
|
||||
}
|
||||
case NCASectionCryptoType::XTS:
|
||||
// TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption.
|
||||
default:
|
||||
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
|
||||
static_cast<u8>(header.raw.header.crypto_type));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header))
|
||||
LOG_ERROR(Loader, "File reader errored out during header read.");
|
||||
|
||||
encrypted = false;
|
||||
|
||||
if (!IsValidNCA(header)) {
|
||||
NCAHeader dec_header{};
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
if (IsValidNCA(dec_header)) {
|
||||
header = dec_header;
|
||||
encrypted = true;
|
||||
} else {
|
||||
if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
|
||||
status = Loader::ResultStatus::ErrorMissingKeys;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorDecrypting;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const std::ptrdiff_t number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
|
||||
std::vector<NCASectionHeader> sections(number_sections);
|
||||
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
|
||||
|
||||
if (encrypted) {
|
||||
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
} else {
|
||||
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
|
||||
}
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
auto section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
const size_t romfs_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
|
||||
section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
|
||||
romfs_offset);
|
||||
if (dec != nullptr) {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
status = Loader::ResultStatus::ErrorMissingKeys;
|
||||
return;
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
section.pfs0.pfs0_header_offset;
|
||||
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
|
||||
if (dec != nullptr) {
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
|
||||
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
}
|
||||
} else {
|
||||
status = Loader::ResultStatus::ErrorMissingKeys;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCA::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
return dirs;
|
||||
}
|
||||
|
||||
std::string NCA::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
NCAContentType NCA::GetType() const {
|
||||
return header.content_type;
|
||||
}
|
||||
|
||||
u64 NCA::GetTitleId() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetRomFS() const {
|
||||
return romfs;
|
||||
}
|
||||
|
||||
VirtualDir NCA::GetExeFS() const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetBaseFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
117
src/core/file_sys/content_archive.h
Normal file
117
src/core/file_sys/content_archive.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
union NCASectionHeader;
|
||||
|
||||
enum class NCAContentType : u8 {
|
||||
Program = 0,
|
||||
Meta = 1,
|
||||
Control = 2,
|
||||
Manual = 3,
|
||||
Data = 4,
|
||||
};
|
||||
|
||||
enum class NCASectionCryptoType : u8 {
|
||||
NONE = 1,
|
||||
XTS = 2,
|
||||
CTR = 3,
|
||||
BKTR = 4,
|
||||
};
|
||||
|
||||
struct NCASectionTableEntry {
|
||||
u32_le media_offset;
|
||||
u32_le media_end_offset;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
};
|
||||
static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
|
||||
|
||||
struct NCAHeader {
|
||||
std::array<u8, 0x100> rsa_signature_1;
|
||||
std::array<u8, 0x100> rsa_signature_2;
|
||||
u32_le magic;
|
||||
u8 is_system;
|
||||
NCAContentType content_type;
|
||||
u8 crypto_type;
|
||||
u8 key_index;
|
||||
u64_le size;
|
||||
u64_le title_id;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u32_le sdk_version;
|
||||
u8 crypto_type_2;
|
||||
INSERT_PADDING_BYTES(15);
|
||||
std::array<u8, 0x10> rights_id;
|
||||
std::array<NCASectionTableEntry, 0x4> section_tables;
|
||||
std::array<std::array<u8, 0x20>, 0x4> hash_tables;
|
||||
std::array<u8, 0x40> key_area;
|
||||
INSERT_PADDING_BYTES(0xC0);
|
||||
};
|
||||
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
|
||||
|
||||
inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
|
||||
// According to switchbrew, an exefs must only contain these two files:
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
|
||||
bool IsValidNCA(const NCAHeader& header);
|
||||
|
||||
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NCA(VirtualFile file);
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
|
||||
VirtualFile GetRomFS() const;
|
||||
VirtualDir GetExeFS() const;
|
||||
|
||||
VirtualFile GetBaseFile() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
|
||||
VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const;
|
||||
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::vector<VirtualFile> files;
|
||||
|
||||
VirtualFile romfs = nullptr;
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualFile file;
|
||||
|
||||
NCAHeader header{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
bool encrypted;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user