Compare commits
514 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfc2f30dc4 | ||
|
|
538f5880ff | ||
|
|
75395605d6 | ||
|
|
9f2719d1a4 | ||
|
|
3f104464de | ||
|
|
69fa2e6525 | ||
|
|
782b7a0ca4 | ||
|
|
ffc2ce89a0 | ||
|
|
976d9ef43c | ||
|
|
5b5e60ffec | ||
|
|
4e9f975935 | ||
|
|
d633397883 | ||
|
|
678d9ccad6 | ||
|
|
94c34f23d7 | ||
|
|
7fbaf62bac | ||
|
|
accdb84993 | ||
|
|
e29492d114 | ||
|
|
80bdb44ead | ||
|
|
c818728513 | ||
|
|
9aac7fbc22 | ||
|
|
6bfabdedfd | ||
|
|
a86e52a375 | ||
|
|
53be058e74 | ||
|
|
d648cd562a | ||
|
|
bfa60e2d4e | ||
|
|
514b74a098 | ||
|
|
49344111cc | ||
|
|
c56822a405 | ||
|
|
29b1d0db0f | ||
|
|
e55d086cc9 | ||
|
|
f8ce672b67 | ||
|
|
0e58bfedfd | ||
|
|
7d46416a16 | ||
|
|
5e677a3178 | ||
|
|
c26e9c4cd1 | ||
|
|
80d6abc08b | ||
|
|
5d86c52a3a | ||
|
|
19c466dfb1 | ||
|
|
bcf1eafb8b | ||
|
|
2d410ddf4d | ||
|
|
92b70a3bf9 | ||
|
|
e8183f9ef0 | ||
|
|
b8ce87103d | ||
|
|
ea17b294ea | ||
|
|
fe8c7e66e2 | ||
|
|
02f8f1bb3e | ||
|
|
d8bcb1e973 | ||
|
|
f0551aef09 | ||
|
|
102db206e0 | ||
|
|
1bde5a3c6a | ||
|
|
86773a7f08 | ||
|
|
cb7c96b96a | ||
|
|
f352ad5c93 | ||
|
|
8812018c1d | ||
|
|
862131ead9 | ||
|
|
78d146f907 | ||
|
|
68658a8385 | ||
|
|
2903f3524e | ||
|
|
2c0b75a744 | ||
|
|
647992e666 | ||
|
|
532ec459b8 | ||
|
|
f6c53526b3 | ||
|
|
943662dc3c | ||
|
|
f2073217a4 | ||
|
|
c00ed8f4ff | ||
|
|
84b6059012 | ||
|
|
4ea425d6cf | ||
|
|
e6f3aad84e | ||
|
|
e11afeb34d | ||
|
|
dc29919bbe | ||
|
|
28538bba9c | ||
|
|
4bd6196b0a | ||
|
|
8a2e96de2e | ||
|
|
c4a99944f2 | ||
|
|
867e1db287 | ||
|
|
0ba7055873 | ||
|
|
3f52bb5677 | ||
|
|
ab8d122384 | ||
|
|
3e5790de79 | ||
|
|
da07373599 | ||
|
|
77fbf29047 | ||
|
|
f926230ab1 | ||
|
|
25bfaffdff | ||
|
|
376f1a4432 | ||
|
|
2f5545f8de | ||
|
|
921dae5e83 | ||
|
|
15805f4e01 | ||
|
|
851c5d67ae | ||
|
|
14248685af | ||
|
|
cc3db2aa43 | ||
|
|
855e7237ff | ||
|
|
aaec1562f8 | ||
|
|
a9abf4e7f8 | ||
|
|
b835d76311 | ||
|
|
b7725812ac | ||
|
|
9f3bf6d157 | ||
|
|
e31c15606b | ||
|
|
64dbc92b61 | ||
|
|
e22e0eb8d7 | ||
|
|
a755f24369 | ||
|
|
59fd910355 | ||
|
|
654427d4d0 | ||
|
|
4ace69de9c | ||
|
|
82979296d2 | ||
|
|
9d69206cd0 | ||
|
|
822ca65d69 | ||
|
|
3bfba23362 | ||
|
|
68f5aff64f | ||
|
|
9513abbb0a | ||
|
|
cf1e4770f9 | ||
|
|
d961d5479e | ||
|
|
e73ac40eaa | ||
|
|
aed884d121 | ||
|
|
bd1c4ec9a0 | ||
|
|
fcdbf0bc53 | ||
|
|
4d220964df | ||
|
|
2c6e4ce0ad | ||
|
|
36a97dd8a2 | ||
|
|
d6e830d877 | ||
|
|
f21ab654db | ||
|
|
8d3ff2b127 | ||
|
|
ad53dc0106 | ||
|
|
8df2a98f75 | ||
|
|
482a03f8a5 | ||
|
|
07823b61a1 | ||
|
|
28181919a6 | ||
|
|
a9e9570d84 | ||
|
|
a40e5b2def | ||
|
|
c33faabb27 | ||
|
|
f2b61ff073 | ||
|
|
7da8e3f812 | ||
|
|
2dbfac652e | ||
|
|
9187350b32 | ||
|
|
fa1c60c33e | ||
|
|
2a4730cbee | ||
|
|
60c2e9e675 | ||
|
|
2bddc03468 | ||
|
|
e34899067b | ||
|
|
6325c3044c | ||
|
|
e58e3719d8 | ||
|
|
a7fda84902 | ||
|
|
4c1c8801a5 | ||
|
|
bbc1437188 | ||
|
|
d3783fcc52 | ||
|
|
885ea2de2a | ||
|
|
94afffe9e5 | ||
|
|
a1b8e5d09a | ||
|
|
682174b112 | ||
|
|
3e729c13cc | ||
|
|
37850eeee5 | ||
|
|
a0055192fe | ||
|
|
c6becfc9f5 | ||
|
|
7d41c1f523 | ||
|
|
12aa127df3 | ||
|
|
470466b31b | ||
|
|
c9ccdfbeac | ||
|
|
7979ccd956 | ||
|
|
01fc969a5f | ||
|
|
366e900376 | ||
|
|
55d272efe6 | ||
|
|
8b857fc7c2 | ||
|
|
ae9604faba | ||
|
|
361a8fa318 | ||
|
|
8dd2e91427 | ||
|
|
daf9cd9358 | ||
|
|
787b191abf | ||
|
|
038bcec111 | ||
|
|
2b514275ad | ||
|
|
9286976948 | ||
|
|
ccd70819c2 | ||
|
|
a49169e819 | ||
|
|
d4d38dd44d | ||
|
|
c182688ad6 | ||
|
|
2590b5a9ea | ||
|
|
918119ae1b | ||
|
|
c6ff4a6f4d | ||
|
|
faf628ad8d | ||
|
|
ccaafaccfc | ||
|
|
77f9ecd32b | ||
|
|
e018a48460 | ||
|
|
b4164d295b | ||
|
|
4b91057688 | ||
|
|
1b04b72653 | ||
|
|
43af31836e | ||
|
|
721a92775d | ||
|
|
e47b57a90f | ||
|
|
8abbc619a1 | ||
|
|
0a8e540681 | ||
|
|
08c0783d34 | ||
|
|
0084cceb20 | ||
|
|
02b36b0eb5 | ||
|
|
49c44e3fae | ||
|
|
62d772eaed | ||
|
|
06db4d94fd | ||
|
|
9d9fc8a675 | ||
|
|
8500ca797f | ||
|
|
256a50ad15 | ||
|
|
b71bda45ae | ||
|
|
9bee885282 | ||
|
|
4dae5a52a8 | ||
|
|
3a1899d143 | ||
|
|
44000971e2 | ||
|
|
675f23aedc | ||
|
|
4de0f1e1c8 | ||
|
|
527b841c15 | ||
|
|
8b76444916 | ||
|
|
97b8c9d2c3 | ||
|
|
8fd266a7c4 | ||
|
|
183c445c30 | ||
|
|
c7c8ffbc13 | ||
|
|
25383b9ff2 | ||
|
|
c41365a56f | ||
|
|
9ad42fb0cf | ||
|
|
b4db662053 | ||
|
|
934ce530f6 | ||
|
|
e9d19add7d | ||
|
|
b9fd1e2bed | ||
|
|
41836f3a17 | ||
|
|
01a4afee42 | ||
|
|
c2f966dbc1 | ||
|
|
bbe82d62b0 | ||
|
|
88d857499b | ||
|
|
433e764bb0 | ||
|
|
4b81d19a1a | ||
|
|
b54cdeb284 | ||
|
|
0740758b25 | ||
|
|
7761e44d18 | ||
|
|
d2ea592ddb | ||
|
|
c17655ce74 | ||
|
|
7606da5611 | ||
|
|
ba02d564f8 | ||
|
|
f9259c0383 | ||
|
|
50259d7bdc | ||
|
|
b31880dc5e | ||
|
|
0526bf1895 | ||
|
|
2dd6411753 | ||
|
|
af809b491e | ||
|
|
8d778c90e2 | ||
|
|
393cc3ef2f | ||
|
|
b8b1747704 | ||
|
|
193bfefce4 | ||
|
|
daae327e86 | ||
|
|
18fac59050 | ||
|
|
ddfdeea3af | ||
|
|
3cc27e4dda | ||
|
|
01d96e1136 | ||
|
|
78d078e183 | ||
|
|
99e23bd0fd | ||
|
|
6b997c8f7f | ||
|
|
36abf67e79 | ||
|
|
e60d281a01 | ||
|
|
78574746bd | ||
|
|
34b2c60f95 | ||
|
|
c7ec7bc1f5 | ||
|
|
434d0922dc | ||
|
|
d36a7a43c5 | ||
|
|
684b616f0d | ||
|
|
07a0242535 | ||
|
|
1487153e06 | ||
|
|
6f7b349461 | ||
|
|
bfc5bacecd | ||
|
|
17a9b0178d | ||
|
|
1f43e5296f | ||
|
|
7228e22098 | ||
|
|
322d0200c8 | ||
|
|
80ec2feee8 | ||
|
|
954fc02fdd | ||
|
|
04cdecb7a1 | ||
|
|
6170337001 | ||
|
|
5edf24b510 | ||
|
|
2424eefad2 | ||
|
|
3a450c1395 | ||
|
|
2e5b5c2358 | ||
|
|
4ee9949639 | ||
|
|
03badbdd9b | ||
|
|
0f7b813d65 | ||
|
|
4de04eba39 | ||
|
|
f17415d431 | ||
|
|
953d49810a | ||
|
|
d34fa7c4fa | ||
|
|
14d8c1b594 | ||
|
|
1aec2ff4d2 | ||
|
|
aa8daaf22a | ||
|
|
8795645d97 | ||
|
|
b3e1ec25fc | ||
|
|
d1abe8e92a | ||
|
|
ea8244301d | ||
|
|
f763e23083 | ||
|
|
b0da7e4262 | ||
|
|
a956d0b0eb | ||
|
|
d8e59a28ea | ||
|
|
67bdd8ed58 | ||
|
|
0a4f0b6a5d | ||
|
|
e77d2b2103 | ||
|
|
6177cbdbe1 | ||
|
|
a1d48b5f52 | ||
|
|
fe83ee102b | ||
|
|
a139fdf4ac | ||
|
|
9ca4718aed | ||
|
|
30448641f2 | ||
|
|
c49c3e9f27 | ||
|
|
053da44ecd | ||
|
|
13891fd62d | ||
|
|
5aaafa6a56 | ||
|
|
dfec9c9a43 | ||
|
|
7a8f484020 | ||
|
|
2d8eba5baf | ||
|
|
7fc5af3622 | ||
|
|
cd81194fc0 | ||
|
|
ef98828d40 | ||
|
|
d6969fa7d4 | ||
|
|
ee35f7adf7 | ||
|
|
5130b8a6a9 | ||
|
|
1559477740 | ||
|
|
a83eb90a78 | ||
|
|
7bbc98cfc3 | ||
|
|
5f309b88db | ||
|
|
77ef4fa907 | ||
|
|
701dedcfad | ||
|
|
42e1bb6d46 | ||
|
|
dfae2d141a | ||
|
|
9cf52d027d | ||
|
|
03276e7490 | ||
|
|
6c449793b8 | ||
|
|
922c7f4e51 | ||
|
|
ce3edaad26 | ||
|
|
84815fa879 | ||
|
|
573a1e7662 | ||
|
|
ec95c73a12 | ||
|
|
1449ed9dbf | ||
|
|
e1981b8b8d | ||
|
|
58783b8a46 | ||
|
|
19af91434e | ||
|
|
81fbc5370d | ||
|
|
d4f33b822b | ||
|
|
137d165672 | ||
|
|
86b39e0677 | ||
|
|
7397289266 | ||
|
|
b1ca56bed2 | ||
|
|
952f010c2c | ||
|
|
4ea572791b | ||
|
|
22fd208e8d | ||
|
|
ba661c8d9a | ||
|
|
50b5bb44a0 | ||
|
|
52a41f482f | ||
|
|
4d4f9cc104 | ||
|
|
96cc9a9279 | ||
|
|
56c6f767ae | ||
|
|
a43ee8d752 | ||
|
|
785c4946dd | ||
|
|
70485e690b | ||
|
|
3f695333cd | ||
|
|
67cc2d5046 | ||
|
|
878adee0a3 | ||
|
|
a67c4e6e02 | ||
|
|
5adbe66ae8 | ||
|
|
46a962eb5d | ||
|
|
f0c75573b1 | ||
|
|
ca4ca8a6dc | ||
|
|
e424615839 | ||
|
|
f8cc5668f8 | ||
|
|
680ab61327 | ||
|
|
e3534700d7 | ||
|
|
b13fbc25b8 | ||
|
|
6207751b00 | ||
|
|
0580112940 | ||
|
|
246b515a86 | ||
|
|
4e35177e23 | ||
|
|
83ec2091c1 | ||
|
|
6ce2c85047 | ||
|
|
de8ff8a1c6 | ||
|
|
286f4c446a | ||
|
|
5f4b746a1e | ||
|
|
86d8563314 | ||
|
|
862bec001b | ||
|
|
b4a8cfbd00 | ||
|
|
d654b3d82e | ||
|
|
dfdd20142e | ||
|
|
cedc1aab4a | ||
|
|
74a7ce1df7 | ||
|
|
80702aa88f | ||
|
|
9cdf5c6c31 | ||
|
|
8ad7268c75 | ||
|
|
9a76e94b3d | ||
|
|
ef584f1a3a | ||
|
|
ca61e298b3 | ||
|
|
87bbefe55f | ||
|
|
93abe1ccf3 | ||
|
|
509734d818 | ||
|
|
e2392fe46f | ||
|
|
0e9e166d85 | ||
|
|
5980aa1e51 | ||
|
|
2ff8044806 | ||
|
|
ec0da3ef64 | ||
|
|
221250d922 | ||
|
|
978f7067ee | ||
|
|
9aef7e5e22 | ||
|
|
6b2937bf76 | ||
|
|
a2d2a6b6dd | ||
|
|
d3ea2df06d | ||
|
|
6e11cfcdf0 | ||
|
|
a0ee10b114 | ||
|
|
bcbec6f37c | ||
|
|
e52c895559 | ||
|
|
52f54c728d | ||
|
|
77f1a676a1 | ||
|
|
a452ff983d | ||
|
|
b0ff3179ef | ||
|
|
4d26550f5f | ||
|
|
ccbc554949 | ||
|
|
31e8a61527 | ||
|
|
9be9600bdc | ||
|
|
12514ccd35 | ||
|
|
104641db07 | ||
|
|
f601f25bcc | ||
|
|
27e10e0442 | ||
|
|
6738fb5fef | ||
|
|
7a35178ee2 | ||
|
|
1158777737 | ||
|
|
febb88efc4 | ||
|
|
024b5fe91a | ||
|
|
0901c33753 | ||
|
|
9bede4eeed | ||
|
|
16730c4c43 | ||
|
|
b9ebab71be | ||
|
|
ed0485c599 | ||
|
|
7653e4babc | ||
|
|
6ecbc6c557 | ||
|
|
c1c89411da | ||
|
|
1780e0e3d0 | ||
|
|
a162a844d2 | ||
|
|
56bc11d952 | ||
|
|
e7b39f47f8 | ||
|
|
6885e7e7ec | ||
|
|
45fa12a05c | ||
|
|
47df844338 | ||
|
|
3df9558593 | ||
|
|
1109db86b7 | ||
|
|
5a06e33859 | ||
|
|
43f57d668c | ||
|
|
3a3fee5abf | ||
|
|
d3b71ff80d | ||
|
|
0b65e9335e | ||
|
|
74632c76ce | ||
|
|
87909d327f | ||
|
|
e7bdf8b22a | ||
|
|
84027f4808 | ||
|
|
73b2dc6d4f | ||
|
|
4be61013a1 | ||
|
|
5ad889f6fd | ||
|
|
7826f0afd9 | ||
|
|
8cdbfe69b1 | ||
|
|
0ff4a5fa39 | ||
|
|
fec32fed18 | ||
|
|
a081dea8ab | ||
|
|
0d3db58657 | ||
|
|
f2e7b29c14 | ||
|
|
e42bcf2314 | ||
|
|
d614193e49 | ||
|
|
2a4044a858 | ||
|
|
6b0d017675 | ||
|
|
56bca83bde | ||
|
|
bbecd13697 | ||
|
|
725ba6cf63 | ||
|
|
adab188c2b | ||
|
|
37a352e9d3 | ||
|
|
50d5414075 | ||
|
|
d9ef20e5a5 | ||
|
|
f8718ae779 | ||
|
|
b294b13584 | ||
|
|
c6a32dc077 | ||
|
|
44b0c19f6a | ||
|
|
35b617b57f | ||
|
|
669a21babb | ||
|
|
5d6bf75296 | ||
|
|
11f45e6015 | ||
|
|
71bc2182c2 | ||
|
|
475a7a4446 | ||
|
|
f15f73a555 | ||
|
|
e35fac2054 | ||
|
|
5275fd2789 | ||
|
|
5085a16d78 | ||
|
|
0fc98958a3 | ||
|
|
8c91d5c166 | ||
|
|
f3a39e0c9c | ||
|
|
d20ede40b1 | ||
|
|
b391e5f638 | ||
|
|
0335a25d1f | ||
|
|
b6844bec60 | ||
|
|
7d1b974bca | ||
|
|
61697864c3 | ||
|
|
efdeab3a1d | ||
|
|
ea97589624 | ||
|
|
24408cce9b | ||
|
|
f2e026a1d8 | ||
|
|
0706d633bf | ||
|
|
600dddf88d | ||
|
|
c13433aee4 | ||
|
|
78add28aab | ||
|
|
eef55f493b | ||
|
|
a45643cb3b | ||
|
|
8942047d41 | ||
|
|
e0027eba85 | ||
|
|
7039ece0a0 | ||
|
|
82b829625b | ||
|
|
737e978f5b | ||
|
|
ceb5f5079c | ||
|
|
471b2a4211 | ||
|
|
812fb30821 | ||
|
|
02560d6482 | ||
|
|
39f6d57c34 | ||
|
|
b957a4862f | ||
|
|
1c75945dc4 | ||
|
|
425cdf946c |
@@ -11,4 +11,5 @@ ninja
|
||||
|
||||
ccache -s
|
||||
|
||||
ctest -VV -C Release
|
||||
# Ignore zlib's tests, since they aren't gated behind a CMake option.
|
||||
ctest -VV -E "(example|example64)" -C Release
|
||||
|
||||
45
.ci/scripts/merge/apply-patches-by-label-private.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Download all pull requests as patches that match a specific label
|
||||
# Usage: python download-patches-by-label.py <Label to Match> <Root Path Folder to DL to>
|
||||
|
||||
import requests, sys, json, shutil, subprocess, os, traceback
|
||||
|
||||
org = os.getenv("PRIVATEMERGEORG", "yuzu-emu")
|
||||
repo = os.getenv("PRIVATEMERGEREPO", "yuzu-private")
|
||||
tagline = sys.argv[3]
|
||||
user = sys.argv[1]
|
||||
|
||||
dl_list = {}
|
||||
|
||||
TAG_NAME = sys.argv[2]
|
||||
|
||||
def check_individual(repo_id, pr_id):
|
||||
url = 'https://%sdev.azure.com/%s/%s/_apis/git/repositories/%s/pullRequests/%s/labels?api-version=5.1-preview.1' % (user, org, repo, repo_id, pr_id)
|
||||
response = requests.get(url)
|
||||
if (response.ok):
|
||||
try:
|
||||
js = response.json()
|
||||
return any(tag.get('name') == TAG_NAME for tag in js['value'])
|
||||
except:
|
||||
return False
|
||||
return False
|
||||
|
||||
def merge_pr(pn, ref):
|
||||
print("Matched PR# %s" % pn)
|
||||
print(subprocess.check_output(["git", "fetch", "https://%sdev.azure.com/%s/_git/%s" % (user, org, repo), ref, "-f"]))
|
||||
print(subprocess.check_output(["git", "merge", "--squash", 'origin/' + ref.replace('refs/heads/','')]))
|
||||
print(subprocess.check_output(["git", "commit", "-m\"Merge %s PR %s\"" % (tagline, pn)]))
|
||||
|
||||
def main():
|
||||
url = 'https://%sdev.azure.com/%s/%s/_apis/git/pullrequests?api-version=5.1' % (user, org, repo)
|
||||
response = requests.get(url)
|
||||
if (response.ok):
|
||||
js = response.json()
|
||||
tagged_prs = filter(lambda pr: check_individual(pr['repository']['id'], pr['pullRequestId']), js['value'])
|
||||
map(lambda pr: merge_pr(pr['pullRequestId'], pr['sourceRefName']), tagged_prs)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except:
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
sys.exit(-1)
|
||||
@@ -1,7 +1,9 @@
|
||||
# Download all pull requests as patches that match a specific label
|
||||
# Usage: python download-patches-by-label.py <Label to Match> <Root Path Folder to DL to>
|
||||
|
||||
import requests, sys, json, urllib3.request, shutil, subprocess
|
||||
import requests, sys, json, urllib3.request, shutil, subprocess, os
|
||||
|
||||
tagline = sys.argv[2]
|
||||
|
||||
http = urllib3.PoolManager()
|
||||
dl_list = {}
|
||||
@@ -12,17 +14,23 @@ def check_individual(labels):
|
||||
return True
|
||||
return False
|
||||
|
||||
try:
|
||||
url = 'https://api.github.com/repos/yuzu-emu/yuzu/pulls'
|
||||
def do_page(page):
|
||||
url = 'https://api.github.com/repos/yuzu-emu/yuzu/pulls?page=%s' % page
|
||||
response = requests.get(url)
|
||||
if (response.ok):
|
||||
j = json.loads(response.content)
|
||||
if j == []:
|
||||
return
|
||||
for pr in j:
|
||||
if (check_individual(pr["labels"])):
|
||||
pn = pr["number"]
|
||||
print("Matched PR# %s" % pn)
|
||||
print(subprocess.check_output(["git", "fetch", "https://github.com/yuzu-emu/yuzu.git", "pull/%s/head:pr-%s" % (pn, pn), "-f"]))
|
||||
print(subprocess.check_output(["git", "merge", "--squash", "pr-%s" % pn]))
|
||||
print(subprocess.check_output(["git", "commit", "-m\"Merge PR %s\"" % pn]))
|
||||
print(subprocess.check_output(["git", "commit", "-m\"Merge %s PR %s\"" % (tagline, pn)]))
|
||||
|
||||
try:
|
||||
for i in range(1,30):
|
||||
do_page(i)
|
||||
except:
|
||||
sys.exit(-1)
|
||||
|
||||
32
.ci/scripts/windows/upload.ps1
Normal file
@@ -0,0 +1,32 @@
|
||||
$GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
|
||||
$GITREV = $(git show -s --format='%h')
|
||||
$RELEASE_DIST = "yuzu-windows-msvc"
|
||||
|
||||
$MSVC_BUILD_ZIP = "yuzu-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
|
||||
$MSVC_BUILD_PDB = "yuzu-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
|
||||
$MSVC_SEVENZIP = "yuzu-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
|
||||
|
||||
$env:BUILD_ZIP = $MSVC_BUILD_ZIP
|
||||
$env:BUILD_SYMBOLS = $MSVC_BUILD_PDB
|
||||
$env:BUILD_UPDATE = $MSVC_SEVENZIP
|
||||
|
||||
$BUILD_DIR = ".\build\bin\Release"
|
||||
|
||||
mkdir pdb
|
||||
Get-ChildItem "$BUILD_DIR\" -Recurse -Filter "*.pdb" | Copy-Item -destination .\pdb
|
||||
7z a -tzip $MSVC_BUILD_PDB .\pdb\*.pdb
|
||||
rm "$BUILD_DIR\*.pdb"
|
||||
mkdir $RELEASE_DIST
|
||||
mkdir "artifacts"
|
||||
|
||||
Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
|
||||
rm "$RELEASE_DIST\*.exe"
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Copy-Item .\license.txt -Destination $RELEASE_DIST
|
||||
Copy-Item .\README.md -Destination $RELEASE_DIST
|
||||
7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
|
||||
7z a $MSVC_SEVENZIP $RELEASE_DIST
|
||||
|
||||
Get-ChildItem . -Filter "*.zip" | Copy-Item -destination "artifacts"
|
||||
Get-ChildItem . -Filter "*.7z" | Copy-Item -destination "artifacts"
|
||||
5
.ci/templates/build-mock.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
steps:
|
||||
- script: mkdir artifacts || echo 'X' > artifacts/T1.txt
|
||||
- publish: artifacts
|
||||
artifact: 'yuzu-$(BuildName)-$(BuildSuffix)'
|
||||
displayName: 'Upload Artifacts'
|
||||
21
.ci/templates/build-msvc.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
cache: 'false'
|
||||
|
||||
steps:
|
||||
- script: mkdir build && cd build && set DATE=`date '+%Y.%m.%d'` && set CI=true && set AZURE_REPO_NAME=yuzu-emu/yuzu-$(BuildName) && set AZURE_REPO_TAG=$(BuildName)-$DATE && cmake -G "Visual Studio 15 2017 Win64" --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. && cd ..
|
||||
displayName: 'Configure CMake'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build'
|
||||
inputs:
|
||||
solution: 'build/yuzu.sln'
|
||||
maximumCpuCount: true
|
||||
configuration: release
|
||||
- task: PowerShell@2
|
||||
displayName: 'Package Artifacts'
|
||||
inputs:
|
||||
targetType: 'filePath'
|
||||
filePath: './.ci/scripts/windows/upload.ps1'
|
||||
- publish: artifacts
|
||||
artifact: 'yuzu-$(BuildName)-windows-msvc'
|
||||
displayName: 'Upload Artifacts'
|
||||
@@ -1,5 +1,6 @@
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
cache: 'false'
|
||||
|
||||
steps:
|
||||
- task: DockerInstaller@0
|
||||
@@ -12,9 +13,9 @@ steps:
|
||||
key: yuzu-v1-$(BuildName)-$(BuildSuffix)-$(CacheSuffix)
|
||||
path: $(System.DefaultWorkingDirectory)/ccache
|
||||
cacheHitVar: CACHE_RESTORED
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/exec.sh && ./.ci/scripts/$(ScriptFolder)/exec.sh
|
||||
- script: export DATE=`date '+%Y.%m.%d'` && export CI=true && export AZURE_REPO_NAME=yuzu-emu/yuzu-$(BuildName) && export AZURE_REPO_TAG=$(BuildName)-$DATE && chmod a+x ./.ci/scripts/$(ScriptFolder)/exec.sh && ./.ci/scripts/$(ScriptFolder)/exec.sh
|
||||
displayName: 'Build'
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/upload.sh && ./.ci/scripts/$(ScriptFolder)/upload.sh
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/upload.sh && RELEASE_NAME=$(BuildName) ./.ci/scripts/$(ScriptFolder)/upload.sh
|
||||
displayName: 'Package Artifacts'
|
||||
- publish: artifacts
|
||||
artifact: 'yuzu-$(BuildName)-$(BuildSuffix)'
|
||||
|
||||
@@ -19,4 +19,5 @@ jobs:
|
||||
needSubmodules: 'true'
|
||||
- template: ./build-single.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
artifactSource: 'false'
|
||||
cache: $(parameters.cache)
|
||||
@@ -4,18 +4,20 @@ jobs:
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
strategy:
|
||||
maxParallel: 10
|
||||
maxParallel: 5
|
||||
matrix:
|
||||
windows:
|
||||
BuildSuffix: 'windows-testing'
|
||||
ScriptFolder: 'windows'
|
||||
steps:
|
||||
- script: sudo apt-get update && sudo apt-get --only-upgrade -y install python3-pip && pip install requests urllib3
|
||||
displayName: 'Prepare Environment'
|
||||
- task: PythonScript@0
|
||||
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||
displayName: 'Determine Testing Status'
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '../scripts/merge/check-label-presence.py'
|
||||
scriptPath: '.ci/scripts/merge/check-label-presence.py'
|
||||
arguments: '$(System.PullRequest.PullRequestNumber) create-testing-build'
|
||||
- ${{ if eq(variables.enabletesting, 'true') }}:
|
||||
- template: ./sync-source.yml
|
||||
@@ -27,4 +29,5 @@ jobs:
|
||||
matchLabel: 'testing-merge'
|
||||
- template: ./build-single.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
artifactSource: 'false'
|
||||
cache: 'false'
|
||||
|
||||
47
.ci/templates/merge-private.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
jobs:
|
||||
- job: merge
|
||||
displayName: 'pull requests'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: recursive
|
||||
- template: ./mergebot-private.yml
|
||||
parameters:
|
||||
matchLabel: '$(BuildName)-merge'
|
||||
matchLabelPublic: '$(PublicBuildName)-merge'
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'Package Source'
|
||||
inputs:
|
||||
rootFolderOrFile: '$(System.DefaultWorkingDirectory)'
|
||||
includeRootFolder: false
|
||||
archiveType: '7z'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/yuzu-$(BuildName)-source.7z'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Upload Artifacts'
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/yuzu-$(BuildName)-source.7z'
|
||||
artifact: 'yuzu-$(BuildName)-source'
|
||||
replaceExistingArchive: true
|
||||
- job: upload_source
|
||||
displayName: 'upload'
|
||||
dependsOn: merge
|
||||
steps:
|
||||
- template: ./sync-source.yml
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
needSubmodules: 'true'
|
||||
- script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh
|
||||
displayName: 'Apply Git Configuration'
|
||||
- script: git tag -a $(BuildName)-$(Build.BuildId) -m "yuzu $(BuildName) $(Build.BuildNumber) $(Build.DefinitionName)"
|
||||
displayName: 'Tag Source'
|
||||
- script: git remote add other $(GitRepoPushChangesURL)
|
||||
displayName: 'Register Repository'
|
||||
- script: git push --follow-tags --force other HEAD:$(GitPushBranch)
|
||||
displayName: 'Update Code'
|
||||
- script: git rev-list -n 1 $(BuildName)-$(Build.BuildId) > $(Build.ArtifactStagingDirectory)/tag-commit.sha
|
||||
displayName: 'Calculate Release Point'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Upload Release Point'
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/tag-commit.sha'
|
||||
artifact: 'yuzu-$(BuildName)-release-point'
|
||||
replaceExistingArchive: true
|
||||
30
.ci/templates/mergebot-private.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
parameters:
|
||||
matchLabel: 'dummy-merge'
|
||||
matchLabelPublic: 'dummy-merge'
|
||||
|
||||
steps:
|
||||
- script: mkdir $(System.DefaultWorkingDirectory)/patches && pip install requests urllib3
|
||||
displayName: 'Prepare Environment'
|
||||
- script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh
|
||||
displayName: 'Apply Git Configuration'
|
||||
- task: PythonScript@0
|
||||
displayName: 'Discover, Download, and Apply Patches (Mainline)'
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '.ci/scripts/merge/apply-patches-by-label.py'
|
||||
arguments: '${{ parameters.matchLabelPublic }} $(MergeTaglinePublic) patches-public'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
- task: PythonScript@0
|
||||
displayName: 'Discover, Download, and Apply Patches (Patreon Public)'
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '.ci/scripts/merge/apply-patches-by-label.py'
|
||||
arguments: '${{ parameters.matchLabel }} "$(MergeTaglinePrivate) Public" patches-mixed-public'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
- task: PythonScript@0
|
||||
displayName: 'Discover, Download, and Apply Patches (Patreon Private)'
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '.ci/scripts/merge/apply-patches-by-label-private.py'
|
||||
arguments: '$(PrivateMergeUser) ${{ parameters.matchLabel }} "$(MergeTaglinePrivate) Private" patches-private'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
@@ -11,5 +11,5 @@ steps:
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '.ci/scripts/merge/apply-patches-by-label.py'
|
||||
arguments: '${{ parameters.matchLabel }} patches'
|
||||
arguments: '${{ parameters.matchLabel }} Tagged patches'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
||||
13
.ci/templates/release-download.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Windows Release'
|
||||
inputs:
|
||||
artifactName: 'yuzu-$(BuildName)-windows-msvc'
|
||||
buildType: 'current'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Linux Release'
|
||||
inputs:
|
||||
artifactName: 'yuzu-$(BuildName)-linux'
|
||||
buildType: 'current'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
11
.ci/templates/release-github.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
steps:
|
||||
- template: ./release-download.yml
|
||||
- task: GitHubRelease@0
|
||||
inputs:
|
||||
action: 'create'
|
||||
title: 'yuzu $(BuildName) #$(Build.BuildId)'
|
||||
assets: '$(Build.ArtifactStagingDirectory)/*'
|
||||
gitHubConnection: $(GitHubReleaseConnectionName)
|
||||
repositoryName: '$(Build.Repository.Name)'
|
||||
target: '$(Build.SourceVersion)'
|
||||
tagSource: 'auto'
|
||||
10
.ci/templates/release-universal.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
steps:
|
||||
- template: ./release-download.yml
|
||||
- task: UniversalPackages@0
|
||||
displayName: Publish Artifacts
|
||||
inputs:
|
||||
command: publish
|
||||
publishDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||
vstsFeedPublish: 'yuzu-$(BuildName)'
|
||||
vstsFeedPackagePublish: 'main'
|
||||
packagePublishDescription: 'Yuzu Windows and Linux Executable Packages'
|
||||
8
.ci/yuzu-mainline-step1.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- stage: merge
|
||||
displayName: 'merge'
|
||||
jobs:
|
||||
- template: ./templates/merge.yml
|
||||
63
.ci/yuzu-mainline-step2.yml
Normal file
@@ -0,0 +1,63 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- stage: format
|
||||
displayName: 'format'
|
||||
jobs:
|
||||
- job: format
|
||||
displayName: 'clang'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- template: ./templates/format-check.yml
|
||||
- stage: build
|
||||
dependsOn: format
|
||||
displayName: 'build'
|
||||
jobs:
|
||||
- job: build
|
||||
displayName: 'standard'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
strategy:
|
||||
maxParallel: 10
|
||||
matrix:
|
||||
linux:
|
||||
BuildSuffix: 'linux'
|
||||
ScriptFolder: 'linux'
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'true'
|
||||
- template: ./templates/build-single.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
cache: 'true'
|
||||
- stage: build_win
|
||||
dependsOn: format
|
||||
displayName: 'build-windows'
|
||||
jobs:
|
||||
- job: build
|
||||
displayName: 'msvc'
|
||||
pool:
|
||||
vmImage: vs2017-win2016
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'true'
|
||||
- template: ./templates/build-msvc.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
cache: 'true'
|
||||
- stage: release
|
||||
displayName: 'Release'
|
||||
dependsOn:
|
||||
- build
|
||||
- build_win
|
||||
jobs:
|
||||
- job: github
|
||||
displayName: 'GitHub Release'
|
||||
steps:
|
||||
- template: ./templates/release-github.yml
|
||||
@@ -1,23 +0,0 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- stage: merge
|
||||
displayName: 'merge'
|
||||
jobs:
|
||||
- template: ./templates/merge.yml
|
||||
- stage: format
|
||||
dependsOn: merge
|
||||
displayName: 'format'
|
||||
jobs:
|
||||
- job: format
|
||||
displayName: 'clang'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- template: ./templates/format-check.yml
|
||||
- stage: build
|
||||
displayName: 'build'
|
||||
dependsOn: format
|
||||
jobs:
|
||||
- template: ./templates/build-standard.yml
|
||||
8
.ci/yuzu-patreon-step1.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- stage: merge
|
||||
displayName: 'merge'
|
||||
jobs:
|
||||
- template: ./templates/merge-private.yml
|
||||
30
.ci/yuzu-patreon-step2.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- stage: format
|
||||
displayName: 'format'
|
||||
jobs:
|
||||
- job: format
|
||||
displayName: 'clang'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- template: ./templates/format-check.yml
|
||||
- stage: build
|
||||
dependsOn: format
|
||||
displayName: 'build'
|
||||
jobs:
|
||||
- job: build
|
||||
displayName: 'windows-msvc'
|
||||
pool:
|
||||
vmImage: vs2017-win2016
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'true'
|
||||
- template: ./templates/build-msvc.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
cache: $(parameters.cache)
|
||||
@@ -1,19 +0,0 @@
|
||||
# Starter pipeline
|
||||
# Start with a minimal pipeline that you can customize to build and deploy your code.
|
||||
# Add steps that build, run tests, deploy, and more:
|
||||
# https://aka.ms/yaml
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- script: echo Hello, world!
|
||||
displayName: 'Run a one-line script'
|
||||
|
||||
- script: |
|
||||
echo Add other tasks to build, test, and deploy your project.
|
||||
echo See https://aka.ms/yaml
|
||||
displayName: 'Run a multi-line script'
|
||||
@@ -15,4 +15,6 @@ stages:
|
||||
dependsOn: format
|
||||
jobs:
|
||||
- template: ./templates/build-standard.yml
|
||||
- template: ./templates/build-testing.yml
|
||||
parameters:
|
||||
cache: 'false'
|
||||
- template: ./templates/build-testing.yml
|
||||
|
||||
6
.gitmodules
vendored
@@ -46,3 +46,9 @@
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/ReinUsesLisp/sirit
|
||||
[submodule "libzip"]
|
||||
path = externals/libzip
|
||||
url = https://github.com/DarkLordZach/libzip
|
||||
[submodule "zlib"]
|
||||
path = externals/zlib
|
||||
url = https://github.com/madler/zlib
|
||||
|
||||
@@ -21,6 +21,8 @@ option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
||||
|
||||
option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implementation of BCAT" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(ENABLE_VULKAN "Enables Vulkan backend" ON)
|
||||
|
||||
@@ -81,6 +81,7 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/register_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/shift.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/warp.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
yuzu emulator
|
||||
=============
|
||||
[](https://travis-ci.org/yuzu-emu/yuzu)
|
||||
[](https://ci.appveyor.com/project/bunnei/yuzu)
|
||||
[](https://dev.azure.com/yuzu-emu/yuzu/)
|
||||
|
||||
yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/).
|
||||
|
||||
It is written in C++ with portability in mind, with builds actively maintained for Windows, Linux and macOS. The emulator is currently only useful for homebrew development and research purposes.
|
||||
It is written in C++ with portability in mind, with builds actively maintained for Windows and Linux. The emulator is capable of running several commercial games.
|
||||
|
||||
yuzu only emulates a subset of Switch hardware and therefore is generally only useful for running/debugging homebrew applications. yuzu can boot some games, to varying degrees of success.
|
||||
yuzu only emulates a subset of Switch hardware and therefore most commercial games **do not** run at full speed or are not fully functional.
|
||||
|
||||
Do you want to check which games are compatible and which ones are not? Please visit our [Compatibility page](https://yuzu-emu.org/game/)!
|
||||
|
||||
yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included.
|
||||
|
||||
|
||||
31
dist/license.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
The icons in this folder and its subfolders have the following licenses:
|
||||
|
||||
Icon Name | License | Origin/Author
|
||||
--- | --- | ---
|
||||
qt_themes/default/icons/16x16/checked.png | Free for non-commercial use
|
||||
qt_themes/default/icons/16x16/failed.png | Free for non-commercial use
|
||||
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use
|
||||
qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use
|
||||
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/48x48/plus.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
|
||||
<!-- TODO: Add the license of the yuzu icon -->
|
||||
BIN
dist/qt_themes/colorful/icons/16x16/lock.png
vendored
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
dist/qt_themes/colorful/icons/256x256/plus_folder.png
vendored
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/bad_folder.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/chip.png
vendored
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
dist/qt_themes/colorful/icons/48x48/folder.png
vendored
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
dist/qt_themes/colorful/icons/48x48/plus.png
vendored
Normal file
|
After Width: | Height: | Size: 496 B |
BIN
dist/qt_themes/colorful/icons/48x48/sd_card.png
vendored
Normal file
|
After Width: | Height: | Size: 680 B |
14
dist/qt_themes/colorful/icons/index.theme
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
[Icon Theme]
|
||||
Name=colorful
|
||||
Comment=Colorful theme
|
||||
Inherits=default
|
||||
Directories=16x16,48x48,256x256
|
||||
|
||||
[16x16]
|
||||
Size=16
|
||||
|
||||
[48x48]
|
||||
Size=48
|
||||
|
||||
[256x256]
|
||||
Size=256
|
||||
15
dist/qt_themes/colorful/style.qrc
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/colorful">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="colorful">
|
||||
<file>style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
4
dist/qt_themes/colorful/style.qss
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
This file is intentionally left blank.
|
||||
We do not want to apply any stylesheet for colorful, only icons.
|
||||
*/
|
||||
BIN
dist/qt_themes/colorful_dark/icons/16x16/lock.png
vendored
Normal file
|
After Width: | Height: | Size: 401 B |
8
dist/qt_themes/colorful_dark/icons/index.theme
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
[Icon Theme]
|
||||
Name=colorful_dark
|
||||
Comment=Colorful theme (Dark style)
|
||||
Inherits=default
|
||||
Directories=16x16
|
||||
|
||||
[16x16]
|
||||
Size=16
|
||||
57
dist/qt_themes/colorful_dark/style.qrc
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/colorful_dark">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/plus.png">../colorful/icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
|
||||
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
|
||||
</qresource>
|
||||
|
||||
<qresource prefix="qss_icons">
|
||||
<file alias="rc/up_arrow_disabled.png">../qdarkstyle/rc/up_arrow_disabled.png</file>
|
||||
<file alias="rc/Hmovetoolbar.png">../qdarkstyle/rc/Hmovetoolbar.png</file>
|
||||
<file alias="rc/stylesheet-branch-end.png">../qdarkstyle/rc/stylesheet-branch-end.png</file>
|
||||
<file alias="rc/branch_closed-on.png">../qdarkstyle/rc/branch_closed-on.png</file>
|
||||
<file alias="rc/stylesheet-vline.png">../qdarkstyle/rc/stylesheet-vline.png</file>
|
||||
<file alias="rc/branch_closed.png">../qdarkstyle/rc/branch_closed.png</file>
|
||||
<file alias="rc/branch_open-on.png">../qdarkstyle/rc/branch_open-on.png</file>
|
||||
<file alias="rc/transparent.png">../qdarkstyle/rc/transparent.png</file>
|
||||
<file alias="rc/right_arrow_disabled.png">../qdarkstyle/rc/right_arrow_disabled.png</file>
|
||||
<file alias="rc/sizegrip.png">../qdarkstyle/rc/sizegrip.png</file>
|
||||
<file alias="rc/close.png">../qdarkstyle/rc/close.png</file>
|
||||
<file alias="rc/close-hover.png">../qdarkstyle/rc/close-hover.png</file>
|
||||
<file alias="rc/close-pressed.png">../qdarkstyle/rc/close-pressed.png</file>
|
||||
<file alias="rc/down_arrow.png">../qdarkstyle/rc/down_arrow.png</file>
|
||||
<file alias="rc/Vmovetoolbar.png">../qdarkstyle/rc/Vmovetoolbar.png</file>
|
||||
<file alias="rc/left_arrow.png">../qdarkstyle/rc/left_arrow.png</file>
|
||||
<file alias="rc/stylesheet-branch-more.png">../qdarkstyle/rc/stylesheet-branch-more.png</file>
|
||||
<file alias="rc/up_arrow.png">../qdarkstyle/rc/up_arrow.png</file>
|
||||
<file alias="rc/right_arrow.png">../qdarkstyle/rc/right_arrow.png</file>
|
||||
<file alias="rc/left_arrow_disabled.png">../qdarkstyle/rc/left_arrow_disabled.png</file>
|
||||
<file alias="rc/Hsepartoolbar.png">../qdarkstyle/rc/Hsepartoolbar.png</file>
|
||||
<file alias="rc/branch_open.png">../qdarkstyle/rc/branch_open.png</file>
|
||||
<file alias="rc/Vsepartoolbar.png">../qdarkstyle/rc/Vsepartoolbar.png</file>
|
||||
<file alias="rc/down_arrow_disabled.png">../qdarkstyle/rc/down_arrow_disabled.png</file>
|
||||
<file alias="rc/undock.png">../qdarkstyle/rc/undock.png</file>
|
||||
<file alias="rc/checkbox_checked_disabled.png">../qdarkstyle/rc/checkbox_checked_disabled.png</file>
|
||||
<file alias="rc/checkbox_checked_focus.png">../qdarkstyle/rc/checkbox_checked_focus.png</file>
|
||||
<file alias="rc/checkbox_checked.png">../qdarkstyle/rc/checkbox_checked.png</file>
|
||||
<file alias="rc/checkbox_indeterminate.png">../qdarkstyle/rc/checkbox_indeterminate.png</file>
|
||||
<file alias="rc/checkbox_indeterminate_focus.png">../qdarkstyle/rc/checkbox_indeterminate_focus.png</file>
|
||||
<file alias="rc/checkbox_unchecked_disabled.png">../qdarkstyle/rc/checkbox_unchecked_disabled.png</file>
|
||||
<file alias="rc/checkbox_unchecked_focus.png">../qdarkstyle/rc/checkbox_unchecked_focus.png</file>
|
||||
<file alias="rc/checkbox_unchecked.png">../qdarkstyle/rc/checkbox_unchecked.png</file>
|
||||
<file alias="rc/radio_checked_disabled.png">../qdarkstyle/rc/radio_checked_disabled.png</file>
|
||||
<file alias="rc/radio_checked_focus.png">../qdarkstyle/rc/radio_checked_focus.png</file>
|
||||
<file alias="rc/radio_checked.png">../qdarkstyle/rc/radio_checked.png</file>
|
||||
<file alias="rc/radio_unchecked_disabled.png">../qdarkstyle/rc/radio_unchecked_disabled.png</file>
|
||||
<file alias="rc/radio_unchecked_focus.png">../qdarkstyle/rc/radio_unchecked_focus.png</file>
|
||||
<file alias="rc/radio_unchecked.png">../qdarkstyle/rc/radio_unchecked.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="colorful_dark">
|
||||
<file alias="style.qss">../qdarkstyle/style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
14
dist/qt_themes/default/default.qrc
vendored
@@ -5,7 +5,21 @@
|
||||
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
|
||||
|
||||
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
|
||||
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
|
||||
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
|
||||
|
||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
BIN
dist/qt_themes/default/icons/16x16/lock.png
vendored
Normal file
|
After Width: | Height: | Size: 279 B |
BIN
dist/qt_themes/default/icons/256x256/plus_folder.png
vendored
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
dist/qt_themes/default/icons/48x48/bad_folder.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
dist/qt_themes/default/icons/48x48/chip.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
dist/qt_themes/default/icons/48x48/folder.png
vendored
Normal file
|
After Width: | Height: | Size: 410 B |
BIN
dist/qt_themes/default/icons/48x48/plus.png
vendored
Normal file
|
After Width: | Height: | Size: 316 B |
BIN
dist/qt_themes/default/icons/48x48/sd_card.png
vendored
Normal file
|
After Width: | Height: | Size: 614 B |
5
dist/qt_themes/default/icons/index.theme
vendored
@@ -1,10 +1,13 @@
|
||||
[Icon Theme]
|
||||
Name=default
|
||||
Comment=default theme
|
||||
Directories=16x16,256x256
|
||||
Directories=16x16,48x48,256x256
|
||||
|
||||
[16x16]
|
||||
Size=16
|
||||
|
||||
[48x48]
|
||||
Size=48
|
||||
|
||||
[256x256]
|
||||
Size=256
|
||||
BIN
dist/qt_themes/qdarkstyle/icons/16x16/lock.png
vendored
Normal file
|
After Width: | Height: | Size: 304 B |
BIN
dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png
vendored
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/chip.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/folder.png
vendored
Normal file
|
After Width: | Height: | Size: 542 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/plus.png
vendored
Normal file
|
After Width: | Height: | Size: 339 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png
vendored
Normal file
|
After Width: | Height: | Size: 676 B |
7
dist/qt_themes/qdarkstyle/icons/index.theme
vendored
@@ -2,10 +2,13 @@
|
||||
Name=qdarkstyle
|
||||
Comment=dark theme
|
||||
Inherits=default
|
||||
Directories=16x16,256x256
|
||||
Directories=16x16,48x48,256x256
|
||||
|
||||
[16x16]
|
||||
Size=16
|
||||
|
||||
|
||||
[48x48]
|
||||
Size=48
|
||||
|
||||
[256x256]
|
||||
Size=256
|
||||
7
dist/qt_themes/qdarkstyle/style.qrc
vendored
@@ -1,6 +1,13 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/qdarkstyle">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="qss_icons">
|
||||
<file>rc/up_arrow_disabled.png</file>
|
||||
|
||||
6
externals/CMakeLists.txt
vendored
@@ -77,6 +77,12 @@ if (ENABLE_VULKAN)
|
||||
add_subdirectory(sirit)
|
||||
endif()
|
||||
|
||||
# zlib
|
||||
add_subdirectory(zlib EXCLUDE_FROM_ALL)
|
||||
|
||||
# libzip
|
||||
add_subdirectory(libzip EXCLUDE_FROM_ALL)
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
|
||||
2
externals/Vulkan-Headers
vendored
2
externals/dynarmic
vendored
2
externals/fmt
vendored
1
externals/libzip
vendored
Submodule
1
externals/zlib
vendored
Submodule
25
license.txt
@@ -337,3 +337,28 @@ proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
|
||||
|
||||
The icons used in this project have the following licenses:
|
||||
|
||||
Icon Name | License | Origin/Author
|
||||
--- | --- | ---
|
||||
checked.png | Free for non-commercial use
|
||||
failed.png | Free for non-commercial use
|
||||
lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
plus_folder.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
bad_folder.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
chip.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
folder.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
sd_card.png (Default, Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
plus_folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
bad_folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
chip.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
sd_card.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||
|
||||
Note:
|
||||
Some icons are different in different themes, and they are separately listed
|
||||
only when they have different licenses/origins.
|
||||
|
||||
@@ -107,6 +107,11 @@ Stream::State AudioRenderer::GetStreamState() const {
|
||||
return stream->GetState();
|
||||
}
|
||||
|
||||
static constexpr u32 VersionFromRevision(u32_le rev) {
|
||||
// "REV7" -> 7
|
||||
return ((rev >> 24) & 0xff) - 0x30;
|
||||
}
|
||||
|
||||
std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
|
||||
// Copy UpdateDataHeader struct
|
||||
UpdateDataHeader config{};
|
||||
@@ -166,6 +171,11 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
|
||||
// Copy output header
|
||||
UpdateDataHeader response_data{worker_params};
|
||||
std::vector<u8> output_params(response_data.total_size);
|
||||
const auto audren_revision = VersionFromRevision(config.revision);
|
||||
if (audren_revision >= 5) {
|
||||
response_data.frame_count = 0x10;
|
||||
response_data.total_size += 0x10;
|
||||
}
|
||||
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
|
||||
|
||||
// Copy output memory pool entries
|
||||
|
||||
@@ -194,21 +194,24 @@ struct UpdateDataHeader {
|
||||
mixes_size = 0x0;
|
||||
sinks_size = config.sink_count * 0x20;
|
||||
performance_manager_size = 0x10;
|
||||
frame_count = 0;
|
||||
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
|
||||
effects_size + sinks_size + performance_manager_size;
|
||||
}
|
||||
|
||||
u32_le revision;
|
||||
u32_le behavior_size;
|
||||
u32_le memory_pools_size;
|
||||
u32_le voices_size;
|
||||
u32_le voice_resource_size;
|
||||
u32_le effects_size;
|
||||
u32_le mixes_size;
|
||||
u32_le sinks_size;
|
||||
u32_le performance_manager_size;
|
||||
INSERT_PADDING_WORDS(6);
|
||||
u32_le total_size;
|
||||
u32_le revision{};
|
||||
u32_le behavior_size{};
|
||||
u32_le memory_pools_size{};
|
||||
u32_le voices_size{};
|
||||
u32_le voice_resource_size{};
|
||||
u32_le effects_size{};
|
||||
u32_le mixes_size{};
|
||||
u32_le sinks_size{};
|
||||
u32_le performance_manager_size{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32_le frame_count{};
|
||||
INSERT_PADDING_WORDS(4);
|
||||
u32_le total_size{};
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ if (DEFINED ENV{CI})
|
||||
elseif(DEFINED ENV{APPVEYOR})
|
||||
set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
|
||||
set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
|
||||
elseif(DEFINED ENV{AZURE})
|
||||
set(BUILD_REPOSITORY $ENV{AZURE_REPO_NAME})
|
||||
set(BUILD_TAG $ENV{AZURE_REPO_TAG})
|
||||
endif()
|
||||
endif()
|
||||
add_custom_command(OUTPUT scm_rev.cpp
|
||||
@@ -55,6 +58,7 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/register_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/shift.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/warp.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Common {
|
||||
@@ -37,4 +38,63 @@ constexpr bool IsWordAligned(T value) {
|
||||
return (value & 0b11) == 0;
|
||||
}
|
||||
|
||||
template <typename T, std::size_t Align = 16>
|
||||
class AlignmentAllocator {
|
||||
public:
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
public:
|
||||
pointer address(reference r) noexcept {
|
||||
return std::addressof(r);
|
||||
}
|
||||
|
||||
const_pointer address(const_reference r) const noexcept {
|
||||
return std::addressof(r);
|
||||
}
|
||||
|
||||
pointer allocate(size_type n) {
|
||||
return static_cast<pointer>(::operator new (n, std::align_val_t{Align}));
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type) {
|
||||
::operator delete (p, std::align_val_t{Align});
|
||||
}
|
||||
|
||||
void construct(pointer p, const value_type& wert) {
|
||||
new (p) value_type(wert);
|
||||
}
|
||||
|
||||
void destroy(pointer p) {
|
||||
p->~value_type();
|
||||
}
|
||||
|
||||
size_type max_size() const noexcept {
|
||||
return size_type(-1) / sizeof(value_type);
|
||||
}
|
||||
|
||||
template <typename T2>
|
||||
struct rebind {
|
||||
using other = AlignmentAllocator<T2, Align>;
|
||||
};
|
||||
|
||||
bool operator!=(const AlignmentAllocator<T, Align>& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
// Returns true if and only if storage allocated from *this
|
||||
// can be deallocated from other, and vice versa.
|
||||
// Always returns true for stateless allocators.
|
||||
bool operator==(const AlignmentAllocator<T, Align>& other) const noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
||||
CLS(Input) \
|
||||
CLS(Network) \
|
||||
CLS(Loader) \
|
||||
CLS(CheatEngine) \
|
||||
CLS(Crypto) \
|
||||
CLS(WebService)
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ enum class Class : ClassType {
|
||||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
Loader, ///< ROM loader
|
||||
CheatEngine, ///< Memory manipulation and engine VM functions
|
||||
Crypto, ///< Cryptographic engine/functions
|
||||
Input, ///< Input emulation
|
||||
Network, ///< Network emulation
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
if (YUZU_ENABLE_BOXCAT)
|
||||
set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h)
|
||||
else()
|
||||
set(BCAT_BOXCAT_ADDITIONAL_SOURCES)
|
||||
endif()
|
||||
|
||||
add_library(core STATIC
|
||||
arm/arm_interface.h
|
||||
arm/arm_interface.cpp
|
||||
@@ -33,8 +39,6 @@ add_library(core STATIC
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/cheat_engine.cpp
|
||||
file_sys/cheat_engine.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
@@ -70,6 +74,8 @@ add_library(core STATIC
|
||||
file_sys/sdmc_factory.h
|
||||
file_sys/submission_package.cpp
|
||||
file_sys/submission_package.h
|
||||
file_sys/system_archive/mii_model.cpp
|
||||
file_sys/system_archive/mii_model.h
|
||||
file_sys/system_archive/ng_word.cpp
|
||||
file_sys/system_archive/ng_word.h
|
||||
file_sys/system_archive/system_archive.cpp
|
||||
@@ -82,6 +88,8 @@ add_library(core STATIC
|
||||
file_sys/vfs_concat.h
|
||||
file_sys/vfs_layered.cpp
|
||||
file_sys/vfs_layered.h
|
||||
file_sys/vfs_libzip.cpp
|
||||
file_sys/vfs_libzip.h
|
||||
file_sys/vfs_offset.cpp
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
@@ -111,6 +119,8 @@ add_library(core STATIC
|
||||
frontend/scope_acquire_window_context.h
|
||||
gdbstub/gdbstub.cpp
|
||||
gdbstub/gdbstub.h
|
||||
hardware_interrupt_manager.cpp
|
||||
hardware_interrupt_manager.h
|
||||
hle/ipc.h
|
||||
hle/ipc_helpers.h
|
||||
hle/kernel/address_arbiter.cpp
|
||||
@@ -239,6 +249,9 @@ add_library(core STATIC
|
||||
hle/service/audio/errors.h
|
||||
hle/service/audio/hwopus.cpp
|
||||
hle/service/audio/hwopus.h
|
||||
hle/service/bcat/backend/backend.cpp
|
||||
hle/service/bcat/backend/backend.h
|
||||
${BCAT_BOXCAT_ADDITIONAL_SOURCES}
|
||||
hle/service/bcat/bcat.cpp
|
||||
hle/service/bcat/bcat.h
|
||||
hle/service/bcat/module.cpp
|
||||
@@ -372,6 +385,7 @@ add_library(core STATIC
|
||||
hle/service/nvdrv/devices/nvmap.h
|
||||
hle/service/nvdrv/interface.cpp
|
||||
hle/service/nvdrv/interface.h
|
||||
hle/service/nvdrv/nvdata.h
|
||||
hle/service/nvdrv/nvdrv.cpp
|
||||
hle/service/nvdrv/nvdrv.h
|
||||
hle/service/nvdrv/nvmemp.cpp
|
||||
@@ -472,6 +486,11 @@ add_library(core STATIC
|
||||
loader/nsp.h
|
||||
loader/xci.cpp
|
||||
loader/xci.h
|
||||
memory/cheat_engine.cpp
|
||||
memory/cheat_engine.h
|
||||
memory/dmnt_cheat_types.h
|
||||
memory/dmnt_cheat_vm.cpp
|
||||
memory/dmnt_cheat_vm.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_setup.h
|
||||
@@ -491,6 +510,15 @@ create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
|
||||
|
||||
if (YUZU_ENABLE_BOXCAT)
|
||||
get_directory_property(OPENSSL_LIBS
|
||||
DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
|
||||
DEFINITION OPENSSL_LIBS)
|
||||
target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT)
|
||||
target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(core PRIVATE web_service)
|
||||
|
||||
@@ -14,11 +14,18 @@
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_core_manager.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hardware_interrupt_manager.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -26,17 +33,17 @@
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/apm/controller.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/glue/manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "file_sys/cheat_engine.h"
|
||||
#include "file_sys/patch_manager.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
@@ -103,7 +110,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
return vfs->OpenFile(path, FileSys::Mode::Read);
|
||||
}
|
||||
struct System::Impl {
|
||||
explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {}
|
||||
explicit Impl(System& system)
|
||||
: kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {}
|
||||
|
||||
Cpu& CurrentCpuCore() {
|
||||
return cpu_core_manager.GetCurrentCore();
|
||||
@@ -151,17 +159,14 @@ struct System::Impl {
|
||||
if (!renderer->Init()) {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
||||
interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system);
|
||||
gpu_core = VideoCore::CreateGPU(system);
|
||||
|
||||
is_powered_on = true;
|
||||
exit_lock = false;
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
GetAndResetPerfStats();
|
||||
perf_stats.BeginSystemFrame();
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
@@ -200,10 +205,34 @@ struct System::Impl {
|
||||
gpu_core->Start();
|
||||
cpu_core_manager.StartThreads();
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
cheat_engine->Initialize();
|
||||
}
|
||||
|
||||
// All threads are started, begin main process execution, now that we're in the clear.
|
||||
main_process->Run(load_parameters->main_thread_priority,
|
||||
load_parameters->main_thread_stack_size);
|
||||
|
||||
if (Settings::values.gamecard_inserted) {
|
||||
if (Settings::values.gamecard_current_game) {
|
||||
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath));
|
||||
} else if (!Settings::values.gamecard_path.empty()) {
|
||||
fs_controller.SetGameCard(
|
||||
GetGameFileFromPath(virtual_filesystem, Settings::values.gamecard_path));
|
||||
}
|
||||
}
|
||||
|
||||
u64 title_id{0};
|
||||
if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
perf_stats = std::make_unique<PerfStats>(title_id);
|
||||
// Reset counters and set time origin to current frame
|
||||
GetAndResetPerfStats();
|
||||
perf_stats->BeginSystemFrame();
|
||||
|
||||
status = ResultStatus::Success;
|
||||
return status;
|
||||
}
|
||||
@@ -217,8 +246,13 @@ struct System::Impl {
|
||||
perf_results.game_fps);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS",
|
||||
perf_stats->GetMeanFrametime());
|
||||
|
||||
is_powered_on = false;
|
||||
exit_lock = false;
|
||||
|
||||
gpu_core->WaitIdle();
|
||||
|
||||
// Shutdown emulation session
|
||||
renderer.reset();
|
||||
@@ -227,6 +261,7 @@ struct System::Impl {
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
perf_stats.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
// Close all CPU/threading state
|
||||
@@ -284,7 +319,7 @@ struct System::Impl {
|
||||
}
|
||||
|
||||
PerfStatsResults GetAndResetPerfStats() {
|
||||
return perf_stats.GetAndResetStats(core_timing.GetGlobalTimeUs());
|
||||
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
Timing::CoreTiming core_timing;
|
||||
@@ -293,16 +328,20 @@ struct System::Impl {
|
||||
FileSys::VirtualFilesystem virtual_filesystem;
|
||||
/// ContentProviderUnion instance
|
||||
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
|
||||
Service::FileSystem::FileSystemController fs_controller;
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager;
|
||||
CpuCoreManager cpu_core_manager;
|
||||
bool is_powered_on = false;
|
||||
bool exit_lock = false;
|
||||
|
||||
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Memory::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
std::array<u8, 0x20> build_id{};
|
||||
|
||||
/// Frontend applets
|
||||
Service::AM::Applets::AppletManager applet_manager;
|
||||
@@ -324,7 +363,7 @@ struct System::Impl {
|
||||
ResultStatus status = ResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
|
||||
Core::PerfStats perf_stats;
|
||||
std::unique_ptr<Core::PerfStats> perf_stats;
|
||||
Core::FrameLimiter frame_limiter;
|
||||
};
|
||||
|
||||
@@ -444,6 +483,14 @@ const Tegra::GPU& System::GPU() const {
|
||||
return *impl->gpu_core;
|
||||
}
|
||||
|
||||
Core::Hardware::InterruptManager& System::InterruptManager() {
|
||||
return *impl->interrupt_manager;
|
||||
}
|
||||
|
||||
const Core::Hardware::InterruptManager& System::InterruptManager() const {
|
||||
return *impl->interrupt_manager;
|
||||
}
|
||||
|
||||
VideoCore::RendererBase& System::Renderer() {
|
||||
return *impl->renderer;
|
||||
}
|
||||
@@ -469,11 +516,11 @@ const Timing::CoreTiming& System::CoreTiming() const {
|
||||
}
|
||||
|
||||
Core::PerfStats& System::GetPerfStats() {
|
||||
return impl->perf_stats;
|
||||
return *impl->perf_stats;
|
||||
}
|
||||
|
||||
const Core::PerfStats& System::GetPerfStats() const {
|
||||
return impl->perf_stats;
|
||||
return *impl->perf_stats;
|
||||
}
|
||||
|
||||
Core::FrameLimiter& System::FrameLimiter() {
|
||||
@@ -508,13 +555,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
|
||||
return impl->debug_context.get();
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end) {
|
||||
impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
|
||||
code_region_start, code_region_end);
|
||||
}
|
||||
|
||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
|
||||
impl->virtual_filesystem = std::move(vfs);
|
||||
}
|
||||
@@ -523,6 +563,13 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
|
||||
return impl->virtual_filesystem;
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 32>& build_id, VAddr main_region_begin,
|
||||
u64 main_region_size) {
|
||||
impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
|
||||
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
|
||||
}
|
||||
|
||||
void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) {
|
||||
impl->applet_manager.SetAppletFrontendSet(std::move(set));
|
||||
}
|
||||
@@ -551,6 +598,14 @@ const FileSys::ContentProvider& System::GetContentProvider() const {
|
||||
return *impl->content_provider;
|
||||
}
|
||||
|
||||
Service::FileSystem::FileSystemController& System::GetFileSystemController() {
|
||||
return impl->fs_controller;
|
||||
}
|
||||
|
||||
const Service::FileSystem::FileSystemController& System::GetFileSystemController() const {
|
||||
return impl->fs_controller;
|
||||
}
|
||||
|
||||
void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider) {
|
||||
impl->content_provider->SetSlot(slot, provider);
|
||||
@@ -580,6 +635,22 @@ const Service::APM::Controller& System::GetAPMController() const {
|
||||
return impl->apm_controller;
|
||||
}
|
||||
|
||||
void System::SetExitLock(bool locked) {
|
||||
impl->exit_lock = locked;
|
||||
}
|
||||
|
||||
bool System::GetExitLock() const {
|
||||
return impl->exit_lock;
|
||||
}
|
||||
|
||||
void System::SetCurrentProcessBuildID(std::array<u8, 32> id) {
|
||||
impl->build_id = id;
|
||||
}
|
||||
|
||||
const std::array<u8, 32>& System::GetCurrentProcessBuildID() const {
|
||||
return impl->build_id;
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
|
||||
return impl->Init(*this, emu_window);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ class EmuWindow;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
class CheatList;
|
||||
class ContentProvider;
|
||||
class ContentProviderUnion;
|
||||
enum class ContentProviderUnionSlot;
|
||||
@@ -36,6 +35,10 @@ class AppLoader;
|
||||
enum class ResultStatus : u16;
|
||||
} // namespace Loader
|
||||
|
||||
namespace Memory {
|
||||
struct CheatEntry;
|
||||
} // namespace Memory
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace AM::Applets {
|
||||
@@ -47,6 +50,10 @@ namespace APM {
|
||||
class Controller;
|
||||
}
|
||||
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
} // namespace FileSystem
|
||||
|
||||
namespace Glue {
|
||||
class ARPManager;
|
||||
}
|
||||
@@ -70,6 +77,10 @@ namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core::Hardware {
|
||||
class InterruptManager;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
@@ -234,6 +245,12 @@ public:
|
||||
/// Provides a constant reference to the core timing instance.
|
||||
const Timing::CoreTiming& CoreTiming() const;
|
||||
|
||||
/// Provides a reference to the interrupt manager instance.
|
||||
Core::Hardware::InterruptManager& InterruptManager();
|
||||
|
||||
/// Provides a constant reference to the interrupt manager instance.
|
||||
const Core::Hardware::InterruptManager& InterruptManager() const;
|
||||
|
||||
/// Provides a reference to the kernel instance.
|
||||
Kernel::KernelCore& Kernel();
|
||||
|
||||
@@ -272,8 +289,9 @@ public:
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
|
||||
u64 main_region_size);
|
||||
|
||||
void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
|
||||
|
||||
@@ -289,6 +307,10 @@ public:
|
||||
|
||||
const FileSys::ContentProvider& GetContentProvider() const;
|
||||
|
||||
Service::FileSystem::FileSystemController& GetFileSystemController();
|
||||
|
||||
const Service::FileSystem::FileSystemController& GetFileSystemController() const;
|
||||
|
||||
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider);
|
||||
|
||||
@@ -304,6 +326,14 @@ public:
|
||||
|
||||
const Service::APM::Controller& GetAPMController() const;
|
||||
|
||||
void SetExitLock(bool locked);
|
||||
|
||||
bool GetExitLock() const;
|
||||
|
||||
void SetCurrentProcessBuildID(std::array<u8, 0x20> id);
|
||||
|
||||
const std::array<u8, 0x20>& GetCurrentProcessBuildID() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
namespace Core::Crypto {
|
||||
|
||||
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
|
||||
constexpr u64 FULL_TICKET_SIZE = 0x400;
|
||||
|
||||
using namespace Common;
|
||||
|
||||
@@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
|
||||
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
|
||||
};
|
||||
|
||||
namespace {
|
||||
template <std::size_t Size>
|
||||
bool IsAllZeroArray(const std::array<u8, Size>& array) {
|
||||
return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
|
||||
}
|
||||
} // namespace
|
||||
|
||||
u64 GetSignatureTypeDataSize(SignatureType type) {
|
||||
switch (type) {
|
||||
case SignatureType::RSA_4096_SHA1:
|
||||
case SignatureType::RSA_4096_SHA256:
|
||||
return 0x200;
|
||||
case SignatureType::RSA_2048_SHA1:
|
||||
case SignatureType::RSA_2048_SHA256:
|
||||
return 0x100;
|
||||
case SignatureType::ECDSA_SHA1:
|
||||
case SignatureType::ECDSA_SHA256:
|
||||
return 0x3C;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
u64 GetSignatureTypePaddingSize(SignatureType type) {
|
||||
switch (type) {
|
||||
case SignatureType::RSA_4096_SHA1:
|
||||
case SignatureType::RSA_4096_SHA256:
|
||||
case SignatureType::RSA_2048_SHA1:
|
||||
case SignatureType::RSA_2048_SHA256:
|
||||
return 0x3C;
|
||||
case SignatureType::ECDSA_SHA1:
|
||||
case SignatureType::ECDSA_SHA256:
|
||||
return 0x40;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
SignatureType Ticket::GetSignatureType() const {
|
||||
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
|
||||
return ticket->sig_type;
|
||||
}
|
||||
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
|
||||
return ticket->sig_type;
|
||||
}
|
||||
if (auto ticket = std::get_if<ECDSATicket>(&data)) {
|
||||
return ticket->sig_type;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
TicketData& Ticket::GetData() {
|
||||
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
|
||||
return ticket->data;
|
||||
}
|
||||
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
|
||||
return ticket->data;
|
||||
}
|
||||
if (auto ticket = std::get_if<ECDSATicket>(&data)) {
|
||||
return ticket->data;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const TicketData& Ticket::GetData() const {
|
||||
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
|
||||
return ticket->data;
|
||||
}
|
||||
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
|
||||
return ticket->data;
|
||||
}
|
||||
if (auto ticket = std::get_if<ECDSATicket>(&data)) {
|
||||
return ticket->data;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
u64 Ticket::GetSize() const {
|
||||
const auto sig_type = GetSignatureType();
|
||||
|
||||
return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) +
|
||||
GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData);
|
||||
}
|
||||
|
||||
Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) {
|
||||
RSA2048Ticket out{};
|
||||
out.sig_type = SignatureType::RSA_2048_SHA256;
|
||||
out.data.rights_id = rights_id;
|
||||
out.data.title_key_common = title_key;
|
||||
return Ticket{out};
|
||||
}
|
||||
|
||||
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
|
||||
Key128 out{};
|
||||
|
||||
@@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
|
||||
}
|
||||
}
|
||||
|
||||
RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
|
||||
if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
|
||||
return {};
|
||||
|
||||
const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
|
||||
|
||||
std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10);
|
||||
std::array<u8, 0x230> extended_dec{};
|
||||
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
|
||||
rsa_1.SetIV(extended_iv);
|
||||
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
|
||||
extended_dec.data(), Op::Decrypt);
|
||||
|
||||
RSAKeyPair<2048> rsa_key{};
|
||||
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
|
||||
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
|
||||
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
|
||||
|
||||
return rsa_key;
|
||||
}
|
||||
|
||||
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
|
||||
AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
|
||||
Key128 mac_key{};
|
||||
@@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
|
||||
std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
|
||||
if (!ticket_save.IsOpen())
|
||||
return {};
|
||||
|
||||
@@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TicketRaw> out;
|
||||
std::vector<Ticket> out;
|
||||
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
|
||||
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
|
||||
buffer[offset + 3] == 0x0) {
|
||||
out.emplace_back();
|
||||
auto& next = out.back();
|
||||
std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
|
||||
offset += next.size();
|
||||
std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
|
||||
offset += FULL_TICKET_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
|
||||
const RSAKeyPair<2048>& key) {
|
||||
u32 cert_authority;
|
||||
std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
|
||||
if (cert_authority == 0)
|
||||
const auto issuer = ticket.GetData().issuer;
|
||||
if (IsAllZeroArray(issuer))
|
||||
return {};
|
||||
if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
|
||||
LOG_INFO(Crypto,
|
||||
"Attempting to parse ticket with non-standard certificate authority {:08X}.",
|
||||
cert_authority);
|
||||
if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
|
||||
LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
|
||||
}
|
||||
|
||||
Key128 rights_id;
|
||||
std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
|
||||
Key128 rights_id = ticket.GetData().rights_id;
|
||||
|
||||
if (rights_id == Key128{})
|
||||
return {};
|
||||
|
||||
Key128 key_temp{};
|
||||
|
||||
if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
|
||||
std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
|
||||
ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
|
||||
return std::make_pair(rights_id, ticket.GetData().title_key_common);
|
||||
}
|
||||
|
||||
mbedtls_mpi D; // RSA Private Exponent
|
||||
@@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
|
||||
mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
|
||||
mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
|
||||
mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
|
||||
mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
|
||||
|
||||
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
|
||||
|
||||
@@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
return {};
|
||||
ASSERT(*offset > 0);
|
||||
|
||||
Key128 key_temp{};
|
||||
std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
|
||||
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
@@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
|
||||
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
|
||||
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
|
||||
} else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
|
||||
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
|
||||
} else {
|
||||
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
|
||||
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
|
||||
@@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
|
||||
// Titlekeys
|
||||
data.DecryptProdInfo(GetBISKey(0));
|
||||
|
||||
const auto eticket_extended_kek = data.GetETicketExtendedKek();
|
||||
eticket_extended_kek = data.GetETicketExtendedKek();
|
||||
WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
|
||||
PopulateTickets();
|
||||
}
|
||||
|
||||
std::vector<u8> extended_iv(0x10);
|
||||
std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
|
||||
std::array<u8, 0x230> extended_dec{};
|
||||
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
|
||||
rsa_1.SetIV(extended_iv);
|
||||
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
|
||||
extended_dec.data(), Op::Decrypt);
|
||||
void KeyManager::PopulateTickets() {
|
||||
const auto rsa_key = GetETicketRSAKey();
|
||||
|
||||
RSAKeyPair<2048> rsa_key{};
|
||||
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
|
||||
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
|
||||
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
|
||||
if (rsa_key == RSAKeyPair<2048>{})
|
||||
return;
|
||||
|
||||
if (!common_tickets.empty() && !personal_tickets.empty())
|
||||
return;
|
||||
|
||||
const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/80000000000000e1",
|
||||
@@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
|
||||
|
||||
const auto blob2 = GetTicketblob(save2);
|
||||
auto res = GetTicketblob(save1);
|
||||
const auto idx = res.size();
|
||||
res.insert(res.end(), blob2.begin(), blob2.end());
|
||||
|
||||
for (const auto& raw : res) {
|
||||
const auto pair = ParseTicket(raw, rsa_key);
|
||||
for (std::size_t i = 0; i < res.size(); ++i) {
|
||||
const auto common = i < idx;
|
||||
const auto pair = ParseTicket(res[i], rsa_key);
|
||||
if (!pair)
|
||||
continue;
|
||||
const auto& [rid, key] = *pair;
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rid.data(), rid.size());
|
||||
|
||||
if (common) {
|
||||
common_tickets[rights_id] = res[i];
|
||||
} else {
|
||||
personal_tickets[rights_id] = res[i];
|
||||
}
|
||||
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::SynthesizeTickets() {
|
||||
for (const auto& key : s128_keys) {
|
||||
if (key.first.type != S128KeyType::Titlekey) {
|
||||
continue;
|
||||
}
|
||||
u128 rights_id{key.first.field1, key.first.field2};
|
||||
Key128 rights_id_2;
|
||||
std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size());
|
||||
const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2);
|
||||
common_tickets.insert_or_assign(rights_id, ticket);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
if (key == Key128{})
|
||||
return;
|
||||
@@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
|
||||
DeriveBase();
|
||||
}
|
||||
|
||||
const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
|
||||
return common_tickets;
|
||||
}
|
||||
|
||||
const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
|
||||
return personal_tickets;
|
||||
}
|
||||
|
||||
bool KeyManager::AddTicketCommon(Ticket raw) {
|
||||
const auto rsa_key = GetETicketRSAKey();
|
||||
if (rsa_key == RSAKeyPair<2048>{})
|
||||
return false;
|
||||
|
||||
const auto pair = ParseTicket(raw, rsa_key);
|
||||
if (!pair)
|
||||
return false;
|
||||
const auto& [rid, key] = *pair;
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rid.data(), rid.size());
|
||||
common_tickets[rights_id] = raw;
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KeyManager::AddTicketPersonalized(Ticket raw) {
|
||||
const auto rsa_key = GetETicketRSAKey();
|
||||
if (rsa_key == RSAKeyPair<2048>{})
|
||||
return false;
|
||||
|
||||
const auto pair = ParseTicket(raw, rsa_key);
|
||||
if (!pair)
|
||||
return false;
|
||||
const auto& [rid, key] = *pair;
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rid.data(), rid.size());
|
||||
common_tickets[rights_id] = raw;
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
|
||||
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
|
||||
{"eticket_rsa_kek_source",
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <variant>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
@@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
|
||||
using Key128 = std::array<u8, 0x10>;
|
||||
using Key256 = std::array<u8, 0x20>;
|
||||
using SHA256Hash = std::array<u8, 0x20>;
|
||||
using TicketRaw = std::array<u8, 0x400>;
|
||||
|
||||
enum class SignatureType {
|
||||
RSA_4096_SHA1 = 0x10000,
|
||||
RSA_2048_SHA1 = 0x10001,
|
||||
ECDSA_SHA1 = 0x10002,
|
||||
RSA_4096_SHA256 = 0x10003,
|
||||
RSA_2048_SHA256 = 0x10004,
|
||||
ECDSA_SHA256 = 0x10005,
|
||||
};
|
||||
|
||||
u64 GetSignatureTypeDataSize(SignatureType type);
|
||||
u64 GetSignatureTypePaddingSize(SignatureType type);
|
||||
|
||||
enum class TitleKeyType : u8 {
|
||||
Common = 0,
|
||||
Personalized = 1,
|
||||
};
|
||||
|
||||
struct TicketData {
|
||||
std::array<u8, 0x40> issuer;
|
||||
union {
|
||||
std::array<u8, 0x100> title_key_block;
|
||||
|
||||
struct {
|
||||
Key128 title_key_common;
|
||||
std::array<u8, 0xF0> title_key_common_pad;
|
||||
};
|
||||
};
|
||||
|
||||
INSERT_PADDING_BYTES(0x1);
|
||||
TitleKeyType type;
|
||||
INSERT_PADDING_BYTES(0x3);
|
||||
u8 revision;
|
||||
INSERT_PADDING_BYTES(0xA);
|
||||
u64 ticket_id;
|
||||
u64 device_id;
|
||||
std::array<u8, 0x10> rights_id;
|
||||
u32 account_id;
|
||||
INSERT_PADDING_BYTES(0x14C);
|
||||
};
|
||||
static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size.");
|
||||
|
||||
struct RSA4096Ticket {
|
||||
SignatureType sig_type;
|
||||
std::array<u8, 0x200> sig_data;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
TicketData data;
|
||||
};
|
||||
|
||||
struct RSA2048Ticket {
|
||||
SignatureType sig_type;
|
||||
std::array<u8, 0x100> sig_data;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
TicketData data;
|
||||
};
|
||||
|
||||
struct ECDSATicket {
|
||||
SignatureType sig_type;
|
||||
std::array<u8, 0x3C> sig_data;
|
||||
INSERT_PADDING_BYTES(0x40);
|
||||
TicketData data;
|
||||
};
|
||||
|
||||
struct Ticket {
|
||||
std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
|
||||
|
||||
SignatureType GetSignatureType() const;
|
||||
TicketData& GetData();
|
||||
const TicketData& GetData() const;
|
||||
u64 GetSize() const;
|
||||
|
||||
static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
|
||||
};
|
||||
|
||||
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
|
||||
@@ -43,6 +117,19 @@ struct RSAKeyPair {
|
||||
std::array<u8, 4> exponent;
|
||||
};
|
||||
|
||||
template <size_t bit_size, size_t byte_size>
|
||||
bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
|
||||
const RSAKeyPair<bit_size, byte_size>& rhs) {
|
||||
return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) ==
|
||||
std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent);
|
||||
}
|
||||
|
||||
template <size_t bit_size, size_t byte_size>
|
||||
bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
|
||||
const RSAKeyPair<bit_size, byte_size>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
enum class KeyCategory : u8 {
|
||||
Standard,
|
||||
Title,
|
||||
@@ -151,22 +238,35 @@ public:
|
||||
|
||||
static bool KeyFileExists(bool title);
|
||||
|
||||
// Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save
|
||||
// 8*43 and the private file to exist.
|
||||
// Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system
|
||||
// save 8*43 and the private file to exist.
|
||||
void DeriveSDSeedLazy();
|
||||
|
||||
bool BaseDeriveNecessary() const;
|
||||
void DeriveBase();
|
||||
void DeriveETicket(PartitionDataManager& data);
|
||||
void PopulateTickets();
|
||||
void SynthesizeTickets();
|
||||
|
||||
void PopulateFromPartitionData(PartitionDataManager& data);
|
||||
|
||||
const std::map<u128, Ticket>& GetCommonTickets() const;
|
||||
const std::map<u128, Ticket>& GetPersonalizedTickets() const;
|
||||
|
||||
bool AddTicketCommon(Ticket raw);
|
||||
bool AddTicketPersonalized(Ticket raw);
|
||||
|
||||
private:
|
||||
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
|
||||
// Map from rights ID to ticket
|
||||
std::map<u128, Ticket> common_tickets;
|
||||
std::map<u128, Ticket> personal_tickets;
|
||||
|
||||
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
|
||||
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
|
||||
std::array<u8, 576> eticket_extended_kek{};
|
||||
|
||||
bool dev_mode;
|
||||
void LoadFromFile(const std::string& filename, bool is_title_keys);
|
||||
@@ -178,6 +278,8 @@ private:
|
||||
|
||||
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
|
||||
|
||||
RSAKeyPair<2048> GetETicketRSAKey() const;
|
||||
|
||||
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
|
||||
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
|
||||
|
||||
@@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
|
||||
std::optional<Key128> DeriveSDSeed();
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
|
||||
|
||||
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
|
||||
std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
|
||||
|
||||
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
|
||||
// 0x140-0x144 is zero)
|
||||
std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
|
||||
// (offset 0x140-0x144 is zero)
|
||||
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
|
||||
const RSAKeyPair<2048>& eticket_extended_key);
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
@@ -480,6 +480,10 @@ void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
|
||||
prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetDecryptedProdInfo() const {
|
||||
return prodinfo_decrypted;
|
||||
}
|
||||
|
||||
std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
|
||||
std::array<u8, 0x240> out{};
|
||||
if (prodinfo_decrypted != nullptr)
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
bool HasProdInfo() const;
|
||||
FileSys::VirtualFile GetProdInfoRaw() const;
|
||||
void DecryptProdInfo(std::array<u8, 0x20> bis_key);
|
||||
FileSys::VirtualFile GetDecryptedProdInfo() const;
|
||||
std::array<u8, 0x240> GetETicketExtendedKek() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -14,10 +18,22 @@ BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir
|
||||
sysnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))),
|
||||
sysnand_placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/placehld"))),
|
||||
usrnand_placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/placehld"))) {}
|
||||
|
||||
BISFactory::~BISFactory() = default;
|
||||
|
||||
VirtualDir BISFactory::GetSystemNANDContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/system/Contents");
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetUserNANDContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user/Contents");
|
||||
}
|
||||
|
||||
RegisteredCache* BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache.get();
|
||||
}
|
||||
@@ -26,9 +42,17 @@ RegisteredCache* BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* BISFactory::GetSystemNANDPlaceholder() const {
|
||||
return sysnand_placeholder.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* BISFactory::GetUserNANDPlaceholder() const {
|
||||
return usrnand_placeholder.get();
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
|
||||
// LayeredFS doesn't work on updates and title id-less homebrew
|
||||
if (title_id == 0 || (title_id & 0x800) > 0)
|
||||
if (title_id == 0 || (title_id & 0xFFF) == 0x800)
|
||||
return nullptr;
|
||||
return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
@@ -39,4 +63,82 @@ VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const {
|
||||
return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
|
||||
switch (id) {
|
||||
case BisPartitionId::CalibrationFile:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/prodinfof");
|
||||
case BisPartitionId::SafeMode:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/safe");
|
||||
case BisPartitionId::System:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/system");
|
||||
case BisPartitionId::User:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user");
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
|
||||
Core::Crypto::KeyManager keys;
|
||||
Core::Crypto::PartitionDataManager pdm{
|
||||
Core::System::GetInstance().GetFilesystem()->OpenDirectory(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)};
|
||||
keys.PopulateFromPartitionData(pdm);
|
||||
|
||||
switch (id) {
|
||||
case BisPartitionId::CalibrationBinary:
|
||||
return pdm.GetDecryptedProdInfo();
|
||||
case BisPartitionId::BootConfigAndPackage2Part1:
|
||||
case BisPartitionId::BootConfigAndPackage2Part2:
|
||||
case BisPartitionId::BootConfigAndPackage2Part3:
|
||||
case BisPartitionId::BootConfigAndPackage2Part4:
|
||||
case BisPartitionId::BootConfigAndPackage2Part5:
|
||||
case BisPartitionId::BootConfigAndPackage2Part6: {
|
||||
const auto new_id = static_cast<u8>(id) -
|
||||
static_cast<u8>(BisPartitionId::BootConfigAndPackage2Part1) +
|
||||
static_cast<u8>(Core::Crypto::Package2Type::NormalMain);
|
||||
return pdm.GetPackage2Raw(static_cast<Core::Crypto::Package2Type>(new_id));
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetImageDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user/Album");
|
||||
}
|
||||
|
||||
u64 BISFactory::GetSystemNANDFreeSpace() const {
|
||||
const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system");
|
||||
if (sys_dir == nullptr)
|
||||
return 0;
|
||||
|
||||
return GetSystemNANDTotalSpace() - sys_dir->GetSize();
|
||||
}
|
||||
|
||||
u64 BISFactory::GetSystemNANDTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.nand_system_size);
|
||||
}
|
||||
|
||||
u64 BISFactory::GetUserNANDFreeSpace() const {
|
||||
const auto usr_dir = GetOrCreateDirectoryRelative(nand_root, "/user");
|
||||
if (usr_dir == nullptr)
|
||||
return 0;
|
||||
|
||||
return GetUserNANDTotalSpace() - usr_dir->GetSize();
|
||||
}
|
||||
|
||||
u64 BISFactory::GetUserNANDTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.nand_user_size);
|
||||
}
|
||||
|
||||
u64 BISFactory::GetFullNANDTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.nand_total_size);
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const {
|
||||
return GetOrCreateDirectoryRelative(nand_root,
|
||||
fmt::format("/system/save/bcat/{:016X}", title_id));
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -10,7 +10,25 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class BisPartitionId : u32 {
|
||||
UserDataRoot = 20,
|
||||
CalibrationBinary = 27,
|
||||
CalibrationFile = 28,
|
||||
BootConfigAndPackage2Part1 = 21,
|
||||
BootConfigAndPackage2Part2 = 22,
|
||||
BootConfigAndPackage2Part3 = 23,
|
||||
BootConfigAndPackage2Part4 = 24,
|
||||
BootConfigAndPackage2Part5 = 25,
|
||||
BootConfigAndPackage2Part6 = 26,
|
||||
SafeMode = 29,
|
||||
System = 31,
|
||||
SystemProperEncryption = 32,
|
||||
SystemProperPartition = 33,
|
||||
User = 30,
|
||||
};
|
||||
|
||||
class RegisteredCache;
|
||||
class PlaceholderCache;
|
||||
|
||||
/// File system interface to the Built-In Storage
|
||||
/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
|
||||
@@ -20,12 +38,31 @@ public:
|
||||
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root);
|
||||
~BISFactory();
|
||||
|
||||
VirtualDir GetSystemNANDContentDirectory() const;
|
||||
VirtualDir GetUserNANDContentDirectory() const;
|
||||
|
||||
RegisteredCache* GetSystemNANDContents() const;
|
||||
RegisteredCache* GetUserNANDContents() const;
|
||||
|
||||
PlaceholderCache* GetSystemNANDPlaceholder() const;
|
||||
PlaceholderCache* GetUserNANDPlaceholder() const;
|
||||
|
||||
VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
VirtualDir GetModificationDumpRoot(u64 title_id) const;
|
||||
|
||||
VirtualDir OpenPartition(BisPartitionId id) const;
|
||||
VirtualFile OpenPartitionStorage(BisPartitionId id) const;
|
||||
|
||||
VirtualDir GetImageDirectory() const;
|
||||
|
||||
u64 GetSystemNANDFreeSpace() const;
|
||||
u64 GetSystemNANDTotalSpace() const;
|
||||
u64 GetUserNANDFreeSpace() const;
|
||||
u64 GetUserNANDTotalSpace() const;
|
||||
u64 GetFullNANDTotalSpace() const;
|
||||
|
||||
VirtualDir GetBCATDirectory(u64 title_id) const;
|
||||
|
||||
private:
|
||||
VirtualDir nand_root;
|
||||
VirtualDir load_root;
|
||||
@@ -33,6 +70,9 @@ private:
|
||||
|
||||
std::unique_ptr<RegisteredCache> sysnand_cache;
|
||||
std::unique_ptr<RegisteredCache> usrnand_cache;
|
||||
|
||||
std::unique_ptr<PlaceholderCache> sysnand_placeholder;
|
||||
std::unique_ptr<PlaceholderCache> usrnand_placeholder;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -12,12 +12,16 @@
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000;
|
||||
constexpr std::array partition_names{
|
||||
"update",
|
||||
"normal",
|
||||
@@ -175,6 +179,26 @@ VirtualDir XCI::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
VirtualDir XCI::ConcatenatedPseudoDirectory() {
|
||||
const auto out = std::make_shared<VectorVfsDirectory>();
|
||||
for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) {
|
||||
const auto& part = GetPartition(part_id);
|
||||
if (part == nullptr)
|
||||
continue;
|
||||
|
||||
for (const auto& file : part->GetFiles())
|
||||
out->AddFile(file);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 0x200> XCI::GetCertificate() const {
|
||||
std::array<u8, 0x200> out;
|
||||
file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET);
|
||||
return out;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
const auto partition_index = static_cast<std::size_t>(part);
|
||||
const auto& partition = partitions[partition_index];
|
||||
|
||||
@@ -91,6 +91,8 @@ public:
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
u64 GetProgramTitleID() const;
|
||||
u32 GetSystemUpdateVersion();
|
||||
u64 GetSystemUpdateTitleID() const;
|
||||
|
||||
bool HasProgramNCA() const;
|
||||
VirtualFile GetProgramNCAFile() const;
|
||||
@@ -106,6 +108,11 @@ public:
|
||||
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
// Creates a directory that contains all the NCAs in the gamecard
|
||||
VirtualDir ConcatenatedPseudoDirectory();
|
||||
|
||||
std::array<u8, 0x200> GetCertificate() const;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
|
||||
|
||||
@@ -120,6 +127,8 @@ private:
|
||||
std::shared_ptr<NCA> program;
|
||||
std::vector<std::shared_ptr<NCA>> ncas;
|
||||
|
||||
u64 update_normal_partition_end;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -1,492 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
|
||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
|
||||
|
||||
u64 Cheat::Address() const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u64));
|
||||
return Common::swap64(out) & 0xFFFFFFFFFF;
|
||||
}
|
||||
|
||||
u64 Cheat::ValueWidth(u64 offset) const {
|
||||
return Value(offset, width);
|
||||
}
|
||||
|
||||
u64 Cheat::Value(u64 offset, u64 width) const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data() + offset, sizeof(u64));
|
||||
out = Common::swap64(out);
|
||||
if (width == 8)
|
||||
return out;
|
||||
return out & ((1ull << (width * CHAR_BIT)) - 1);
|
||||
}
|
||||
|
||||
u32 Cheat::KeypadValue() const {
|
||||
u32 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u32));
|
||||
return Common::swap32(out) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
|
||||
VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
|
||||
this->main_region_begin = main_begin;
|
||||
this->main_region_end = main_end;
|
||||
this->heap_region_begin = heap_begin;
|
||||
this->heap_region_end = heap_end;
|
||||
this->writer = writer;
|
||||
this->reader = reader;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
|
||||
|
||||
void CheatList::Execute() {
|
||||
MICROPROFILE_SCOPE(Cheat_Engine);
|
||||
|
||||
std::fill(scratch.begin(), scratch.end(), 0);
|
||||
in_standard = false;
|
||||
for (std::size_t i = 0; i < master_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(master_list[i].second);
|
||||
}
|
||||
|
||||
in_standard = true;
|
||||
for (std::size_t i = 0; i < standard_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(standard_list[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
|
||||
: master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
|
||||
|
||||
bool CheatList::EvaluateConditional(const Cheat& cheat) const {
|
||||
using ComparisonFunction = bool (*)(u64, u64);
|
||||
constexpr std::array<ComparisonFunction, 6> comparison_functions{
|
||||
[](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
|
||||
[](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
|
||||
[](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
|
||||
};
|
||||
|
||||
if (cheat.type == CodeType::ConditionalInput) {
|
||||
const auto applet_resource =
|
||||
system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
|
||||
if (applet_resource == nullptr) {
|
||||
LOG_WARNING(
|
||||
Common_Filesystem,
|
||||
"Attempted to evaluate input conditional, but applet resource is not initialized!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto press_state =
|
||||
applet_resource
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
|
||||
.GetAndResetPressState();
|
||||
return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
|
||||
}
|
||||
|
||||
ASSERT(cheat.type == CodeType::Conditional);
|
||||
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
|
||||
auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
|
||||
const auto addr = cheat.Address() + offset;
|
||||
|
||||
return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::ProcessBlockPairs(const Block& block) {
|
||||
block_pairs.clear();
|
||||
|
||||
u64 scope = 0;
|
||||
std::map<u64, u64> pairs;
|
||||
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
const auto& cheat = block[i];
|
||||
|
||||
switch (cheat.type) {
|
||||
case CodeType::Conditional:
|
||||
case CodeType::ConditionalInput:
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
break;
|
||||
case CodeType::EndConditional: {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
break;
|
||||
}
|
||||
case CodeType::Loop: {
|
||||
if (cheat.end_of_loop) {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
} else {
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheatList::WriteImmediate(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = cheat.Address() + offset + register_3;
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
|
||||
cheat.Value(8, cheat.width));
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditional(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
void CheatList::EndConditional(const Cheat& cheat) {
|
||||
LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
|
||||
}
|
||||
|
||||
void CheatList::Loop(const Cheat& cheat) {
|
||||
if (cheat.end_of_loop.Value())
|
||||
ASSERT(!cheat.end_of_loop.Value());
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
ASSERT(iter->first < iter->second);
|
||||
|
||||
const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
|
||||
for (s32 i = initial_value; i >= 0; --i) {
|
||||
register_3 = static_cast<u64>(i);
|
||||
for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
|
||||
current_index = c;
|
||||
ExecuteSingleCheat(
|
||||
(in_standard ? standard_list : master_list)[current_block].second[c]);
|
||||
}
|
||||
}
|
||||
|
||||
current_index = iter->second;
|
||||
}
|
||||
|
||||
void CheatList::LoadImmediate(const Cheat& cheat) {
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
|
||||
cheat.Value(4, 8));
|
||||
register_3 = cheat.Value(4, 8);
|
||||
}
|
||||
|
||||
void CheatList::LoadIndexed(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
|
||||
LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
|
||||
cheat.register_3, addr);
|
||||
register_3 = reader(cheat.width, SanitizeAddress(addr));
|
||||
}
|
||||
|
||||
void CheatList::StoreIndexed(const Cheat& cheat) {
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr =
|
||||
register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
|
||||
cheat.Value(4, cheat.width), addr);
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::RegisterArithmetic(const Cheat& cheat) {
|
||||
using ArithmeticFunction = u64 (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
|
||||
[](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
|
||||
[](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
|
||||
[](u64 a, u64 b) { return a >> b; },
|
||||
};
|
||||
|
||||
using ArithmeticOverflowCheck = bool (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
|
||||
};
|
||||
|
||||
static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
|
||||
"Missing or have extra arithmetic overflow checks compared to functions!");
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
|
||||
auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
auto* overflow_function =
|
||||
arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
|
||||
cheat.register_3, cheat.ValueWidth(4));
|
||||
|
||||
if (overflow_function(register_3, cheat.ValueWidth(4))) {
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"overflow will occur when performing arithmetic operation={:02X} with operands "
|
||||
"a={:016X}, b={:016X}!",
|
||||
static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
register_3 = function(register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditionalInput(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat))
|
||||
return;
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
VAddr CheatList::SanitizeAddress(VAddr in) const {
|
||||
if ((in < main_region_begin || in >= main_region_end) &&
|
||||
(in < heap_region_begin || in >= heap_region_end)) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return 0; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
|
||||
using CheatOperationFunction = void (CheatList::*)(const Cheat&);
|
||||
constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
|
||||
&CheatList::WriteImmediate, &CheatList::BeginConditional,
|
||||
&CheatList::EndConditional, &CheatList::Loop,
|
||||
&CheatList::LoadImmediate, &CheatList::LoadIndexed,
|
||||
&CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
|
||||
&CheatList::BeginConditionalInput,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u8>(cheat.type.Value());
|
||||
ASSERT(index < sizeof(cheat_operation_functions));
|
||||
const auto op = cheat_operation_functions[index];
|
||||
(this->*op)(cheat);
|
||||
}
|
||||
|
||||
void CheatList::ExecuteBlock(const Block& block) {
|
||||
encountered_loops.clear();
|
||||
|
||||
ProcessBlockPairs(block);
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
current_index = i;
|
||||
ExecuteSingleCheat(block[i]);
|
||||
i = current_index;
|
||||
}
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const {
|
||||
return {system, std::move(master), std::move(standard)};
|
||||
}
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
|
||||
std::stringstream ss;
|
||||
ss.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(ss, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
CheatList::ProgramSegment master_list;
|
||||
CheatList::ProgramSegment standard_list;
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
const auto master = line[0] == '{';
|
||||
const auto begin = master ? line.find('{') : line.find('[');
|
||||
const auto end = master ? line.rfind('}') : line.rfind(']');
|
||||
|
||||
ASSERT(begin != std::string::npos && end != std::string::npos);
|
||||
|
||||
const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
|
||||
CheatList::Block block{};
|
||||
|
||||
while (i < lines.size() - 1) {
|
||||
line = lines[++i];
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.size() < 8)
|
||||
continue;
|
||||
|
||||
Cheat out{};
|
||||
out.raw = ParseSingleLineCheat(line);
|
||||
block.push_back(out);
|
||||
}
|
||||
|
||||
(master ? master_list : standard_list).emplace_back(patch_name, block);
|
||||
}
|
||||
}
|
||||
|
||||
return MakeCheatList(system, master_list, standard_list);
|
||||
}
|
||||
|
||||
std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
|
||||
std::array<u8, 16> out{};
|
||||
|
||||
if (line.size() < 8)
|
||||
return out;
|
||||
|
||||
const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
|
||||
std::memcpy(out.data(), word1.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 17 || line[8] != ' ')
|
||||
return out;
|
||||
|
||||
const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
|
||||
std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 26 || line[17] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
|
||||
type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
|
||||
std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
|
||||
std::memset(out.data() + 4, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
|
||||
std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 35 || line[26] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
|
||||
std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
|
||||
std::memset(out.data() + 8, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
|
||||
std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
u64 MemoryReadImpl(u32 width, VAddr addr) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return Memory::Read8(addr);
|
||||
case 2:
|
||||
return Memory::Read16(addr);
|
||||
case 4:
|
||||
return Memory::Read32(addr);
|
||||
case 8:
|
||||
return Memory::Read64(addr);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
Memory::Write8(addr, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
Memory::Write16(addr, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
Memory::Write32(addr, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
Memory::Write64(addr, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end)
|
||||
: cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
|
||||
event = core_timing.RegisterEvent(
|
||||
"CheatEngine::FrameCallback::" + build_id,
|
||||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
|
||||
|
||||
const auto& vm_manager = system.CurrentProcess()->VMManager();
|
||||
for (auto& list : this->cheats) {
|
||||
list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
|
||||
code_region_end, vm_manager.GetHeapRegionEndAddress(),
|
||||
&MemoryWriteImpl, &MemoryReadImpl);
|
||||
}
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
core_timing.UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
|
||||
for (auto& list : cheats) {
|
||||
list.Execute();
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -1,234 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class CodeType : u32 {
|
||||
// 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Writes a T sized value Y to the address A added to the value of register R in memory domain M
|
||||
WriteImmediate = 0,
|
||||
|
||||
// 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Compares the T sized value Y to the value at address A in memory domain M using the
|
||||
// conditional function C. If success, continues execution. If failure, jumps to the matching
|
||||
// EndConditional statement.
|
||||
Conditional = 1,
|
||||
|
||||
// 20000000
|
||||
// Terminates a Conditional or ConditionalInput block.
|
||||
EndConditional = 2,
|
||||
|
||||
// 300R0000 VVVVVVVV
|
||||
// Starts looping V times, storing the current count in register R.
|
||||
// Loop block is terminated with a matching 310R0000.
|
||||
Loop = 3,
|
||||
|
||||
// 400R0000 VVVVVVVV VVVVVVVV
|
||||
// Sets the value of register R to the value V.
|
||||
LoadImmediate = 4,
|
||||
|
||||
// 5TMRI0AA AAAAAAAA
|
||||
// Sets the value of register R to the value of width T at address A in memory domain M, with
|
||||
// the current value of R added to the address if I == 1.
|
||||
LoadIndexed = 5,
|
||||
|
||||
// 6T0RIFG0 VVVVVVVV VVVVVVVV
|
||||
// Writes the value V of width T to the memory address stored in register R. Adds the value of
|
||||
// register G to the final calculation if F is nonzero. Increments the value of register R by T
|
||||
// after operation if I is nonzero.
|
||||
StoreIndexed = 6,
|
||||
|
||||
// 7T0RA000 VVVVVVVV
|
||||
// Performs the arithmetic operation A on the value in register R and the value V of width T,
|
||||
// storing the result in register R.
|
||||
RegisterArithmetic = 7,
|
||||
|
||||
// 8KKKKKKK
|
||||
// Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
|
||||
// execution continues. If none are, execution skips to the next EndConditional command.
|
||||
ConditionalInput = 8,
|
||||
};
|
||||
|
||||
enum class MemoryType : u32 {
|
||||
// Addressed relative to start of main NSO
|
||||
MainNSO = 0,
|
||||
|
||||
// Addressed relative to start of heap
|
||||
Heap = 1,
|
||||
};
|
||||
|
||||
enum class ArithmeticOp : u32 {
|
||||
Add = 0,
|
||||
Sub = 1,
|
||||
Mult = 2,
|
||||
LShift = 3,
|
||||
RShift = 4,
|
||||
};
|
||||
|
||||
enum class ComparisonOp : u32 {
|
||||
GreaterThan = 1,
|
||||
GreaterThanEqual = 2,
|
||||
LessThan = 3,
|
||||
LessThanEqual = 4,
|
||||
Equal = 5,
|
||||
Inequal = 6,
|
||||
};
|
||||
|
||||
union Cheat {
|
||||
std::array<u8, 16> raw;
|
||||
|
||||
BitField<4, 4, CodeType> type;
|
||||
BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
|
||||
BitField<0, 4, u32> end_of_loop;
|
||||
BitField<12, 4, MemoryType> memory_type;
|
||||
BitField<8, 4, u32> register_3;
|
||||
BitField<8, 4, ComparisonOp> comparison_op;
|
||||
BitField<20, 4, u32> load_from_register;
|
||||
BitField<20, 4, u32> increment_register;
|
||||
BitField<20, 4, ArithmeticOp> arithmetic_op;
|
||||
BitField<16, 4, u32> add_additional_register;
|
||||
BitField<28, 4, u32> register_6;
|
||||
|
||||
u64 Address() const;
|
||||
u64 ValueWidth(u64 offset) const;
|
||||
u64 Value(u64 offset, u64 width) const;
|
||||
u32 KeypadValue() const;
|
||||
};
|
||||
|
||||
class CheatParser;
|
||||
|
||||
// Represents a full collection of cheats for a game. The Execute function should be called every
|
||||
// interval that all cheats should be executed. Clients should not directly instantiate this class
|
||||
// (hence private constructor), they should instead receive an instance from CheatParser, which
|
||||
// guarantees the list is always in an acceptable state.
|
||||
class CheatList {
|
||||
public:
|
||||
friend class CheatParser;
|
||||
|
||||
using Block = std::vector<Cheat>;
|
||||
using ProgramSegment = std::vector<std::pair<std::string, Block>>;
|
||||
|
||||
// (width in bytes, address, value)
|
||||
using MemoryWriter = void (*)(u32, VAddr, u64);
|
||||
// (width in bytes, address) -> value
|
||||
using MemoryReader = u64 (*)(u32, VAddr);
|
||||
|
||||
void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
|
||||
MemoryWriter writer, MemoryReader reader);
|
||||
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
|
||||
|
||||
void ProcessBlockPairs(const Block& block);
|
||||
void ExecuteSingleCheat(const Cheat& cheat);
|
||||
|
||||
void ExecuteBlock(const Block& block);
|
||||
|
||||
bool EvaluateConditional(const Cheat& cheat) const;
|
||||
|
||||
// Individual cheat operations
|
||||
void WriteImmediate(const Cheat& cheat);
|
||||
void BeginConditional(const Cheat& cheat);
|
||||
void EndConditional(const Cheat& cheat);
|
||||
void Loop(const Cheat& cheat);
|
||||
void LoadImmediate(const Cheat& cheat);
|
||||
void LoadIndexed(const Cheat& cheat);
|
||||
void StoreIndexed(const Cheat& cheat);
|
||||
void RegisterArithmetic(const Cheat& cheat);
|
||||
void BeginConditionalInput(const Cheat& cheat);
|
||||
|
||||
VAddr SanitizeAddress(VAddr in) const;
|
||||
|
||||
// Master Codes are defined as codes that cannot be disabled and are run prior to all
|
||||
// others.
|
||||
ProgramSegment master_list;
|
||||
// All other codes
|
||||
ProgramSegment standard_list;
|
||||
|
||||
bool in_standard = false;
|
||||
|
||||
// 16 (0x0-0xF) scratch registers that can be used by cheats
|
||||
std::array<u64, 16> scratch{};
|
||||
|
||||
MemoryWriter writer = nullptr;
|
||||
MemoryReader reader = nullptr;
|
||||
|
||||
u64 main_region_begin{};
|
||||
u64 heap_region_begin{};
|
||||
u64 main_region_end{};
|
||||
u64 heap_region_end{};
|
||||
|
||||
u64 current_block{};
|
||||
// The current index of the cheat within the current Block
|
||||
u64 current_index{};
|
||||
|
||||
// The 'stack' of the program. When a conditional or loop statement is encountered, its index is
|
||||
// pushed onto this queue. When a end block is encountered, the condition is checked.
|
||||
std::map<u64, u64> block_pairs;
|
||||
|
||||
std::set<u64> encountered_loops;
|
||||
|
||||
const Core::System* system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
|
||||
|
||||
protected:
|
||||
CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
|
||||
|
||||
private:
|
||||
std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
~CheatEngine();
|
||||
|
||||
private:
|
||||
void FrameCallback(u64 userdata, s64 cycles_late);
|
||||
|
||||
std::vector<CheatList> cheats;
|
||||
|
||||
Core::Timing::EventType* event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -528,6 +528,14 @@ u64 NCA::GetTitleId() const {
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
std::array<u8, 16> NCA::GetRightsId() const {
|
||||
return header.rights_id;
|
||||
}
|
||||
|
||||
u32 NCA::GetSDKVersion() const {
|
||||
return header.sdk_version;
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ public:
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
std::array<u8, 0x10> GetRightsId() const;
|
||||
u32 GetSDKVersion() const;
|
||||
bool IsUpdate() const;
|
||||
|
||||
VirtualFile GetRomFS() const;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -63,7 +64,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
|
||||
if (Settings::values.dump_exefs) {
|
||||
LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
|
||||
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
|
||||
const auto dump_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
|
||||
VfsRawCopyD(exefs, exefs_dir);
|
||||
@@ -88,7 +90,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
}
|
||||
|
||||
// LayeredExeFS
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir != nullptr && load_dir->GetSize() > 0) {
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(
|
||||
@@ -174,7 +177,8 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||
if (Settings::values.dump_nso) {
|
||||
LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
|
||||
title_id);
|
||||
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
|
||||
const auto dump_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
|
||||
const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
|
||||
@@ -186,7 +190,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return nso;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
@@ -224,7 +234,13 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
@@ -232,9 +248,10 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
|
||||
const std::array<u8, 0x20>& build_id_,
|
||||
const VirtualDir& base_path, bool upper) {
|
||||
namespace {
|
||||
std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder(
|
||||
const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
|
||||
const VirtualDir& base_path, bool upper) {
|
||||
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
@@ -252,31 +269,39 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextCheatParser parser;
|
||||
return parser.Parse(system, data);
|
||||
Memory::TextCheatParser parser;
|
||||
return parser.Parse(
|
||||
system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size()));
|
||||
}
|
||||
|
||||
std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 32>& build_id_) const {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
} // Anonymous namespace
|
||||
|
||||
std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
|
||||
const Core::System& system, const std::array<u8, 32>& build_id_) const {
|
||||
const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<CheatList> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
std::vector<Memory::CheatEntry> out;
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto cheats_dir = subdir->GetSubdirectory("cheats");
|
||||
if (cheats_dir != nullptr) {
|
||||
auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
|
||||
if (res.has_value()) {
|
||||
out.push_back(std::move(*res));
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
continue;
|
||||
}
|
||||
|
||||
res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
|
||||
if (res.has_value())
|
||||
out.push_back(std::move(*res));
|
||||
if (res.has_value()) {
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +309,8 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
|
||||
load_dir == nullptr || load_dir->GetSize() <= 0) {
|
||||
return;
|
||||
@@ -393,6 +419,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
|
||||
VirtualFile update_raw) const {
|
||||
if (title_id == 0)
|
||||
return {};
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
@@ -423,7 +451,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto mod_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -51,8 +51,8 @@ public:
|
||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
std::vector<CheatList> CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 0x20>& build_id) const;
|
||||
std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
@@ -48,18 +49,21 @@ static bool FollowsTwoDigitDirFormat(std::string_view name) {
|
||||
static bool FollowsNcaIdFormat(std::string_view name) {
|
||||
static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
|
||||
std::regex_constants::icase);
|
||||
return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex);
|
||||
static const std::regex nca_id_cnmt_regex(
|
||||
"[0-9A-F]{32}\\.cnmt.nca", std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||
return (name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex)) ||
|
||||
(name.size() == 41 && std::regex_match(name.begin(), name.end(), nca_id_cnmt_regex));
|
||||
}
|
||||
|
||||
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
|
||||
bool within_two_digit) {
|
||||
if (!within_two_digit) {
|
||||
return fmt::format("/{}.nca", Common::HexToString(nca_id, second_hex_upper));
|
||||
}
|
||||
bool within_two_digit, bool cnmt_suffix) {
|
||||
if (!within_two_digit)
|
||||
return fmt::format(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca",
|
||||
Common::HexToString(nca_id, second_hex_upper));
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
|
||||
return fmt::format("/000000{:02X}/{}.nca", hash[0],
|
||||
return fmt::format(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca", hash[0],
|
||||
Common::HexToString(nca_id, second_hex_upper));
|
||||
}
|
||||
|
||||
@@ -127,6 +131,156 @@ std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
|
||||
return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
PlaceholderCache::PlaceholderCache(VirtualDir dir_) : dir(std::move(dir_)) {}
|
||||
|
||||
bool PlaceholderCache::Create(const NcaID& id, u64 size) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
if (dir->GetFileRelative(path) != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
|
||||
const auto dirname = fmt::format("000000{:02X}", hash[0]);
|
||||
|
||||
const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
|
||||
|
||||
if (dir2 == nullptr)
|
||||
return false;
|
||||
|
||||
const auto file = dir2->CreateFile(fmt::format("{}.nca", Common::HexToString(id, false)));
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
return file->Resize(size);
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Delete(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
if (dir->GetFileRelative(path) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
|
||||
const auto dirname = fmt::format("000000{:02X}", hash[0]);
|
||||
|
||||
const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
|
||||
|
||||
const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false)));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Exists(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
return dir->GetFileRelative(path) != nullptr;
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
return file->WriteBytes(data, offset) == data.size();
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Register(RegisteredCache* cache, const NcaID& placeholder,
|
||||
const NcaID& install) const {
|
||||
const auto path = GetRelativePathFromNcaID(placeholder, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
const auto res = cache->RawInstallNCA(NCA{file}, &VfsRawCopy, false, install);
|
||||
|
||||
if (res != InstallResult::Success)
|
||||
return false;
|
||||
|
||||
return Delete(placeholder);
|
||||
}
|
||||
|
||||
bool PlaceholderCache::CleanAll() const {
|
||||
return dir->GetParentDirectory()->CleanSubdirectoryRecursive(dir->GetName());
|
||||
}
|
||||
|
||||
std::optional<std::array<u8, 0x10>> PlaceholderCache::GetRightsID(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
NCA nca{file};
|
||||
|
||||
if (nca.GetStatus() != Loader::ResultStatus::Success &&
|
||||
nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto rights_id = nca.GetRightsId();
|
||||
if (rights_id == NcaID{})
|
||||
return std::nullopt;
|
||||
|
||||
return rights_id;
|
||||
}
|
||||
|
||||
u64 PlaceholderCache::Size(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return 0;
|
||||
|
||||
return file->GetSize();
|
||||
}
|
||||
|
||||
bool PlaceholderCache::SetSize(const NcaID& id, u64 new_size) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
return file->Resize(new_size);
|
||||
}
|
||||
|
||||
std::vector<NcaID> PlaceholderCache::List() const {
|
||||
std::vector<NcaID> out;
|
||||
for (const auto& sdir : dir->GetSubdirectories()) {
|
||||
for (const auto& file : sdir->GetFiles()) {
|
||||
const auto name = file->GetName();
|
||||
if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' &&
|
||||
name[35] == 'a') {
|
||||
out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
NcaID PlaceholderCache::Generate() {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
|
||||
|
||||
NcaID out{};
|
||||
|
||||
const auto v1 = distribution(gen);
|
||||
const auto v2 = distribution(gen);
|
||||
std::memcpy(out.data(), &v1, sizeof(u64));
|
||||
std::memcpy(out.data() + sizeof(u64), &v2, sizeof(u64));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
std::string_view path) const {
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
@@ -169,14 +323,18 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
|
||||
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
|
||||
VirtualFile file;
|
||||
// Try all four modes of file storage:
|
||||
// (bit 1 = uppercase/lower, bit 0 = within a two-digit dir)
|
||||
// 00: /000000**/{:032X}.nca
|
||||
// 01: /{:032X}.nca
|
||||
// 10: /000000**/{:032x}.nca
|
||||
// 11: /{:032x}.nca
|
||||
for (u8 i = 0; i < 4; ++i) {
|
||||
const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0);
|
||||
// Try all five relevant modes of file storage:
|
||||
// (bit 2 = uppercase/lower, bit 1 = within a two-digit dir, bit 0 = .cnmt suffix)
|
||||
// 000: /000000**/{:032X}.nca
|
||||
// 010: /{:032X}.nca
|
||||
// 100: /000000**/{:032x}.nca
|
||||
// 110: /{:032x}.nca
|
||||
// 111: /{:032x}.cnmt.nca
|
||||
for (u8 i = 0; i < 8; ++i) {
|
||||
if ((i % 2) == 1 && i != 7)
|
||||
continue;
|
||||
const auto path =
|
||||
GetRelativePathFromNcaID(id, (i & 0b100) == 0, (i & 0b010) == 0, (i & 0b001) == 0b001);
|
||||
file = OpenFileOrDirectoryConcat(dir, path);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
@@ -472,7 +630,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
|
||||
memcpy(id.data(), hash.data(), 16);
|
||||
}
|
||||
|
||||
std::string path = GetRelativePathFromNcaID(id, false, true);
|
||||
std::string path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
|
||||
LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
|
||||
|
||||
@@ -25,6 +25,8 @@ enum class NCAContentType : u8;
|
||||
enum class TitleType : u8;
|
||||
|
||||
struct ContentRecord;
|
||||
struct MetaRecord;
|
||||
class RegisteredCache;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
@@ -89,6 +91,27 @@ protected:
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
class PlaceholderCache {
|
||||
public:
|
||||
explicit PlaceholderCache(VirtualDir dir);
|
||||
|
||||
bool Create(const NcaID& id, u64 size) const;
|
||||
bool Delete(const NcaID& id) const;
|
||||
bool Exists(const NcaID& id) const;
|
||||
bool Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const;
|
||||
bool Register(RegisteredCache* cache, const NcaID& placeholder, const NcaID& install) const;
|
||||
bool CleanAll() const;
|
||||
std::optional<std::array<u8, 0x10>> GetRightsID(const NcaID& id) const;
|
||||
u64 Size(const NcaID& id) const;
|
||||
bool SetSize(const NcaID& id, u64 new_size) const;
|
||||
std::vector<NcaID> List() const;
|
||||
|
||||
static NcaID Generate();
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
};
|
||||
|
||||
/*
|
||||
* A class that catalogues NCAs in the registered directory structure.
|
||||
* Nintendo's registered format follows this structure:
|
||||
@@ -103,6 +126,8 @@ protected:
|
||||
* when 4GB splitting can be ignored.)
|
||||
*/
|
||||
class RegisteredCache : public ContentProvider {
|
||||
friend class PlaceholderCache;
|
||||
|
||||
public:
|
||||
// Parsing function defines the conversion from raw file to NCA. If there are other steps
|
||||
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -34,7 +35,7 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
|
||||
this->update_raw = std::move(update_raw);
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const {
|
||||
if (!updatable)
|
||||
return MakeResult<VirtualFile>(file);
|
||||
|
||||
@@ -43,7 +44,8 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
|
||||
ContentRecordType type) const {
|
||||
std::shared_ptr<NCA> res;
|
||||
|
||||
switch (storage) {
|
||||
@@ -51,13 +53,17 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
|
||||
res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandSystem:
|
||||
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
res =
|
||||
Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry(
|
||||
title_id, type);
|
||||
break;
|
||||
case StorageId::NandUser:
|
||||
res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
|
||||
res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry(
|
||||
title_id, type);
|
||||
break;
|
||||
case StorageId::SdCard:
|
||||
res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type);
|
||||
res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry(
|
||||
title_id, type);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
|
||||
|
||||
@@ -33,8 +33,8 @@ public:
|
||||
~RomFSFactory();
|
||||
|
||||
void SetPackedUpdate(VirtualFile update_raw);
|
||||
ResultVal<VirtualFile> OpenCurrentProcess();
|
||||
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
|
||||
ResultVal<VirtualFile> OpenCurrentProcess() const;
|
||||
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
|
||||
@@ -15,22 +15,8 @@ namespace FileSys {
|
||||
|
||||
constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
|
||||
|
||||
std::string SaveDataDescriptor::DebugInfo() const {
|
||||
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, "
|
||||
"rank={}, index={}]",
|
||||
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
|
||||
static_cast<u8>(rank), index);
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) {
|
||||
namespace {
|
||||
void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) {
|
||||
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
|
||||
if (meta.zero_1 != 0) {
|
||||
LOG_WARNING(Service_FS,
|
||||
@@ -65,23 +51,51 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDat
|
||||
"non-zero ({:016X}{:016X})",
|
||||
meta.user_id[1], meta.user_id[0]);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::string save_directory =
|
||||
std::string SaveDataDescriptor::DebugInfo() const {
|
||||
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, "
|
||||
"save_id={:016X}, "
|
||||
"rank={}, index={}]",
|
||||
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
|
||||
static_cast<u8>(rank), index);
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
|
||||
const SaveDataDescriptor& meta) const {
|
||||
PrintSaveDataDescriptorWarnings(meta);
|
||||
|
||||
const auto save_directory =
|
||||
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
|
||||
|
||||
// TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods.
|
||||
// But, user_ids don't match so this works for now.
|
||||
auto out = dir->CreateDirectoryRelative(save_directory);
|
||||
|
||||
// Return an error if the save data doesn't actually exist.
|
||||
if (out == nullptr) {
|
||||
// TODO(DarkLordZach): Find out correct error code.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return MakeResult<VirtualDir>(std::move(out));
|
||||
}
|
||||
|
||||
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
|
||||
const SaveDataDescriptor& meta) const {
|
||||
|
||||
const auto save_directory =
|
||||
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
|
||||
|
||||
auto out = dir->GetDirectoryRelative(save_directory);
|
||||
|
||||
if (out == nullptr) {
|
||||
// TODO(bunnei): This is a work-around to always create a save data directory if it does not
|
||||
// already exist. This is a hack, as we do not understand yet how this works on hardware.
|
||||
// Without a save data directory, many games will assert on boot. This should not have any
|
||||
// bad side-effects.
|
||||
out = dir->CreateDirectoryRelative(save_directory);
|
||||
}
|
||||
|
||||
// Return an error if the save data doesn't actually exist.
|
||||
if (out == nullptr) {
|
||||
// TODO(Subv): Find out correct error code.
|
||||
@@ -152,7 +166,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
|
||||
}
|
||||
|
||||
void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
|
||||
SaveDataSize new_value) {
|
||||
SaveDataSize new_value) const {
|
||||
const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
|
||||
const auto dir = GetOrCreateDirectoryRelative(this->dir, path);
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ public:
|
||||
explicit SaveDataFactory(VirtualDir dir);
|
||||
~SaveDataFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta);
|
||||
ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
|
||||
ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
|
||||
|
||||
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
|
||||
|
||||
@@ -73,7 +74,8 @@ public:
|
||||
u128 user_id, u64 save_id);
|
||||
|
||||
SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const;
|
||||
void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value);
|
||||
void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
|
||||
SaveDataSize new_value) const;
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -14,16 +15,38 @@ SDMCFactory::SDMCFactory(VirtualDir dir_)
|
||||
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
|
||||
[](const VirtualFile& file, const NcaID& id) {
|
||||
return NAX{file, id}.GetDecrypted();
|
||||
})) {}
|
||||
})),
|
||||
placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {}
|
||||
|
||||
SDMCFactory::~SDMCFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SDMCFactory::Open() {
|
||||
ResultVal<VirtualDir> SDMCFactory::Open() const {
|
||||
return MakeResult<VirtualDir>(dir);
|
||||
}
|
||||
|
||||
VirtualDir SDMCFactory::GetSDMCContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents");
|
||||
}
|
||||
|
||||
RegisteredCache* SDMCFactory::GetSDMCContents() const {
|
||||
return contents.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {
|
||||
return placeholder.get();
|
||||
}
|
||||
|
||||
VirtualDir SDMCFactory::GetImageDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album");
|
||||
}
|
||||
|
||||
u64 SDMCFactory::GetSDMCFreeSpace() const {
|
||||
return GetSDMCTotalSpace() - dir->GetSize();
|
||||
}
|
||||
|
||||
u64 SDMCFactory::GetSDMCTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.sdmc_size);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
namespace FileSys {
|
||||
|
||||
class RegisteredCache;
|
||||
class PlaceholderCache;
|
||||
|
||||
/// File system interface to the SDCard archive
|
||||
class SDMCFactory {
|
||||
@@ -18,13 +19,23 @@ public:
|
||||
explicit SDMCFactory(VirtualDir dir);
|
||||
~SDMCFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open();
|
||||
ResultVal<VirtualDir> Open() const;
|
||||
|
||||
VirtualDir GetSDMCContentDirectory() const;
|
||||
|
||||
RegisteredCache* GetSDMCContents() const;
|
||||
PlaceholderCache* GetSDMCPlaceholder() const;
|
||||
|
||||
VirtualDir GetImageDirectory() const;
|
||||
|
||||
u64 GetSDMCFreeSpace() const;
|
||||
u64 GetSDMCTotalSpace() const;
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
std::unique_ptr<RegisteredCache> contents;
|
||||
std::unique_ptr<PlaceholderCache> placeholder;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
@@ -78,6 +79,10 @@ Loader::ResultStatus NSP::GetStatus() const {
|
||||
}
|
||||
|
||||
Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
|
||||
if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) {
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
const auto iter = program_status.find(title_id);
|
||||
if (iter == program_status.end())
|
||||
return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
|
||||
@@ -85,12 +90,29 @@ Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
|
||||
}
|
||||
|
||||
u64 NSP::GetFirstTitleID() const {
|
||||
if (IsExtractedType()) {
|
||||
return GetProgramTitleID();
|
||||
}
|
||||
|
||||
if (program_status.empty())
|
||||
return 0;
|
||||
return program_status.begin()->first;
|
||||
}
|
||||
|
||||
u64 NSP::GetProgramTitleID() const {
|
||||
if (IsExtractedType()) {
|
||||
if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ProgramMetadata meta;
|
||||
if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) {
|
||||
return meta.GetTitleID();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const auto out = GetFirstTitleID();
|
||||
if ((out & 0x800) == 0)
|
||||
return out;
|
||||
@@ -102,6 +124,10 @@ u64 NSP::GetProgramTitleID() const {
|
||||
}
|
||||
|
||||
std::vector<u64> NSP::GetTitleIDs() const {
|
||||
if (IsExtractedType()) {
|
||||
return {GetProgramTitleID()};
|
||||
}
|
||||
|
||||
std::vector<u64> out;
|
||||
out.reserve(ncas.size());
|
||||
for (const auto& kv : ncas)
|
||||
@@ -222,7 +248,8 @@ void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
|
||||
|
||||
void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
|
||||
if (outer_file->GetName().size() < 9 ||
|
||||
outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
46
src/core/file_sys/system_archive/mii_model.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/file_sys/system_archive/mii_model.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
namespace MiiModelData {
|
||||
|
||||
constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD;
|
||||
constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD;
|
||||
constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD;
|
||||
constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD;
|
||||
constexpr auto SHAPE_HIGH = NFSR_STANDARD;
|
||||
constexpr auto SHAPE_MID = NFSR_STANDARD;
|
||||
|
||||
} // namespace MiiModelData
|
||||
|
||||
VirtualDir MiiModel() {
|
||||
auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{},
|
||||
std::vector<VirtualDir>{}, "data");
|
||||
|
||||
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>(
|
||||
MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
|
||||
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>(
|
||||
MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
|
||||
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>(
|
||||
MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
|
||||
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>(
|
||||
MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
|
||||
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>(
|
||||
MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
|
||||
out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>(
|
||||
MiiModelData::SHAPE_MID, "ShapeMid.dat"));
|
||||
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
} // namespace FileSys::SystemArchive
|
||||
13
src/core/file_sys/system_archive/mii_model.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
VirtualDir MiiModel();
|
||||
|
||||
} // namespace FileSys::SystemArchive
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/system_archive/mii_model.h"
|
||||
#include "core/file_sys/system_archive/ng_word.h"
|
||||
#include "core/file_sys/system_archive/system_archive.h"
|
||||
#include "core/file_sys/system_archive/system_version.h"
|
||||
@@ -24,7 +25,7 @@ struct SystemArchiveDescriptor {
|
||||
constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
|
||||
{0x0100000000000800, "CertStore", nullptr},
|
||||
{0x0100000000000801, "ErrorMessage", nullptr},
|
||||
{0x0100000000000802, "MiiModel", nullptr},
|
||||
{0x0100000000000802, "MiiModel", &MiiModel},
|
||||
{0x0100000000000803, "BrowserDll", nullptr},
|
||||
{0x0100000000000804, "Help", nullptr},
|
||||
{0x0100000000000805, "SharedFont", nullptr},
|
||||
|
||||