mirror of
https://github.com/flyinghead/flycast.git
synced 2026-11-07 17:11:09 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a14c02e2bd | ||
|
|
802874e79d | ||
|
|
1fac3d3fef | ||
|
|
800d0ec2d8 | ||
|
|
6a6038f910 | ||
|
|
f86e4c6ea4 | ||
|
|
7b1df94c8e | ||
|
|
973e569a7d | ||
|
|
13cfe43858 | ||
|
|
e778e529be | ||
|
|
2c83abcce4 | ||
|
|
07535d22e8 | ||
|
|
98ceceed1e | ||
|
|
23d212f4f0 | ||
|
|
917a7552f9 | ||
|
|
697d15a133 | ||
|
|
34e1211871 | ||
|
|
cd89ae96ab | ||
|
|
01db8a203e | ||
|
|
6d49796a15 | ||
|
|
907d15155e | ||
|
|
e2f1484571 | ||
|
|
fe58ee8c20 | ||
|
|
7c22066aa3 | ||
|
|
3a498b1cb5 | ||
|
|
c52e6ccf56 | ||
|
|
de7597f0bc | ||
|
|
5f35ec7d72 | ||
|
|
fa9d39a355 | ||
|
|
db065c5700 | ||
|
|
620699865b | ||
|
|
a9bd1c74fd | ||
|
|
5543a95c4f | ||
|
|
78d5d09c1e | ||
|
|
31697241b7 | ||
|
|
e9f4020c56 | ||
|
|
5eb52090ad | ||
|
|
128e6ce29c | ||
|
|
38a3d83c77 | ||
|
|
19ed307f74 | ||
|
|
d207cd9198 | ||
|
|
1d356d5e55 | ||
|
|
32c2304459 | ||
|
|
34ad8c8262 | ||
|
|
abfc411392 | ||
|
|
9981e84912 | ||
|
|
b5acd1b533 | ||
|
|
6bb0fc3e18 | ||
|
|
08afcbe6b9 | ||
|
|
e9dd6187cb | ||
|
|
a5dc811814 | ||
|
|
5c86428c04 | ||
|
|
258f5c11d4 | ||
|
|
91c3084d52 | ||
|
|
506b4efd04 | ||
|
|
80471d57b0 | ||
|
|
ce8689101f | ||
|
|
518c496251 | ||
|
|
804edb00d7 | ||
|
|
f03faffd2f | ||
|
|
fa23eef593 | ||
|
|
658abb386a | ||
|
|
fa3c526e4f | ||
|
|
e6d3365c0b | ||
|
|
7422d1b831 | ||
|
|
6dafd41eaa | ||
|
|
ed953fcbfd | ||
|
|
ec56b5f260 | ||
|
|
bc3b8c71da | ||
|
|
0d9853df9a | ||
|
|
df7aedf506 | ||
|
|
df5feec55c | ||
|
|
bbdef06a09 | ||
|
|
b4c38d652e | ||
|
|
4dca1988da | ||
|
|
552a86086b | ||
|
|
9ec8b7e658 | ||
|
|
7d69c9cfd4 | ||
|
|
5ff3f974d9 | ||
|
|
f83fda7069 | ||
|
|
59ed35a7ea | ||
|
|
04669ebe41 | ||
|
|
98ce9fb5d7 | ||
|
|
2a57c9ffd9 | ||
|
|
2ea7920fdc | ||
|
|
9d4c337349 | ||
|
|
89b72a8e3e | ||
|
|
fe04dd6503 | ||
|
|
b1b80d8849 | ||
|
|
e36e9df2dc | ||
|
|
ea087b9140 | ||
|
|
869038f40a | ||
|
|
0a304f1396 | ||
|
|
628bd3dbb1 | ||
|
|
2ac5c772f0 | ||
|
|
fff68b3f57 | ||
|
|
e6707469d2 | ||
|
|
a99c8667b4 | ||
|
|
49ad26a7e5 | ||
|
|
da5be1a0a7 | ||
|
|
dd7a5f0620 | ||
|
|
6d73f7191c | ||
|
|
215a5af165 | ||
|
|
c0fb036d60 | ||
|
|
8933f9fe0c | ||
|
|
4509ee903c | ||
|
|
04b74c3a11 | ||
|
|
36c975e391 | ||
|
|
bcad2d76d0 | ||
|
|
9869ea8852 | ||
|
|
ea22ee8723 | ||
|
|
b777c15fdc | ||
|
|
93379c489f | ||
|
|
b03335bca9 | ||
|
|
604ebd58fe | ||
|
|
eddf263586 | ||
|
|
3f70e47adb | ||
|
|
0abac3465d | ||
|
|
a9b451c6f2 | ||
|
|
6838fbd642 | ||
|
|
34cd222e12 | ||
|
|
fb286f777c | ||
|
|
0ff538115f | ||
|
|
5aca173e80 | ||
|
|
2f0917bb7b | ||
|
|
f2013c652a | ||
|
|
230d83654f | ||
|
|
fa04b20837 | ||
|
|
52c8ecef1e | ||
|
|
d3c90b0f84 | ||
|
|
fe4cf1d586 | ||
|
|
08ffbe6cf1 | ||
|
|
b35b829a12 | ||
|
|
a75d1cff53 | ||
|
|
62d7a0432a | ||
|
|
066c82ddfd | ||
|
|
1145248aa7 | ||
|
|
daa46ec63e | ||
|
|
9814e2fe40 | ||
|
|
d8a76809e5 | ||
|
|
fd9d39b127 | ||
|
|
cc4a59b1f2 | ||
|
|
8f511f8960 | ||
|
|
9bdb0ad323 | ||
|
|
5c96b69ff0 | ||
|
|
7db29cc2fd | ||
|
|
162bc02007 | ||
|
|
12bb43652f | ||
|
|
71b739faee | ||
|
|
57e33d323a | ||
|
|
b4d7e75977 | ||
|
|
58b283fc02 | ||
|
|
149e651d24 | ||
|
|
c37be698db | ||
|
|
453dcefc12 | ||
|
|
ce3abe5a0b | ||
|
|
e2722869ff | ||
|
|
bbe606c8f7 | ||
|
|
5803d8aa14 | ||
|
|
180e670605 | ||
|
|
f831592f64 | ||
|
|
ba04273a25 | ||
|
|
804fcb4a9e | ||
|
|
c3763d8fc4 | ||
|
|
9473c0377a | ||
|
|
9c6c8e59d6 | ||
|
|
d367284ddf | ||
|
|
105f760613 | ||
|
|
8c5446706e | ||
|
|
7fcd6976fa | ||
|
|
982f26a0da | ||
|
|
d5e74ad756 | ||
|
|
9dbca96328 | ||
|
|
44e4c7b50d | ||
|
|
643374c91e | ||
|
|
33d5b38163 | ||
|
|
caa55c0297 | ||
|
|
a6242fd653 | ||
|
|
02c779ddc1 | ||
|
|
b0284ba9ba | ||
|
|
29a54e36ce | ||
|
|
7cc58de0c3 | ||
|
|
2550d10902 | ||
|
|
565f5174f7 | ||
|
|
5aa091fde6 | ||
|
|
a172e00013 | ||
|
|
f58719d9b7 | ||
|
|
8041f02984 | ||
|
|
d4fc077410 | ||
|
|
41406fb31a | ||
|
|
e293832bef | ||
|
|
253970d8e5 | ||
|
|
63aecfd491 | ||
|
|
4126f1464f | ||
|
|
670b5001d4 | ||
|
|
2805df5484 | ||
|
|
0cd66cea5c | ||
|
|
d93612734b | ||
|
|
95e28f412f | ||
|
|
a749e51559 | ||
|
|
e82f38a8cc | ||
|
|
f09d1f22ef | ||
|
|
89bf160a9d | ||
|
|
e9b7fb6510 | ||
|
|
602da7d447 | ||
|
|
8f877e69a2 | ||
|
|
c8ce4ab57f | ||
|
|
1607eb5715 | ||
|
|
fdbfb0037c | ||
|
|
4bc3fde130 | ||
|
|
620ccc4f2a | ||
|
|
3c99cb3190 | ||
|
|
d84c41b349 | ||
|
|
347681c5e2 | ||
|
|
3bc049ed42 | ||
|
|
a5aabdef69 | ||
|
|
2802e80ab5 | ||
|
|
8a78914ba0 | ||
|
|
7e7740be64 | ||
|
|
48703f6472 | ||
|
|
9df8866840 | ||
|
|
22c8c46dbd | ||
|
|
0137a91eab | ||
|
|
99617be3d3 | ||
|
|
84b57ff6ce | ||
|
|
b34c4cde8d | ||
|
|
08c63bd39b | ||
|
|
02a335e2dd | ||
|
|
0e65e6bf5b | ||
|
|
a63c892410 | ||
|
|
624bee9316 | ||
|
|
65cdc8f090 | ||
|
|
c941cf094b | ||
|
|
3a10f245f8 | ||
|
|
5d41d96e39 | ||
|
|
e698b8a2cb | ||
|
|
2f7f2fd1cf | ||
|
|
ebfce147ba | ||
|
|
8412d3d8a3 | ||
|
|
8369fe164d | ||
|
|
f26a82d262 | ||
|
|
4b22470a9a | ||
|
|
d34015b4a4 | ||
|
|
d79ce95475 | ||
|
|
5400069d3c | ||
|
|
8b7dad34ee | ||
|
|
5201780ec2 | ||
|
|
fedf54fbb7 | ||
|
|
f6f5927e1f | ||
|
|
2f84da4966 | ||
|
|
baaad2e8b9 | ||
|
|
f0faed63cb | ||
|
|
938ecb04d2 | ||
|
|
4ac80670f7 | ||
|
|
e975526b22 | ||
|
|
d2fd5cdfab | ||
|
|
c286463d6b | ||
|
|
3b809c8762 | ||
|
|
3aa2c67782 | ||
|
|
c7ef5c6a0a | ||
|
|
e2b45eb551 | ||
|
|
eb5f74258f | ||
|
|
5b8a0ae038 | ||
|
|
12d9caff93 | ||
|
|
10057deebf | ||
|
|
324d9c10bb | ||
|
|
e64b28a759 | ||
|
|
675ce1b63b | ||
|
|
63d19693bc | ||
|
|
8b5cb7513b | ||
|
|
940f5bb911 | ||
|
|
7ec978e852 | ||
|
|
d70a265804 | ||
|
|
1251a53aed | ||
|
|
fc7b8c247d | ||
|
|
59ab660649 | ||
|
|
526b52b141 | ||
|
|
c6fe11017c | ||
|
|
bcbc43fad6 | ||
|
|
d7c59e9ab5 | ||
|
|
14487d96ec | ||
|
|
f52c275c90 | ||
|
|
a27a81ba18 | ||
|
|
d1d7373d43 | ||
|
|
80cc71952e | ||
|
|
8c155b02c8 | ||
|
|
5ff2bc0c42 | ||
|
|
e435d72aa8 | ||
|
|
c0f2cf4319 | ||
|
|
f68e60fe92 | ||
|
|
1e60ef78c7 | ||
|
|
a9c5ef8b41 | ||
|
|
d234bcf7d9 | ||
|
|
751295151e | ||
|
|
619082fe46 | ||
|
|
7fc81607e3 | ||
|
|
1e68c83356 | ||
|
|
436e125d59 | ||
|
|
e4c96293e4 | ||
|
|
3ca5099fb0 | ||
|
|
444635372e | ||
|
|
abf284fc2d | ||
|
|
b4f89c9926 | ||
|
|
b043043568 | ||
|
|
8542ff2dc4 | ||
|
|
34f2a70a6b | ||
|
|
3d85218f0a | ||
|
|
2c7c687240 | ||
|
|
f6dead8c39 | ||
|
|
6de588bf57 | ||
|
|
1536d588f6 | ||
|
|
d3c64bf1f5 | ||
|
|
2f10d39910 | ||
|
|
7e917bc53a | ||
|
|
4a9ca07838 | ||
|
|
3e81e56499 | ||
|
|
39c991671f | ||
|
|
10dee60cda | ||
|
|
6925586fa2 | ||
|
|
d447d3d2db | ||
|
|
55a8d6094b | ||
|
|
8492a4e5e2 | ||
|
|
1370b74fb9 | ||
|
|
46deb1dd7a | ||
|
|
7baf5c0a5b | ||
|
|
ce19baebc5 | ||
|
|
00892506d7 | ||
|
|
34bb486e54 | ||
|
|
1583b75dd7 | ||
|
|
569dc1cd1a | ||
|
|
8c0da03a15 | ||
|
|
3153ac762e | ||
|
|
b49dd9d184 | ||
|
|
4941fdf934 | ||
|
|
81d4756fe9 | ||
|
|
f35f0d51ac | ||
|
|
db8147e91f | ||
|
|
91d1430b42 | ||
|
|
cc1f7ca1c1 | ||
|
|
bb304918f8 | ||
|
|
f3047b2ef1 | ||
|
|
3e60beb69e | ||
|
|
9310bf4799 | ||
|
|
d39b89bfb3 | ||
|
|
ce5f9d32db | ||
|
|
cf144bcfa7 | ||
|
|
22d029ba1e | ||
|
|
c00a62c909 | ||
|
|
8acadcb66c | ||
|
|
4be8a48466 | ||
|
|
07e435c55f | ||
|
|
ead8ab2223 | ||
|
|
be1b7f2fce | ||
|
|
4f356e846a | ||
|
|
85d6a0125c | ||
|
|
1b6f880ccf | ||
|
|
744e9e4aa5 | ||
|
|
5993a20644 | ||
|
|
15ecfcc7e8 | ||
|
|
958517a621 | ||
|
|
acb4966f54 | ||
|
|
25b2602751 | ||
|
|
31d5ddb920 | ||
|
|
dfbbf8913c | ||
|
|
4505fc5787 | ||
|
|
e279abd0b5 | ||
|
|
ae5416a5cb | ||
|
|
aef3d2d337 | ||
|
|
9bc041873a | ||
|
|
ccd7525c0b | ||
|
|
ce2f566ef3 | ||
|
|
a3373af43d | ||
|
|
cc9f764a64 | ||
|
|
677dc930f8 | ||
|
|
99da46b4cc | ||
|
|
8c773f5ab0 | ||
|
|
00ec2b15a3 | ||
|
|
321bdaf08b | ||
|
|
aef9266d92 | ||
|
|
16345dfdd0 | ||
|
|
ed41f982c1 | ||
|
|
b95e7fc4cc | ||
|
|
d16548fc35 | ||
|
|
ad855bd812 | ||
|
|
3395780fd2 | ||
|
|
602191ebe7 | ||
|
|
ad03e10c16 | ||
|
|
f58c86f030 | ||
|
|
8f2351b1b7 | ||
|
|
4dbe02895e | ||
|
|
4b4d0b6db2 | ||
|
|
3d81741df6 | ||
|
|
2814b11bc3 | ||
|
|
a5f4ec6310 | ||
|
|
bd0bf5e3ba | ||
|
|
99ab85ffe7 | ||
|
|
69d9fa5332 | ||
|
|
476ed40c08 | ||
|
|
c3c710ec07 | ||
|
|
bba324169a | ||
|
|
cb364c9ed0 | ||
|
|
01dc4f6ec9 | ||
|
|
3e96eec972 | ||
|
|
c7780ec324 | ||
|
|
fae9ed63b5 | ||
|
|
0bf7570db2 | ||
|
|
625fce5d52 | ||
|
|
f5cb17bf82 | ||
|
|
31a69ac5b8 | ||
|
|
1f4a99ae7f | ||
|
|
993250403d | ||
|
|
c1e0d07e23 | ||
|
|
b73190bdb5 | ||
|
|
8c13a7d832 | ||
|
|
0cf01f83a6 | ||
|
|
6a0c4772e8 | ||
|
|
13a2639f8d | ||
|
|
3f6928595a | ||
|
|
c43109deeb | ||
|
|
42555bcf4b | ||
|
|
80683f9670 | ||
|
|
875156510b | ||
|
|
f5da7ffb8d | ||
|
|
63c2be61a4 | ||
|
|
9954019b13 | ||
|
|
97a0396af8 | ||
|
|
f301ad1f4a | ||
|
|
6d2befc804 | ||
|
|
7f26348ed8 | ||
|
|
ecdf05fef8 | ||
|
|
45c31c149d | ||
|
|
45e9b720d1 | ||
|
|
05b270f05c | ||
|
|
1120aa7fa1 | ||
|
|
8d58f6a8db | ||
|
|
e31b0cfd79 | ||
|
|
9feee44de7 | ||
|
|
fb69bd8fbc | ||
|
|
4ef64d1881 | ||
|
|
b71fb72f0a | ||
|
|
c887068253 | ||
|
|
23073131ad | ||
|
|
273fc8fbdd | ||
|
|
ade2d1dece | ||
|
|
48f9db752e | ||
|
|
adabc694e7 | ||
|
|
94e9b349f9 | ||
|
|
3eef0196cf | ||
|
|
55d310be71 | ||
|
|
3339416089 | ||
|
|
2c48c0188a | ||
|
|
d72856c1cc | ||
|
|
ba5b3c71ec | ||
|
|
75feafbbf0 | ||
|
|
5b08957655 | ||
|
|
788cbfb64b | ||
|
|
30b42a8a74 | ||
|
|
a6dc61f064 | ||
|
|
cd6002595e | ||
|
|
45ebe22d40 | ||
|
|
805d014b5d | ||
|
|
c577e29f43 | ||
|
|
02ed5fa340 | ||
|
|
b31f92f8ea | ||
|
|
2a6e109b1d | ||
|
|
821f049a79 | ||
|
|
fb43c91ab9 | ||
|
|
91ea2c8af1 | ||
|
|
d75b0b1810 | ||
|
|
da15c3d4a5 | ||
|
|
27b30acae4 | ||
|
|
cc3ff811e8 | ||
|
|
21eb24f86d | ||
|
|
976d3bed28 | ||
|
|
22e28c0a7c | ||
|
|
9504ab85be | ||
|
|
4254c7edf1 | ||
|
|
a30c27b471 | ||
|
|
f142e34973 | ||
|
|
d0d3661350 | ||
|
|
ea9464f7c3 | ||
|
|
c4d757635b | ||
|
|
a429fba21d | ||
|
|
945376a3a6 | ||
|
|
f86e195017 | ||
|
|
75aefbb7b8 | ||
|
|
4fc65efbf0 | ||
|
|
fa3c8f2117 | ||
|
|
d8ba8e409a | ||
|
|
fcdcc0d37a | ||
|
|
8df7e3be41 | ||
|
|
90301d5c31 | ||
|
|
6a071668be | ||
|
|
94bf208b5b | ||
|
|
7775b3d0d0 | ||
|
|
5fa1886fdf | ||
|
|
fce8b4d6f9 | ||
|
|
b7bf027645 | ||
|
|
b7dba4b7a4 | ||
|
|
39bdd878ff | ||
|
|
16cfce7aed | ||
|
|
e0ad9f6125 | ||
|
|
991d90d089 | ||
|
|
d676dae7b8 | ||
|
|
f64341d2b7 | ||
|
|
f3ea74fa28 | ||
|
|
d07c09ea16 | ||
|
|
1aba1b467e | ||
|
|
5be5eeb905 | ||
|
|
2f842238a5 | ||
|
|
3de74672ce | ||
|
|
b76b1c7275 | ||
|
|
392a429e8b | ||
|
|
0491f2e6a5 | ||
|
|
33d9a58cf9 | ||
|
|
401332d520 | ||
|
|
8511d1ad3c | ||
|
|
7ce50e5229 | ||
|
|
99fc194dd0 | ||
|
|
d794b53512 | ||
|
|
bd8f7c8a75 | ||
|
|
594d82b223 | ||
|
|
fd72eebbe3 | ||
|
|
fea8699884 | ||
|
|
d00b5d4c17 | ||
|
|
74cbee908f | ||
|
|
1652143f5f | ||
|
|
4886dcb1c2 | ||
|
|
75bca2a166 | ||
|
|
13df86c520 | ||
|
|
16fc172ffc | ||
|
|
dfb2ae8987 | ||
|
|
4529a31372 | ||
|
|
5a29ac489f | ||
|
|
c4c3c2ae93 | ||
|
|
8a228e4e16 | ||
|
|
0a3c68f7cc | ||
|
|
4905dc21c8 | ||
|
|
98dfb4c32a | ||
|
|
df2bdfc9bf | ||
|
|
e86c1c992f | ||
|
|
5b6cc33a75 | ||
|
|
e37518524f | ||
|
|
994f72f954 | ||
|
|
5978b3a058 | ||
|
|
bb12d5c72d | ||
|
|
b127c96cf3 | ||
|
|
0d3beb1c6d | ||
|
|
9bd3179745 | ||
|
|
5833fb3a47 | ||
|
|
49ec396e24 | ||
|
|
5607b8eebd | ||
|
|
35888406d3 | ||
|
|
6b3bbd6f85 | ||
|
|
ef689fd35d | ||
|
|
6a78f27bef | ||
|
|
97de155fd8 | ||
|
|
f183f30262 | ||
|
|
065c91f9ad | ||
|
|
f65907a256 | ||
|
|
161d057d4c | ||
|
|
a7ee2e9a66 | ||
|
|
e08ec86962 | ||
|
|
7653b1c811 | ||
|
|
1475812a94 | ||
|
|
c8a724ad63 | ||
|
|
2cfce747c5 | ||
|
|
37a9077304 | ||
|
|
f8bdf6c35e | ||
|
|
f4c2d001bd | ||
|
|
f3ddec5f1e | ||
|
|
13c7ddc6c9 | ||
|
|
c24d7fb0fe | ||
|
|
3ebc79a5ce | ||
|
|
27a913fc02 | ||
|
|
97442c0411 | ||
|
|
5b12f9ca68 | ||
|
|
6a298289a9 | ||
|
|
59ef8579d2 | ||
|
|
6989a0aa2c | ||
|
|
bd24812d0c | ||
|
|
15889616dd | ||
|
|
e60efed8b5 | ||
|
|
11fcadf37e | ||
|
|
88b8a0b51e | ||
|
|
8e9a0f9c1f | ||
|
|
dd50fcf81c | ||
|
|
7b8656b944 | ||
|
|
00ae6c42a2 | ||
|
|
8c4c463c38 | ||
|
|
14f759eeb7 | ||
|
|
329b6a1e34 | ||
|
|
9c28800384 | ||
|
|
602982fb56 | ||
|
|
cb830e0bd3 | ||
|
|
ec3d85a96c | ||
|
|
d3bc72b0d0 | ||
|
|
4170e783b9 | ||
|
|
083ccb984a | ||
|
|
0220207545 | ||
|
|
aee4cd34fc | ||
|
|
a99cce27bb | ||
|
|
b2941a8593 | ||
|
|
3ad072f9aa | ||
|
|
14374084d5 | ||
|
|
5c7a241897 | ||
|
|
7fcc5fa2f7 | ||
|
|
cf2fe027f7 | ||
|
|
b13685f0bb | ||
|
|
cf7b6bdcb0 | ||
|
|
230d971d4d | ||
|
|
894c61ed64 | ||
|
|
82cadfde22 | ||
|
|
1586cbaea9 | ||
|
|
2113e4e249 | ||
|
|
5a150f4121 | ||
|
|
f178e5d76e | ||
|
|
9222fa8442 | ||
|
|
77359cefb2 | ||
|
|
a6dc38a35b | ||
|
|
eab952fcbe | ||
|
|
1666eb0875 | ||
|
|
5bce763f66 | ||
|
|
0f4fca415b | ||
|
|
f2fe918379 | ||
|
|
a9be4d111c | ||
|
|
ce07a7fbf5 | ||
|
|
e44c03fa0e | ||
|
|
1dac3695fd | ||
|
|
997f6179dd | ||
|
|
bed524f0d0 | ||
|
|
410b37b041 | ||
|
|
ead4b51d6f | ||
|
|
043bb5ee99 | ||
|
|
4747e61908 | ||
|
|
548cf6fe2e | ||
|
|
339d288dfb | ||
|
|
8f7e4bf2d3 | ||
|
|
922ca3e54b | ||
|
|
4b09de2fc1 | ||
|
|
98db7a6cd5 | ||
|
|
3405b5b2a6 | ||
|
|
4320685ff8 | ||
|
|
cd3fe7393c | ||
|
|
2da0d392fd | ||
|
|
8d1bf3382c | ||
|
|
b1489d665e | ||
|
|
fc56bd283e | ||
|
|
807e4a20ed | ||
|
|
943311752e | ||
|
|
e7ad12b031 | ||
|
|
b2bc6251c7 | ||
|
|
6f38dc3210 | ||
|
|
4bb32466bd | ||
|
|
e26d356265 | ||
|
|
265c5c6c3d | ||
|
|
2b1eb55daa | ||
|
|
bd136b1856 | ||
|
|
bd8adf1da1 | ||
|
|
11625176c2 | ||
|
|
c64441a02f | ||
|
|
8e9eee246d | ||
|
|
d0ec876936 | ||
|
|
b19cbcbf29 | ||
|
|
bf2bd7efed | ||
|
|
d15d5be44a | ||
|
|
70c8d1d22b | ||
|
|
3917c0181b | ||
|
|
6f58d204b6 | ||
|
|
d450c84a14 | ||
|
|
3b7036d14a | ||
|
|
5d628f8167 | ||
|
|
d1ed878446 | ||
|
|
28ea3dc69c | ||
|
|
031a645ffc | ||
|
|
0a526d7c94 | ||
|
|
3d594054eb | ||
|
|
3b651463dc | ||
|
|
4e6b5b270b | ||
|
|
fd8f2e24b8 | ||
|
|
82b4c55f09 | ||
|
|
8176646c5a | ||
|
|
3f0dd626c1 | ||
|
|
8edd8fc447 | ||
|
|
0c7dca9cb7 | ||
|
|
086e8bc20c | ||
|
|
11880bca30 | ||
|
|
37187467e7 | ||
|
|
7b0c106fc2 | ||
|
|
fd654614ae | ||
|
|
4ee24ca612 | ||
|
|
4b3704953f | ||
|
|
6ea9a34e8a | ||
|
|
c018d46d00 | ||
|
|
400da2bdfe | ||
|
|
3b5536dd43 | ||
|
|
34e2f426ac | ||
|
|
a246964a99 | ||
|
|
b4b4b3ab1e | ||
|
|
af5f67c15d | ||
|
|
909ea496ac | ||
|
|
3347dc1a46 | ||
|
|
dd5e148e2b | ||
|
|
1f818ca750 | ||
|
|
ab6b95ff4d | ||
|
|
760c62a3da | ||
|
|
b2d8ad633d | ||
|
|
f9b47b6f91 | ||
|
|
372ef91211 | ||
|
|
3375f6c476 | ||
|
|
c5396a041e | ||
|
|
851dd22664 | ||
|
|
ac32a80352 | ||
|
|
eff11d6a07 | ||
|
|
b6c135b0c6 | ||
|
|
0fe7073496 | ||
|
|
d5c5839a92 | ||
|
|
7ac45a7e46 | ||
|
|
0243f81c26 | ||
|
|
2dfa647445 | ||
|
|
9c5408a6d3 | ||
|
|
d3dcb13ef9 | ||
|
|
2cb2ac23e0 | ||
|
|
e38b0ac01f | ||
|
|
09b27ddd5c | ||
|
|
33833cfd1e | ||
|
|
3c5c1e0b66 | ||
|
|
3f446a5835 | ||
|
|
df3c676a04 | ||
|
|
9ff3ab618c | ||
|
|
ba33b8c3f7 | ||
|
|
eab13e85bc | ||
|
|
a1f1c9825a | ||
|
|
43a9c1384a | ||
|
|
b4ab21a768 | ||
|
|
30365d13ef | ||
|
|
f7eae0ff9e | ||
|
|
e26f170558 | ||
|
|
40e400ab08 | ||
|
|
5743502cdd | ||
|
|
8f33b63153 | ||
|
|
6fca05f00e | ||
|
|
8c07a2e1d6 | ||
|
|
ffbe266413 | ||
|
|
5d3b7fae6b | ||
|
|
738b95d2be | ||
|
|
48c58dbd18 | ||
|
|
54b06a5c34 | ||
|
|
d3a46a1e33 | ||
|
|
4fe2f4760f | ||
|
|
c0809d771d | ||
|
|
54ed92e2b0 | ||
|
|
6ea61b9084 | ||
|
|
f3e0f09ca9 | ||
|
|
301b1a0f00 | ||
|
|
b787427446 | ||
|
|
5df5c70994 | ||
|
|
75defb590b | ||
|
|
fbeaf8ba16 | ||
|
|
3f79b6baed | ||
|
|
34f095a91e | ||
|
|
09247b4503 | ||
|
|
109e084e18 | ||
|
|
af043c55cd | ||
|
|
04999d8969 | ||
|
|
26b8684e78 | ||
|
|
eefcb0e05d | ||
|
|
449d256995 | ||
|
|
0897f0ed8a | ||
|
|
9bdb90e605 | ||
|
|
5b75975237 | ||
|
|
c73336a86f | ||
|
|
27318443a7 | ||
|
|
df93c069f2 | ||
|
|
7358f29556 | ||
|
|
8801b2ae16 | ||
|
|
b10db5a078 | ||
|
|
71341a27e4 | ||
|
|
7e7f645f37 | ||
|
|
146e90dae6 | ||
|
|
bb5bd71437 | ||
|
|
e8217ca0c5 | ||
|
|
8f033723a1 | ||
|
|
b097aa3331 | ||
|
|
64b9e51af0 | ||
|
|
9b91430513 | ||
|
|
d9ec0cb3ab | ||
|
|
39d68ec7fa | ||
|
|
a1a782df38 | ||
|
|
acc106e988 | ||
|
|
11de0eda13 | ||
|
|
a7148ad7ae | ||
|
|
27f19a5b54 | ||
|
|
b973453317 | ||
|
|
ffaada9e9d | ||
|
|
1953eb0eea | ||
|
|
c3275d3c6f | ||
|
|
5e769d5cd2 | ||
|
|
f26e10e44a | ||
|
|
3d60fc8c60 | ||
|
|
7adeb53aee | ||
|
|
eacadf24a6 | ||
|
|
8e14c7f394 | ||
|
|
a842edb09e | ||
|
|
e2247e4872 | ||
|
|
eb662da389 | ||
|
|
2b79f93d6b | ||
|
|
ffc32d2d86 | ||
|
|
892cdb7cea | ||
|
|
90ed30f70c | ||
|
|
34f8ab292f | ||
|
|
e1ab9af89d | ||
|
|
3330225de4 | ||
|
|
58ce9a4159 | ||
|
|
1b5c8678a4 | ||
|
|
c20ec680a8 | ||
|
|
f62582a4bd | ||
|
|
1273c06d4b | ||
|
|
332be9c8aa | ||
|
|
77776be20b | ||
|
|
f7676faa68 | ||
|
|
10a5ed6cec | ||
|
|
b9ba3f1e63 | ||
|
|
34fda43d9c | ||
|
|
b602cc9c57 | ||
|
|
63534c5d56 | ||
|
|
0267de4997 |
No files matched your search
@@ -1,44 +1,48 @@
|
||||
name: Android CI
|
||||
name: Android
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Android
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
|
||||
steps:
|
||||
- name: Set up build environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache libcurl4-openssl-dev ninja-build
|
||||
sudo apt-get -y install ccache libcurl4-openssl-dev
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: android-ccache-${{ github.sha }}
|
||||
restore-keys: android-ccache-
|
||||
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-android-${{ github.sha }}
|
||||
restore-keys: ccache-android-
|
||||
|
||||
- name: Disable release signing
|
||||
run: sed -i 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/g' shell/android-studio/flycast/build.gradle
|
||||
run: sed -i 's/signingConfigs.getByName("release")/signingConfigs.getByName("debug")/' shell/android-studio/flycast/build.gradle.kts
|
||||
if: github.repository != 'flyinghead/flycast' || github.event_name != 'push'
|
||||
|
||||
- name: Bump version code
|
||||
uses: chkfung/[email protected]
|
||||
with:
|
||||
gradlePath: shell/android-studio/flycast/build.gradle
|
||||
versionCode: ${{ github.run_number }}
|
||||
run: sed -i 's/versionCode = 8/versionCode = ${{ github.run_number }}/' shell/android-studio/flycast/build.gradle.kts
|
||||
|
||||
- name: Gradle
|
||||
working-directory: shell/android-studio
|
||||
@@ -47,7 +51,7 @@ jobs:
|
||||
SENTRY_UPLOAD_URL: ${{ secrets.SENTRY_UPLOAD_URL }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-release.apk
|
||||
path: shell/android-studio/flycast/build/outputs/apk/release/flycast-release.apk
|
||||
@@ -81,18 +85,8 @@ jobs:
|
||||
rclone copy shell/android-studio/flycast/build/outputs/apk/release scaleway:flycast-builds/android/${GITHUB_REF#refs/}-$GITHUB_SHA --exclude '*.json'
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: mathieu-bour/setup-sentry-cli@v2
|
||||
env:
|
||||
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
with:
|
||||
url: https://sentry.io
|
||||
token: ${{ env.SENTRY_TOKEN }}
|
||||
organization: flycast
|
||||
project: minidump
|
||||
version: 2.21.2
|
||||
if: ${{ env.SENTRY_TOKEN != '' }}
|
||||
|
||||
- uses: getsentry/action-setup-cli@v1
|
||||
|
||||
- name: Upload symbols to Sentry
|
||||
run: |
|
||||
VERSION=$(git describe --tags --always)
|
||||
@@ -101,5 +95,7 @@ jobs:
|
||||
sentry-cli debug-files upload symbols
|
||||
shell: bash
|
||||
env:
|
||||
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
if: ${{ env.SENTRY_TOKEN != '' }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
SENTRY_ORG: flycast
|
||||
SENTRY_PROJECT: minidump
|
||||
if: ${{ env.SENTRY_AUTH_TOKEN != '' && github.event_name == 'push' }}
|
||||
+30
-18
@@ -1,6 +1,10 @@
|
||||
name: BSD CI
|
||||
name: BSD
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -11,38 +15,46 @@ jobs:
|
||||
matrix:
|
||||
operating_system: [ freebsd, netbsd, openbsd ]
|
||||
architecture: [ arm64, x86-64 ]
|
||||
libretro: [ OFF, ON ]
|
||||
include:
|
||||
- operating_system: freebsd
|
||||
version: '14.2'
|
||||
pkginstall: sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2 libcdio
|
||||
version: '15.1'
|
||||
pkginstall: sudo pkg upgrade -y && sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libcdio libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2
|
||||
- operating_system: netbsd
|
||||
version: '10.1'
|
||||
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache cmake gcc12 git libao libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2 libcdio && export PATH=/usr/pkg/gcc12/bin:$PATH
|
||||
version: '11.0'
|
||||
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache clang cmake git libao libcdio libusb1 libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2
|
||||
- operating_system: openbsd
|
||||
version: '7.6'
|
||||
pkginstall: sudo pkg_add ccache cmake git libao libzip miniupnpc ninja pkgconf pulseaudio sdl2 libcdio
|
||||
version: '8.0'
|
||||
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libcdio libusb1-1.0.30 libzip lua-5.4.8p2 miniupnpc ninja pulseaudio sdl2
|
||||
exclude:
|
||||
- architecture: arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }}
|
||||
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-
|
||||
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-${{ github.sha }}
|
||||
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-
|
||||
|
||||
- uses: cross-platform-actions/[email protected]
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@v1
|
||||
with:
|
||||
operating_system: ${{ matrix.operating_system }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
version: ${{ matrix.version }}
|
||||
environment_variables: CCACHE_DIR
|
||||
run: |
|
||||
${{ matrix.pkginstall }}
|
||||
cmake -B build -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
cmake --build build --config Release
|
||||
|
||||
- name: Set up build environment
|
||||
run: ${{ matrix.pkginstall }}
|
||||
shell: cpa.sh {0}
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release -G Ninja -DLIBRETRO=${{ matrix.libretro }}
|
||||
cmake --build build --config Release
|
||||
shell: cpa.sh {0}
|
||||
+76
-42
@@ -1,6 +1,10 @@
|
||||
name: C/C++ CI
|
||||
name: Linux, macOS, Windows
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -17,14 +21,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- {name: i686-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release}
|
||||
- {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON, destDir: osx, buildType: RelWithDebInfo}
|
||||
- {name: apple-ios, os: macos-latest, shell: sh, cmakeArgs: -DCMAKE_SYSTEM_NAME=iOS -G Xcode, destDir: ios, buildType: Release}
|
||||
- {name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo}
|
||||
- {name: x86_64-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release}
|
||||
- {name: x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo}
|
||||
- {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release}
|
||||
- {name: libretro-x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release}
|
||||
- { name: i686-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release }
|
||||
- { name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON -DMOLTENVK_DYLIB=$MOLTENVK_DYLIB, destDir: osx, buildType: RelWithDebInfo, packagePath: artifact/flycast-apple-darwin.zip }
|
||||
- { name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
|
||||
- { name: arm64-pc-linux-gnu, os: ubuntu-22.04-arm, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
|
||||
- { name: x86_64-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release }
|
||||
- { name: x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo, packagePath: artifact/flycast-x86_64-w64-mingw32.zip }
|
||||
- { name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release }
|
||||
- { name: libretro-x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release }
|
||||
|
||||
steps:
|
||||
- name: Set up build environment (macOS)
|
||||
@@ -33,32 +37,42 @@ jobs:
|
||||
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
|
||||
brew update || :
|
||||
brew install libao ldid pulseaudio
|
||||
brew uninstall --ignore-dependencies zstd
|
||||
VULKAN_VER=1.3.261.1 && aria2c https://sdk.lunarg.com/sdk/download/$VULKAN_VER/mac/vulkansdk-macos-$VULKAN_VER.dmg
|
||||
hdiutil attach ./vulkansdk-macos-*.dmg -mountpoint /Volumes/VulkanSDK
|
||||
sudo /Volumes/VulkanSDK/InstallVulkan.app/Contents/MacOS/InstallVulkan --root $HOME/VulkanSDK --accept-licenses --default-answer --confirm-command install
|
||||
hdiutil detach /Volumes/VulkanSDK
|
||||
echo "VULKAN_SDK=$HOME/VulkanSDK/macOS" >> $GITHUB_ENV
|
||||
|
||||
moltenvk_tag="v1.4.2"
|
||||
moltenvk_root="$RUNNER_TEMP/MoltenVK-$moltenvk_tag"
|
||||
rm -rf "$moltenvk_root"
|
||||
mkdir -p "$moltenvk_root"
|
||||
aria2c "https://github.com/KhronosGroup/MoltenVK/releases/download/$moltenvk_tag/MoltenVK-macos.tar" -d "$RUNNER_TEMP" -o "MoltenVK-macos-$moltenvk_tag.tar"
|
||||
tar -xf "$RUNNER_TEMP/MoltenVK-macos-$moltenvk_tag.tar" -C "$moltenvk_root"
|
||||
moltenvk_dylib="$(find "$moltenvk_root" -type f -path '*/macOS/*' -name libMoltenVK.dylib | head -n 1)"
|
||||
if [ -z "$moltenvk_dylib" ]; then
|
||||
echo "MoltenVK $moltenvk_tag was downloaded, but macOS libMoltenVK.dylib was not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "MOLTENVK_DYLIB=$moltenvk_dylib" >> $GITHUB_ENV
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- name: Set up build environment (Linux)
|
||||
run: |
|
||||
sudo add-apt-repository ppa:christianrauch/libdecoration
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev libcdio-dev libfuse2
|
||||
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev libcurl4-openssl-dev libcdio-dev libfuse2 locales
|
||||
sudo apt-get -y install libwayland-dev libdecor-0-dev libaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxkbcommon-dev libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
|
||||
sudo sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sudo locale-gen
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Set up build environment (Windows, MinGW)
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
install: git make mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-lua mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-toolchain mingw-w64-x86_64-libcdio
|
||||
install: git make
|
||||
pacboy: ccache:p cmake:p libcdio:p lua:p ninja:p SDL2:p toolchain:p
|
||||
if: matrix.config.shell == 'msys2 {0}'
|
||||
|
||||
- name: Set up build environment (Windows, Visual Studio)
|
||||
run: |
|
||||
choco install ccache directx-sdk ninja
|
||||
choco install ccache directx-sdk
|
||||
echo DXSDK_DIR=C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)>> %GITHUB_ENV%
|
||||
if: matrix.config.shell == 'cmd'
|
||||
|
||||
@@ -67,20 +81,36 @@ jobs:
|
||||
arch: ${{ matrix.config.arch }}
|
||||
if: matrix.config.shell == 'cmd'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Compile a universal OpenMP (macOS)
|
||||
run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew reinstall --build-from-source --formula ./shell/apple/libomp.rb
|
||||
- name: Compile universal dependencies (macOS)
|
||||
run: |
|
||||
for formula in ./shell/apple/libpng-flycast.rb; do
|
||||
libname=$(basename "$formula" .rb)
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew install --build-from-source --formula "$formula" || \
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew reinstall --build-from-source --formula "$formula"
|
||||
|
||||
prefix=$(brew --prefix $libname)
|
||||
# Find and verify the static library
|
||||
libfile=$(find "$prefix/lib" -name "*.a" | head -n1)
|
||||
lipo -info "$libfile"
|
||||
|
||||
# Export prefix for subsequent CMake steps
|
||||
if [ -z "$CMAKE_PREFIX_PATH" ]; then
|
||||
CMAKE_PREFIX_PATH="$prefix"
|
||||
else
|
||||
CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH:$prefix"
|
||||
fi
|
||||
done
|
||||
echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
|
||||
# After the last brew download: brew's own curl links brew's zstd
|
||||
brew uninstall --ignore-dependencies zstd
|
||||
if: matrix.config.name == 'apple-darwin'
|
||||
|
||||
- name: Compile OpenMP.xcframework (iOS)
|
||||
run: ./shell/apple/emulator-ios/OpenMP/build_ios_openmp.sh --disableSimulator
|
||||
if: matrix.config.name == 'apple-ios'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ matrix.config.name }}-${{ github.sha }}
|
||||
@@ -99,7 +129,7 @@ jobs:
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config.buildType }} -DCMAKE_INSTALL_PREFIX=artifact -DENABLE_CTEST=ON ${{ matrix.config.cmakeArgs }}
|
||||
cmake --build build --config ${{ matrix.config.buildType }}
|
||||
./build/flycast
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu'
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Dump symbols
|
||||
run: |
|
||||
@@ -114,7 +144,7 @@ jobs:
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu'
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Dump symbols and make AppImage (linux)
|
||||
run: |
|
||||
@@ -122,29 +152,34 @@ jobs:
|
||||
dump_syms artifact/bin/flycast > symbols/flycast.sym 2>/dev/null
|
||||
strip artifact/bin/flycast
|
||||
shell/linux/make-appimage.sh . artifact/bin appimage
|
||||
mv flycast-x86_64.AppImage artifact/bin
|
||||
ARCH=$(uname -m)
|
||||
mv flycast-${ARCH}.AppImage artifact/bin
|
||||
rm artifact/bin/flycast
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu'
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: flycast-${{ matrix.config.name }}
|
||||
path: |
|
||||
artifact/bin
|
||||
artifact/lib/libretro
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Package app (macos)
|
||||
run: |
|
||||
cd artifact/bin
|
||||
codesign --verify --deep --strict --all-architectures --verbose=4 Flycast.app
|
||||
zip -rm flycast.app.zip Flycast.app
|
||||
cp flycast.app.zip "../../${{ matrix.config.packagePath }}"
|
||||
if: matrix.config.name == 'apple-darwin'
|
||||
|
||||
- name: Package app (windows)
|
||||
run: |
|
||||
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
|
||||
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
|
||||
rm artifact/bin/flycast.exe
|
||||
cp artifact/bin/flycast.zip "${{ matrix.config.packagePath }}"
|
||||
if: matrix.config.name == 'x86_64-w64-mingw32'
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-${{ matrix.config.name }}
|
||||
archive: ${{ !matrix.config.packagePath }}
|
||||
path: |
|
||||
${{ matrix.config.packagePath || 'artifact/bin' }}
|
||||
${{ !matrix.config.packagePath && 'artifact/lib/libretro' || '' }}
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
@@ -166,7 +201,7 @@ jobs:
|
||||
project: minidump
|
||||
version: 2.21.2
|
||||
if: ${{ env.SENTRY_TOKEN != '' }}
|
||||
|
||||
|
||||
- name: Upload symbols to Sentry (Windows, macOS, Linux)
|
||||
run: |
|
||||
VERSION=$(git describe --tags --always)
|
||||
@@ -176,5 +211,4 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu') }}
|
||||
|
||||
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu') && github.event_name == 'push' }}
|
||||
@@ -15,18 +15,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 18
|
||||
distribution: zulu
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Upload Source
|
||||
shell: bash
|
||||
|
||||
@@ -12,18 +12,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 18
|
||||
distribution: zulu
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
|
||||
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
git commit -m "Fetch translations & Recreate libretro_core_options_intl.h"
|
||||
|
||||
- name: GitHub Push
|
||||
uses: ad-m/github-push-action@v0.8.0
|
||||
uses: ad-m/github-push-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.ref }}
|
||||
@@ -1,6 +1,10 @@
|
||||
name: Nintendo Switch CI
|
||||
name: Nintendo Switch
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -8,29 +12,32 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: devkitpro/devkita64:latest
|
||||
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- {name: standalone, destDir: switch}
|
||||
- {name: libretro, cmakeArgs: -DLIBRETRO=ON}
|
||||
- { name: standalone, destDir: switch }
|
||||
- { name: libretro, cmakeArgs: -DLIBRETRO=ON }
|
||||
|
||||
steps:
|
||||
- name: Set up build environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache
|
||||
sudo apt-get -y install ccache gh
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
|
||||
- name: Mark git repository as safe
|
||||
run: git config --global --add safe.directory $PWD
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.ccache
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-switch-${{ matrix.config.name }}-${{ github.sha }}
|
||||
restore-keys: ccache-switch-${{ matrix.config.name }}-
|
||||
|
||||
@@ -39,10 +46,12 @@ jobs:
|
||||
$DEVKITPRO/portlibs/switch/bin/aarch64-none-elf-cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=artifact -G Ninja ${{ matrix.config.cmakeArgs }}
|
||||
cmake --build build --config Release --target install
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-switch-${{ matrix.config.name }}
|
||||
path: artifact/bin
|
||||
path: |
|
||||
artifact/bin
|
||||
artifact/lib/libretro
|
||||
|
||||
- name: Package app
|
||||
run: zip -m artifact/bin/flycast.nro.zip artifact/bin/flycast.nro
|
||||
@@ -52,7 +61,6 @@ jobs:
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
version: v1.69.1
|
||||
|
||||
- name: Upload to S3
|
||||
run: rclone copy artifact/bin scaleway:flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA
|
||||
|
||||
+19
-11
@@ -1,21 +1,29 @@
|
||||
name: Windows UWP CI
|
||||
name: Xbox, UWP
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: UWP
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2022
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ Win32, x64, ARM64 ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.22000.0
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -A ${{ matrix.arch }} -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.22000.0
|
||||
cmake --build build --config Release -- /m
|
||||
shell: cmd
|
||||
|
||||
@@ -24,10 +32,10 @@ jobs:
|
||||
- name: Package app
|
||||
run: |
|
||||
mkdir build\artifact
|
||||
cd build\AppPackages\flycast\flycast_*_x64_Test
|
||||
cd build\AppPackages\flycast\flycast_*_Test
|
||||
mkdir tmp
|
||||
ren *.msix flycast.msix
|
||||
makeappx.exe unpack /p .\flycast.msix /d tmp
|
||||
makeappx unpack /p .\flycast.msix /d tmp
|
||||
copy ..\..\..\Release\*.dll tmp
|
||||
makeappx pack /d tmp /p ..\..\..\artifact\flycast.appx
|
||||
|
||||
@@ -35,9 +43,9 @@ jobs:
|
||||
run: signtool sign /f shell\uwp\sign_cert.pfx /p '${{ secrets.SIGN_CERT_PWD }}' /v /fd SHA256 build\artifact\flycast.appx
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-uwp
|
||||
name: flycast-uwp-${{ matrix.arch }}
|
||||
path: build/artifact
|
||||
|
||||
- name: Setup Rclone
|
||||
@@ -48,4 +56,4 @@ jobs:
|
||||
- name: Upload to S3
|
||||
run: rclone copy build/artifact scaleway:flycast-builds/xbox/${GITHUB_REF#refs/}-$GITHUB_SHA
|
||||
shell: bash
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push' && matrix.arch == 'x64'
|
||||
+70
-14
@@ -9,39 +9,51 @@
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
CORENAME: flycast
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON
|
||||
|
||||
.core-defs-linux:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -DCMAKE_BUILD_TYPE=Release
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE
|
||||
CC: /usr/bin/gcc-12
|
||||
CXX: /usr/bin/g++-12
|
||||
|
||||
.core-defs-osx-x64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||
EXTRA_PATH: Release
|
||||
|
||||
.core-defs-osx-arm64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode -DCMAKE_BUILD_TYPE=Release
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode
|
||||
EXTRA_PATH: Release
|
||||
|
||||
.core-defs-ios-arm64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DCMAKE_BUILD_TYPE=Release
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_OPENMP=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0
|
||||
IOS_MINVER: 13.0
|
||||
MINVER: 13.0
|
||||
|
||||
.core-defs-libnx:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_OPENMP=OFF
|
||||
|
||||
.core-defs-android:
|
||||
extends: .core-defs
|
||||
script:
|
||||
- cmake -DLIBRETRO=ON -DCMAKE_BUILD_TYPE=Release -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI -DANDROID_ARM_MODE=arm "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR
|
||||
- echo y | /android-sdk-linux/cmdline-tools/latest/bin/sdkmanager "ndk;${ANDROID_NDK_VERSION}"
|
||||
- cmake -DLIBRETRO=ON -DUSE_OPENMP=OFF -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI -DANDROID_ARM_MODE=arm -DANDROID_WEAK_API_DEFS=ON "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR
|
||||
- cmake --build $BUILD_DIR --target ${CORENAME}_libretro --config Release -- -j $NUMPROC
|
||||
- mv $BUILD_DIR/${CORENAME}_libretro.so $LIBNAME
|
||||
- if [ $STRIP_CORE_LIB -eq 1 ]; then $NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip $LIBNAME; fi
|
||||
variables:
|
||||
API_LEVEL: 18
|
||||
ANDROID_NDK_VERSION: 29.0.14206865
|
||||
API_LEVEL: 21
|
||||
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
|
||||
|
||||
# Inclusion templates, required for the build to work
|
||||
include:
|
||||
@@ -73,18 +85,23 @@ include:
|
||||
|
||||
################################## CONSOLES ################################
|
||||
# Nintendo Switch
|
||||
# - project: 'libretro-infrastructure/ci-templates'
|
||||
# file: '/libnx-static.yml'
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/libnx-static-cmake.yml'
|
||||
|
||||
# tvOS arm64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/tvos-cmake.yml'
|
||||
|
||||
#################################### MISC ##################################
|
||||
# webOS 32-bit (LGTV)
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/webos-cmake.yml'
|
||||
|
||||
# Stages for building
|
||||
stages:
|
||||
- build-prepare
|
||||
- build-shared
|
||||
# - build-static
|
||||
- build-static
|
||||
|
||||
##############################################################################
|
||||
#################################### STAGES ##################################
|
||||
@@ -108,12 +125,21 @@ libretro-build-linux-x64:
|
||||
extends:
|
||||
- .libretro-linux-cmake-x86_64
|
||||
- .core-defs-linux
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports
|
||||
|
||||
# Linux 32-bit
|
||||
libretro-build-linux-i686:
|
||||
extends:
|
||||
- .libretro-linux-cmake-x86
|
||||
- .core-defs-linux
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-i386-ubuntu:backports
|
||||
|
||||
# Linux 64-bit (ARM)
|
||||
libretro-build-linux-aarch64:
|
||||
extends:
|
||||
- .libretro-linux-cmake-aarch64
|
||||
- .core-defs-linux
|
||||
|
||||
|
||||
# MacOS 64-bit
|
||||
libretro-build-osx-x64:
|
||||
@@ -161,13 +187,43 @@ libretro-build-ios-arm64:
|
||||
|
||||
################################### CONSOLES #################################
|
||||
# Nintendo Switch
|
||||
#libretro-build-libnx-aarch64:
|
||||
# extends:
|
||||
# - .libretro-libnx-static-retroarch-master
|
||||
# - .core-defs
|
||||
libretro-build-libnx-aarch64:
|
||||
extends:
|
||||
- .libretro-libnx-static-cmake-retroarch-master
|
||||
- .core-defs-libnx
|
||||
# The libnx image ships CMake 3.18.4 and this tree asks for 3.22.1, so the
|
||||
# job stops at configure. Put a newer one in front of it - in /tmp, since
|
||||
# the job does not run as a user who can write /opt.
|
||||
before_script:
|
||||
- !reference [.libretro-libnx-static-cmake, before_script]
|
||||
- |
|
||||
CMAKE_VER=3.31.6
|
||||
curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VER}/cmake-${CMAKE_VER}-linux-x86_64.tar.gz" | tar -xz -C /tmp
|
||||
export PATH="/tmp/cmake-${CMAKE_VER}-linux-x86_64/bin:$PATH"
|
||||
# The template's script, with one word changed. It builds --target
|
||||
# ${CORENAME}_libretro, and on Switch that target is flycast's own objects
|
||||
# and nothing else. `combined` is the target CMakeLists.txt defines for
|
||||
# exactly this case: it folds in xxhash, chdr, zip, elf and the resources,
|
||||
# which is what RetroArch links against afterwards.
|
||||
script:
|
||||
- cmake $CORE_ARGS "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR -DCMAKE_BUILD_TYPE=Release -DLIBRETRO_STATIC=ON -DLIBRETRO_SUFFIX=_libnx -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake -DNSWITCH=ON -DCFLAGS=-DHAVE_LIBNX=1 -DCXXFLAGS=-DHAVE_LIBNX=1
|
||||
- cmake --build $BUILD_DIR --target combined --config Release -- -j $NUMPROC
|
||||
- mv $BUILD_DIR/$EXTRA_PATH/${CORENAME}_libretro_libnx.a retroarch-precompiled/libretro_libnx.a
|
||||
- cd retroarch-precompiled/
|
||||
- make -f Makefile.libnx -j10
|
||||
- mv retroarch_switch.nro ../${CORENAME}_libretro_libnx.nro
|
||||
- mv retroarch_switch.elf ../${CORENAME}_libretro_libnx.elf
|
||||
- cd ../
|
||||
|
||||
# tvOS arm64
|
||||
libretro-build-tvos-arm64:
|
||||
extends:
|
||||
- .libretro-tvos-cmake-arm64
|
||||
- .core-defs-ios-arm64
|
||||
|
||||
#################################### MISC ##################################
|
||||
# webOS 32-bit
|
||||
libretro-build-webos-armv7a:
|
||||
extends:
|
||||
- .libretro-webos-armv7a-cmake
|
||||
- .core-defs
|
||||
+24
@@ -44,3 +44,27 @@
|
||||
[submodule "core/deps/asio"]
|
||||
path = core/deps/asio
|
||||
url = https://github.com/flyinghead/asio.git
|
||||
[submodule "core/deps/xbyak"]
|
||||
path = core/deps/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
[submodule "core/deps/libjuice"]
|
||||
path = core/deps/libjuice
|
||||
url = https://github.com/flyinghead/libjuice.git
|
||||
[submodule "core/deps/websocketpp"]
|
||||
path = core/deps/websocketpp
|
||||
url = https://github.com/zaphoyd/websocketpp.git
|
||||
[submodule "core/deps/DreamPicoPort-API"]
|
||||
path = core/deps/DreamPicoPort-API
|
||||
url = https://github.com/OrangeFox86/DreamPicoPort-API.git
|
||||
[submodule "core/deps/tinygettext"]
|
||||
path = core/deps/tinygettext
|
||||
url = https://github.com/flyinghead/tinygettext
|
||||
[submodule "core/deps/freetype"]
|
||||
path = core/deps/freetype
|
||||
url = https://gitlab.freedesktop.org/freetype/freetype.git
|
||||
[submodule "core/deps/gamesdk"]
|
||||
path = core/deps/gamesdk
|
||||
url = https://github.com/flyinghead/gamesdk.git
|
||||
[submodule "core/deps/basis_universal"]
|
||||
path = core/deps/basis_universal
|
||||
url = https://github.com/BinomialLLC/basis_universal.git
|
||||
+305
-676
File diff suppressed because it is too large.
Load diff
@@ -12,43 +12,88 @@
|
||||
|
||||
Information about configuration and supported features can be found on [**TheArcadeStriker's flycast wiki**](https://github.com/TheArcadeStriker/flycast-wiki/wiki).
|
||||
|
||||
Join us on our [**Discord server**](https://discord.gg/X8YWP8w) for a chat.
|
||||
Join us on our [**Discord server**](https://discord.gg/X8YWP8w) for a chat.
|
||||
|
||||
## Downloads      
|
||||
|
||||
Get builds for your system from the [**builds page**](https://flyinghead.github.io/flycast-builds/) or [**GitHub Releases**](https://github.com/flyinghead/flycast/releases).
|
||||
|
||||
- **Latest master builds:** regular builds from the `master` branch with recent fixes and updates.
|
||||
- **Nightly dev builds:** experimental builds with the latest features and changes.
|
||||
- **Stable tagged releases:** versioned release builds published on GitHub Releases.
|
||||
|
||||
Automated test results are available from the builds page as well.
|
||||
|
||||
## Install
|
||||
|
||||
### Android 
|
||||
Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
|
||||
|
||||
 Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
|
||||
|
||||
### Flatpak (Linux )
|
||||
|
||||
1. [Set up Flatpak](https://www.flatpak.org/setup/).
|
||||
|
||||
2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast):
|
||||
|
||||
`flatpak install -y org.flycast.Flycast`
|
||||
 `flatpak install -y org.flycast.Flycast`
|
||||
|
||||
3. Run Flycast:
|
||||
|
||||
`flatpak run org.flycast.Flycast`
|
||||
 `flatpak run org.flycast.Flycast`
|
||||
|
||||
### Homebrew (MacOS )
|
||||
### Homebrew (macOS )
|
||||
|
||||
1. [Set up Homebrew](https://brew.sh).
|
||||
1. [Set up Homebrew](https://brew.sh) or run `brew update` if already installed.
|
||||
|
||||
2. Install Flycast via Homebrew:
|
||||
2. Choose one channel:
|
||||
|
||||
`brew install --cask flycast`
|
||||
| Channel | Install command |
|
||||
| -------------------- | ------------------------------------------------------- |
|
||||
| Master (recommended) | `brew install --cask flyinghead/flycast/flycast@master` |
|
||||
| Stable | `brew install --cask flyinghead/flycast/flycast` |
|
||||
| Nightly dev | `brew install --cask flyinghead/flycast/flycast@dev` |
|
||||
|
||||
3. Run Flycast from your Application folder
|
||||
|
||||
 See the <a href="https://github.com/flyinghead/homebrew-flycast#readme">Flycast tap</a> for updating, uninstalling, and switching channels.
|
||||
|
||||
### iOS
|
||||
|
||||
 Due to persistent harassment from an iOS user, support for this platform has been dropped.
|
||||
|
||||
### Xbox One/Series 
|
||||
|
||||
Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
|
||||
 Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
|
||||
|
||||
### Binaries      
|
||||
## Build from source
|
||||
|
||||
Get fresh builds for your system [**on the builds page**](https://flyinghead.github.io/flycast-builds/).
|
||||
### macOS
|
||||
|
||||
**New:** Now automated test results are available as well.
|
||||
 Right-click the bootstrap script and choose **Open**:
|
||||
|
||||
 `shell/apple/generate_xcode_project.command`
|
||||
|
||||
### Windows
|
||||
|
||||
 Double-click the bootstrap script:
|
||||
|
||||
 `shell\windows\generate_vs_project.bat`
|
||||
|
||||
### Linux
|
||||
|
||||
#### Dependencies
|
||||
|
||||
- **C/C++ compiler toolchain** (e.g. `gcc`/`g++`)
|
||||
- **CMake**
|
||||
- **make**
|
||||
- **libcurl** (development headers)
|
||||
- **libudev** (development headers)
|
||||
- **SDL2** (development headers)
|
||||
- **Graphics API**: Vulkan, OpenGL
|
||||
|
||||
#### Build
|
||||
|
||||
### Build instructions:
|
||||
```
|
||||
$ git clone --recursive https://github.com/flyinghead/flycast.git
|
||||
$ cd flycast
|
||||
@@ -56,3 +101,7 @@ $ mkdir build && cd build
|
||||
$ cmake ..
|
||||
$ make
|
||||
```
|
||||
|
||||
## Packaging status
|
||||
|
||||
[](https://repology.org/project/flycast/versions)
|
||||
@@ -0,0 +1,3 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
achievements.cpp
|
||||
achievements.h)
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "oslib/oslib.h"
|
||||
#include "emulator.h"
|
||||
#include "stdclass.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include <cassert>
|
||||
#include <rc_client.h>
|
||||
#include <rc_hash.h>
|
||||
@@ -62,6 +63,7 @@ public:
|
||||
void deserialize(Deserializer& deser);
|
||||
|
||||
static Achievements& Instance();
|
||||
void setHostOverride(const std::string& host);
|
||||
|
||||
private:
|
||||
bool createClient();
|
||||
@@ -104,6 +106,7 @@ private:
|
||||
|
||||
rc_client_t *rc_client = nullptr;
|
||||
bool loggedOn = false;
|
||||
bool hostOverrideActive = false;
|
||||
std::atomic_bool loadingGame {};
|
||||
bool active = false;
|
||||
bool paused = false;
|
||||
@@ -155,6 +158,10 @@ bool canPause() {
|
||||
return Achievements::Instance().canPause();
|
||||
}
|
||||
|
||||
void setHostOverride(const std::string& host) {
|
||||
Achievements::Instance().setHostOverride(host);
|
||||
}
|
||||
|
||||
void serialize(Serializer& ser) {
|
||||
Achievements::Instance().serialize(ser);
|
||||
}
|
||||
@@ -242,9 +249,45 @@ bool Achievements::createClient()
|
||||
|
||||
rc_client_set_userdata(rc_client, this);
|
||||
|
||||
if (!config::AchievementsHostUrl.get().empty()) {
|
||||
rc_client_set_host(rc_client, config::AchievementsHostUrl.get().c_str());
|
||||
hostOverrideActive = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Achievements::setHostOverride(const std::string& host)
|
||||
{
|
||||
config::AchievementsHostUrl.set(host);
|
||||
hostOverrideActive = !host.empty();
|
||||
// rc_client_set_host sets the global URL even when rc_client is null
|
||||
rc_client_set_host(rc_client, host.empty() ? nullptr : host.c_str());
|
||||
if (rc_client != nullptr)
|
||||
{
|
||||
if (!host.empty())
|
||||
{
|
||||
rc_client_set_hardcore_enabled(rc_client, 0);
|
||||
if (!loggedOn
|
||||
&& !config::AchievementsUserName.get().empty()
|
||||
&& !config::AchievementsToken.get().empty())
|
||||
{
|
||||
INFO_LOG(COMMON, "RA: Retrying login via proxy host '%s'", host.c_str());
|
||||
rc_client_begin_login_with_token(rc_client,
|
||||
config::AchievementsUserName.get().c_str(),
|
||||
config::AchievementsToken.get().c_str(),
|
||||
clientLoginWithTokenCallback, nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool restoredHardcore = config::AchievementsHardcoreMode.get();
|
||||
rc_client_set_hardcore_enabled(rc_client, (int)restoredHardcore);
|
||||
settings.raHardcoreMode = restoredHardcore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Achievements::loadCache()
|
||||
{
|
||||
cachePath = get_writable_data_path("achievements/");
|
||||
@@ -283,7 +326,8 @@ std::pair<std::string, bool> Achievements::getCachedImage(const char *url)
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream stream;
|
||||
std::ostringstream stream;
|
||||
stream.imbue(std::locale::classic());
|
||||
stream << std::hex << hash << ".png";
|
||||
return std::make_pair(cachePath + stream.str(), false);
|
||||
}
|
||||
@@ -303,7 +347,8 @@ std::string Achievements::getOrDownloadImage(const char *url)
|
||||
int rc = http::get(url, content, content_type);
|
||||
if (!http::success(rc))
|
||||
return {};
|
||||
std::stringstream stream;
|
||||
std::ostringstream stream;
|
||||
stream.imbue(std::locale::classic());
|
||||
stream << std::hex << hash << ".png";
|
||||
std::string localPath = cachePath + stream.str();
|
||||
FILE *f = nowide::fopen(localPath.c_str(), "wb");
|
||||
@@ -338,9 +383,10 @@ void Achievements::authenticationSuccess(const rc_client_user_t *user)
|
||||
int rc = rc_client_user_get_image_url(user, url.data(), url.size());
|
||||
if (rc == RC_OK)
|
||||
{
|
||||
asyncTask([this, url]() {
|
||||
asyncTask([this, url]()
|
||||
{
|
||||
std::string image = getOrDownloadImage(url.c_str());
|
||||
std::string text = "User " + config::AchievementsUserName.get() + " authenticated";
|
||||
std::string text = strprintf(i18n::T("User %s authenticated"), config::AchievementsUserName.get().c_str());
|
||||
notifier.notify(Notification::Login, image, text);
|
||||
});
|
||||
}
|
||||
@@ -356,7 +402,7 @@ void Achievements::clientLoginWithTokenCallback(int result, const char *error_me
|
||||
if (result != RC_OK)
|
||||
{
|
||||
WARN_LOG(COMMON, "RA Login failed: %s", error_message);
|
||||
notifier.notify(Notification::Login, "", "RetroAchievements authentication failed", error_message);
|
||||
notifier.notify(Notification::Login, "", i18n::Ts("RetroAchievements authentication failed"), error_message);
|
||||
return;
|
||||
}
|
||||
achievements->authenticationSuccess(rc_client_get_user_info(client));
|
||||
@@ -391,7 +437,7 @@ void Achievements::clientLoginWithPasswordCallback(int result, const char *error
|
||||
if (!user || !user->token)
|
||||
{
|
||||
WARN_LOG(COMMON, "RA: rc_client_get_user_info() returned NULL");
|
||||
promise->set_exception(std::make_exception_ptr(FlycastException("No user token returned")));
|
||||
promise->set_exception(std::make_exception_ptr(FlycastException(i18n::Ts("No user token returned"))));
|
||||
delete promise;
|
||||
return;
|
||||
}
|
||||
@@ -543,11 +589,11 @@ void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_
|
||||
// TODO case RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD:
|
||||
|
||||
case RC_CLIENT_EVENT_DISCONNECTED:
|
||||
notifyError("RetroAchievements disconnected");
|
||||
notifyError(i18n::Ts("RetroAchievements disconnected"));
|
||||
break;
|
||||
|
||||
case RC_CLIENT_EVENT_RECONNECTED:
|
||||
notifyError("RetroAchievements reconnected");
|
||||
notifyError(i18n::Ts("RetroAchievements reconnected"));
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -577,7 +623,7 @@ void Achievements::handleUnlockEvent(const rc_client_event_t *event)
|
||||
{
|
||||
asyncTask([this, url, title, description]() {
|
||||
std::string image = getOrDownloadImage(url.c_str());
|
||||
std::string text = "Achievement " + title + " unlocked!";
|
||||
std::string text = strprintf(i18n::T("Achievement %s unlocked!"), title.c_str());
|
||||
notifier.notify(Notification::Login, image, text, description);
|
||||
});
|
||||
}
|
||||
@@ -617,21 +663,21 @@ void Achievements::handleLeaderboardStarted(const rc_client_event_t *event)
|
||||
{
|
||||
const rc_client_leaderboard_t *leaderboard = event->leaderboard;
|
||||
INFO_LOG(COMMON, "RA: Leaderboard started: %s", leaderboard->title);
|
||||
std::string text = "Leaderboard " + std::string(leaderboard->title) + " started";
|
||||
std::string text = strprintf(i18n::T("Leaderboard %s started"), leaderboard->title);
|
||||
notifier.notify(Notification::Unlocked, "", text, leaderboard->description);
|
||||
}
|
||||
void Achievements::handleLeaderboardFailed(const rc_client_event_t *event)
|
||||
{
|
||||
const rc_client_leaderboard_t *leaderboard = event->leaderboard;
|
||||
INFO_LOG(COMMON, "RA: Leaderboard failed: %s", leaderboard->title);
|
||||
std::string text = "Leaderboard " + std::string(leaderboard->title) + " failed";
|
||||
std::string text = strprintf(i18n::T("Leaderboard %s failed"), leaderboard->title);
|
||||
notifier.notify(Notification::Unlocked, "", text, leaderboard->description);
|
||||
}
|
||||
void Achievements::handleLeaderboardSubmitted(const rc_client_event_t *event)
|
||||
{
|
||||
const rc_client_leaderboard_t *leaderboard = event->leaderboard;
|
||||
INFO_LOG(COMMON, "RA: Leaderboard submitted: %s", leaderboard->title);
|
||||
std::string text = "Leaderboard " + std::string(leaderboard->title) + " submitted";
|
||||
std::string text = strprintf(i18n::T("Leaderboard %s submitted"), leaderboard->title);
|
||||
notifier.notify(Notification::Unlocked, "", text, leaderboard->description);
|
||||
}
|
||||
void Achievements::handleShowLeaderboardTracker(const rc_client_event_t *event)
|
||||
@@ -656,12 +702,10 @@ void Achievements::handleUpdateLeaderboardTracker(const rc_client_event_t *event
|
||||
void Achievements::handleGameCompleted(const rc_client_event_t *event)
|
||||
{
|
||||
const rc_client_game_t* game = rc_client_get_game_info(rc_client);
|
||||
std::string text1 = (rc_client_get_hardcore_enabled(rc_client) ? "Mastered " : "Completed ") + std::string(game->title);
|
||||
std::string text1 = strprintf(rc_client_get_hardcore_enabled(rc_client) ? i18n::T("Mastered %s") : i18n::T("Completed %s"), game->title);
|
||||
rc_client_user_game_summary_t summary;
|
||||
rc_client_get_user_game_summary(rc_client, &summary);
|
||||
std::stringstream ss;
|
||||
ss << summary.num_unlocked_achievements << " achievements, " << summary.points_unlocked << " points";
|
||||
std::string text2(ss.str());
|
||||
std::string text2 = strprintf(i18n::T("%d achievements, %d points"), summary.num_unlocked_achievements, summary.points_unlocked);
|
||||
std::string text3 = rc_client_get_user_info(rc_client)->display_name;
|
||||
std::string url(512, '\0');
|
||||
if (rc_client_game_get_image_url(game, url.data(), url.size()) != RC_OK)
|
||||
@@ -853,7 +897,9 @@ void Achievements::loadGame()
|
||||
if (!gameHash.empty())
|
||||
{
|
||||
// settings.raHardcoreMode is set before enabling cheats and loading the initial savestate
|
||||
rc_client_set_hardcore_enabled(rc_client, settings.raHardcoreMode);
|
||||
// Hardcore is disabled when routing through a custom host (e.g. RAOfflineProxy).
|
||||
bool effectiveHardcore = settings.raHardcoreMode && !hostOverrideActive;
|
||||
rc_client_set_hardcore_enabled(rc_client, effectiveHardcore);
|
||||
rc_client_begin_load_game(rc_client, gameHash.c_str(), [](int result, const char *error_message, rc_client_t *client, void *userdata) {
|
||||
((Achievements *)userdata)->gameLoaded(result, error_message);
|
||||
}, this);
|
||||
@@ -907,15 +953,14 @@ void Achievements::gameLoaded(int result, const char *errorMessage)
|
||||
rc_client_get_user_game_summary(rc_client, &summary);
|
||||
std::string text2;
|
||||
if (summary.num_core_achievements > 0)
|
||||
text2 = "You have " + std::to_string(summary.num_unlocked_achievements)
|
||||
+ " of " + std::to_string(summary.num_core_achievements) + " achievements unlocked.";
|
||||
text2 = strprintf(i18n::T("You have %d of %d achievements unlocked."), summary.num_unlocked_achievements, summary.num_core_achievements);
|
||||
else
|
||||
text2 = "This game has no achievements.";
|
||||
text2 = i18n::Ts("This game has no achievements.");
|
||||
asyncTask([this, url, text1, text2]() {
|
||||
std::string image;
|
||||
if (!url.empty())
|
||||
image = getOrDownloadImage(url.c_str());
|
||||
std::string text3 = settings.raHardcoreMode ? "Hardcore Mode" : "";
|
||||
std::string text3 = settings.raHardcoreMode ? i18n::T("Hardcore Mode") : "";
|
||||
notifier.notify(Notification::Login, image, text1, text2, text3);
|
||||
});
|
||||
}
|
||||
@@ -946,14 +991,14 @@ void Achievements::diskChange()
|
||||
rc_client_begin_change_media_from_hash(rc_client, hash.c_str(), [](int result, const char *errorMessage, rc_client_t *client, void *userdata) {
|
||||
if (result == RC_HARDCORE_DISABLED) {
|
||||
settings.raHardcoreMode = false;
|
||||
notifier.notify(Notification::Login, "", "Hardcore mode disabled", "Unrecognized media inserted");
|
||||
notifier.notify(Notification::Login, "", i18n::Ts("Hardcore mode disabled"), i18n::Ts("Unrecognized media inserted"));
|
||||
}
|
||||
else if (result != RC_OK)
|
||||
{
|
||||
settings.raHardcoreMode = false;
|
||||
if (errorMessage == nullptr)
|
||||
errorMessage = rc_error_str(result);
|
||||
notifier.notify(Notification::Login, "", "Media change failed", errorMessage);
|
||||
notifier.notify(Notification::Login, "", i18n::Ts("Media change failed"), errorMessage);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ bool isActive();
|
||||
Game getCurrentGame();
|
||||
std::vector<Achievement> getAchievementList();
|
||||
bool canPause();
|
||||
void setHostOverride(const std::string& host);
|
||||
|
||||
#else
|
||||
|
||||
|
||||
+30
-10
@@ -19,9 +19,9 @@
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "7zArchive.h"
|
||||
#include "deps/lzma/7z.h"
|
||||
#include "deps/lzma/7zCrc.h"
|
||||
#include "deps/lzma/Alloc.h"
|
||||
#include "lzma/7z.h"
|
||||
#include "lzma/7zCrc.h"
|
||||
#include "lzma/Alloc.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -29,20 +29,40 @@
|
||||
|
||||
static bool crc_tables_generated;
|
||||
|
||||
bool SzArchive::Open(FILE *file)
|
||||
SRes SzArchive::ArchiveStream::Read(const ISeekInStream *p, void *buf, size_t *size)
|
||||
{
|
||||
auto file_archive = CONTAINER_FROM_VTBL(p, ArchiveStream, vt);
|
||||
*size = file_archive->file->read(buf, 1, *size);
|
||||
return file_archive->file->error() ? SZ_ERROR_READ : SZ_OK;
|
||||
}
|
||||
|
||||
SRes SzArchive::ArchiveStream::Seek(const ISeekInStream *p, Int64 *pos, ESzSeek origin)
|
||||
{
|
||||
auto file_archive = CONTAINER_FROM_VTBL(p, ArchiveStream, vt);
|
||||
if (file_archive->file->seek(*pos, origin) == 0) {
|
||||
*pos = file_archive->file->tell();
|
||||
return SZ_OK;
|
||||
}
|
||||
else {
|
||||
return SZ_ERROR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
bool SzArchive::Open(hostfs::File *file)
|
||||
{
|
||||
SzArEx_Init(&szarchive);
|
||||
|
||||
File_Close(&archiveStream.file);
|
||||
File_Construct(&archiveStream.file);
|
||||
archiveStream.file.file = file;
|
||||
if (archiveStream.file != nullptr)
|
||||
delete archiveStream.file;
|
||||
archiveStream.vt.Read = ArchiveStream::Read;
|
||||
archiveStream.vt.Seek = ArchiveStream::Seek;
|
||||
archiveStream.file = file;
|
||||
|
||||
FileInStream_CreateVTable(&archiveStream);
|
||||
LookToRead2_CreateVTable(&lookStream, 0);
|
||||
lookStream.buf = (Byte *)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
|
||||
if (lookStream.buf == NULL)
|
||||
{
|
||||
File_Close(&archiveStream.file);
|
||||
delete archiveStream.file;
|
||||
return false;
|
||||
}
|
||||
lookStream.bufSize = kInputBufSize;
|
||||
@@ -115,7 +135,7 @@ SzArchive::~SzArchive()
|
||||
{
|
||||
if (lookStream.buf != NULL)
|
||||
{
|
||||
File_Close(&archiveStream.file);
|
||||
delete archiveStream.file;
|
||||
ISzAlloc_Free(&g_Alloc, lookStream.buf);
|
||||
if (out_buffer != NULL)
|
||||
ISzAlloc_Free(&g_Alloc, out_buffer);
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "archive.h"
|
||||
#include "deps/lzma/7z.h"
|
||||
#include "deps/lzma/7zFile.h"
|
||||
#include "lzma/7z.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -40,14 +39,23 @@ public:
|
||||
ArchiveFile *OpenFileByCrc(u32 crc) override;
|
||||
|
||||
protected:
|
||||
bool Open(FILE *file) override;
|
||||
bool Open(hostfs::File *file) override;
|
||||
|
||||
private:
|
||||
struct ArchiveStream
|
||||
{
|
||||
static SRes Read(const ISeekInStream *p, void *buf, size_t *size);
|
||||
static SRes Seek(const ISeekInStream *p, Int64 *pos, ESzSeek origin);
|
||||
|
||||
ISeekInStream vt;
|
||||
hostfs::File *file;
|
||||
};
|
||||
|
||||
CSzArEx szarchive;
|
||||
UInt32 block_idx; /* it can have any value before first call (if outBuffer = 0) */
|
||||
Byte *out_buffer; /* it must be 0 before first call for each new archive. */
|
||||
size_t out_buffer_size; /* it can have any value before first call (if outBuffer = 0) */
|
||||
CFileInStream archiveStream;
|
||||
ArchiveStream archiveStream;
|
||||
CLookToRead2 lookStream;
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
7zArchive.cpp
|
||||
7zArchive.h
|
||||
archive.cpp
|
||||
archive.h
|
||||
rzip.cpp
|
||||
rzip.h
|
||||
ZipArchive.cpp
|
||||
ZipArchive.h)
|
||||
+118
-3
@@ -20,18 +20,133 @@
|
||||
*/
|
||||
#include "ZipArchive.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
ZipArchive::~ZipArchive()
|
||||
{
|
||||
zip_close(zip);
|
||||
}
|
||||
|
||||
bool ZipArchive::Open(FILE *file)
|
||||
static zip_int64_t ZipSourceCallback(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd)
|
||||
{
|
||||
using Error = std::array<int, 2>;
|
||||
static Error error = {ZIP_ER_OK, 0};
|
||||
auto file = static_cast<hostfs::File*>(userdata);
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case ZIP_SOURCE_OPEN:
|
||||
return 0;
|
||||
|
||||
case ZIP_SOURCE_READ:
|
||||
{
|
||||
const auto bytes_read = file->read(data, 1, len);
|
||||
|
||||
if (file->error())
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_CLOSE:
|
||||
return 0;
|
||||
|
||||
case ZIP_SOURCE_STAT:
|
||||
{
|
||||
auto size = file->size();
|
||||
|
||||
if (size == -1)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto stat = static_cast<zip_stat_t*>(data);
|
||||
zip_stat_init(stat);
|
||||
|
||||
stat->valid |= ZIP_STAT_SIZE;
|
||||
stat->size = size;
|
||||
|
||||
return sizeof(*stat);
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_ERROR:
|
||||
{
|
||||
auto error_out = static_cast<std::array<int, 2>*>(data);
|
||||
*error_out = error;
|
||||
return sizeof(*error_out);
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_FREE:
|
||||
delete file;
|
||||
return 0;
|
||||
|
||||
case ZIP_SOURCE_SEEK:
|
||||
{
|
||||
zip_error_t error;
|
||||
auto seek_info = ZIP_SOURCE_GET_ARGS(zip_source_args_seek, data, len, &error);
|
||||
|
||||
if (seek_info == nullptr)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (file->seek(seek_info->offset, seek_info->whence) != 0)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_TELL:
|
||||
{
|
||||
const auto position = file->tell();
|
||||
|
||||
if (position == -1)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_SUPPORTS:
|
||||
return zip_source_make_command_bitmap(
|
||||
ZIP_SOURCE_OPEN,
|
||||
ZIP_SOURCE_READ,
|
||||
ZIP_SOURCE_CLOSE,
|
||||
ZIP_SOURCE_STAT,
|
||||
ZIP_SOURCE_ERROR,
|
||||
ZIP_SOURCE_FREE,
|
||||
ZIP_SOURCE_SEEK,
|
||||
ZIP_SOURCE_TELL,
|
||||
ZIP_SOURCE_SUPPORTS,
|
||||
-1
|
||||
);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Unsupported command.
|
||||
error = {ZIP_ER_OPNOTSUPP, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ZipArchive::Open(hostfs::File *file)
|
||||
{
|
||||
zip_error_t error;
|
||||
zip_source_t *source = zip_source_filep_create(file, 0, -1, &error);
|
||||
zip_source_t *source = zip_source_function_create(ZipSourceCallback, file, &error);
|
||||
if (source == nullptr)
|
||||
{
|
||||
std::fclose(file);
|
||||
delete file;
|
||||
return false;
|
||||
}
|
||||
zip = zip_open_from_source(source, 0, nullptr);
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
ArchiveFile* OpenFile(const char* name) override;
|
||||
ArchiveFile* OpenFileByCrc(u32 crc) override;
|
||||
|
||||
bool Open(FILE *file) override;
|
||||
bool Open(hostfs::File *file) override;
|
||||
bool Open(const void *data, size_t size);
|
||||
|
||||
ArchiveFile *OpenFirstFile();
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
Archive *OpenArchive(const std::string& path)
|
||||
{
|
||||
FILE *file = nullptr;
|
||||
hostfs::File *file = nullptr;
|
||||
hostfs::FileInfo fileInfo;
|
||||
try {
|
||||
fileInfo = hostfs::storage().getFileInfo(path);
|
||||
@@ -74,7 +74,7 @@ Archive *OpenArchive(const std::string& path)
|
||||
|
||||
bool Archive::Open(const char* path)
|
||||
{
|
||||
FILE *file = nowide::fopen(path, "rb");
|
||||
hostfs::File *file = hostfs::storage().openFile(path, "rb");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
return Open(file);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "oslib/storage.h"
|
||||
|
||||
class ArchiveFile
|
||||
{
|
||||
@@ -39,7 +40,7 @@ public:
|
||||
virtual ArchiveFile *OpenFileByCrc(u32 crc) = 0;
|
||||
|
||||
protected:
|
||||
virtual bool Open(FILE *file) = 0;
|
||||
virtual bool Open(hostfs::File *file) = 0;
|
||||
|
||||
private:
|
||||
bool Open(const char *name);
|
||||
|
||||
+19
-19
@@ -23,27 +23,27 @@
|
||||
|
||||
const u8 RZipHeader[8] = { '#', 'R', 'Z', 'I', 'P', 'v', 1, '#' };
|
||||
|
||||
bool RZipFile::Open(FILE *file, bool write)
|
||||
bool RZipFile::Open(hostfs::File *file, bool write)
|
||||
{
|
||||
verify(this->file == nullptr);
|
||||
verify(file != nullptr);
|
||||
startOffset = std::ftell(file);
|
||||
startOffset = file->tell();
|
||||
if (!write)
|
||||
{
|
||||
u8 header[sizeof(RZipHeader)];
|
||||
if (std::fread(header, sizeof(header), 1, file) != 1
|
||||
if (file->read(header, sizeof(header), 1) != 1
|
||||
|| memcmp(header, RZipHeader, sizeof(header))
|
||||
|| std::fread(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1
|
||||
|| std::fread(&size, sizeof(size), 1, file) != 1)
|
||||
|| file->read(&maxChunkSize, sizeof(maxChunkSize), 1) != 1
|
||||
|| file->read(&size, sizeof(size), 1) != 1)
|
||||
{
|
||||
std::fseek(file, startOffset, SEEK_SET);
|
||||
file->seek(startOffset, SEEK_SET);
|
||||
return false;
|
||||
}
|
||||
// savestates created on 32-bit platforms used to have a 32-bit size
|
||||
if (size >> 32 != 0)
|
||||
{
|
||||
size &= 0xffffffff;
|
||||
std::fseek(file, -4, SEEK_CUR);
|
||||
file->seek(-4, SEEK_CUR);
|
||||
}
|
||||
chunk = new u8[maxChunkSize];
|
||||
chunkIndex = 0;
|
||||
@@ -52,11 +52,11 @@ bool RZipFile::Open(FILE *file, bool write)
|
||||
else
|
||||
{
|
||||
maxChunkSize = 1_MB;
|
||||
if (std::fwrite(RZipHeader, sizeof(RZipHeader), 1, file) != 1
|
||||
|| std::fwrite(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1
|
||||
|| std::fwrite(&size, sizeof(size), 1, file) != 1)
|
||||
if (file->write(RZipHeader, sizeof(RZipHeader), 1) != 1
|
||||
|| file->write(&maxChunkSize, sizeof(maxChunkSize), 1) != 1
|
||||
|| file->write(&size, sizeof(size), 1) != 1)
|
||||
{
|
||||
std::fseek(file, startOffset, SEEK_SET);
|
||||
file->seek(startOffset, SEEK_SET);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ bool RZipFile::Open(FILE *file, bool write)
|
||||
|
||||
bool RZipFile::Open(const std::string& path, bool write)
|
||||
{
|
||||
FILE *f = nowide::fopen(path.c_str(), write ? "wb" : "rb");
|
||||
hostfs::File *f = hostfs::storage().openFile(path.c_str(), write ? "wb" : "rb");
|
||||
if (f == nullptr)
|
||||
return false;
|
||||
if (!Open(f, write)) {
|
||||
@@ -83,10 +83,10 @@ void RZipFile::Close()
|
||||
{
|
||||
if (write)
|
||||
{
|
||||
std::fseek(file, startOffset + sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET);
|
||||
std::fwrite(&size, sizeof(size), 1, file);
|
||||
file->seek(startOffset + sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET);
|
||||
file->write(&size, sizeof(size), 1);
|
||||
}
|
||||
std::fclose(file);
|
||||
delete file;
|
||||
file = nullptr;
|
||||
if (chunk != nullptr)
|
||||
{
|
||||
@@ -110,12 +110,12 @@ size_t RZipFile::Read(void *data, size_t length)
|
||||
chunkSize = 0;
|
||||
chunkIndex = 0;
|
||||
u32 zippedSize;
|
||||
if (std::fread(&zippedSize, sizeof(zippedSize), 1, file) != 1)
|
||||
if (file->read(&zippedSize, sizeof(zippedSize), 1) != 1)
|
||||
break;
|
||||
if (zippedSize == 0)
|
||||
continue;
|
||||
u8 *zipped = new u8[zippedSize];
|
||||
if (std::fread(zipped, zippedSize, 1, file) != 1)
|
||||
if (file->read(zipped, zippedSize, 1) != 1)
|
||||
{
|
||||
delete [] zipped;
|
||||
break;
|
||||
@@ -161,8 +161,8 @@ size_t RZipFile::Write(const void *data, size_t length)
|
||||
break;
|
||||
}
|
||||
u32 sz = (u32)zippedSize;
|
||||
if (std::fwrite(&sz, sizeof(sz), 1, file) != 1
|
||||
|| std::fwrite(zipped, zippedSize, 1, file) != 1)
|
||||
if (file->write(&sz, sizeof(sz), 1) != 1
|
||||
|| file->write(zipped, zippedSize, 1) != 1)
|
||||
{
|
||||
rv = 0;
|
||||
break;
|
||||
|
||||
+4
-3
@@ -21,6 +21,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include "oslib/storage.h"
|
||||
|
||||
class RZipFile
|
||||
{
|
||||
@@ -28,15 +29,15 @@ public:
|
||||
~RZipFile() { Close(); }
|
||||
|
||||
bool Open(const std::string& path, bool write);
|
||||
bool Open(FILE *file, bool write);
|
||||
bool Open(hostfs::File *file, bool write);
|
||||
void Close();
|
||||
size_t Size() const { return size; }
|
||||
size_t Read(void *data, size_t length);
|
||||
size_t Write(const void *data, size_t length);
|
||||
FILE *rawFile() const { return file; }
|
||||
hostfs::File *rawFile() const { return file; }
|
||||
|
||||
private:
|
||||
FILE *file = nullptr;
|
||||
hostfs::File *file = nullptr;
|
||||
u64 size = 0;
|
||||
u32 maxChunkSize = 0;
|
||||
u8 *chunk = nullptr;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
audiostream.h)
|
||||
|
||||
if(NOT LIBRETRO)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
audiobackend_alsa.cpp
|
||||
audiobackend_coreaudio.cpp
|
||||
audiobackend_directsound.cpp
|
||||
audiobackend_libao.cpp
|
||||
audiobackend_null.cpp
|
||||
audiobackend_oboe.cpp
|
||||
audiobackend_omx.cpp
|
||||
audiobackend_oss.cpp
|
||||
audiobackend_pulseaudio.cpp
|
||||
audiobackend_sdl2.cpp
|
||||
audiostream.cpp)
|
||||
endif()
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
{
|
||||
snd_pcm_hw_params_t *params;
|
||||
|
||||
std::string device = cfgLoadStr("alsa", "device", "");
|
||||
std::string device = config::loadStr("alsa", "device");
|
||||
|
||||
int rc = -1;
|
||||
if (!device.empty() && device != "auto") {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "audiostream.h"
|
||||
|
||||
#include "oslib/i18n.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
@@ -9,7 +9,7 @@ class NullAudioBackend : public AudioBackend
|
||||
|
||||
public:
|
||||
NullAudioBackend()
|
||||
: AudioBackend("null", "No Audio") {}
|
||||
: AudioBackend("null", Tnop("No Audio")) {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
@@ -48,6 +48,10 @@ public:
|
||||
return samples;
|
||||
}
|
||||
|
||||
std::string getName() const override {
|
||||
return i18n::Ts(name);
|
||||
}
|
||||
|
||||
private:
|
||||
the_clock::time_point last_time;
|
||||
};
|
||||
|
||||
@@ -19,12 +19,13 @@
|
||||
#ifdef USE_OBOE
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include "stdclass.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include <oboe/Oboe.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include "stdclass.h"
|
||||
|
||||
class OboeBackend : AudioBackend
|
||||
{
|
||||
@@ -77,7 +78,7 @@ class OboeBackend : AudioBackend
|
||||
|
||||
public:
|
||||
OboeBackend()
|
||||
: AudioBackend("Oboe", "Automatic AAudio / OpenSL selection"), audioCallback(this), errorCallback(this) {}
|
||||
: AudioBackend("Oboe", Tnop("Automatic AAudio / OpenSL selection")), audioCallback(this), errorCallback(this) {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
@@ -195,6 +196,11 @@ public:
|
||||
}
|
||||
return std::max(0, result.value());
|
||||
}
|
||||
|
||||
|
||||
std::string getName() const override {
|
||||
return i18n::Ts(name);
|
||||
}
|
||||
};
|
||||
static OboeBackend oboeBackend;
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class SDLAudioBackend : AudioBackend
|
||||
|
||||
public:
|
||||
SDLAudioBackend()
|
||||
: AudioBackend("sdl2", "Simple DirectMedia Layer 2 Audio") {}
|
||||
: AudioBackend("sdl2", "Simple DirectMedia Layer 2") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ AudioBackend *AudioBackend::getBackend(const std::string& slug)
|
||||
autoBackend = sdlBackend;
|
||||
if (autoBackend == nullptr)
|
||||
autoBackend = backends->front();
|
||||
INFO_LOG(AUDIO, "Auto-selected audio backend \"%s\" (%s).", autoBackend->slug.c_str(), autoBackend->name.c_str());
|
||||
INFO_LOG(AUDIO, "Auto-selected audio backend \"%s\" (%s).", autoBackend->slug.c_str(), autoBackend->getName().c_str());
|
||||
|
||||
return autoBackend;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ void InitAudio()
|
||||
}
|
||||
if (currentBackend != nullptr)
|
||||
{
|
||||
INFO_LOG(AUDIO, "Initializing audio backend \"%s\" (%s)...", currentBackend->slug.c_str(), currentBackend->name.c_str());
|
||||
INFO_LOG(AUDIO, "Initializing audio backend \"%s\" (%s)...", currentBackend->slug.c_str(), currentBackend->getName().c_str());
|
||||
if (!currentBackend->init())
|
||||
{
|
||||
currentBackend = nullptr;
|
||||
@@ -114,7 +114,7 @@ void TermAudio()
|
||||
StopAudioRecording();
|
||||
audio_recording_started = rec_started;
|
||||
currentBackend->term();
|
||||
INFO_LOG(AUDIO, "Terminating audio backend \"%s\" (%s)...", currentBackend->slug.c_str(), currentBackend->name.c_str());
|
||||
INFO_LOG(AUDIO, "Terminating audio backend \"%s\" (%s)...", currentBackend->slug.c_str(), currentBackend->getName().c_str());
|
||||
currentBackend = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
virtual void termRecord() {}
|
||||
|
||||
std::string slug;
|
||||
std::string name;
|
||||
virtual std::string getName() const { return name; }
|
||||
|
||||
static size_t getCount() { return backends == nullptr ? 0 : backends->size(); }
|
||||
static AudioBackend *getBackend(size_t index) { return backends == nullptr ? nullptr : (*backends)[index]; }
|
||||
@@ -46,6 +46,7 @@ protected:
|
||||
: slug(slug), name(name) {
|
||||
registerAudioBackend(this);
|
||||
}
|
||||
std::string name;
|
||||
|
||||
private:
|
||||
static void registerAudioBackend(AudioBackend *backend)
|
||||
@@ -72,8 +73,10 @@ constexpr u32 SAMPLE_COUNT = 512; // AudioBackend::push() is always called with
|
||||
class RingBuffer
|
||||
{
|
||||
std::vector<u8> buffer;
|
||||
std::atomic_int readCursor { 0 };
|
||||
std::atomic_int writeCursor { 0 };
|
||||
// The producer and the audio callback each update one cursor: keep them on
|
||||
// separate cache lines.
|
||||
alignas(64) std::atomic_int readCursor { 0 };
|
||||
alignas(64) std::atomic_int writeCursor { 0 };
|
||||
|
||||
u32 readSize() {
|
||||
return (u32)((writeCursor - readCursor + buffer.size()) % buffer.size());
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||
#define TARGET_UWP
|
||||
#define TARGET_UWP 1
|
||||
#endif
|
||||
#ifdef HAVE_D3D11
|
||||
#define USE_DX11
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
option.h)
|
||||
|
||||
if(NOT LIBRETRO)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
cfg.cpp
|
||||
cfg.h
|
||||
cl.cpp
|
||||
ini.cpp
|
||||
ini.h
|
||||
option.cpp)
|
||||
endif()
|
||||
+68
-74
@@ -1,37 +1,33 @@
|
||||
#include "cfg.h"
|
||||
#include "ini.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
static std::string cfgPath;
|
||||
static bool save_config = true;
|
||||
static bool autoSave = true;
|
||||
|
||||
static emucfg::ConfigFile cfgdb;
|
||||
|
||||
static void saveConfigFile()
|
||||
namespace config
|
||||
{
|
||||
FILE* cfgfile = nowide::fopen(cfgPath.c_str(), "wt");
|
||||
if (!cfgfile)
|
||||
{
|
||||
|
||||
static std::string cfgPath;
|
||||
static bool autoSaving = true;
|
||||
static IniFile cfgdb;
|
||||
|
||||
static void saveFile()
|
||||
{
|
||||
hostfs::File* cfgfile = hostfs::storage().openFile(cfgPath.c_str(), "wt");
|
||||
if (!cfgfile) {
|
||||
WARN_LOG(COMMON, "Error: Unable to open file '%s' for saving", cfgPath.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
cfgdb.save(cfgfile);
|
||||
std::fclose(cfgfile);
|
||||
delete cfgfile;
|
||||
}
|
||||
}
|
||||
void cfgSaveStr(const std::string& section, const std::string& key, const std::string& value)
|
||||
{
|
||||
cfgdb.set(section, key, value);
|
||||
|
||||
if (save_config && autoSave)
|
||||
saveConfigFile();
|
||||
static void autoSave() {
|
||||
if (autoSaving)
|
||||
saveFile();
|
||||
}
|
||||
|
||||
bool cfgOpen()
|
||||
bool open()
|
||||
{
|
||||
if (get_writable_config_path("").empty())
|
||||
// Config dir not set (android onboarding)
|
||||
@@ -41,10 +37,10 @@ bool cfgOpen()
|
||||
std::string config_path_read = get_readonly_config_path(filename);
|
||||
cfgPath = get_writable_config_path(filename);
|
||||
|
||||
FILE* cfgfile = nowide::fopen(config_path_read.c_str(), "r");
|
||||
if(cfgfile != NULL) {
|
||||
cfgdb.parse(cfgfile);
|
||||
std::fclose(cfgfile);
|
||||
hostfs::File* cfgfile = hostfs::storage().openFile(config_path_read.c_str(), "rt");
|
||||
if (cfgfile != nullptr) {
|
||||
cfgdb.load(cfgfile);
|
||||
delete cfgfile;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -55,87 +51,85 @@ bool cfgOpen()
|
||||
{
|
||||
// Config file didn't exist
|
||||
INFO_LOG(COMMON, "Creating new empty config file at '%s'", cfgPath.c_str());
|
||||
saveConfigFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
// There was some other error (may be a permissions problem or something like that)
|
||||
save_config = false;
|
||||
saveFile();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string cfgLoadStr(const std::string& section, const std::string& key, const std::string& def)
|
||||
{
|
||||
return cfgdb.get(section, key, def);
|
||||
void saveStr(const std::string& section, const std::string& key, const std::string& value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
void cfgSaveInt(const std::string& section, const std::string& key, s32 value)
|
||||
{
|
||||
cfgSaveStr(section, key, std::to_string(value));
|
||||
std::string loadStr(const std::string& section, const std::string& key, const std::string& def) {
|
||||
return cfgdb.getString(section, key, def);
|
||||
}
|
||||
|
||||
s32 cfgLoadInt(const std::string& section, const std::string& key, s32 def)
|
||||
{
|
||||
return cfgdb.get_int(section, key, def);
|
||||
void saveInt(const std::string& section, const std::string& key, int value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
int64_t cfgLoadInt64(const std::string& section, const std::string& key, int64_t def) {
|
||||
return cfgdb.get_int64(section, key, def);
|
||||
}
|
||||
void cfgSaveInt64(const std::string& section, const std::string& key, int64_t value) {
|
||||
cfgdb.set_int64(section, key, value);
|
||||
int loadInt(const std::string& section, const std::string& key, int def) {
|
||||
return cfgdb.getInt(section, key, def);
|
||||
}
|
||||
|
||||
void cfgSaveBool(const std::string& section, const std::string& key, bool value)
|
||||
{
|
||||
cfgSaveStr(section, key, value ? "yes" : "no");
|
||||
int64_t loadInt64(const std::string& section, const std::string& key, int64_t def) {
|
||||
return cfgdb.getInt64(section, key, def);
|
||||
}
|
||||
void saveInt64(const std::string& section, const std::string& key, int64_t value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
bool cfgLoadBool(const std::string& section, const std::string& key, bool def)
|
||||
{
|
||||
return cfgdb.get_bool(section, key, def);
|
||||
void saveBool(const std::string& section, const std::string& key, bool value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
void cfgSetVirtual(const std::string& section, const std::string& key, const std::string& value)
|
||||
{
|
||||
bool loadBool(const std::string& section, const std::string& key, bool def) {
|
||||
return cfgdb.getBool(section, key, def);
|
||||
}
|
||||
|
||||
void setTransient(const std::string& section, const std::string& key, const std::string& value) {
|
||||
cfgdb.set(section, key, value, true);
|
||||
}
|
||||
|
||||
bool cfgIsVirtual(const std::string& section, const std::string& key)
|
||||
{
|
||||
return cfgdb.is_virtual(section, key);
|
||||
bool isTransient(const std::string& section, const std::string& key) {
|
||||
return cfgdb.isTransient(section, key);
|
||||
}
|
||||
|
||||
bool cfgHasSection(const std::string& section)
|
||||
{
|
||||
return cfgdb.has_section(section);
|
||||
bool hasSection(const std::string& section) {
|
||||
return cfgdb.hasSection(section);
|
||||
}
|
||||
|
||||
void cfgDeleteSection(const std::string& section)
|
||||
{
|
||||
cfgdb.delete_section(section);
|
||||
void deleteSection(const std::string& section) {
|
||||
cfgdb.deleteSection(section);
|
||||
}
|
||||
|
||||
void cfgDeleteEntry(const std::string& section, const std::string& key)
|
||||
{
|
||||
cfgdb.delete_entry(section, key);
|
||||
void deleteEntry(const std::string& section, const std::string& key) {
|
||||
cfgdb.deleteEntry(section, key);
|
||||
}
|
||||
|
||||
void cfgSetAutoSave(bool autoSave)
|
||||
void setAutoSave(bool autoSaving)
|
||||
{
|
||||
::autoSave = autoSave;
|
||||
if (autoSave)
|
||||
saveConfigFile();
|
||||
config::autoSaving = autoSaving;
|
||||
if (autoSaving)
|
||||
saveFile();
|
||||
}
|
||||
|
||||
void cfgSaveFloat(const std::string& section, const std::string& key, float value)
|
||||
{
|
||||
cfgdb.set_float(section, key, value);
|
||||
void saveFloat(const std::string& section, const std::string& key, float value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
float cfgLoadFloat(const std::string& section, const std::string& key, float def)
|
||||
{
|
||||
return cfgdb.get_float(section, key, def);
|
||||
float loadFloat(const std::string& section, const std::string& key, float def) {
|
||||
return cfgdb.getFloat(section, key, def);
|
||||
}
|
||||
|
||||
std::vector<std::string> getEntries(const std::string& section) {
|
||||
return cfgdb.getEntryNames(section);
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
+26
-19
@@ -1,24 +1,31 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "types.h"
|
||||
namespace config
|
||||
{
|
||||
|
||||
bool cfgOpen();
|
||||
s32 cfgLoadInt(const std::string& section, const std::string& key, s32 def);
|
||||
void cfgSaveInt(const std::string& section, const std::string& key, s32 value);
|
||||
int64_t cfgLoadInt64(const std::string& section, const std::string& key, int64_t def);
|
||||
void cfgSaveInt64(const std::string& section, const std::string& key, int64_t value);
|
||||
std::string cfgLoadStr(const std::string& section, const std::string& key, const std::string& def);
|
||||
void cfgSaveStr(const std::string& section, const std::string& key, const std::string& value);
|
||||
void cfgSaveBool(const std::string& section, const std::string& key, bool value);
|
||||
bool cfgLoadBool(const std::string& section, const std::string& key, bool def);
|
||||
void cfgSaveFloat(const std::string& section, const std::string& key, float value);
|
||||
float cfgLoadFloat(const std::string& section, const std::string& key, float def);
|
||||
void cfgSetVirtual(const std::string& section, const std::string& key, const std::string& value);
|
||||
bool cfgIsVirtual(const std::string& section, const std::string& key);
|
||||
bool open();
|
||||
int loadInt(const std::string& section, const std::string& key, int def = 0);
|
||||
void saveInt(const std::string& section, const std::string& key, int value);
|
||||
int64_t loadInt64(const std::string& section, const std::string& key, int64_t def = 0);
|
||||
void saveInt64(const std::string& section, const std::string& key, int64_t value);
|
||||
std::string loadStr(const std::string& section, const std::string& key, const std::string& def = {});
|
||||
void saveStr(const std::string& section, const std::string& key, const std::string& value);
|
||||
bool loadBool(const std::string& section, const std::string& key, bool def = false);
|
||||
void saveBool(const std::string& section, const std::string& key, bool value);
|
||||
float loadFloat(const std::string& section, const std::string& key, float def = 0.f);
|
||||
void saveFloat(const std::string& section, const std::string& key, float value);
|
||||
void setTransient(const std::string& section, const std::string& key, const std::string& value);
|
||||
bool isTransient(const std::string& section, const std::string& key);
|
||||
|
||||
void ParseCommandLine(int argc, char *argv[]);
|
||||
void parseCommandLine(int argc, const char * const argv[]);
|
||||
|
||||
void cfgSetAutoSave(bool autoSave);
|
||||
bool cfgHasSection(const std::string& section);
|
||||
void cfgDeleteSection(const std::string& section);
|
||||
void cfgDeleteEntry(const std::string& section, const std::string& key);
|
||||
void setAutoSave(bool autoSave);
|
||||
bool hasSection(const std::string& section);
|
||||
void deleteSection(const std::string& section);
|
||||
void deleteEntry(const std::string& section, const std::string& key);
|
||||
|
||||
std::vector<std::string> getEntries(const std::string& section);
|
||||
}
|
||||
+197
-114
@@ -1,143 +1,226 @@
|
||||
/*
|
||||
Command line parsing
|
||||
*/
|
||||
Copyright 2025 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "cfg/cfg.h"
|
||||
#include "stdclass.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "cfg/cfg.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
static int setconfig(char *arg[], int cl)
|
||||
namespace config
|
||||
{
|
||||
int rv=0;
|
||||
|
||||
if (cl < 1)
|
||||
static void usage(const char *exe)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [option]... [<rom path>]\n", exe);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, "-config section:key=value,... set a transient config value.\n");
|
||||
fprintf(stderr, " Transient config values won't be saved to emu.cfg.\n");
|
||||
fprintf(stderr, "-help display this help\n");
|
||||
}
|
||||
|
||||
static void parseConfigOption(const std::string& str)
|
||||
{
|
||||
char inQuote = '\0';
|
||||
std::string section, key, value;
|
||||
int step = 0; // section, key, value
|
||||
for (char c : str)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid number of parameters, format is section:key=value,section:key=value,...");
|
||||
return rv;
|
||||
}
|
||||
|
||||
std::string value(arg[1]);
|
||||
for(;;)
|
||||
{
|
||||
auto seppos = value.find(':');
|
||||
if (seppos == std::string::npos)
|
||||
if (inQuote != '\0' && c == inQuote)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value,section:key=value,...", value.c_str());
|
||||
break;
|
||||
}
|
||||
auto eqpos = value.find('=', seppos);
|
||||
if (eqpos == std::string::npos)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value,section:key=value,...", value.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
auto commapos = value.find(',', eqpos);
|
||||
|
||||
std::string sect = trim_ws(value.substr(0, seppos));
|
||||
std::string key = trim_ws(value.substr(seppos + 1, eqpos - seppos - 1));
|
||||
auto endofcurrent = (commapos == std::string::npos) ? value.size() : commapos;
|
||||
std::string next = (commapos == std::string::npos) ? "" : trim_ws(value.substr(endofcurrent + 1, value.size() - endofcurrent));
|
||||
value = trim_ws(value.substr(eqpos + 1, endofcurrent - eqpos - 1));
|
||||
|
||||
if (sect.empty() || key.empty())
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter, format is section:key=value,section:key=value,...");
|
||||
break;
|
||||
}
|
||||
|
||||
INFO_LOG(COMMON, "Virtual cfg %s:%s=%s", sect.c_str(), key.c_str(), value.c_str());
|
||||
|
||||
cfgSetVirtual(sect, key, value);
|
||||
|
||||
if (commapos == std::string::npos)
|
||||
rv++;
|
||||
|
||||
if (commapos != std::string::npos)
|
||||
{
|
||||
value = next;
|
||||
inQuote = false;
|
||||
step = 0;
|
||||
setTransient(section, key, value);
|
||||
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
|
||||
section.clear();
|
||||
key.clear();
|
||||
value.clear();
|
||||
continue;
|
||||
}
|
||||
else if (cl>=3 && strcmp(arg[2],",")==0)
|
||||
switch (c)
|
||||
{
|
||||
cl-=2;
|
||||
arg+=2;
|
||||
rv++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
case ':':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
section = trim_trailing_ws(section);
|
||||
if (section.empty()) {
|
||||
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
|
||||
return;
|
||||
}
|
||||
step = 1;
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case '=':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
|
||||
return;
|
||||
case 1:
|
||||
key = trim_trailing_ws(key);
|
||||
if (key.empty()) {
|
||||
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
|
||||
return;
|
||||
}
|
||||
step = 2;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
case '"':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
section += c;
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
if (inQuote == '\0') {
|
||||
inQuote = c;
|
||||
value.clear();
|
||||
}
|
||||
else
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
// ignore consecutive commas
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
if (inQuote != '\0') {
|
||||
value += c;
|
||||
}
|
||||
else
|
||||
{
|
||||
step = 0;
|
||||
setTransient(section, key, value);
|
||||
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
|
||||
section.clear();
|
||||
key.clear();
|
||||
value.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
// Ignore leading spaces. Inner spaces are kept (game ids such as "INITIAL D Ver.2")
|
||||
// and trailing ones are trimmed at the ':' and '=' separators
|
||||
if (!section.empty())
|
||||
section += c;
|
||||
break;
|
||||
case 1:
|
||||
if (!key.empty())
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
section += c;
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (step == 2) {
|
||||
setTransient(section, key, value);
|
||||
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int showhelp()
|
||||
{
|
||||
printf("Usage: flycast [OPTION]... [CONTENT]\n\n");
|
||||
printf("Options:\n");
|
||||
printf("-config section:key=value add a virtual config value;\n");
|
||||
printf(" virtual config values won't be saved to the .cfg file\n");
|
||||
printf(" unless a different value is written to them\n");
|
||||
printf("-help display this help\n");
|
||||
|
||||
exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ParseCommandLine(int argc,char* argv[])
|
||||
void parseCommandLine(int argc, const char * const argv[])
|
||||
{
|
||||
settings.content.path.clear();
|
||||
int cl=argc-2;
|
||||
char** arg=argv+1;
|
||||
while(cl>=0)
|
||||
const char *exe = argv[0];
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (stricmp(*arg,"-help")==0 || stricmp(*arg,"--help")==0)
|
||||
{
|
||||
showhelp();
|
||||
if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) {
|
||||
usage(exe);
|
||||
exit(0);
|
||||
}
|
||||
else if (stricmp(*arg,"-config")==0 || stricmp(*arg,"--config")==0)
|
||||
if (!strcmp(argv[i], "-config") || !strcmp(argv[i], "--config"))
|
||||
{
|
||||
int as=setconfig(arg,cl);
|
||||
cl-=as;
|
||||
arg+=as;
|
||||
if (i < argc - 1)
|
||||
parseConfigOption(argv[++i]);
|
||||
continue;
|
||||
}
|
||||
#if defined(__APPLE__)
|
||||
else if (!strncmp(*arg, "-NSDocumentRevisions", 20))
|
||||
// macOS
|
||||
if (!strncmp(argv[i], "-NSDocumentRevisions", 20)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (argv[i][0] == '-') {
|
||||
WARN_LOG(COMMON, "Ignoring unknown command line option '%s'", argv[i]);
|
||||
continue;
|
||||
}
|
||||
std::string extension = get_file_extension(argv[i]);
|
||||
if (extension == "cdi" || extension == "chd"
|
||||
|| extension == "gdi"|| extension == "cue")
|
||||
{
|
||||
arg++;
|
||||
cl--;
|
||||
INFO_LOG(COMMON, "Using '%s' as CD image", argv[i]);
|
||||
settings.content.path = argv[i];
|
||||
}
|
||||
#endif
|
||||
else if ((*arg)[0] == '-')
|
||||
else if (extension == "elf")
|
||||
{
|
||||
WARN_LOG(COMMON, "Ignoring unknown command line option '%s'", *arg);
|
||||
INFO_LOG(COMMON, "Using '%s' as reios elf file", argv[i]);
|
||||
setTransient("config", "bios.UseReios", "yes");
|
||||
settings.content.path = argv[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
char* extension = strrchr(*arg, '.');
|
||||
|
||||
if (extension
|
||||
&& (stricmp(extension, ".cdi") == 0 || stricmp(extension, ".chd") == 0
|
||||
|| stricmp(extension, ".gdi") == 0 || stricmp(extension, ".cue") == 0))
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as cd image", *arg);
|
||||
settings.content.path = *arg;
|
||||
}
|
||||
else if (extension && stricmp(extension, ".elf") == 0)
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as reios elf file", *arg);
|
||||
cfgSetVirtual("config", "bios.UseReios", "yes");
|
||||
settings.content.path = *arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as rom", *arg);
|
||||
settings.content.path = *arg;
|
||||
}
|
||||
else {
|
||||
INFO_LOG(COMMON, "Using '%s' as rom", argv[i]);
|
||||
settings.content.path = argv[i];
|
||||
}
|
||||
arg++;
|
||||
cl--;
|
||||
if (i < argc - 1)
|
||||
WARN_LOG(COMMON, "Rest of command line ignored: '%s'...", argv[i + 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
+311
-297
@@ -1,319 +1,333 @@
|
||||
/*
|
||||
Copyright 2025 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ini.h"
|
||||
#include "types.h"
|
||||
#include <sstream>
|
||||
#include "stdclass.h"
|
||||
|
||||
namespace emucfg {
|
||||
#include <cctype>
|
||||
|
||||
/* ConfigEntry */
|
||||
namespace config {
|
||||
|
||||
const std::string& ConfigEntry::get_string() const
|
||||
const IniFile::Entry *IniFile::getEntry(const std::string& sectionName, const std::string& name) const
|
||||
{
|
||||
return this->value;
|
||||
auto it = sections.find(sectionName);
|
||||
if (it == sections.end())
|
||||
return nullptr;
|
||||
const Section& section = it->second;
|
||||
auto it2 = section.entries.find(name);
|
||||
if (it2 == section.entries.end())
|
||||
return nullptr;
|
||||
return &it2->second;
|
||||
}
|
||||
|
||||
int ConfigEntry::get_int()
|
||||
const std::string *IniFile::getRaw(const std::string& section, const std::string& name) const
|
||||
{
|
||||
if (strstr(this->value.c_str(), "0x") != NULL)
|
||||
{
|
||||
return (int)strtoul(this->value.c_str(), NULL, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
return atoi(this->value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t ConfigEntry::get_int64()
|
||||
{
|
||||
int base = 10;
|
||||
if (strstr(this->value.c_str(), "0x") != nullptr)
|
||||
base = 16;
|
||||
return (int64_t)strtoll(this->value.c_str(), nullptr, base);
|
||||
}
|
||||
|
||||
bool ConfigEntry::get_bool()
|
||||
{
|
||||
if (stricmp(this->value.c_str(), "yes") == 0 ||
|
||||
stricmp(this->value.c_str(), "true") == 0 ||
|
||||
stricmp(this->value.c_str(), "on") == 0 ||
|
||||
stricmp(this->value.c_str(), "1") == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
float ConfigEntry::get_float()
|
||||
{
|
||||
return atof(this->value.c_str());
|
||||
}
|
||||
|
||||
/* ConfigSection */
|
||||
|
||||
bool ConfigSection::has_entry(const std::string& name)
|
||||
{
|
||||
return (this->entries.count(name) == 1);
|
||||
}
|
||||
|
||||
ConfigEntry* ConfigSection::get_entry(const std::string& name)
|
||||
{
|
||||
if(this->has_entry(name))
|
||||
{
|
||||
return &this->entries[name];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ConfigSection::set(const std::string& name, const std::string& value)
|
||||
{
|
||||
ConfigEntry new_entry = { value };
|
||||
this->entries[name] = new_entry;
|
||||
}
|
||||
|
||||
void ConfigSection::delete_entry(const std::string& name)
|
||||
{
|
||||
this->entries.erase(name);
|
||||
}
|
||||
|
||||
/* ConfigFile */
|
||||
|
||||
ConfigSection* ConfigFile::add_section(const std::string& name, bool is_virtual)
|
||||
{
|
||||
ConfigSection new_section;
|
||||
if (is_virtual)
|
||||
{
|
||||
this->virtual_sections.insert(std::make_pair(name, new_section));
|
||||
return &this->virtual_sections[name];
|
||||
}
|
||||
this->sections.insert(std::make_pair(name, new_section));
|
||||
return &this->sections[name];
|
||||
}
|
||||
|
||||
bool ConfigFile::has_section(const std::string& name)
|
||||
{
|
||||
return (this->virtual_sections.count(name) == 1 || this->sections.count(name) == 1);
|
||||
}
|
||||
|
||||
bool ConfigFile::has_entry(const std::string& section_name, const std::string& entry_name)
|
||||
{
|
||||
ConfigSection* section = this->get_section(section_name, true);
|
||||
if ((section != NULL) && section->has_entry(entry_name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
section = this->get_section(section_name, false);
|
||||
return ((section != NULL) && section->has_entry(entry_name));
|
||||
}
|
||||
|
||||
ConfigSection* ConfigFile::get_section(const std::string& name, bool is_virtual)
|
||||
{
|
||||
if(is_virtual)
|
||||
{
|
||||
if (this->virtual_sections.count(name) == 1)
|
||||
{
|
||||
return &this->virtual_sections[name];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->sections.count(name) == 1)
|
||||
{
|
||||
return &this->sections[name];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConfigEntry* ConfigFile::get_entry(const std::string& section_name, const std::string& entry_name)
|
||||
{
|
||||
ConfigSection* section = this->get_section(section_name, true);
|
||||
if(section != NULL && section->has_entry(entry_name))
|
||||
{
|
||||
return section->get_entry(entry_name);
|
||||
}
|
||||
|
||||
section = this->get_section(section_name, false);
|
||||
if(section != NULL)
|
||||
{
|
||||
return section->get_entry(entry_name);
|
||||
}
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
std::string ConfigFile::get(const std::string& section_name, const std::string& entry_name, const std::string& default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_string();
|
||||
}
|
||||
}
|
||||
|
||||
int ConfigFile::get_int(const std::string& section_name, const std::string& entry_name, int default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_int();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t ConfigFile::get_int64(const std::string& section_name, const std::string& entry_name, int64_t default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_int64();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigFile::get_bool(const std::string& section_name, const std::string& entry_name, bool default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_bool();
|
||||
}
|
||||
}
|
||||
|
||||
float ConfigFile::get_float(const std::string& section_name, const std::string& entry_name, float default_value)
|
||||
{
|
||||
ConfigEntry* entry = get_entry(section_name, entry_name);
|
||||
const Entry *entry = getEntry(section, name);
|
||||
if (entry == nullptr)
|
||||
return default_value;
|
||||
return nullptr;
|
||||
if (entry->transient)
|
||||
return &entry->transientValue;
|
||||
else
|
||||
return entry->get_float();
|
||||
return &entry->value;
|
||||
}
|
||||
|
||||
void ConfigFile::set(const std::string& section_name, const std::string& entry_name, const std::string& value, bool is_virtual)
|
||||
const std::string& IniFile::getString(const std::string& section, const std::string& entry, const std::string& defaultValue) const
|
||||
{
|
||||
ConfigSection* section = this->get_section(section_name, is_virtual);
|
||||
if(section == NULL)
|
||||
{
|
||||
section = this->add_section(section_name, is_virtual);
|
||||
}
|
||||
section->set(entry_name, value);
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
else
|
||||
return *pValue;
|
||||
}
|
||||
|
||||
void ConfigFile::set_int(const std::string& section_name, const std::string& entry_name, int value, bool is_virtual)
|
||||
bool IniFile::getBool(const std::string& section, const std::string& entry, bool defaultValue) const
|
||||
{
|
||||
std::stringstream str_value;
|
||||
str_value << value;
|
||||
this->set(section_name, entry_name, str_value.str(), is_virtual);
|
||||
}
|
||||
|
||||
void ConfigFile::set_int64(const std::string& section_name, const std::string& entry_name, int64_t value, bool is_virtual)
|
||||
{
|
||||
std::stringstream str_value;
|
||||
str_value << value;
|
||||
this->set(section_name, entry_name, str_value.str(), is_virtual);
|
||||
}
|
||||
|
||||
void ConfigFile::set_bool(const std::string& section_name, const std::string& entry_name, bool value, bool is_virtual)
|
||||
{
|
||||
std::string str_value = (value ? "yes" : "no");
|
||||
this->set(section_name, entry_name, str_value, is_virtual);
|
||||
}
|
||||
|
||||
void ConfigFile::set_float(const std::string& section_name, const std::string& entry_name, float value, bool is_virtual)
|
||||
{
|
||||
std::stringstream str_value;
|
||||
str_value << value;
|
||||
this->set(section_name, entry_name, str_value.str(), is_virtual);
|
||||
}
|
||||
|
||||
void ConfigFile::parse(FILE* file)
|
||||
{
|
||||
if (file == nullptr)
|
||||
return;
|
||||
|
||||
char line[512];
|
||||
std::string section;
|
||||
int cline = 0;
|
||||
while (true)
|
||||
{
|
||||
if (std::fgets(line, sizeof(line), file) == nullptr)
|
||||
break;
|
||||
cline++;
|
||||
std::string s(line);
|
||||
s = trim_ws(s, " \r\n");
|
||||
if (s.empty())
|
||||
continue;
|
||||
if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']')
|
||||
{
|
||||
section = s.substr(1, s.length() - 2);
|
||||
continue;
|
||||
}
|
||||
auto eqpos = s.find('=');
|
||||
if (eqpos == std::string::npos)
|
||||
{
|
||||
WARN_LOG(COMMON, "Malformed entry on config - ignoring line %d: %s", cline, s.c_str());
|
||||
continue;
|
||||
}
|
||||
std::string property = trim_ws(s.substr(0, eqpos));
|
||||
std::string value = trim_ws(s.substr(eqpos + 1));
|
||||
if (value.length() >= 2 && value[0] == '"' && value[value.length() - 1] == '"')
|
||||
value = value.substr(1, value.length() - 2);
|
||||
set(section, property, value);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigFile::save(FILE* file)
|
||||
{
|
||||
for (const auto& [section_name, section] : this->sections)
|
||||
{
|
||||
if (!section_name.empty())
|
||||
std::fprintf(file, "[%s]\n", section_name.c_str());
|
||||
|
||||
for (const auto& [entry_name, entry] : section.entries)
|
||||
{
|
||||
std::fprintf(file, "%s = %s\n", entry_name.c_str(), entry.get_string().c_str());
|
||||
}
|
||||
if (!section_name.empty())
|
||||
std::fputc('\n', file);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigFile::delete_section(const std::string& section_name) {
|
||||
sections.erase(section_name);
|
||||
virtual_sections.erase(section_name);
|
||||
}
|
||||
|
||||
void ConfigFile::delete_entry(const std::string& section_name, const std::string& entry_name)
|
||||
{
|
||||
ConfigSection *section = get_section(section_name, false);
|
||||
if (section != NULL)
|
||||
section->delete_entry(entry_name);
|
||||
}
|
||||
|
||||
bool ConfigFile::is_virtual(const std::string& section_name, const std::string& entry_name)
|
||||
{
|
||||
ConfigSection *section = get_section(section_name, true);
|
||||
if (section == nullptr)
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
if (!stricmp("yes", pValue->c_str())
|
||||
|| !stricmp("true", pValue->c_str())
|
||||
|| !stricmp("on", pValue->c_str())
|
||||
|| !stricmp("1", pValue->c_str()))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
return section->has_entry(entry_name);
|
||||
}
|
||||
|
||||
} // namespace emucfg
|
||||
static bool hasHexPrefix(const std::string& s)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (pos + 1 < s.length() && std::isspace(static_cast<unsigned char>(s[pos])))
|
||||
pos++;
|
||||
if (pos + 1 >= s.length())
|
||||
return false;
|
||||
return (s[pos] == '0' && (s[pos + 1] == 'x' || s[pos + 1] == 'X'));
|
||||
}
|
||||
|
||||
int IniFile::getInt(const std::string& section, const std::string& entry, int defaultValue) const
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
std::istringstream ss(*pValue);
|
||||
ss.imbue(std::locale::classic());
|
||||
if (hasHexPrefix(*pValue))
|
||||
ss.setf(std::ios_base::hex, std::ios_base::basefield);
|
||||
else
|
||||
ss.setf(std::ios_base::dec, std::ios_base::basefield);
|
||||
unsigned value = static_cast<unsigned>(defaultValue);
|
||||
ss >> value;
|
||||
if (ss)
|
||||
return static_cast<int>(value);
|
||||
// Compatibility for options migrated from Option<bool> to Option<int>.
|
||||
std::string token = *pValue;
|
||||
string_tolower(token);
|
||||
if (token == "yes" || token == "true" || token == "on")
|
||||
return 1;
|
||||
if (token == "no" || token == "false" || token == "off")
|
||||
return 0;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
int64_t IniFile::getInt64(const std::string& section, const std::string& entry, int64_t defaultValue) const
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
std::istringstream ss(*pValue);
|
||||
ss.imbue(std::locale::classic());
|
||||
if (hasHexPrefix(*pValue))
|
||||
ss.setf(std::ios_base::hex, std::ios_base::basefield);
|
||||
else
|
||||
ss.setf(std::ios_base::dec, std::ios_base::basefield);
|
||||
int64_t value = defaultValue;
|
||||
ss >> value;
|
||||
return value;
|
||||
}
|
||||
|
||||
float IniFile::getFloat(const std::string& section, const std::string& entry, float defaultValue) const
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr || pValue->empty())
|
||||
return defaultValue;
|
||||
std::istringstream ss(*pValue);
|
||||
ss.imbue(std::locale::classic());
|
||||
float value = defaultValue;
|
||||
ss >> value;
|
||||
return value;
|
||||
}
|
||||
|
||||
void IniFile::setRaw(const std::string& sectionName, const std::string& entryName, const std::string& value, bool transient)
|
||||
{
|
||||
Section& section = sections[sectionName];
|
||||
Entry& entry = section.entries[entryName];
|
||||
if (entry.name.empty())
|
||||
entry.name = entryName;
|
||||
if (transient) {
|
||||
entry.transientValue = value;
|
||||
entry.transient = true;
|
||||
}
|
||||
else {
|
||||
entry.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string handleEscapeSeq(const std::string& s)
|
||||
{
|
||||
std::string ret;
|
||||
ret.reserve(s.length());
|
||||
for (size_t i = 0; i < s.length(); i++)
|
||||
{
|
||||
char c = s[i];
|
||||
if (c == '\\' && i < s.length() - 1)
|
||||
{
|
||||
switch (s[++i])
|
||||
{
|
||||
case 'n':
|
||||
ret += '\n';
|
||||
break;
|
||||
case 't':
|
||||
ret += '\t';
|
||||
break;
|
||||
case 'r':
|
||||
ret += '\r';
|
||||
break;
|
||||
case 'f':
|
||||
ret += '\f';
|
||||
break;
|
||||
case '\\':
|
||||
ret += '\\';
|
||||
break;
|
||||
default:
|
||||
WARN_LOG(COMMON, "Unrecognized escape sequence [\\%c] in [%s]", s[i], s.c_str());
|
||||
ret += '\\';
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void IniFile::load(const std::string& data, bool cEscape)
|
||||
{
|
||||
std::istringstream fss(data);
|
||||
int cline = 1;
|
||||
std::string curSection;
|
||||
for (std::string line; std::getline(fss, line); cline++)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (pos < line.length() && std::isspace((int8_t)line[pos]))
|
||||
pos++;
|
||||
if (pos == line.length() || line[pos] == ';')
|
||||
// Ignore empty lines and comments
|
||||
continue;
|
||||
if (line[pos] == '[')
|
||||
{
|
||||
// Section
|
||||
size_t end = line.find(']', ++pos);
|
||||
if (end == line.npos) {
|
||||
WARN_LOG(COMMON, "Missing ']' character - ignoring line %d: %s", cline, line.c_str());
|
||||
continue;
|
||||
}
|
||||
curSection = line.substr(pos, end - pos);
|
||||
if (cEscape)
|
||||
curSection = handleEscapeSeq(curSection);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Entry
|
||||
pos = line.find('=', pos);
|
||||
if (pos == line.npos) {
|
||||
WARN_LOG(COMMON, "Malformed entry - ignoring line %d: %s", cline, line.c_str());
|
||||
continue;
|
||||
}
|
||||
std::string entry = trim_ws(line.substr(0, pos), " \t");
|
||||
if (entry.empty()) {
|
||||
WARN_LOG(COMMON, "Invalid empty entry name - ignoring line %d: %s", cline, line.c_str());
|
||||
continue;
|
||||
}
|
||||
std::string value = line.substr(pos + 1);
|
||||
// Space characters before and after the equal sign are for readability, and not part of the entry name or value
|
||||
if (!value.empty() && value[0] == ' ')
|
||||
value = value.substr(1);
|
||||
if (!value.empty() && value[0] == '"')
|
||||
{
|
||||
// quoted value
|
||||
size_t end = value.rfind('"');
|
||||
if (end == value.npos || end == 0)
|
||||
end = value.length();
|
||||
value = value.substr(1, end - 1);
|
||||
}
|
||||
if (cEscape) {
|
||||
entry = handleEscapeSeq(entry);
|
||||
value = handleEscapeSeq(value);
|
||||
}
|
||||
set(curSection, entry, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IniFile::load(hostfs::File *file, bool cEscape)
|
||||
{
|
||||
std::string data;
|
||||
for (;;)
|
||||
{
|
||||
char buffer[4096];
|
||||
int n = file->read(buffer, 1, sizeof(buffer));
|
||||
if (n <= 0)
|
||||
break;
|
||||
data += std::string(buffer, buffer + n);
|
||||
}
|
||||
load(data, cEscape);
|
||||
}
|
||||
|
||||
void IniFile::save(std::string& data) const
|
||||
{
|
||||
std::ostringstream ss;
|
||||
for (const auto& [sectionName, section] : sections)
|
||||
{
|
||||
bool sectionSaved = sectionName.empty();
|
||||
for (const auto& [name, entry] : section.entries)
|
||||
{
|
||||
if (entry.transient && entry.value.empty())
|
||||
// Don't save transient-only values
|
||||
continue;
|
||||
if (!sectionSaved) {
|
||||
ss << '[' << sectionName << ']' << std::endl;
|
||||
sectionSaved = true;
|
||||
}
|
||||
ss << name << " = " << entry.value << std::endl;
|
||||
}
|
||||
ss << std::endl;
|
||||
}
|
||||
data = ss.str();
|
||||
}
|
||||
|
||||
void IniFile::save(hostfs::File *file) const
|
||||
{
|
||||
std::string data;
|
||||
save(data);
|
||||
file->write(data.c_str(), 1, data.length());
|
||||
}
|
||||
|
||||
bool IniFile::hasSection(const std::string& section) const {
|
||||
return sections.count(section) != 0;
|
||||
}
|
||||
bool IniFile::hasEntry(const std::string& section, const std::string& name) const {
|
||||
return getEntry(section, name) != nullptr;
|
||||
}
|
||||
bool IniFile::isTransient(const std::string& section, const std::string& name) const
|
||||
{
|
||||
const Entry *entry = getEntry(section, name);
|
||||
if (entry == nullptr)
|
||||
return false;
|
||||
else
|
||||
return entry->transient;
|
||||
}
|
||||
|
||||
void IniFile::deleteSection(const std::string& section) {
|
||||
sections.erase(section);
|
||||
}
|
||||
void IniFile::deleteEntry(const std::string& sectionName, const std::string& entry)
|
||||
{
|
||||
auto it = sections.find(sectionName);
|
||||
if (it == sections.end())
|
||||
return;
|
||||
Section& section = it->second;
|
||||
section.entries.erase(entry);
|
||||
}
|
||||
|
||||
std::vector<std::string> IniFile::getEntryNames(const std::string& sectionName) const
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
auto it = sections.find(sectionName);
|
||||
if (it != sections.end())
|
||||
{
|
||||
const Section& section = it->second;
|
||||
for (const auto& [name, entry] : section.entries)
|
||||
v.push_back(name);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
+96
-53
@@ -1,62 +1,105 @@
|
||||
#pragma once
|
||||
/*
|
||||
Copyright 2025 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "oslib/storage.h"
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
|
||||
namespace emucfg {
|
||||
namespace config {
|
||||
|
||||
struct ConfigEntry {
|
||||
std::string value;
|
||||
const std::string& get_string() const;
|
||||
int get_int();
|
||||
bool get_bool();
|
||||
int64_t get_int64();
|
||||
float get_float();
|
||||
class IniFile
|
||||
{
|
||||
public:
|
||||
void load(hostfs::File *file, bool cEscape = false);
|
||||
void load(const std::string& data, bool cEscape = false);
|
||||
void save(hostfs::File *file) const;
|
||||
void save(std::string& data) const;
|
||||
bool hasSection(const std::string& section) const;
|
||||
bool hasEntry(const std::string& section, const std::string& name) const;
|
||||
bool isTransient(const std::string& section, const std::string& name) const;
|
||||
void deleteSection(const std::string& section);
|
||||
void deleteEntry(const std::string& section, const std::string& name);
|
||||
|
||||
template<typename T>
|
||||
void set(const std::string& section, const std::string& entry, T value, bool transient = false) {
|
||||
setRaw(section, entry, std::to_string(value), transient);
|
||||
}
|
||||
|
||||
const std::string& getString(const std::string& section, const std::string& entry, const std::string& defaultValue = {}) const;
|
||||
const std::string& get(const std::string& section, const std::string& entry, const std::string& defaultValue = {}) const {
|
||||
return getString(section, entry, defaultValue);
|
||||
}
|
||||
bool getBool(const std::string& section, const std::string& entry, bool defaultValue = false) const;
|
||||
int getInt(const std::string& section, const std::string& entry, int defaultValue = 0) const;
|
||||
int64_t getInt64(const std::string& section, const std::string& entry, int64_t defaultValue = 0ll) const;
|
||||
float getFloat(const std::string& section, const std::string& entry, float defaultValue = 0.f) const;
|
||||
|
||||
std::vector<std::string> getEntryNames(const std::string& section) const;
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
std::string name;
|
||||
std::string value;
|
||||
std::string transientValue;
|
||||
bool transient = false;
|
||||
};
|
||||
struct Section
|
||||
{
|
||||
std::string name;
|
||||
std::map<std::string, Entry> entries;
|
||||
};
|
||||
const Entry *getEntry(const std::string& section, const std::string& entry) const;
|
||||
const std::string *getRaw(const std::string& section, const std::string& name) const;
|
||||
void setRaw(const std::string& section, const std::string& name, const std::string& value, bool transient);
|
||||
|
||||
std::map<std::string, Section> sections;
|
||||
};
|
||||
|
||||
struct ConfigSection {
|
||||
std::map<std::string, ConfigEntry> entries;
|
||||
bool has_entry(const std::string& name);
|
||||
void set(const std::string& name, const std::string& value);
|
||||
void delete_entry(const std::string& name);
|
||||
ConfigEntry* get_entry(const std::string& name);
|
||||
};
|
||||
|
||||
struct ConfigFile {
|
||||
private:
|
||||
std::map<std::string, ConfigSection> sections;
|
||||
std::map<std::string, ConfigSection> virtual_sections;
|
||||
ConfigSection* add_section(const std::string& name, bool is_virtual);
|
||||
ConfigSection* get_section(const std::string& name, bool is_virtual);
|
||||
ConfigEntry* get_entry(const std::string& section_name, const std::string& entry_name);
|
||||
|
||||
|
||||
public:
|
||||
bool has_section(const std::string& name);
|
||||
bool has_entry(const std::string& section_name, const std::string& entry_name);
|
||||
bool is_virtual(const std::string& section_name, const std::string& entry_name);
|
||||
|
||||
void parse(FILE* file);
|
||||
void save(FILE* file);
|
||||
|
||||
/* getting values */
|
||||
std::string get(const std::string& section_name, const std::string& entry_name, const std::string& default_value = "");
|
||||
int get_int(const std::string& section_name, const std::string& entry_name, int default_value = 0);
|
||||
int64_t get_int64(const std::string& section_name, const std::string& entry_name, int64_t default_value = 0);
|
||||
bool get_bool(const std::string& section_name, const std::string& entry_name, bool default_value = false);
|
||||
float get_float(const std::string& section_name, const std::string& entry_name, float default_value = 0.f);
|
||||
/* setting values */
|
||||
void set(const std::string& section_name, const std::string& entry_name, const std::string& value, bool is_virtual = false);
|
||||
void set_int(const std::string& section_name, const std::string& entry_name, int value, bool is_virtual = false);
|
||||
void set_int64(const std::string& section_name, const std::string& entry_name, int64_t value, bool is_virtual = false);
|
||||
void set_bool(const std::string& section_name, const std::string& entry_name, bool value, bool is_virtual = false);
|
||||
void set_float(const std::string& section_name, const std::string& entry_name, float value, bool is_virtual = false);
|
||||
|
||||
void delete_section(const std::string& section_name);
|
||||
void delete_entry(const std::string& section_name, const std::string& entry_name);
|
||||
};
|
||||
|
||||
} // namespace emucfg
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, const std::string& value, bool transient) {
|
||||
setRaw(section, entry, value, transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, std::string value, bool transient) {
|
||||
setRaw(section, entry, value, transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, const char *value, bool transient) {
|
||||
setRaw(section, entry, value, transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, bool value, bool transient) {
|
||||
setRaw(section, entry, value ? "yes" : "no", transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, float value, bool transient)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
ss.precision(7);
|
||||
ss << value;
|
||||
setRaw(section, entry, ss.str(), transient);
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
+54
-5
@@ -17,7 +17,7 @@
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "option.h"
|
||||
#include "network/naomi_network.h"
|
||||
#include "network/net_handshake.h"
|
||||
#include "debug/gdb_server.h"
|
||||
|
||||
namespace config {
|
||||
@@ -33,6 +33,7 @@ Option<int> Cable("Dreamcast.Cable", 3); // TV Composite
|
||||
Option<int> Region("Dreamcast.Region", 1); // USA
|
||||
Option<int> Broadcast("Dreamcast.Broadcast", 0); // NTSC
|
||||
Option<int> Language("Dreamcast.Language", 1); // English
|
||||
OptionString UILanguage("UILanguage", "");
|
||||
Option<bool> AutoLoadState("Dreamcast.AutoLoadState");
|
||||
Option<bool> AutoSaveState("Dreamcast.AutoSaveState");
|
||||
Option<int, false> SavestateSlot("Dreamcast.SavestateSlot");
|
||||
@@ -40,6 +41,7 @@ Option<bool> ForceFreePlay("ForceFreePlay", true);
|
||||
Option<bool, false> FetchBoxart("FetchBoxart", true);
|
||||
Option<bool, false> BoxartDisplayMode("BoxartDisplayMode", true);
|
||||
Option<int, false> UIScaling("UIScaling", 100);
|
||||
Option<int, false> UITheme("UITheme", 0);
|
||||
|
||||
// Sound
|
||||
|
||||
@@ -71,12 +73,16 @@ Option<bool> ShowFPS("rend.ShowFPS");
|
||||
Option<bool> RenderToTextureBuffer("rend.RenderToTextureBuffer");
|
||||
Option<bool> TranslucentPolygonDepthMask("rend.TranslucentPolygonDepthMask");
|
||||
Option<bool> ModifierVolumes("rend.ModifierVolumes", true);
|
||||
Option<int> TextureUpscale("rend.TextureUpscale", 1);
|
||||
Option<int> TextureUpscale("rend.TextureUpscale2", 1);
|
||||
Option<int> MaxFilteredTextureSize("rend.MaxFilteredTextureSize", 256);
|
||||
Option<float> ExtraDepthScale("rend.ExtraDepthScale", 1.f);
|
||||
Option<bool> CustomTextures("rend.CustomTextures");
|
||||
Option<int> PreloadCustomTextures("rend.PreloadCustomTextures", 0);
|
||||
Option<bool> DumpTextures("rend.DumpTextures");
|
||||
Option<bool> DumpUniqueTextures("rend.DumpUniqueTextures");
|
||||
Option<bool> DumpReplacedTextures("rend.DumpReplacedTextures");
|
||||
Option<int> ScreenStretching("rend.ScreenStretching", 100);
|
||||
Option<bool> StretchToFill("rend.StretchToFill");
|
||||
Option<bool> Fog("rend.Fog", true);
|
||||
Option<bool> FloatVMUs("rend.FloatVMUs");
|
||||
Option<bool> Rotate90("rend.Rotate90");
|
||||
@@ -98,6 +104,8 @@ Option<int> SkipFrame("ta.skip");
|
||||
Option<int> MaxThreads("pvr.MaxThreads", 3);
|
||||
Option<int> AutoSkipFrame("pvr.AutoSkipFrame", 0);
|
||||
Option<int> RenderResolution("rend.Resolution", 480);
|
||||
Option<bool> IntegerScale("rend.IntegerScale", false);
|
||||
Option<bool> LinearInterpolation("rend.LinearInterpolation", true);
|
||||
Option<bool> VSync("rend.vsync", true);
|
||||
Option<int64_t> PixelBufferSize("rend.PixelBufferSize", 512_MB);
|
||||
Option<int> AnisotropicFiltering("rend.AnisotropicFiltering", 1);
|
||||
@@ -113,6 +121,21 @@ Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", false);
|
||||
Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
|
||||
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
|
||||
Option<bool> CustomGpuDriver("rend.CustomGpuDriver", false);
|
||||
Option<bool> FramePacing("rend.FramePacing", true);
|
||||
|
||||
CustomTexturePreloadMode customTexturePreloadMode()
|
||||
{
|
||||
const auto mode = static_cast<CustomTexturePreloadMode>(PreloadCustomTextures.get());
|
||||
switch (mode)
|
||||
{
|
||||
case CustomTexturePreloadMode::Off:
|
||||
case CustomTexturePreloadMode::SystemMemory:
|
||||
case CustomTexturePreloadMode::VideoMemory:
|
||||
return mode;
|
||||
default:
|
||||
return CustomTexturePreloadMode::Off;
|
||||
}
|
||||
}
|
||||
#ifdef VIDEO_ROUTING
|
||||
Option<bool, false> VideoRouting("rend.VideoRouting", false);
|
||||
Option<bool, false> VideoRoutingScale("rend.VideoRoutingScale", false);
|
||||
@@ -133,6 +156,15 @@ Option<bool> RamMod32MB("Dreamcast.RamMod32MB", false);
|
||||
Option<bool> OpenGlChecks("OpenGlChecks", false, "validate");
|
||||
|
||||
Option<std::vector<std::string>, false> ContentPath("Dreamcast.ContentPath");
|
||||
Option<std::vector<std::string>, false> BiosPath("Dreamcast.BiosPath");
|
||||
Option<std::string, false> VMUPath("Dreamcast.VMUPath");
|
||||
Option<std::vector<std::string>, false> SavestatePath("Dreamcast.SavestatePath");
|
||||
Option<std::string, false> SavePath("Dreamcast.SavePath");
|
||||
Option<std::vector<std::string>, false> TexturePath("Dreamcast.TexturePath");
|
||||
Option<std::string, false> TextureDumpPath("Dreamcast.TextureDumpPath");
|
||||
Option<std::string, false> BoxartPath("Dreamcast.BoxartPath");
|
||||
Option<std::vector<std::string>, false> MappingsPath("Dreamcast.MappingsPath");
|
||||
Option<std::vector<std::string>, false> CheatPath("Dreamcast.CheatPath");
|
||||
Option<bool, false> HideLegacyNaomiRoms("Dreamcast.HideLegacyNaomiRoms", true);
|
||||
Option<bool, false> UploadCrashLogs("UploadCrashLogs", true);
|
||||
Option<bool, false> DiscordPresence("DiscordPresence", true);
|
||||
@@ -151,9 +183,10 @@ Option<float> ProfilerFrameWarningTime("Profiler.FrameWarningTime", 1.0f / 55.0f
|
||||
|
||||
Option<bool> NetworkEnable("Enable", false, "network");
|
||||
Option<bool> ActAsServer("ActAsServer", false, "network");
|
||||
Option<bool> NaomiSatellite("NaomiSatellite", false, "network");
|
||||
OptionString DNS("DNS", "dns.flyca.st", "network");
|
||||
OptionString NetworkServer("server", "", "network");
|
||||
Option<int> LocalPort("LocalPort", NaomiNetwork::SERVER_PORT, "network");
|
||||
Option<int> LocalPort("LocalPort", defaultNaomiServerPort(), "network");
|
||||
Option<bool> EmulateBBA("EmulateBBA", false, "network");
|
||||
Option<bool> EnableUPnP("EnableUPnP", true, "network");
|
||||
Option<bool> GGPOEnable("GGPO", false, "network");
|
||||
@@ -166,7 +199,7 @@ Option<int> GGPOChatTimeout("GGPOChatTimeout", 10, "network");
|
||||
Option<bool> NetworkOutput("NetworkOutput", false, "network");
|
||||
Option<int> MultiboardSlaves("MultiboardSlaves", 1, "network");
|
||||
Option<bool> BattleCableEnable("BattleCable", false, "network");
|
||||
Option<bool> UseDCNet("DCNet", false, "network");
|
||||
Option<bool> UseDCNet("DCNet", true, "network");
|
||||
OptionString ISPUsername("ISPUsername", "flycast1", "network");
|
||||
|
||||
#ifdef USE_OMX
|
||||
@@ -199,7 +232,21 @@ std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices {{
|
||||
{{Option<MapleDeviceType>("device4.1", MDT_None, "input"),
|
||||
Option<MapleDeviceType>("device4.2", MDT_None, "input")}},
|
||||
}};
|
||||
Option<bool> PerGameVmu("PerGameVmu", false, "config");
|
||||
|
||||
std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices{{
|
||||
{{Option<int>("device1.1.net", 0, "input"),
|
||||
Option<int>("device1.2.net", 0, "input")}},
|
||||
|
||||
{{Option<int>("device2.1.net", 0, "input"),
|
||||
Option<int>("device2.2.net", 0, "input")}},
|
||||
|
||||
{{Option<int>("device3.1.net", 0, "input"),
|
||||
Option<int>("device3.2.net", 0, "input")}},
|
||||
|
||||
{{Option<int>("device4.1.net", 0, "input"),
|
||||
Option<int>("device4.2.net", 0, "input")}},
|
||||
}};
|
||||
Option<bool> PerGameVmu("PerGameVmu", true, "config");
|
||||
#ifdef _WIN32
|
||||
Option<bool, false> UseRawInput("RawInput", false, "input");
|
||||
#endif
|
||||
@@ -212,8 +259,10 @@ Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
|
||||
// RetroAchievements
|
||||
|
||||
Option<bool> EnableAchievements("Enabled", false, "achievements");
|
||||
Option<bool> ShowAchievementPopups("Show", true, "achievements");
|
||||
Option<bool> AchievementsHardcoreMode("HardcoreMode", false, "achievements");
|
||||
OptionString AchievementsUserName("UserName", "", "achievements");
|
||||
OptionString AchievementsToken("Token", "", "achievements");
|
||||
OptionString AchievementsHostUrl("HostUrl", "", "achievements");
|
||||
|
||||
} // namespace config
|
||||
+52
-23
@@ -17,16 +17,16 @@
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
#include "cfg.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#ifdef LIBRETRO
|
||||
#include <libretro.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace config {
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
{
|
||||
if (gameId.empty())
|
||||
return;
|
||||
if (!cfgHasSection(gameId))
|
||||
if (!hasSection(gameId))
|
||||
return;
|
||||
perGameConfig = true;
|
||||
}
|
||||
@@ -65,10 +65,10 @@ public:
|
||||
}
|
||||
|
||||
void save() {
|
||||
cfgSetAutoSave(false);
|
||||
setAutoSave(false);
|
||||
for (const auto& o : options)
|
||||
o->save();
|
||||
cfgSetAutoSave(true);
|
||||
setAutoSave(true);
|
||||
}
|
||||
|
||||
void setGameId(const std::string& gameId) {
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
this->perGameConfig = perGameConfig;
|
||||
if (!perGameConfig) {
|
||||
if (!gameId.empty())
|
||||
cfgDeleteSection(gameId);
|
||||
deleteSection(gameId);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
else
|
||||
{
|
||||
set(doLoad(section, name));
|
||||
if (cfgIsVirtual(section, name))
|
||||
if (isTransient(section, name))
|
||||
override(value);
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public:
|
||||
if (value == doLoad(section, name))
|
||||
{
|
||||
// delete existing per-game option if any
|
||||
cfgDeleteEntry(settings.gameId, section + "." + name);
|
||||
deleteEntry(settings.gameId, section + "." + name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -173,14 +173,14 @@ protected:
|
||||
std::enable_if_t<std::is_same_v<U, bool>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return cfgLoadBool(section, name, value);
|
||||
return loadBool(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same<U, int64_t>::value, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return cfgLoadInt64(section, name, value);
|
||||
return loadInt64(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
@@ -188,28 +188,28 @@ protected:
|
||||
&& !std::is_same_v<U, bool> && !std::is_same_v<U, int64_t>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return (T)cfgLoadInt(section, name, (int)value);
|
||||
return (T)loadInt(section, name, (int)value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<U, std::string>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return cfgLoadStr(section, name, value);
|
||||
return loadStr(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<float, U>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return cfgLoadFloat(section, name, value);
|
||||
return loadFloat(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<std::vector<std::string>, U>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
std::string paths = cfgLoadStr(section, name, "");
|
||||
std::string paths = loadStr(section, name);
|
||||
if (paths.empty())
|
||||
return value;
|
||||
std::string::size_type start = 0;
|
||||
@@ -268,14 +268,14 @@ protected:
|
||||
std::enable_if_t<std::is_same_v<U, bool>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
cfgSaveBool(section, name, value);
|
||||
saveBool(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same<U, int64_t>::value>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
cfgSaveInt64(section, name, value);
|
||||
saveInt64(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
@@ -283,21 +283,21 @@ protected:
|
||||
&& !std::is_same_v<U, bool> && !std::is_same_v<U, int64_t>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
cfgSaveInt(section, name, (int)value);
|
||||
saveInt(section, name, (int)value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<U, std::string>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
cfgSaveStr(section, name, value);
|
||||
saveStr(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<float, U>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
cfgSaveFloat(section, name, value);
|
||||
saveFloat(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
@@ -332,7 +332,7 @@ protected:
|
||||
else
|
||||
s += v;
|
||||
}
|
||||
cfgSaveStr(section, name, s);
|
||||
saveStr(section, name, s);
|
||||
}
|
||||
|
||||
std::string section;
|
||||
@@ -360,6 +360,7 @@ extern Option<int> Cable; // 0 -> VGA, 1 -> VGA, 2 -> RGB, 3 -> TV Composite
|
||||
extern Option<int> Region; // 0 -> JP, 1 -> USA, 2 -> EU, 3 -> default
|
||||
extern Option<int> Broadcast; // 0 -> NTSC, 1 -> PAL, 2 -> PAL/M, 3 -> PAL/N, 4 -> default
|
||||
extern Option<int> Language; // 0 -> JP, 1 -> EN, 2 -> DE, 3 -> FR, 4 -> SP, 5 -> IT, 6 -> default
|
||||
extern OptionString UILanguage;
|
||||
extern Option<bool> AutoLoadState;
|
||||
extern Option<bool> AutoSaveState;
|
||||
extern Option<int, false> SavestateSlot;
|
||||
@@ -367,6 +368,7 @@ extern Option<bool> ForceFreePlay;
|
||||
extern Option<bool, false> FetchBoxart;
|
||||
extern Option<bool, false> BoxartDisplayMode;
|
||||
extern Option<int, false> UIScaling;
|
||||
extern Option<int, false> UITheme; // 0 -> Dark, 1 -> Light, 2 -> Dreamcast, 3 -> High Contrast, 4 -> Nintendo, 5 -> Aqua Chill
|
||||
|
||||
// Sound
|
||||
|
||||
@@ -445,8 +447,19 @@ extern Option<int> PerPixelLayers;
|
||||
#endif
|
||||
extern Option<float> ExtraDepthScale;
|
||||
extern Option<bool> CustomTextures;
|
||||
enum class CustomTexturePreloadMode : int
|
||||
{
|
||||
Off = 0,
|
||||
SystemMemory = 1,
|
||||
VideoMemory = 2,
|
||||
};
|
||||
extern Option<int> PreloadCustomTextures;
|
||||
CustomTexturePreloadMode customTexturePreloadMode();
|
||||
extern Option<bool> DumpTextures;
|
||||
extern Option<bool> DumpUniqueTextures;
|
||||
extern Option<bool> DumpReplacedTextures;
|
||||
extern Option<int> ScreenStretching; // in percent. 150 means stretch from 4/3 to 6/3
|
||||
extern Option<bool> StretchToFill;
|
||||
extern Option<bool> Fog;
|
||||
extern Option<bool> FloatVMUs;
|
||||
extern Option<bool> Rotate90;
|
||||
@@ -459,6 +472,8 @@ extern Option<int> SkipFrame;
|
||||
extern Option<int> MaxThreads;
|
||||
extern Option<int> AutoSkipFrame; // 0: none, 1: some, 2: more
|
||||
extern Option<int> RenderResolution;
|
||||
extern Option<bool> IntegerScale;
|
||||
extern Option<bool> LinearInterpolation;
|
||||
extern Option<bool> VSync;
|
||||
extern Option<int64_t> PixelBufferSize;
|
||||
extern Option<int> AnisotropicFiltering;
|
||||
@@ -469,6 +484,7 @@ extern Option<bool> NativeDepthInterpolation;
|
||||
extern Option<bool> EmulateFramebuffer;
|
||||
extern Option<bool> FixUpscaleBleedingEdge;
|
||||
extern Option<bool> CustomGpuDriver;
|
||||
extern Option<bool> FramePacing;
|
||||
#ifdef VIDEO_ROUTING
|
||||
extern Option<bool, false> VideoRouting;
|
||||
extern Option<bool, false> VideoRoutingScale;
|
||||
@@ -489,6 +505,15 @@ extern Option<bool> RamMod32MB;
|
||||
extern Option<bool> OpenGlChecks;
|
||||
|
||||
extern Option<std::vector<std::string>, false> ContentPath;
|
||||
extern Option<std::vector<std::string>, false> BiosPath;
|
||||
extern Option<std::string, false> VMUPath;
|
||||
extern Option<std::vector<std::string>, false> SavestatePath;
|
||||
extern Option<std::string, false> SavePath;
|
||||
extern Option<std::vector<std::string>, false> TexturePath;
|
||||
extern Option<std::string, false> TextureDumpPath;
|
||||
extern Option<std::string, false> BoxartPath;
|
||||
extern Option<std::vector<std::string>, false> MappingsPath;
|
||||
extern Option<std::vector<std::string>, false> CheatPath;
|
||||
extern Option<bool, false> HideLegacyNaomiRoms;
|
||||
extern Option<bool, false> UploadCrashLogs;
|
||||
extern Option<bool, false> DiscordPresence;
|
||||
@@ -507,6 +532,7 @@ extern Option<float> ProfilerFrameWarningTime;
|
||||
|
||||
extern Option<bool> NetworkEnable;
|
||||
extern Option<bool> ActAsServer;
|
||||
extern Option<bool> NaomiSatellite;
|
||||
extern OptionString DNS;
|
||||
extern OptionString NetworkServer;
|
||||
extern Option<int> LocalPort;
|
||||
@@ -537,6 +563,7 @@ extern Option<int> VirtualGamepadVibration;
|
||||
extern Option<int> VirtualGamepadTransparency;
|
||||
extern std::array<Option<MapleDeviceType>, 4> MapleMainDevices;
|
||||
extern std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices;
|
||||
extern std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices;
|
||||
extern Option<bool> PerGameVmu;
|
||||
#ifdef _WIN32
|
||||
extern Option<bool, false> UseRawInput;
|
||||
@@ -552,8 +579,10 @@ extern Option<std::string, false> LuaFileName;
|
||||
// RetroAchievements
|
||||
|
||||
extern Option<bool> EnableAchievements;
|
||||
extern Option<bool> ShowAchievementPopups;
|
||||
extern Option<bool> AchievementsHardcoreMode;
|
||||
extern OptionString AchievementsUserName;
|
||||
extern OptionString AchievementsToken;
|
||||
extern OptionString AchievementsHostUrl;
|
||||
|
||||
} // namespace config
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <libchdr/chd.h>
|
||||
|
||||
#include "oslib/storage.h"
|
||||
|
||||
namespace CHD
|
||||
{
|
||||
namespace Callbacks
|
||||
{
|
||||
// Moved out here to avoid being duplicated by the template.
|
||||
inline uint64_t fsize(struct chd_core_file *core_file)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
return file->size();
|
||||
}
|
||||
|
||||
inline size_t fread(void* buffer, size_t size, size_t count, struct chd_core_file *core_file)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
return file->read(buffer, size, count);
|
||||
}
|
||||
|
||||
inline int fclose(struct chd_core_file *core_file)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
delete file;
|
||||
delete core_file;
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int fseek(struct chd_core_file *core_file, int64_t offset, int whence)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
return file->seek(offset, whence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Ts>
|
||||
inline auto chd_open_file(hostfs::File *file, Ts &&...args)
|
||||
{
|
||||
auto core_file = new struct chd_core_file();
|
||||
|
||||
core_file->argp = file;
|
||||
core_file->fsize = CHD::Callbacks::fsize;
|
||||
core_file->fread = CHD::Callbacks::fread;
|
||||
core_file->fclose = CHD::Callbacks::fclose;
|
||||
core_file->fseek = CHD::Callbacks::fseek;
|
||||
|
||||
return chd_open_core_file(core_file, std::forward<Ts>(args)...);
|
||||
}
|
||||
+126
-71
@@ -28,6 +28,12 @@
|
||||
#include "cfg/option.h"
|
||||
#include "emulator.h"
|
||||
#include "oslib/storage.h"
|
||||
#include "oslib/i18n.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace i18n;
|
||||
|
||||
const WidescreenCheat CheatManager::widescreen_cheats[] =
|
||||
{
|
||||
@@ -76,14 +82,15 @@ const WidescreenCheat CheatManager::widescreen_cheats[] =
|
||||
{ "MK-51136", nullptr, { 0x2BDDD0 }, { 0x43700000 } }, // Crazy Taxi 2 (USA)
|
||||
// { "HDR-0159", nullptr, { 0x2FBBD0 }, { 0x43700000 } }, // Crazy Taxi 2 (JP) not working
|
||||
{ "T13004N", nullptr, { 0x016D94 }, { 0x44234E73 } }, // Cyber Troopers - Virtual On - Oratorio Tangram (USA)
|
||||
{ "HDR-0040", nullptr, { 0x016D94 }, { 0x44234E73 } }, // Cyber Troopers - Virtual On - Oratorio Tangram (JP)
|
||||
// D2 (USA)
|
||||
{ "MK-51036", nullptr, { 0x4B5CF4, 0x4B5CC4, 0x3E92A0, 0x3E92A8, 0x3E92C0, 0x3E92C8 },
|
||||
{ 0x3F400000, 0x43F00000, 0, 0, 0, 0 } },
|
||||
// D2 (JP)
|
||||
{ "T30006M", nullptr, { 0x4CF42C, 0x4CF45C, 0x3E1A36, 0x3E1A34, 0x3E1A3C, 0x3E1A54, 0x3E1A5C },
|
||||
{ 0x43F00000, 0x3F400000, 0x08010000, 0, 0, 0, 0 } },
|
||||
{ "MK-5103750", nullptr, { 0x1FE270 }, { 0x43700000 } }, // Daytona USA (PAL)
|
||||
// breaks online connection { "MK-51037", nullptr, { 0x1FC6D0 }, { 0x43700000 } }, // Daytona USA (USA)
|
||||
{ "MK-5103750", nullptr, { 0x1FE270 }, { 0x43700000 }, { 0x43a00000 } }, // Daytona USA (PAL)
|
||||
{ "MK-51037", nullptr, { 0x1FC6D0 }, { 0x43700000 }, { 0x43a00000 } }, // Daytona USA (USA)
|
||||
{ "T9501N-50", nullptr, { 0x9821D4 }, { 0x3F400000 } }, // Deadly Skies (PAL)
|
||||
{ "T8116D 50", nullptr, { 0x2E5530 }, { 0x43700000 } }, // Dead or Alive 2 (PAL)
|
||||
{ "T3601N", nullptr, { 0x2F0670 }, { 0x43700000 } }, // Dead or Alive 2 (USA)
|
||||
@@ -169,6 +176,7 @@ const WidescreenCheat CheatManager::widescreen_cheats[] =
|
||||
// { "T-9502D-50", nullptr, { 0xBDE9B0, 0xBDE9C4 }, { 0x3F400000, 0x3FA00000 } }, // Nightmare Creatures II (PAL)
|
||||
{ "MK-5110250", nullptr, { 0x87B5A4 }, { 0x43700000 } }, // Outtrigger (PAL)
|
||||
{ "HDR-0118", nullptr, { 0x83E284 }, { 0x43700000 } }, // Outtrigger (JP)
|
||||
{ "MK-51102", nullptr, { 0x83E284 }, { 0x43700000 } }, // Outtrigger (US)
|
||||
{ "T15103D 50", nullptr, { 0x1EEE78 }, { 0x3F400000 } }, // PenPen (PAL)
|
||||
{ "T17001M", nullptr, { 0x1C3828 }, { 0x3F400000 } }, // PenPen TriIcelon (JP)
|
||||
{ "MK-5110050", nullptr, { 0x548E04, 0x0923C0 }, { 0x43E80000, 0x3F966666 } }, // Phantasy Star Online (PAL) TODO
|
||||
@@ -235,10 +243,10 @@ const WidescreenCheat CheatManager::widescreen_cheats[] =
|
||||
{ "MK-5105950", nullptr, { 0x231EF8, 0x1EF370 }, { 0x43800000, 0x7C1EF400 } }, // Shenmue (PAL) code 1 reduces clipping
|
||||
{ "MK-51059", nullptr, { 0x230250 }, { 0x43800000 }, { 0x43a00000 } }, // Shenmue (USA) Clipping
|
||||
{ "HDR-0016", nullptr, { 0x22E8A0, 0x1EBE70 }, { 0x43800000, 0x7C1EBF00 } }, // Shenmue (JP) code 1 reduces clipping
|
||||
{ "MK-5118450", nullptr, { 0x31186C }, { 0x43800000 } }, // Shenmue II (PAL) 01160FF4 0000E100 for black bars in cutscenes
|
||||
{ "MK-5118450", nullptr, { 0x31186C }, { 0x43800000 }, { 0x43a00000 } }, // Shenmue II (PAL) 01160FF4 0000E100 for black bars in cutscenes
|
||||
// Shenmue II (PAL) Alternative code without clipping or black bars. Might be demanding on real hardware.
|
||||
// 02311880 C3A00000, 0227E198 35AA359E, 01160FF4 0000E100
|
||||
{ "HDR-0164", nullptr, { 0x30D67C }, { 0x43700000 } }, // Shenmue II (JP) Clipping
|
||||
{ "HDR-0164", nullptr, { 0x30D67C }, { 0x43700000 }, { 0x43a00000 } }, // Shenmue II (JP) Clipping
|
||||
{ "T9505D", nullptr, { 0xD1FB14, 0x096A4C }, { 0x3F400000, 0x3FAAAAAB } }, // Silent Scope (PAL) Choose 60Hz in game options
|
||||
{ "MK-51052", " E ", { 0x502A84 }, { 0x3F400000 } }, // Skies of Arcadia (PAL)
|
||||
{ "MK-51052", " U ", { 0x599158 }, { 0x3F400000 } }, // Skies of Arcadia (USA)
|
||||
@@ -388,24 +396,24 @@ void CheatManager::loadCheatFile(const std::string& filename)
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* cheatfile = hostfs::storage().openFile(filename, "r");
|
||||
hostfs::File* cheatfile = hostfs::storage().openFile(filename, "r");
|
||||
if (cheatfile == nullptr)
|
||||
{
|
||||
WARN_LOG(COMMON, "Cannot open cheat file '%s'", filename.c_str());
|
||||
return;
|
||||
}
|
||||
emucfg::ConfigFile cfg;
|
||||
cfg.parse(cheatfile);
|
||||
fclose(cheatfile);
|
||||
config::IniFile cfg;
|
||||
cfg.load(cheatfile);
|
||||
delete cheatfile;
|
||||
|
||||
int count = cfg.get_int("", "cheats", 0);
|
||||
int count = cfg.getInt("", "cheats", 0);
|
||||
cheats.clear();
|
||||
for (int i = 0; count == 0 || i < count; i++)
|
||||
{
|
||||
std::string prefix = "cheat" + std::to_string(i) + "_";
|
||||
Cheat cheat{};
|
||||
cheat.description = cfg.get("", prefix + "desc", "Cheat " + std::to_string(i + 1));
|
||||
cheat.address = cfg.get_int("", prefix + "address", -1);
|
||||
cheat.description = cfg.getString("", prefix + "desc", "Cheat " + std::to_string(i + 1));
|
||||
cheat.address = cfg.getInt("", prefix + "address", -1);
|
||||
if (count == 0 && cheat.address == (u32)-1)
|
||||
break;
|
||||
if (cheat.address >= RAM_SIZE)
|
||||
@@ -413,27 +421,27 @@ void CheatManager::loadCheatFile(const std::string& filename)
|
||||
WARN_LOG(COMMON, "Invalid address %x", cheat.address);
|
||||
continue;
|
||||
}
|
||||
cheat.type = (Cheat::Type)cfg.get_int("", prefix + "cheat_type", (int)Cheat::Type::disabled);
|
||||
cheat.size = 1 << cfg.get_int("", prefix + "memory_search_size", 0);
|
||||
cheat.value = cfg.get_int("", prefix + "value", cheat.value);
|
||||
cheat.repeatCount = cfg.get_int("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cheat.repeatValueIncrement = cfg.get_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cheat.repeatAddressIncrement = cfg.get_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
cheat.enabled = cfg.get_bool("", prefix + "enable", false);
|
||||
cheat.destAddress = cfg.get_int("", prefix + "dest_address", 0);
|
||||
cheat.type = (Cheat::Type)cfg.getInt("", prefix + "cheat_type", (int)Cheat::Type::disabled);
|
||||
cheat.size = 1 << cfg.getInt("", prefix + "memory_search_size", 0);
|
||||
cheat.value = cfg.getInt("", prefix + "value", cheat.value);
|
||||
cheat.repeatCount = cfg.getInt("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cheat.repeatValueIncrement = cfg.getInt("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cheat.repeatAddressIncrement = cfg.getInt("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
cheat.enabled = cfg.getBool("", prefix + "enable", false);
|
||||
cheat.destAddress = cfg.getInt("", prefix + "dest_address", 0);
|
||||
if (cheat.destAddress >= RAM_SIZE)
|
||||
{
|
||||
WARN_LOG(COMMON, "Invalid address %x", cheat.destAddress);
|
||||
continue;
|
||||
}
|
||||
cheat.valueMask = cfg.get_int("", prefix + "address_bit_position", 0);
|
||||
cheat.valueMask = cfg.getInt("", prefix + "address_bit_position", 0);
|
||||
if (cheat.type != Cheat::Type::disabled)
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
setActive(!cheats.empty());
|
||||
INFO_LOG(COMMON, "%d cheats loaded", (int)cheats.size());
|
||||
if (!cheats.empty())
|
||||
cfgSaveStr("cheats", gameId, filename);
|
||||
config::saveStr("cheats", gameId, filename);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -445,13 +453,42 @@ void CheatManager::reset(const std::string& gameId)
|
||||
cheats.clear();
|
||||
setActive(false);
|
||||
this->gameId = gameId;
|
||||
|
||||
#ifndef LIBRETRO
|
||||
if (!settings.raHardcoreMode)
|
||||
if (!settings.raHardcoreMode && !gameId.empty())
|
||||
{
|
||||
std::string cheatFile = cfgLoadStr("cheats", gameId, "");
|
||||
std::string cheatFile = config::loadStr("cheats", gameId);
|
||||
if (!cheatFile.empty())
|
||||
loadCheatFile(cheatFile);
|
||||
else
|
||||
{
|
||||
// Try to auto-locate a cheat file in user-defined CheatPath
|
||||
std::string romName = settings.content.fileName;
|
||||
const char* exts[] = { ".cht", ".txt" };
|
||||
for (const auto& base : config::CheatPath.get())
|
||||
{
|
||||
if (base.empty())
|
||||
continue;
|
||||
for (const char* ext : exts)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string candidate = hostfs::storage().getSubPath(base, romName + ext);
|
||||
if (hostfs::storage().exists(candidate))
|
||||
{
|
||||
loadCheatFile(candidate);
|
||||
config::saveStr("cheats", gameId, candidate);
|
||||
goto found_cheats;
|
||||
}
|
||||
}
|
||||
catch (const hostfs::StorageException&)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
found_cheats:
|
||||
#endif
|
||||
size_t cheatCount = cheats.size();
|
||||
if (gameId == "Fixed BOOT strapper") // Extreme Hunting 2
|
||||
@@ -472,12 +509,6 @@ void CheatManager::reset(const std::string& gameId)
|
||||
else if (gameId == "T8120D 50") { // Dave Mirra BMX (EU)
|
||||
cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x003011cc, 0x42200000, true); // 40.0 ms
|
||||
}
|
||||
else if (gameId == "MK-0100") { // F355 US
|
||||
cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00131668, 1000, true);
|
||||
}
|
||||
else if (gameId == "T8118D 50") { // F355 EU
|
||||
cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00135588, 1000, true);
|
||||
}
|
||||
else if (gameId == "SAMURAI SPIRITS 6" || gameId == "T0002M") {
|
||||
cheats.emplace_back(Cheat::Type::setValue, "fix depth", true, 16, 0x0003e602, 0x0009, true); // nop (shift by 8 bits instead of 10)
|
||||
}
|
||||
@@ -530,18 +561,42 @@ void CheatManager::reset(const std::string& gameId)
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0013f150, 0x2fd62fe6, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0013f150, 0xe000000b, true);
|
||||
}
|
||||
else if (gameId == "HDR-0124") { // Hundred Swords
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x006558ac, 0x1f414f22, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x006558ac, 0xe000000b, true);
|
||||
}
|
||||
else if (gameId == "T43903M") { // Culdcept II
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x00800524, 0x2fd62fe6, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x00800524, 0xe000000b, true);
|
||||
}
|
||||
else if (gameId == "T40214N") { // The Next Tetris (US)
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass speed ifeq", true, 32, 0x0016d5d4, 0x2f862fe6, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass speed check", true, 32, 0x0016d5d4, 0xe001000b, true);
|
||||
else if (gameId == "HDR-0118") { // Outtrigger (JP)
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x00139f54, 0x2fd62fe6, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x00139f54, 0xe000000b, true);
|
||||
}
|
||||
else if (gameId == "HDR-0100") // F355 (JP)
|
||||
{
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0ba980, 0x2fd62fe6, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0ba980, 0xe000000b, true);
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth check ifeq", true, 32, 0x0a2d3c, 0xed048900, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth check", true, 32, 0x0a2d3c, 0xed048b00, true); // BT -> BF
|
||||
}
|
||||
else if (gameId == "MK-51140") { // Ooga Booga
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "disable net check ifeq", true, 16, 0x00085d2c, 0x3630, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "disable net sync check", true, 16, 0x00085d2c, 0x3330, true);
|
||||
}
|
||||
else if (gameId == "HDR-0006") { // Nettou Golf
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0d5fbc, 0x2fd62fe6, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0d5fbc, 0xe000000b, true);
|
||||
}
|
||||
else if (gameId == "SEGA STRIKE FIGHTER IN JPN-SLAVE")
|
||||
{
|
||||
// hack the slave sync procedure
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "easy sync ifeq", true, 32, 0x08b66c, 0x40600000, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "easy sync", true, 32, 0x08b66c, 0x4cbebc20, true); // 3.5 -> 100000000
|
||||
}
|
||||
else if (gameId == "AIRLINE PILOTS IN JAPAN-SLAVE")
|
||||
{
|
||||
// hack the slave sync procedure
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "easy sync ifeq", true, 32, 0x052ee0, 0x40200000, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "easy sync", true, 32, 0x052ee0, 0x4cbebc20, true); // 2.5 -> 100000000
|
||||
}
|
||||
|
||||
|
||||
if (cheats.size() > cheatCount)
|
||||
setActive(true);
|
||||
@@ -722,12 +777,12 @@ static std::vector<u32> parseCodes(const std::string& s)
|
||||
}
|
||||
}
|
||||
else if (!curCode.empty())
|
||||
throw FlycastException("Invalid cheat code");
|
||||
throw FlycastException(Ts("Invalid cheat code"));
|
||||
}
|
||||
if (!curCode.empty())
|
||||
{
|
||||
if (curCode.length() != 8)
|
||||
throw FlycastException("Invalid cheat code");
|
||||
throw FlycastException(Ts("Invalid cheat code"));
|
||||
codes.push_back(strtoul(curCode.c_str(), nullptr, 16));
|
||||
}
|
||||
|
||||
@@ -809,7 +864,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// 8/16/32-bit write
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
throw FlycastException(Ts("Missing value"));
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = code == 0 ? 8 : code == 1 ? 16 : 32;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
@@ -827,7 +882,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
// Group write
|
||||
int count = codes[i] & 0xffff;
|
||||
if (i + count + 1 >= codes.size())
|
||||
throw FlycastException("Missing values");
|
||||
throw FlycastException(Ts("Missing values"));
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
@@ -848,7 +903,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// 8-bit inc/decrement
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
throw FlycastException(Ts("Missing value"));
|
||||
cheat.type = subcode == 1 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 8;
|
||||
cheat.value = codes[i] & 0xff;
|
||||
@@ -861,7 +916,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// 16-bit inc/decrement
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
throw FlycastException(Ts("Missing value"));
|
||||
cheat.type = subcode == 3 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 16;
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
@@ -874,7 +929,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// 32-bit inc/decrement
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing address or value");
|
||||
throw FlycastException(Ts("Missing address or value"));
|
||||
cheat.type = subcode == 5 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
@@ -883,7 +938,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported cheat type");
|
||||
throw FlycastException(Ts("Unsupported cheat type"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -891,7 +946,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// 32-bit repeat write
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing count or value");
|
||||
throw FlycastException(Ts("Missing count or value"));
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
@@ -905,7 +960,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// copy bytes
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing count or destination address");
|
||||
throw FlycastException(Ts("Missing count or destination address"));
|
||||
cheat.type = Cheat::Type::copy;
|
||||
cheat.size = 8;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
@@ -917,7 +972,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
|
||||
case 7:
|
||||
// change decryption type: 071000XX (example: 07100005)
|
||||
throw FlycastException("Master codes aren't supported");
|
||||
throw FlycastException(Ts("Master codes aren't supported"));
|
||||
|
||||
// TODO 0xb delay applying codes: 0b0xxxxx
|
||||
// Delay putting on codes for xxxxx cycles. Default 1000 (0x3e7)
|
||||
@@ -929,7 +984,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// enable next code if eq/neq/lt/gt
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing count or destination address");
|
||||
throw FlycastException(Ts("Missing count or destination address"));
|
||||
cheat.size = 16;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
switch (codes[++i] >> 16)
|
||||
@@ -947,7 +1002,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
cheat.type = Cheat::Type::runNextIfGt;
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported conditional code");
|
||||
throw FlycastException(Ts("Unsupported conditional code"));
|
||||
}
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
cheats.push_back(cheat);
|
||||
@@ -957,7 +1012,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
{
|
||||
// multiline enable codes if eq/neq/lt/gt
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing test address");
|
||||
throw FlycastException(Ts("Missing test address"));
|
||||
cheat.size = 16;
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
conditionLimit = i + 1 + ((codes[i] >> 16) & 0xff);
|
||||
@@ -976,7 +1031,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
cheat.type = Cheat::Type::runNextIfGt;
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported conditional code");
|
||||
throw FlycastException(Ts("Unsupported conditional code"));
|
||||
}
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
conditionCheat = cheat;
|
||||
@@ -987,15 +1042,15 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
// 16-Bit Write Once Immediately. (Activator code)
|
||||
|
||||
default:
|
||||
throw FlycastException("Unsupported cheat type");
|
||||
throw FlycastException(Ts("Unsupported cheat type"));
|
||||
}
|
||||
}
|
||||
#ifndef LIBRETRO
|
||||
std::string path = cfgLoadStr("cheats", gameId, "");
|
||||
std::string path = config::loadStr("cheats", gameId);
|
||||
if (path == "")
|
||||
{
|
||||
path = get_game_save_prefix() + ".cht";
|
||||
cfgSaveStr("cheats", gameId, path);
|
||||
config::saveStr("cheats", gameId, path);
|
||||
}
|
||||
saveCheatFile(path);
|
||||
#endif
|
||||
@@ -1009,7 +1064,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
|
||||
void CheatManager::saveCheatFile(const std::string& filename)
|
||||
{
|
||||
#ifndef LIBRETRO
|
||||
emucfg::ConfigFile cfg;
|
||||
config::IniFile cfg;
|
||||
|
||||
int i = 0;
|
||||
for (const Cheat& cheat : cheats)
|
||||
@@ -1017,15 +1072,15 @@ void CheatManager::saveCheatFile(const std::string& filename)
|
||||
if (cheat.builtIn)
|
||||
continue;
|
||||
std::string prefix = "cheat" + std::to_string(i) + "_";
|
||||
cfg.set_int("", prefix + "address", cheat.address);
|
||||
cfg.set_int("", prefix + "address_bit_position", cheat.valueMask);
|
||||
cfg.set_bool("", prefix + "big_endian", false);
|
||||
cfg.set_int("", prefix + "cheat_type", (int)cheat.type);
|
||||
cfg.set("", prefix + "address", cheat.address);
|
||||
cfg.set("", prefix + "address_bit_position", cheat.valueMask);
|
||||
cfg.set("", prefix + "big_endian", false);
|
||||
cfg.set("", prefix + "cheat_type", (int)cheat.type);
|
||||
cfg.set("", prefix + "code", "");
|
||||
cfg.set("", prefix + "desc", cheat.description);
|
||||
cfg.set_int("", prefix + "dest_address", cheat.destAddress);
|
||||
cfg.set_bool("", prefix + "enable", false); // force all cheats disabled at start
|
||||
cfg.set_int("", prefix + "handler", 1);
|
||||
cfg.set("", prefix + "dest_address", cheat.destAddress);
|
||||
cfg.set("", prefix + "enable", false); // force all cheats disabled at start
|
||||
cfg.set("", prefix + "handler", 1);
|
||||
int memSize;
|
||||
switch (cheat.size) {
|
||||
case 1:
|
||||
@@ -1048,18 +1103,18 @@ void CheatManager::saveCheatFile(const std::string& filename)
|
||||
memSize = 5;
|
||||
break;
|
||||
}
|
||||
cfg.set_int("", prefix + "memory_search_size", memSize);
|
||||
cfg.set_int("", prefix + "value", cheat.value);
|
||||
cfg.set_int("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cfg.set_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cfg.set_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
cfg.set("", prefix + "memory_search_size", memSize);
|
||||
cfg.set("", prefix + "value", cheat.value);
|
||||
cfg.set("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cfg.set("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cfg.set("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
i++;
|
||||
}
|
||||
cfg.set_int("", "cheats", i);
|
||||
FILE *fp = hostfs::storage().openFile(filename.c_str(), "w");
|
||||
cfg.set("", "cheats", i);
|
||||
hostfs::File *fp = hostfs::storage().openFile(filename.c_str(), "w");
|
||||
if (fp == nullptr)
|
||||
throw FlycastException("Can't save cheat file");
|
||||
throw FlycastException(Ts("Can't save cheat file"));
|
||||
cfg.save(fp);
|
||||
fclose(fp);
|
||||
delete fp;
|
||||
#endif
|
||||
}
|
||||
@@ -69,6 +69,9 @@ struct Cheat
|
||||
class CheatManager
|
||||
{
|
||||
public:
|
||||
~CheatManager() {
|
||||
reset("");
|
||||
}
|
||||
void reset(const std::string& gameId);
|
||||
void apply();
|
||||
size_t cheatCount() const { return cheats.size(); }
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "hw/sh4/dyna/shil.h"
|
||||
#include "cfg/option.h"
|
||||
#include <array>
|
||||
#include <signal.h>
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
|
||||
#ifndef SIGTRAP
|
||||
|
||||
+57
-44
@@ -69,6 +69,22 @@ public:
|
||||
asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void send(const std::string& msg)
|
||||
{
|
||||
if (!msg.empty())
|
||||
{
|
||||
if (!outMessage.empty()) {
|
||||
WARN_LOG(COMMON, "Message dropped: %s. Already sending: %s", msg.c_str(), outMessage.c_str());
|
||||
return;
|
||||
}
|
||||
outMessage = msg;
|
||||
asio::async_write(socket, asio::buffer(outMessage),
|
||||
std::bind(&Connection::writeDone, shared_from_this(),
|
||||
asio::placeholders::error,
|
||||
asio::placeholders::bytes_transferred));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Connection(GdbServer& server, asio::io_context& io_context)
|
||||
: server(server), io_context(io_context), socket(io_context) {
|
||||
@@ -101,30 +117,18 @@ private:
|
||||
|
||||
void handlePacket(const std::error_code& ec, size_t len);
|
||||
|
||||
void send(const std::string& msg)
|
||||
{
|
||||
if (msg.empty())
|
||||
start();
|
||||
else
|
||||
asio::async_write(socket, asio::buffer(msg),
|
||||
std::bind(&Connection::writeDone, shared_from_this(),
|
||||
asio::placeholders::error,
|
||||
asio::placeholders::bytes_transferred));
|
||||
|
||||
}
|
||||
|
||||
void writeDone(const std::error_code& ec, size_t len)
|
||||
{
|
||||
if (ec)
|
||||
WARN_LOG(COMMON, "Write error: %s", ec.message().c_str());
|
||||
else
|
||||
start();
|
||||
outMessage.clear();
|
||||
}
|
||||
|
||||
GdbServer& server;
|
||||
asio::io_context& io_context;
|
||||
asio::ip::tcp::socket socket;
|
||||
std::string message;
|
||||
std::string outMessage;
|
||||
friend super;
|
||||
};
|
||||
|
||||
@@ -213,7 +217,9 @@ public:
|
||||
if (!attached)
|
||||
return;
|
||||
agent.debugTrap(event);
|
||||
reportException();
|
||||
std::string pkt = handleCommand("?"); // report exception
|
||||
if (connection)
|
||||
io_context->post([this, pkt]() { connection->send(pkt); });
|
||||
postDebugTrapNeeded = true;
|
||||
throw Stop();
|
||||
}
|
||||
@@ -234,6 +240,7 @@ private:
|
||||
ERROR_LOG(COMMON, "Gdb server exception: %s", e.what());
|
||||
}
|
||||
attached = false;
|
||||
connection.reset();
|
||||
}
|
||||
|
||||
std::string handleCommand(const std::string& packet)
|
||||
@@ -390,6 +397,7 @@ private:
|
||||
} catch (const Error& e) {
|
||||
ERROR_LOG(COMMON, "%s", e.what());
|
||||
attached = false;
|
||||
connection.reset();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -659,25 +667,21 @@ private:
|
||||
if (pkt.rfind("vAttach;", 0) == 0)
|
||||
return { "S05" };
|
||||
else if (pkt.rfind("vCont?", 0) == 0)
|
||||
// supported vCont actions - (c)ontinue, (C)ontinue with signal, (s)tep, (S)tep with signal, (r)ange-step
|
||||
return { "vCont;c;C;s;S;t;r" };
|
||||
// supported vCont actions - (c)ontinue, (s)tep, (r)ange-step
|
||||
return { "vCont;c;s;r" };
|
||||
else if (pkt.rfind("vCont", 0) == 0)
|
||||
{
|
||||
std::string vContCmd = pkt.substr(strlen("vCont;"));
|
||||
std::vector<std::string> replies;
|
||||
switch (vContCmd[0])
|
||||
{
|
||||
case 'c':
|
||||
case 'C':
|
||||
doContinue(vContCmd);
|
||||
doContinue(vContCmd.substr(0, 1));
|
||||
return {};
|
||||
case 's':
|
||||
return { step(EXCEPT_NONE) };
|
||||
case 'S':
|
||||
replies.push_back(step());
|
||||
[[fallthrough]];
|
||||
case 'r':
|
||||
{
|
||||
std::vector<std::string> replies;
|
||||
u32 from, to;
|
||||
if (sscanf(vContCmd.c_str(), "r%x,%x", &from, &to) == 2)
|
||||
{
|
||||
@@ -722,7 +726,8 @@ private:
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(COMMON, "unknown v packet: %s", pkt.c_str());
|
||||
if (pkt != "vMustReplyEmpty")
|
||||
WARN_LOG(COMMON, "unknown v packet: %s", pkt.c_str());
|
||||
return { "" };
|
||||
}
|
||||
}
|
||||
@@ -744,7 +749,10 @@ private:
|
||||
std::vector<std::string> stepRange(u32 from, u32 to)
|
||||
{
|
||||
try {
|
||||
agent.stepRange(from, to);
|
||||
if (from + 2 >= to)
|
||||
agent.step();
|
||||
else
|
||||
agent.stepRange(from, to);
|
||||
return { "OK", "S05" };
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
@@ -858,8 +866,9 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void clientConnected() {
|
||||
void clientConnected(Connection::Ptr connection) {
|
||||
attached = true;
|
||||
this->connection = connection;
|
||||
agentInterrupt();
|
||||
}
|
||||
|
||||
@@ -889,6 +898,7 @@ private:
|
||||
std::thread thread;
|
||||
std::unique_ptr<asio::io_context> io_context;
|
||||
int port = DEFAULT_PORT;
|
||||
Connection::Ptr connection;
|
||||
friend class TcpAcceptor;
|
||||
friend class Connection;
|
||||
|
||||
@@ -901,7 +911,7 @@ static GdbServer gdbServer;
|
||||
void TcpAcceptor::handleAccept(Connection::Ptr newConnection, const std::error_code& error)
|
||||
{
|
||||
if (!error) {
|
||||
server.clientConnected();
|
||||
server.clientConnected(newConnection);
|
||||
newConnection->start();
|
||||
}
|
||||
start();
|
||||
@@ -921,27 +931,30 @@ void Connection::handlePacket(const std::error_code& ec, size_t len)
|
||||
try {
|
||||
if (msg[0] == '\03') { // break
|
||||
send(server.handleCommand(msg));
|
||||
return;
|
||||
}
|
||||
if (msg[0] != '$') {
|
||||
else if (msg[0] != '$') {
|
||||
// Ignore unexpected chars
|
||||
send("");
|
||||
return;
|
||||
}
|
||||
u8 cksum = 0;
|
||||
for (unsigned i = 1; i < len - 3; i++)
|
||||
cksum += (u8)msg[i];
|
||||
if (cksum != (unpack(msg[len - 2]) << 4 | unpack(msg[len - 1]))) {
|
||||
// Invalid checksum
|
||||
WARN_LOG(COMMON, "Connection::handlePacket: invalid checksum: [%s]", msg.c_str());
|
||||
send("-");
|
||||
}
|
||||
else {
|
||||
// Positive ack
|
||||
std::string reply = "+";
|
||||
reply += server.handleCommand(msg.substr(1, msg.length() - 4));
|
||||
send(reply);
|
||||
else
|
||||
{
|
||||
u8 cksum = 0;
|
||||
for (unsigned i = 1; i < len - 3; i++)
|
||||
cksum += (u8)msg[i];
|
||||
if (cksum != (unpack(msg[len - 2]) << 4 | unpack(msg[len - 1])))
|
||||
{
|
||||
// Invalid checksum
|
||||
WARN_LOG(COMMON, "Connection::handlePacket: invalid checksum: [%s]", msg.c_str());
|
||||
send("-");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Positive ack
|
||||
std::string reply = "+";
|
||||
reply += server.handleCommand(msg.substr(1, msg.length() - 4));
|
||||
send(reply);
|
||||
}
|
||||
}
|
||||
start();
|
||||
} catch (...) {
|
||||
// terminate the connection
|
||||
}
|
||||
|
||||
Submodule
+1
Submodule core/deps/DreamPicoPort-API added at e06d9f6c59.
+1
-1
Submodule core/deps/SDL updated: 2359383fc1...00d4f1c2c4.
+1
-1
Submodule core/deps/Syphon updated: 486dd8e4ef...53ec412a78.
+1
-1
Submodule core/deps/Vulkan-Headers updated: 85c2334e92...b5c8f99619.
Submodule core/deps/VulkanMemoryAllocator updated: 6eb62e1515...1d8f600fd4.
+1
-1
Submodule core/deps/asio updated: d3402006e8...3321a1a753.
Submodule
+1
Submodule core/deps/basis_universal added at 9bebe16726.
+1
-1
Submodule core/deps/breakpad updated: 1ab24bcc81...6f3c4af788.
+59
-62
@@ -4,33 +4,29 @@
|
||||
#include <errno.h>
|
||||
#include "cdipsr.h"
|
||||
|
||||
// Global variables
|
||||
|
||||
unsigned long temp_value;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
unsigned long ask_type(FILE *fsource, long header_position)
|
||||
unsigned long ask_type(hostfs::File *fsource, long header_position)
|
||||
{
|
||||
|
||||
unsigned char filename_length;
|
||||
unsigned long track_mode;
|
||||
uint32_t temp_value = 0;
|
||||
|
||||
fseek(fsource, header_position, SEEK_SET);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
fsource->seek(header_position, SEEK_SET);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
if (temp_value != 0)
|
||||
fseek(fsource, 8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
fseek(fsource, 24, SEEK_CUR);
|
||||
fread(&filename_length, 1, 1, fsource);
|
||||
fseek(fsource, filename_length, SEEK_CUR);
|
||||
fseek(fsource, 19, SEEK_CUR);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
fsource->seek(8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
fsource->seek(24, SEEK_CUR);
|
||||
fsource->read(&filename_length, 1, 1);
|
||||
fsource->seek(filename_length, SEEK_CUR);
|
||||
fsource->seek(19, SEEK_CUR);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
if (temp_value == 0x80000000)
|
||||
fseek(fsource, 8, SEEK_CUR); // DJ4
|
||||
fseek(fsource, 16, SEEK_CUR);
|
||||
fread(&track_mode, 4, 1, fsource);
|
||||
fseek(fsource, header_position, SEEK_SET);
|
||||
fsource->seek(8, SEEK_CUR); // DJ4
|
||||
fsource->seek(16, SEEK_CUR);
|
||||
fsource->read(&track_mode, 4, 1);
|
||||
fsource->seek(header_position, SEEK_SET);
|
||||
return (track_mode);
|
||||
}
|
||||
|
||||
@@ -38,47 +34,48 @@ unsigned long track_mode;
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
bool CDI_read_track (FILE *fsource, image_s *image, track_s *track)
|
||||
bool CDI_read_track (hostfs::File *fsource, image_s *image, track_s *track)
|
||||
{
|
||||
|
||||
unsigned char TRACK_START_MARK[10] = { 0, 0, 0x01, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
unsigned char current_start_mark[10];
|
||||
uint32_t temp_value = 0;
|
||||
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
if (temp_value != 0)
|
||||
fseek(fsource, 8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
fsource->seek(8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
|
||||
fread(¤t_start_mark, 10, 1, fsource);
|
||||
fsource->read(¤t_start_mark, 10, 1);
|
||||
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) {
|
||||
printf("CDI_read_track: Unsupported format: Could not find the track start mark\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
fread(¤t_start_mark, 10, 1, fsource);
|
||||
fsource->read(¤t_start_mark, 10, 1);
|
||||
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) {
|
||||
printf("CDI_read_track: Unsupported format: Could not find the track start mark\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fread(&track->filename_length, 1, 1, fsource);
|
||||
fseek(fsource, track->filename_length, SEEK_CUR);
|
||||
fseek(fsource, 11, SEEK_CUR);
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->read(&track->filename_length, 1, 1);
|
||||
fsource->seek(track->filename_length, SEEK_CUR);
|
||||
fsource->seek(11, SEEK_CUR);
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
if (temp_value == 0x80000000)
|
||||
fseek(fsource, 8, SEEK_CUR); // DJ4
|
||||
fseek(fsource, 2, SEEK_CUR);
|
||||
fread(&track->pregap_length, 4, 1, fsource);
|
||||
fread(&track->length, 4, 1, fsource);
|
||||
fseek(fsource, 6, SEEK_CUR);
|
||||
fread(&track->mode, 4, 1, fsource);
|
||||
fseek(fsource, 12, SEEK_CUR);
|
||||
fread(&track->start_lba, 4, 1, fsource);
|
||||
fread(&track->total_length, 4, 1, fsource);
|
||||
fseek(fsource, 16, SEEK_CUR);
|
||||
fread(&track->sector_size_value, 4, 1, fsource);
|
||||
fsource->seek(8, SEEK_CUR); // DJ4
|
||||
fsource->seek(2, SEEK_CUR);
|
||||
fsource->read(&track->pregap_length, 4, 1);
|
||||
fsource->read(&track->length, 4, 1);
|
||||
fsource->seek(6, SEEK_CUR);
|
||||
fsource->read(&track->mode, 4, 1);
|
||||
fsource->seek(12, SEEK_CUR);
|
||||
fsource->read(&track->start_lba, 4, 1);
|
||||
fsource->read(&track->total_length, 4, 1);
|
||||
fsource->seek(16, SEEK_CUR);
|
||||
fsource->read(&track->sector_size_value, 4, 1);
|
||||
|
||||
switch(track->sector_size_value)
|
||||
{
|
||||
@@ -96,34 +93,34 @@ bool CDI_read_track (FILE *fsource, image_s *image, track_s *track)
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fsource, 29, SEEK_CUR);
|
||||
fsource->seek(29, SEEK_CUR);
|
||||
if (image->version != CDI_V2)
|
||||
{
|
||||
fseek(fsource, 5, SEEK_CUR);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
fsource->seek(5, SEEK_CUR);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
if (temp_value == 0xffffffff)
|
||||
fseek(fsource, 78, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
fsource->seek(78, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void CDI_skip_next_session (FILE *fsource, image_s *image)
|
||||
void CDI_skip_next_session (hostfs::File *fsource, image_s *image)
|
||||
{
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fseek(fsource, 8, SEEK_CUR);
|
||||
if (image->version != CDI_V2) fseek(fsource, 1, SEEK_CUR);
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->seek(8, SEEK_CUR);
|
||||
if (image->version != CDI_V2) fsource->seek(1, SEEK_CUR);
|
||||
}
|
||||
|
||||
bool CDI_get_tracks (FILE *fsource, image_s *image)
|
||||
bool CDI_get_tracks (hostfs::File *fsource, image_s *image)
|
||||
{
|
||||
return fread(&image->tracks, 2, 1, fsource) == 1;
|
||||
return fsource->read(&image->tracks, 2, 1) == 1;
|
||||
}
|
||||
|
||||
bool CDI_init (FILE *fsource, image_s *image, const char *fsourcename)
|
||||
bool CDI_init (hostfs::File *fsource, image_s *image, const char *fsourcename)
|
||||
{
|
||||
fseek(fsource, 0, SEEK_END);
|
||||
image->length = ftell(fsource);
|
||||
fsource->seek(0, SEEK_END);
|
||||
image->length = fsource->tell();
|
||||
|
||||
if (image->length < 8)
|
||||
{
|
||||
@@ -131,9 +128,9 @@ bool CDI_init (FILE *fsource, image_s *image, const char *fsourcename)
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fsource, image->length-8, SEEK_SET);
|
||||
if (fread(&image->version, 4, 1, fsource) != 1
|
||||
|| fread(&image->header_offset, 4, 1, fsource) != 1)
|
||||
fsource->seek(image->length-8, SEEK_SET);
|
||||
if (fsource->read(&image->version, 4, 1) != 1
|
||||
|| fsource->read(&image->header_offset, 4, 1) != 1)
|
||||
return false;
|
||||
|
||||
if ((image->version != CDI_V2 && image->version != CDI_V3 && image->version != CDI_V35)
|
||||
@@ -145,17 +142,17 @@ bool CDI_init (FILE *fsource, image_s *image, const char *fsourcename)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDI_get_sessions (FILE *fsource, image_s *image)
|
||||
bool CDI_get_sessions (hostfs::File *fsource, image_s *image)
|
||||
{
|
||||
#ifndef DEBUG_CDI
|
||||
if (image->version == CDI_V35)
|
||||
fseek(fsource, (image->length - image->header_offset), SEEK_SET);
|
||||
fsource->seek((image->length - image->header_offset), SEEK_SET);
|
||||
else
|
||||
fseek(fsource, image->header_offset, SEEK_SET);
|
||||
fsource->seek(image->header_offset, SEEK_SET);
|
||||
|
||||
#else
|
||||
fseek(fsource, 0L, SEEK_SET);
|
||||
fsource->seek(0L, SEEK_SET);
|
||||
#endif
|
||||
return fread(&image->sessions, 2, 1, fsource) == 1;
|
||||
return fsource->read(&image->sessions, 2, 1) == 1;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef __CDI_H__
|
||||
#define __CDI_H__
|
||||
|
||||
#include "../../oslib/storage.h"
|
||||
|
||||
/* Basic structures */
|
||||
|
||||
typedef struct image_s
|
||||
@@ -36,11 +38,11 @@ typedef struct track_s
|
||||
#define CDI_V3 0x80000005
|
||||
#define CDI_V35 0x80000006
|
||||
|
||||
unsigned long ask_type(FILE *fsource, long header_position);
|
||||
bool CDI_init(FILE *fsource, image_s *image, const char *fsourcename);
|
||||
bool CDI_get_sessions(FILE *fsource, image_s *image);
|
||||
bool CDI_get_tracks(FILE *fsource, image_s *image);
|
||||
bool CDI_read_track(FILE *fsource, image_s *image, track_s *track);
|
||||
void CDI_skip_next_session(FILE *fsource, image_s *image);
|
||||
unsigned long ask_type(hostfs::File *fsource, long header_position);
|
||||
bool CDI_init(hostfs::File *fsource, image_s *image, const char *fsourcename);
|
||||
bool CDI_get_sessions(hostfs::File *fsource, image_s *image);
|
||||
bool CDI_get_tracks(hostfs::File *fsource, image_s *image);
|
||||
bool CDI_read_track(hostfs::File *fsource, image_s *image, track_s *track);
|
||||
void CDI_skip_next_session(hostfs::File *fsource, image_s *image);
|
||||
|
||||
#endif
|
||||
Submodule
+1
Submodule core/deps/freetype added at 0a0221a134.
Submodule
+1
Submodule core/deps/gamesdk added at 4ac9952e6a.
@@ -56,7 +56,7 @@ public:
|
||||
*/
|
||||
#if defined(_WIN32)
|
||||
# include "platform_windows.h"
|
||||
#elif defined(__unix__) || defined(__APPLE__) || defined(__SWITCH__)
|
||||
#elif defined(__unix__) || defined(__APPLE__) || defined(__SWITCH__) || defined(__HAIKU__)
|
||||
# include "platform_linux.h"
|
||||
#else
|
||||
# error Unsupported platform
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
static const int UDP_HEADER_SIZE = 28; /* Size of IP + UDP headers */
|
||||
static const int NUM_SYNC_PACKETS = 5;
|
||||
static const int SYNC_RETRY_INTERVAL = 2000;
|
||||
|
||||
static bool IsValidAppData(const UdpMsg *msg, int len)
|
||||
{
|
||||
const int header_size = (const uint8 *)msg->u.app_data.data - (const uint8 *)msg;
|
||||
if (len < header_size)
|
||||
return false;
|
||||
return msg->u.app_data.size <= MAX_APPDATA_SIZE
|
||||
&& msg->u.app_data.size <= len - header_size;
|
||||
}
|
||||
static const int SYNC_FIRST_RETRY_INTERVAL = 500;
|
||||
static const int RUNNING_RETRY_INTERVAL = 200;
|
||||
static const int KEEP_ALIVE_INTERVAL = 200;
|
||||
@@ -320,6 +329,13 @@ UdpProtocol::HandlesMsg(sockaddr_in &from,
|
||||
void
|
||||
UdpProtocol::OnMsg(UdpMsg *msg, int len)
|
||||
{
|
||||
if (len < (int)sizeof(msg->hdr)) {
|
||||
return;
|
||||
}
|
||||
if (msg->hdr.type == UdpMsg::AppData && !IsValidAppData(msg, len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
typedef bool (UdpProtocol::*DispatchFn)(UdpMsg *msg, int len);
|
||||
static const DispatchFn table[] = {
|
||||
@@ -791,6 +807,8 @@ UdpProtocol::ClearSendQueue()
|
||||
|
||||
void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
|
||||
{
|
||||
if (len < 0 || len > MAX_APPDATA_SIZE || (data == nullptr && len != 0))
|
||||
return;
|
||||
if (_udp == nullptr)
|
||||
return;
|
||||
if (_current_state != Synchronzied && _current_state != Running)
|
||||
@@ -806,6 +824,8 @@ void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
|
||||
|
||||
bool UdpProtocol::OnAppData(UdpMsg *msg, int len)
|
||||
{
|
||||
if (!IsValidAppData(msg, len))
|
||||
return false;
|
||||
UdpProtocol::Event evt(UdpProtocol::Event::AppData);
|
||||
evt.u.app_data.spectators = msg->u.app_data.spectators != 0;
|
||||
evt.u.app_data.size = msg->u.app_data.size;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
#if defined(__unix__) || defined(__APPLE__) || defined(__SWITCH__)
|
||||
#if defined(__unix__) || defined(__APPLE__) || defined(__SWITCH__) || defined(__HAIKU__)
|
||||
|
||||
#include "platform_linux.h"
|
||||
#include <time.h>
|
||||
|
||||
@@ -14,7 +14,8 @@ _input_queues(NULL),
|
||||
_framecount = 0;
|
||||
_last_confirmed_frame = -1;
|
||||
_max_prediction_frames = 0;
|
||||
memset(&_savedstate, 0, sizeof(_savedstate));
|
||||
_savedstate = {};
|
||||
_rollingback = false;
|
||||
}
|
||||
|
||||
Sync::~Sync()
|
||||
|
||||
+1
-1
Submodule core/deps/glslang updated: 76b52ebf77...fc9889c889.
@@ -2,8 +2,10 @@
|
||||
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
|
||||
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
@@ -15,12 +17,19 @@
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2026-01-19: DirectX11: Added 'SamplerNearest' in ImGui_ImplDX11_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'.
|
||||
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
|
||||
// 2025-06-11: DirectX11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
|
||||
// 2025-05-07: DirectX11: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows).
|
||||
// 2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler.
|
||||
// 2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap.
|
||||
// 2024-10-07: DirectX11: Expose selected render state in ImGui_ImplDX11_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
|
||||
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
|
||||
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
|
||||
// 2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
|
||||
// 2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.
|
||||
// 2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).
|
||||
// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
|
||||
// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX11_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
|
||||
// 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
|
||||
// 2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
|
||||
// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
|
||||
@@ -45,7 +54,20 @@
|
||||
#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
|
||||
#endif
|
||||
|
||||
// Clang/GCC warnings with -Weverything
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
||||
#endif
|
||||
|
||||
/* Flycast: Use ImTextureDX11 instead
|
||||
// DirectX11 data
|
||||
struct ImGui_ImplDX11_Texture
|
||||
{
|
||||
ID3D11Texture2D* pTexture;
|
||||
ID3D11ShaderResourceView* pTextureView;
|
||||
};
|
||||
*/
|
||||
struct ImGui_ImplDX11_Data
|
||||
{
|
||||
ID3D11Device* pd3dDevice;
|
||||
@@ -57,10 +79,9 @@ struct ImGui_ImplDX11_Data
|
||||
ID3D11InputLayout* pInputLayout;
|
||||
ID3D11Buffer* pVertexConstantBuffer;
|
||||
ID3D11PixelShader* pPixelShader;
|
||||
ID3D11SamplerState* pFontSampler;
|
||||
ID3D11SamplerState* pTextureSampler;
|
||||
ID3D11SamplerState* pPointSampler;
|
||||
ImTextureDX11 FontTexture;
|
||||
ID3D11SamplerState* pTexSamplerLinear;
|
||||
ID3D11SamplerState* pTexSamplerLinearBorder;
|
||||
ID3D11SamplerState* pTexSamplerNearest;
|
||||
ID3D11RasterizerState* pRasterizerState;
|
||||
ID3D11BlendState* pBlendState;
|
||||
ID3D11DepthStencilState* pDepthStencilState;
|
||||
@@ -83,106 +104,24 @@ static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()
|
||||
}
|
||||
|
||||
// Functions
|
||||
static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx)
|
||||
static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11DeviceContext* device_ctx)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
|
||||
// Setup viewport
|
||||
D3D11_VIEWPORT vp;
|
||||
memset(&vp, 0, sizeof(D3D11_VIEWPORT));
|
||||
vp.Width = draw_data->DisplaySize.x;
|
||||
vp.Height = draw_data->DisplaySize.y;
|
||||
D3D11_VIEWPORT vp = {};
|
||||
vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x;
|
||||
vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y;
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
vp.TopLeftX = vp.TopLeftY = 0;
|
||||
ctx->RSSetViewports(1, &vp);
|
||||
|
||||
// Setup shader and vertex buffers
|
||||
unsigned int stride = sizeof(ImDrawVert);
|
||||
unsigned int offset = 0;
|
||||
ctx->IASetInputLayout(bd->pInputLayout);
|
||||
ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
|
||||
ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
|
||||
ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
|
||||
ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
|
||||
ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
|
||||
ctx->GSSetShader(nullptr, nullptr, 0);
|
||||
ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
|
||||
// Setup blend state
|
||||
const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
|
||||
ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
|
||||
ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
|
||||
ctx->RSSetState(bd->pRasterizerState);
|
||||
}
|
||||
|
||||
// Render function
|
||||
void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
|
||||
{
|
||||
// Avoid rendering when minimized
|
||||
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
|
||||
return;
|
||||
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
ID3D11DeviceContext* ctx = bd->pd3dDeviceContext;
|
||||
|
||||
// Create and grow vertex/index buffers if needed
|
||||
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
|
||||
{
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
|
||||
D3D11_BUFFER_DESC desc;
|
||||
memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
|
||||
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
desc.MiscFlags = 0;
|
||||
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
|
||||
return;
|
||||
}
|
||||
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
|
||||
{
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
|
||||
D3D11_BUFFER_DESC desc;
|
||||
memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
|
||||
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload vertex/index data into a single contiguous GPU buffer
|
||||
D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
|
||||
if (ctx->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
|
||||
return;
|
||||
if (ctx->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
|
||||
return;
|
||||
ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
|
||||
ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
||||
{
|
||||
const ImDrawList* cmd_list = draw_data->CmdLists[n];
|
||||
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
vtx_dst += cmd_list->VtxBuffer.Size;
|
||||
idx_dst += cmd_list->IdxBuffer.Size;
|
||||
}
|
||||
ctx->Unmap(bd->pVB, 0);
|
||||
ctx->Unmap(bd->pIB, 0);
|
||||
device_ctx->RSSetViewports(1, &vp);
|
||||
|
||||
// Setup orthographic projection matrix into our constant buffer
|
||||
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
|
||||
D3D11_MAPPED_SUBRESOURCE mapped_resource;
|
||||
if (device_ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) == S_OK)
|
||||
{
|
||||
D3D11_MAPPED_SUBRESOURCE mapped_resource;
|
||||
if (ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK)
|
||||
return;
|
||||
VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;
|
||||
float L = draw_data->DisplayPos.x;
|
||||
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
|
||||
@@ -196,9 +135,93 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
|
||||
{ (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
|
||||
};
|
||||
memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
|
||||
ctx->Unmap(bd->pVertexConstantBuffer, 0);
|
||||
device_ctx->Unmap(bd->pVertexConstantBuffer, 0);
|
||||
}
|
||||
|
||||
// Setup shader and vertex buffers
|
||||
unsigned int stride = sizeof(ImDrawVert);
|
||||
unsigned int offset = 0;
|
||||
device_ctx->IASetInputLayout(bd->pInputLayout);
|
||||
device_ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
|
||||
device_ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
|
||||
device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
device_ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
|
||||
device_ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
|
||||
device_ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
|
||||
device_ctx->GSSetShader(nullptr, nullptr, 0);
|
||||
device_ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
device_ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
device_ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
|
||||
// Setup render state
|
||||
const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
|
||||
device_ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
|
||||
device_ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
|
||||
device_ctx->RSSetState(bd->pRasterizerState);
|
||||
}
|
||||
|
||||
// Render function
|
||||
void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
|
||||
{
|
||||
// Avoid rendering when minimized
|
||||
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
|
||||
return;
|
||||
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
ID3D11DeviceContext* device = bd->pd3dDeviceContext;
|
||||
|
||||
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
|
||||
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
|
||||
if (draw_data->Textures != nullptr)
|
||||
for (ImTextureData* tex : *draw_data->Textures)
|
||||
if (tex->Status != ImTextureStatus_OK)
|
||||
ImGui_ImplDX11_UpdateTexture(tex);
|
||||
|
||||
// Create and grow vertex/index buffers if needed
|
||||
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
|
||||
{
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
|
||||
D3D11_BUFFER_DESC desc = {};
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
|
||||
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
desc.MiscFlags = 0;
|
||||
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
|
||||
return;
|
||||
}
|
||||
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
|
||||
{
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
|
||||
D3D11_BUFFER_DESC desc = {};
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
|
||||
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload vertex/index data into a single contiguous GPU buffer
|
||||
D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
|
||||
if (device->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
|
||||
return;
|
||||
if (device->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
|
||||
return;
|
||||
ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
|
||||
ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
|
||||
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||
{
|
||||
memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
vtx_dst += draw_list->VtxBuffer.Size;
|
||||
idx_dst += draw_list->IdxBuffer.Size;
|
||||
}
|
||||
device->Unmap(bd->pVB, 0);
|
||||
device->Unmap(bd->pIB, 0);
|
||||
|
||||
// Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
|
||||
struct BACKUP_DX11_STATE
|
||||
{
|
||||
@@ -226,114 +249,143 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
|
||||
};
|
||||
BACKUP_DX11_STATE old = {};
|
||||
old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
|
||||
ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
|
||||
ctx->RSGetViewports(&old.ViewportsCount, old.Viewports);
|
||||
ctx->RSGetState(&old.RS);
|
||||
ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
|
||||
ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
|
||||
ctx->PSGetShaderResources(0, 1, &old.PSShaderResource);
|
||||
ctx->PSGetSamplers(0, 1, &old.PSSampler);
|
||||
device->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
|
||||
device->RSGetViewports(&old.ViewportsCount, old.Viewports);
|
||||
device->RSGetState(&old.RS);
|
||||
device->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
|
||||
device->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
|
||||
device->PSGetShaderResources(0, 1, &old.PSShaderResource);
|
||||
device->PSGetSamplers(0, 1, &old.PSSampler);
|
||||
old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;
|
||||
ctx->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
|
||||
ctx->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
|
||||
ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
|
||||
ctx->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
|
||||
device->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
|
||||
device->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
|
||||
device->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
|
||||
device->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
|
||||
|
||||
ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology);
|
||||
ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
|
||||
ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
|
||||
ctx->IAGetInputLayout(&old.InputLayout);
|
||||
device->IAGetPrimitiveTopology(&old.PrimitiveTopology);
|
||||
device->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
|
||||
device->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
|
||||
device->IAGetInputLayout(&old.InputLayout);
|
||||
|
||||
// Setup desired DX state
|
||||
ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
|
||||
ImGui_ImplDX11_SetupRenderState(draw_data, device);
|
||||
|
||||
// Setup render state structure (for callbacks and custom texture bindings)
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
ImGui_ImplDX11_RenderState render_state;
|
||||
render_state.Device = bd->pd3dDevice;
|
||||
render_state.DeviceContext = bd->pd3dDeviceContext;
|
||||
render_state.SamplerLinear = bd->pTexSamplerLinear;
|
||||
render_state.SamplerNearest = bd->pTexSamplerNearest;
|
||||
render_state.VertexConstantBuffer = bd->pVertexConstantBuffer;
|
||||
platform_io.Renderer_RenderState = &render_state;
|
||||
|
||||
// Render command lists
|
||||
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||
int global_idx_offset = 0;
|
||||
int global_vtx_offset = 0;
|
||||
ImVec2 clip_off = draw_data->DisplayPos;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
||||
ImVec2 clip_scale = draw_data->FramebufferScale;
|
||||
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||
{
|
||||
const ImDrawList* cmd_list = draw_data->CmdLists[n];
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
|
||||
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
|
||||
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback != nullptr)
|
||||
{
|
||||
// User callback, registered via ImDrawList::AddCallback()
|
||||
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
|
||||
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
|
||||
ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
|
||||
ImGui_ImplDX11_SetupRenderState(draw_data, device);
|
||||
else
|
||||
pcmd->UserCallback(cmd_list, pcmd);
|
||||
pcmd->UserCallback(draw_list, pcmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Project scissor/clipping rectangles into framebuffer space
|
||||
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
|
||||
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
|
||||
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
|
||||
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
|
||||
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
||||
continue;
|
||||
|
||||
// Apply scissor/clipping rectangle
|
||||
const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
|
||||
ctx->RSSetScissorRects(1, &r);
|
||||
device->RSSetScissorRects(1, &r);
|
||||
|
||||
// Bind texture, Draw
|
||||
ImTextureDX11 *tex = (ImTextureDX11 *)pcmd->GetTexID();
|
||||
if (tex != &bd->FontTexture)
|
||||
{
|
||||
if (tex->pointSampling)
|
||||
ctx->PSSetSamplers(0, 1, &bd->pPointSampler);
|
||||
else
|
||||
ctx->PSSetSamplers(0, 1, &bd->pTextureSampler);
|
||||
}
|
||||
else
|
||||
ctx->PSSetSamplers(0, 1, &bd->pFontSampler);
|
||||
if((ImTextureID)tex != ImGui::GetIO().Fonts->TexID.GetTexID())
|
||||
{
|
||||
if (tex->pointSampling)
|
||||
device->PSSetSamplers(0, 1, &bd->pTexSamplerNearest);
|
||||
else
|
||||
device->PSSetSamplers(0, 1, &bd->pTexSamplerLinearBorder);
|
||||
}
|
||||
else
|
||||
device->PSSetSamplers(0, 1, &bd->pTexSamplerLinear);
|
||||
ID3D11ShaderResourceView* texture_srv = tex->shaderResourceView;
|
||||
ctx->PSSetShaderResources(0, 1, &texture_srv);
|
||||
ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
|
||||
device->PSSetShaderResources(0, 1, &texture_srv);
|
||||
device->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
|
||||
}
|
||||
}
|
||||
global_idx_offset += cmd_list->IdxBuffer.Size;
|
||||
global_vtx_offset += cmd_list->VtxBuffer.Size;
|
||||
global_idx_offset += draw_list->IdxBuffer.Size;
|
||||
global_vtx_offset += draw_list->VtxBuffer.Size;
|
||||
}
|
||||
platform_io.Renderer_RenderState = nullptr;
|
||||
|
||||
// Restore modified DX state
|
||||
ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
|
||||
ctx->RSSetViewports(old.ViewportsCount, old.Viewports);
|
||||
ctx->RSSetState(old.RS); if (old.RS) old.RS->Release();
|
||||
ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
|
||||
ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
|
||||
ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
|
||||
ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
|
||||
ctx->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
|
||||
device->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
|
||||
device->RSSetViewports(old.ViewportsCount, old.Viewports);
|
||||
device->RSSetState(old.RS); if (old.RS) old.RS->Release();
|
||||
device->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
|
||||
device->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
|
||||
device->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
|
||||
device->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
|
||||
device->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
|
||||
for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();
|
||||
ctx->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
|
||||
ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
|
||||
ctx->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
|
||||
device->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
|
||||
device->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
|
||||
device->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
|
||||
for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();
|
||||
ctx->IASetPrimitiveTopology(old.PrimitiveTopology);
|
||||
ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
|
||||
ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
|
||||
ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
|
||||
device->IASetPrimitiveTopology(old.PrimitiveTopology);
|
||||
device->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
|
||||
device->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
|
||||
device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_CreateFontsTexture()
|
||||
static void ImGui_ImplDX11_DestroyTexture(ImTextureData* tex)
|
||||
{
|
||||
// Build texture atlas
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
|
||||
// Upload texture to graphics system
|
||||
if (ImTextureDX11* backend_tex = (ImTextureDX11*)tex->BackendUserData)
|
||||
{
|
||||
IM_ASSERT(backend_tex == (ImTextureDX11*)tex->TexID);
|
||||
backend_tex->shaderResourceView->Release();
|
||||
backend_tex->pTexture->Release();
|
||||
IM_DELETE(backend_tex);
|
||||
|
||||
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
|
||||
tex->SetTexID(ImTextureID_Invalid);
|
||||
tex->BackendUserData = nullptr;
|
||||
}
|
||||
tex->SetStatus(ImTextureStatus_Destroyed);
|
||||
}
|
||||
|
||||
void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
if (tex->Status == ImTextureStatus_WantCreate)
|
||||
{
|
||||
// Create and upload new texture to graphics system
|
||||
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
|
||||
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
|
||||
unsigned int* pixels = (unsigned int*)tex->GetPixels();
|
||||
ImTextureDX11* backend_tex = IM_NEW(ImTextureDX11)();
|
||||
|
||||
// Create texture
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.Width = (UINT)tex->Width;
|
||||
desc.Height = (UINT)tex->Height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
@@ -341,14 +393,12 @@ static void ImGui_ImplDX11_CreateFontsTexture()
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
desc.CPUAccessFlags = 0;
|
||||
|
||||
ID3D11Texture2D* pTexture = nullptr;
|
||||
D3D11_SUBRESOURCE_DATA subResource;
|
||||
subResource.pSysMem = pixels;
|
||||
subResource.SysMemPitch = desc.Width * 4;
|
||||
subResource.SysMemSlicePitch = 0;
|
||||
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
|
||||
IM_ASSERT(pTexture != nullptr);
|
||||
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture);
|
||||
IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!");
|
||||
|
||||
// Create texture view
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
|
||||
@@ -357,35 +407,29 @@ static void ImGui_ImplDX11_CreateFontsTexture()
|
||||
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D.MipLevels = desc.MipLevels;
|
||||
srvDesc.Texture2D.MostDetailedMip = 0;
|
||||
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->FontTexture.shaderResourceView);
|
||||
pTexture->Release();
|
||||
bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srvDesc, &backend_tex->shaderResourceView);
|
||||
IM_ASSERT(backend_tex->shaderResourceView != nullptr && "Backend failed to create texture!");
|
||||
|
||||
// Store identifiers
|
||||
tex->SetTexID((ImTextureID)backend_tex);
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
tex->BackendUserData = backend_tex;
|
||||
}
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts->SetTexID((ImTextureID)&bd->FontTexture);
|
||||
|
||||
// Create texture samplers
|
||||
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
|
||||
else if (tex->Status == ImTextureStatus_WantUpdates)
|
||||
{
|
||||
D3D11_SAMPLER_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
|
||||
desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
|
||||
desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
|
||||
desc.MipLODBias = 0.f;
|
||||
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
|
||||
desc.MinLOD = 0.f;
|
||||
desc.MaxLOD = 0.f;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
|
||||
|
||||
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
|
||||
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTextureSampler);
|
||||
|
||||
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pPointSampler);
|
||||
// Update selected blocks. We only ever write to textures regions which have never been used before!
|
||||
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
|
||||
ImTextureDX11* backend_tex = (ImTextureDX11*)tex->BackendUserData;
|
||||
IM_ASSERT(backend_tex == (ImTextureDX11*)tex->TexID);
|
||||
for (ImTextureRect& r : tex->Updates)
|
||||
{
|
||||
D3D11_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r .h), (UINT)1 };
|
||||
bd->pd3dDeviceContext->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0);
|
||||
}
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
}
|
||||
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
|
||||
ImGui_ImplDX11_DestroyTexture(tex);
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX11_CreateDeviceObjects()
|
||||
@@ -393,8 +437,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
if (!bd->pd3dDevice)
|
||||
return false;
|
||||
if (bd->pFontSampler)
|
||||
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
|
||||
// By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
|
||||
// If you would like to use this DX11 sample code but remove this dependency you can:
|
||||
@@ -457,7 +500,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
|
||||
|
||||
// Create the constant buffer
|
||||
{
|
||||
D3D11_BUFFER_DESC desc;
|
||||
D3D11_BUFFER_DESC desc = {};
|
||||
desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
@@ -537,7 +580,28 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
|
||||
bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
|
||||
}
|
||||
|
||||
ImGui_ImplDX11_CreateFontsTexture();
|
||||
// Create texture samplers
|
||||
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
|
||||
{
|
||||
D3D11_SAMPLER_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
desc.MipLODBias = 0.f;
|
||||
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
|
||||
desc.MinLOD = 0.f;
|
||||
desc.MaxLOD = 0.f;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear);
|
||||
|
||||
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
|
||||
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinearBorder);
|
||||
|
||||
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -548,15 +612,14 @@ void ImGui_ImplDX11_InvalidateDeviceObjects()
|
||||
if (!bd->pd3dDevice)
|
||||
return;
|
||||
|
||||
if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
|
||||
if (bd->pTextureSampler) { bd->pTextureSampler->Release(); bd->pTextureSampler = nullptr; }
|
||||
if (bd->pPointSampler) { bd->pPointSampler->Release(); bd->pPointSampler = nullptr; }
|
||||
if (bd->FontTexture.shaderResourceView) {
|
||||
bd->FontTexture.shaderResourceView->Release();
|
||||
bd->FontTexture.shaderResourceView = nullptr;
|
||||
// We copied data->FontTexture.shaderResourceView to io.Fonts->TexID so let's clear that as well.
|
||||
ImGui::GetIO().Fonts->SetTexID(0);
|
||||
}
|
||||
// Destroy all textures
|
||||
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
|
||||
if (tex->RefCount == 1)
|
||||
ImGui_ImplDX11_DestroyTexture(tex);
|
||||
|
||||
if (bd->pTexSamplerLinear) { bd->pTexSamplerLinear->Release(); bd->pTexSamplerLinear = nullptr; }
|
||||
if (bd->pTexSamplerLinearBorder){ bd->pTexSamplerLinearBorder->Release(); bd->pTexSamplerLinearBorder = nullptr; }
|
||||
if (bd->pTexSamplerNearest) { bd->pTexSamplerNearest->Release(); bd->pTexSamplerNearest = nullptr; }
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
|
||||
@@ -579,6 +642,10 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_dx11";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
|
||||
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||
|
||||
// Get factory from device
|
||||
IDXGIDevice* pDXGIDevice = nullptr;
|
||||
@@ -606,14 +673,17 @@ void ImGui_ImplDX11_Shutdown()
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
|
||||
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
if (bd->pFactory) { bd->pFactory->Release(); }
|
||||
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
|
||||
if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); }
|
||||
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
|
||||
platform_io.ClearRendererHandlers();
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
@@ -622,8 +692,9 @@ void ImGui_ImplDX11_NewFrame()
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?");
|
||||
|
||||
if (!bd->pFontSampler)
|
||||
ImGui_ImplDX11_CreateDeviceObjects();
|
||||
if (!bd->pVertexShader)
|
||||
if (!ImGui_ImplDX11_CreateDeviceObjects())
|
||||
IM_ASSERT(0 && "ImGui_ImplDX11_CreateDeviceObjects() failed!");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Regular → Executable
+24
-4
@@ -2,8 +2,10 @@
|
||||
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
|
||||
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
@@ -19,20 +21,38 @@
|
||||
|
||||
struct ID3D11Device;
|
||||
struct ID3D11DeviceContext;
|
||||
struct ID3D11Texture2D;
|
||||
struct ID3D11ShaderResourceView;
|
||||
struct ID3D11SamplerState;
|
||||
struct ID3D11Buffer;
|
||||
|
||||
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
|
||||
|
||||
// Use if you want to reset your rendering device without losing Dear ImGui state.
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
// Use if you want to reset your rendering state without using ImGui_ImplDX11_RenderDrawData.
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
|
||||
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex);
|
||||
|
||||
// Selected render state exposed to applications and callbacks.
|
||||
struct ImGui_ImplDX11_RenderState
|
||||
{
|
||||
ID3D11Device* Device;
|
||||
ID3D11DeviceContext* DeviceContext;
|
||||
ID3D11SamplerState* SamplerLinear;
|
||||
ID3D11SamplerState* SamplerNearest;
|
||||
ID3D11Buffer* VertexConstantBuffer;
|
||||
};
|
||||
|
||||
// ImTextureID should be a pointer to this struct
|
||||
struct ImTextureDX11
|
||||
{
|
||||
ID3D11Texture2D *pTexture;
|
||||
ID3D11ShaderResourceView *shaderResourceView;
|
||||
bool pointSampling;
|
||||
};
|
||||
|
||||
Regular → Executable
+259
-165
@@ -2,8 +2,10 @@
|
||||
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
@@ -15,6 +17,9 @@
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
|
||||
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
|
||||
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
|
||||
// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575)
|
||||
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
|
||||
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
|
||||
@@ -42,15 +47,21 @@
|
||||
// DirectX
|
||||
#include <d3d9.h>
|
||||
|
||||
// Clang/GCC warnings with -Weverything
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
||||
#endif
|
||||
|
||||
// DirectX data
|
||||
struct ImGui_ImplDX9_Data
|
||||
{
|
||||
LPDIRECT3DDEVICE9 pd3dDevice;
|
||||
LPDIRECT3DVERTEXBUFFER9 pVB;
|
||||
LPDIRECT3DINDEXBUFFER9 pIB;
|
||||
ImTextureDX9 FontTexture;
|
||||
int VertexBufferSize;
|
||||
int IndexBufferSize;
|
||||
bool HasRgbaSupport;
|
||||
|
||||
ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
|
||||
};
|
||||
@@ -88,41 +99,41 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
|
||||
vp.Height = (DWORD)draw_data->DisplaySize.y;
|
||||
vp.MinZ = 0.0f;
|
||||
vp.MaxZ = 1.0f;
|
||||
bd->pd3dDevice->SetViewport(&vp);
|
||||
|
||||
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
|
||||
device->SetViewport(&vp);
|
||||
|
||||
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
|
||||
bd->pd3dDevice->SetPixelShader(nullptr);
|
||||
bd->pd3dDevice->SetVertexShader(nullptr);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE);
|
||||
bd->pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||
bd->pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
bd->pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
device->SetPixelShader(nullptr);
|
||||
device->SetVertexShader(nullptr);
|
||||
device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
|
||||
device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
|
||||
device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
device->SetRenderState(D3DRS_ZENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
|
||||
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
|
||||
device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
|
||||
device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
|
||||
device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
|
||||
device->SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_CLIPPING, TRUE);
|
||||
device->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||
device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
// Setup orthographic projection matrix
|
||||
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
|
||||
@@ -140,9 +151,9 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
|
||||
0.0f, 0.0f, 0.5f, 0.0f,
|
||||
(L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f
|
||||
} } };
|
||||
bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
|
||||
bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
|
||||
bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
|
||||
device->SetTransform(D3DTS_WORLD, &mat_identity);
|
||||
device->SetTransform(D3DTS_VIEW, &mat_identity);
|
||||
device->SetTransform(D3DTS_PROJECTION, &mat_projection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,33 +164,65 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
|
||||
return;
|
||||
|
||||
// Create and grow buffers if needed
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
|
||||
|
||||
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
|
||||
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
|
||||
if (draw_data->Textures != nullptr)
|
||||
for (ImTextureData* tex : *draw_data->Textures)
|
||||
if (tex->Status != ImTextureStatus_OK)
|
||||
ImGui_ImplDX9_UpdateTexture(tex);
|
||||
|
||||
// Create and grow buffers if needed
|
||||
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
|
||||
{
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
|
||||
if (bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
|
||||
if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
|
||||
return;
|
||||
}
|
||||
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
|
||||
{
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
|
||||
if (bd->pd3dDevice->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
|
||||
if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Removed in Flycast
|
||||
// Backup the DX9 state
|
||||
IDirect3DStateBlock9* state_block = nullptr;
|
||||
if (device->CreateStateBlock(D3DSBT_ALL, &state_block) < 0)
|
||||
return;
|
||||
if (state_block->Capture() < 0)
|
||||
{
|
||||
state_block->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
|
||||
D3DMATRIX last_world, last_view, last_projection;
|
||||
device->GetTransform(D3DTS_WORLD, &last_world);
|
||||
device->GetTransform(D3DTS_VIEW, &last_view);
|
||||
device->GetTransform(D3DTS_PROJECTION, &last_projection);
|
||||
*/
|
||||
// Allocate buffers
|
||||
CUSTOMVERTEX* vtx_dst;
|
||||
ImDrawIdx* idx_dst;
|
||||
if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
|
||||
{
|
||||
/* Removed in Flycast
|
||||
state_block->Release();
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
|
||||
{
|
||||
bd->pVB->Unlock();
|
||||
/* Removed in Flycast
|
||||
state_block->Release();
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,11 +230,10 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
// FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
|
||||
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR
|
||||
// 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
||||
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||
{
|
||||
const ImDrawList* cmd_list = draw_data->CmdLists[n];
|
||||
const ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data;
|
||||
for (int i = 0; i < cmd_list->VtxBuffer.Size; i++)
|
||||
const ImDrawVert* vtx_src = draw_list->VtxBuffer.Data;
|
||||
for (int i = 0; i < draw_list->VtxBuffer.Size; i++)
|
||||
{
|
||||
vtx_dst->pos[0] = vtx_src->pos.x;
|
||||
vtx_dst->pos[1] = vtx_src->pos.y;
|
||||
@@ -202,14 +244,14 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
vtx_dst++;
|
||||
vtx_src++;
|
||||
}
|
||||
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
idx_dst += cmd_list->IdxBuffer.Size;
|
||||
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
idx_dst += draw_list->IdxBuffer.Size;
|
||||
}
|
||||
bd->pVB->Unlock();
|
||||
bd->pIB->Unlock();
|
||||
bd->pd3dDevice->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
|
||||
bd->pd3dDevice->SetIndices(bd->pIB);
|
||||
bd->pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
|
||||
device->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
|
||||
device->SetIndices(bd->pIB);
|
||||
device->SetFVF(D3DFVF_CUSTOMVERTEX);
|
||||
|
||||
// Setup desired DX state
|
||||
ImGui_ImplDX9_SetupRenderState(draw_data);
|
||||
@@ -219,12 +261,11 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
int global_vtx_offset = 0;
|
||||
int global_idx_offset = 0;
|
||||
ImVec2 clip_off = draw_data->DisplayPos;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
||||
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||
{
|
||||
const ImDrawList* cmd_list = draw_data->CmdLists[n];
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
|
||||
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
|
||||
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback != nullptr)
|
||||
{
|
||||
// User callback, registered via ImDrawList::AddCallback()
|
||||
@@ -232,7 +273,7 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
|
||||
ImGui_ImplDX9_SetupRenderState(draw_data);
|
||||
else
|
||||
pcmd->UserCallback(cmd_list, pcmd);
|
||||
pcmd->UserCallback(draw_list, pcmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -246,73 +287,51 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
|
||||
const ImTextureDX9 *tex = (const ImTextureDX9 *)pcmd->GetTexID();
|
||||
const LPDIRECT3DTEXTURE9 texture = tex->d3dTexture;
|
||||
bd->pd3dDevice->SetTexture(0, texture);
|
||||
if (tex != &bd->FontTexture)
|
||||
{
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
||||
if (tex->pointSampling)
|
||||
{
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
|
||||
}
|
||||
else
|
||||
{
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
}
|
||||
bd->pd3dDevice->SetScissorRect(&r);
|
||||
bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
|
||||
device->SetTexture(0, texture);
|
||||
if((ImTextureID)tex != ImGui::GetIO().Fonts->TexID.GetTexID())
|
||||
{
|
||||
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
||||
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
||||
if (tex->pointSampling)
|
||||
{
|
||||
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
|
||||
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
|
||||
}
|
||||
else
|
||||
{
|
||||
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
}
|
||||
device->SetScissorRect(&r);
|
||||
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)draw_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
|
||||
}
|
||||
}
|
||||
global_idx_offset += cmd_list->IdxBuffer.Size;
|
||||
global_vtx_offset += cmd_list->VtxBuffer.Size;
|
||||
global_idx_offset += draw_list->IdxBuffer.Size;
|
||||
global_vtx_offset += draw_list->VtxBuffer.Size;
|
||||
}
|
||||
/* Removed in Flycast
|
||||
// Restore the DX9 transform
|
||||
device->SetTransform(D3DTS_WORLD, &last_world);
|
||||
device->SetTransform(D3DTS_VIEW, &last_view);
|
||||
device->SetTransform(D3DTS_PROJECTION, &last_projection);
|
||||
|
||||
// Restore the DX9 state
|
||||
state_block->Apply();
|
||||
state_block->Release();
|
||||
*/
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
|
||||
static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IMGUI_CHECKVERSION();
|
||||
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||
|
||||
// Setup backend capabilities flags
|
||||
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_dx9";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
|
||||
bd->pd3dDevice = device;
|
||||
bd->pd3dDevice->AddRef();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_Shutdown()
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
static bool ImGui_ImplDX9_CheckFormatSupport(IDirect3DDevice9* pDevice, D3DFORMAT format)
|
||||
{
|
||||
IDirect3D9* pd3d = nullptr;
|
||||
LPDIRECT3D9 pd3d = nullptr;
|
||||
if (pDevice->GetDirect3D(&pd3d) != D3D_OK)
|
||||
return false;
|
||||
D3DDEVICE_CREATION_PARAMETERS param = {};
|
||||
@@ -328,58 +347,134 @@ static bool ImGui_ImplDX9_CheckFormatSupport(IDirect3DDevice9* pDevice, D3DFORMA
|
||||
return support;
|
||||
}
|
||||
|
||||
static bool ImGui_ImplDX9_CreateFontsTexture()
|
||||
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
|
||||
{
|
||||
// Build texture atlas
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
unsigned char* pixels;
|
||||
int width, height, bytes_per_pixel;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
|
||||
IMGUI_CHECKVERSION();
|
||||
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||
|
||||
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
|
||||
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
const bool rgba_support = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
|
||||
if (!rgba_support && io.Fonts->TexPixelsUseColors)
|
||||
{
|
||||
ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel);
|
||||
for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++)
|
||||
*dst = IMGUI_COL_TO_DX9_ARGB(*src);
|
||||
pixels = (unsigned char*)dst_start;
|
||||
}
|
||||
#else
|
||||
const bool rgba_support = false;
|
||||
#endif
|
||||
// Setup backend capabilities flags
|
||||
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_dx9";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
|
||||
|
||||
// Upload texture to graphics system
|
||||
bd->FontTexture.d3dTexture = nullptr;
|
||||
if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, rgba_support ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture.d3dTexture, nullptr) < 0)
|
||||
return false;
|
||||
D3DLOCKED_RECT tex_locked_rect;
|
||||
if (bd->FontTexture.d3dTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK)
|
||||
return false;
|
||||
for (int y = 0; y < height; y++)
|
||||
memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel);
|
||||
bd->FontTexture.d3dTexture->UnlockRect(0);
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts->SetTexID((ImTextureID)&bd->FontTexture);
|
||||
|
||||
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
if (!rgba_support && io.Fonts->TexPixelsUseColors)
|
||||
ImGui::MemFree(pixels);
|
||||
#endif
|
||||
bd->pd3dDevice = device;
|
||||
bd->pd3dDevice->AddRef();
|
||||
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_Shutdown()
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
|
||||
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
|
||||
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
|
||||
platform_io.ClearRendererHandlers();
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
|
||||
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
|
||||
{
|
||||
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors);
|
||||
#else
|
||||
const bool convert_rgba_to_bgra = false;
|
||||
IM_UNUSED(tex_use_colors);
|
||||
#endif
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y);
|
||||
ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y);
|
||||
if (convert_rgba_to_bgra)
|
||||
for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy
|
||||
*dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p);
|
||||
else
|
||||
memcpy(dst_p, src_p, w * 4); // Raw copy
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
|
||||
if (tex->Status == ImTextureStatus_WantCreate)
|
||||
{
|
||||
// Create and upload new texture to graphics system
|
||||
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
|
||||
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
|
||||
LPDIRECT3DTEXTURE9 dx_tex = nullptr;
|
||||
HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr);
|
||||
if (hr < 0)
|
||||
{
|
||||
IM_ASSERT(hr >= 0 && "Backend failed to create texture!");
|
||||
return;
|
||||
}
|
||||
|
||||
D3DLOCKED_RECT locked_rect;
|
||||
if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK)
|
||||
{
|
||||
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height);
|
||||
dx_tex->UnlockRect(0);
|
||||
}
|
||||
|
||||
// Store identifiers
|
||||
ImTextureDX9* tex_wrapper = new ImTextureDX9{dx_tex, false};
|
||||
tex->SetTexID((ImTextureID)tex_wrapper);
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
}
|
||||
else if (tex->Status == ImTextureStatus_WantUpdates)
|
||||
{
|
||||
// Update selected blocks. We only ever write to textures regions which have never been used before!
|
||||
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
|
||||
const ImTextureDX9 *tex_wrapper = (const ImTextureDX9 *)tex->TexID;
|
||||
const LPDIRECT3DTEXTURE9 backend_tex = tex_wrapper->d3dTexture;
|
||||
RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) };
|
||||
D3DLOCKED_RECT locked_rect;
|
||||
if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK)
|
||||
for (ImTextureRect& r : tex->Updates)
|
||||
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4,
|
||||
(ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h);
|
||||
backend_tex->UnlockRect(0);
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
}
|
||||
else if (tex->Status == ImTextureStatus_WantDestroy)
|
||||
{
|
||||
ImTextureDX9 *tex_wrapper = (ImTextureDX9 *)tex->TexID;
|
||||
if (tex_wrapper)
|
||||
{
|
||||
IM_ASSERT(tex_wrapper == (ImTextureDX9*)tex->TexID);
|
||||
tex_wrapper->d3dTexture->Release();
|
||||
tex_wrapper->d3dTexture = nullptr;
|
||||
|
||||
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
|
||||
tex->SetTexID(ImTextureID_Invalid);
|
||||
}
|
||||
tex->SetStatus(ImTextureStatus_Destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX9_CreateDeviceObjects()
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
if (!bd || !bd->pd3dDevice)
|
||||
return false;
|
||||
if (!ImGui_ImplDX9_CreateFontsTexture())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -388,24 +483,23 @@ void ImGui_ImplDX9_InvalidateDeviceObjects()
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
if (!bd || !bd->pd3dDevice)
|
||||
return;
|
||||
|
||||
// Destroy all textures
|
||||
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
|
||||
if (tex->RefCount == 1)
|
||||
{
|
||||
tex->SetStatus(ImTextureStatus_WantDestroy);
|
||||
ImGui_ImplDX9_UpdateTexture(tex);
|
||||
}
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
if (bd->FontTexture.d3dTexture)
|
||||
{
|
||||
bd->FontTexture.d3dTexture->Release();
|
||||
bd->FontTexture.d3dTexture = nullptr;
|
||||
// We copied bd->FontTexture to io.Fonts->TexID so let's clear that as well.
|
||||
ImGui::GetIO().Fonts->SetTexID(0);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_NewFrame()
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?");
|
||||
|
||||
if (!bd->FontTexture.d3dTexture)
|
||||
ImGui_ImplDX9_CreateDeviceObjects();
|
||||
IM_UNUSED(bd);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Regular → Executable
+8
-3
@@ -2,8 +2,10 @@
|
||||
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
@@ -20,12 +22,13 @@
|
||||
struct IDirect3DDevice9;
|
||||
struct IDirect3DTexture9;
|
||||
|
||||
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9* device);
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
|
||||
|
||||
// Use if you want to reset your rendering device without losing Dear ImGui state.
|
||||
// Use if you want to reset your rendering state without using ImGui_ImplDX9_RenderDrawData.
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
|
||||
@@ -35,5 +38,7 @@ struct ImTextureDX9
|
||||
IDirect3DTexture9 *d3dTexture;
|
||||
bool pointSampling;
|
||||
};
|
||||
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex);
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
Regular → Executable
+345
-164
@@ -11,9 +11,64 @@
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2025-12-11: OpenGL: Fixed embedded loader multiple init/shutdown cycles broken on some platforms. (#8792, #9112)
|
||||
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
|
||||
// 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792)
|
||||
// 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures (#8802) + restore non-WebGL/ES update path that doesn't require a CPU-side copy.
|
||||
// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture().
|
||||
// 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664)
|
||||
// 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406)
|
||||
// 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap.
|
||||
// 2024-06-28: OpenGL: ImGui_ImplOpenGL3_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL3_DestroyFontsTexture(). (#7748)
|
||||
// 2024-05-07: OpenGL: Update loader for Linux to support EGL/GLVND. (#7562)
|
||||
// 2024-04-16: OpenGL: Detect ES3 contexts on desktop based on version string, to e.g. avoid calling glPolygonMode() on them. (#7447)
|
||||
// 2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink.
|
||||
// 2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accommodating for NetBSD systems having only "libGL.so.3" available. (#6983)
|
||||
// 2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445)
|
||||
// 2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333)
|
||||
// 2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375)
|
||||
// 2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333)
|
||||
// 2023-03-23: OpenGL: Properly restoring "no shader program bound" if it was the case prior to running the rendering function. (#6267, #6220, #6224)
|
||||
// 2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns nullptr. (#6154, #4445, #3530)
|
||||
// 2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224)
|
||||
// 2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes).
|
||||
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
|
||||
// 2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'.
|
||||
// 2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127).
|
||||
// 2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states.
|
||||
// 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers.
|
||||
// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions.
|
||||
// 2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader.
|
||||
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
|
||||
// 2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state.
|
||||
// 2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader.
|
||||
// 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version.
|
||||
// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
|
||||
// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater.
|
||||
// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer.
|
||||
// 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state.
|
||||
// 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state.
|
||||
// 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x)
|
||||
// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre-3.3 context which have the defines set by a loader.
|
||||
// 2020-07-10: OpenGL: Added support for glad2 OpenGL loader.
|
||||
// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX.
|
||||
// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix.
|
||||
// 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset.
|
||||
// 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader.
|
||||
// 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader.
|
||||
// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders.
|
||||
// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.
|
||||
// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.
|
||||
// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
|
||||
// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
|
||||
// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.
|
||||
// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.
|
||||
// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).
|
||||
// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader.
|
||||
// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
|
||||
// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).
|
||||
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
|
||||
// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT).
|
||||
// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.
|
||||
// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.
|
||||
// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES".
|
||||
// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.
|
||||
@@ -21,7 +76,7 @@
|
||||
// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.
|
||||
// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
|
||||
// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.
|
||||
// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer.
|
||||
// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer.
|
||||
// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150".
|
||||
// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.
|
||||
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.
|
||||
@@ -37,17 +92,17 @@
|
||||
// version version string
|
||||
//----------------------------------------
|
||||
// 2.0 110 "#version 110"
|
||||
// 2.1 120
|
||||
// 3.0 130
|
||||
// 3.1 140
|
||||
// 2.1 120 "#version 120"
|
||||
// 3.0 130 "#version 130"
|
||||
// 3.1 140 "#version 140"
|
||||
// 3.2 150 "#version 150"
|
||||
// 3.3 330
|
||||
// 4.0 400
|
||||
// 3.3 330 "#version 330 core"
|
||||
// 4.0 400 "#version 400 core"
|
||||
// 4.1 410 "#version 410 core"
|
||||
// 4.2 420
|
||||
// 4.3 430
|
||||
// ES 2.0 100 "#version 100"
|
||||
// ES 3.0 300 "#version 300 es"
|
||||
// 4.2 420 "#version 410 core"
|
||||
// 4.3 430 "#version 430 core"
|
||||
// ES 2.0 100 "#version 100" = WebGL 1.0
|
||||
// ES 3.0 300 "#version 300 es" = WebGL 2.0
|
||||
//----------------------------------------
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
@@ -55,6 +110,7 @@
|
||||
#endif
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DISABLE
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <stdio.h>
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
|
||||
@@ -69,76 +125,110 @@
|
||||
#include "rend/gles/glcache.h"
|
||||
|
||||
// OpenGL Data
|
||||
static char g_GlslVersionString[32] = "";
|
||||
static GLuint g_FontTexture = 0;
|
||||
static GLuint g_ShaderHandle = 0;
|
||||
static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0;
|
||||
static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0;
|
||||
static unsigned int g_VboHandle = 0, g_ElementsHandle = 0;
|
||||
struct ImGui_ImplOpenGL3_Data
|
||||
{
|
||||
GLuint GlVersion;
|
||||
char GlslVersionString[32];
|
||||
GLuint ShaderHandle;
|
||||
GLint AttribLocationTex;
|
||||
GLint AttribLocationProjMtx;
|
||||
GLuint AttribLocationVtxPos;
|
||||
GLuint AttribLocationVtxUV;
|
||||
GLuint AttribLocationVtxColor;
|
||||
unsigned int VboHandle, ElementsHandle;
|
||||
bool HasPolygonMode;
|
||||
bool HasBindSampler;
|
||||
bool HasClipOrigin;
|
||||
ImVector<char> TempBuffer;
|
||||
|
||||
ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
|
||||
static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()
|
||||
{
|
||||
return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
|
||||
}
|
||||
|
||||
// Functions
|
||||
static bool ImGui_ImplOpenGL3_CreateDeviceObjects();
|
||||
static void ImGui_ImplOpenGL3_DestroyDeviceObjects();
|
||||
bool ImGui_ImplOpenGL3_CreateDeviceObjects();
|
||||
void ImGui_ImplOpenGL3_DestroyDeviceObjects();
|
||||
|
||||
bool ImGui_ImplOpenGL3_Init()
|
||||
bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IMGUI_CHECKVERSION();
|
||||
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||
|
||||
// Setup backend capabilities flags
|
||||
ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_opengl3";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures;
|
||||
|
||||
// Store GLSL version string so we can refer to it later in case we recreate shaders. Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure.
|
||||
const char* glsl_version;
|
||||
if (theGLContext.isGLES())
|
||||
glsl_version = "#version 100"; // OpenGL ES 2.0
|
||||
else
|
||||
// Flycast: Detect version using GL context
|
||||
if (glsl_version == nullptr)
|
||||
{
|
||||
if (GLGraphicsContext::Instance()->isGLES())
|
||||
glsl_version = "#version 100"; // OpenGL ES 2.0
|
||||
else
|
||||
#if defined(__APPLE__)
|
||||
glsl_version = "#version 140"; // OpenGL 3.1
|
||||
glsl_version = "#version 140"; // OpenGL 3.1
|
||||
#else
|
||||
glsl_version = "#version 130"; // OpenGL 3.0
|
||||
glsl_version = "#version 130"; // OpenGL 3.0
|
||||
#endif
|
||||
}
|
||||
IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));
|
||||
strcpy(bd->GlslVersionString, glsl_version);
|
||||
strcat(bd->GlslVersionString, "\n");
|
||||
|
||||
// Detect extensions we support
|
||||
const bool isGLES = GLGraphicsContext::Instance()->isGLES();
|
||||
const int majorVer = GLGraphicsContext::Instance()->getMajorVersion();
|
||||
bd->HasPolygonMode = !isGLES;
|
||||
if (isGLES)
|
||||
// GLES 3.0+
|
||||
bd->HasBindSampler = majorVer >= 3;
|
||||
else
|
||||
// OpenGL 3.3+
|
||||
bd->HasBindSampler = majorVer > 3
|
||||
|| (majorVer == 3 && GLGraphicsContext::Instance()->getMinorVersion() >= 3);
|
||||
#ifdef GL_CLIP_ORIGIN
|
||||
bd->HasClipOrigin = (majorVer >= 4 && glClipControl != NULL);
|
||||
#endif
|
||||
IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString));
|
||||
strcpy(g_GlslVersionString, glsl_version);
|
||||
strcat(g_GlslVersionString, "\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplOpenGL3_Shutdown()
|
||||
{
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
|
||||
ImGui_ImplOpenGL3_DestroyDeviceObjects();
|
||||
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures;
|
||||
platform_io.ClearRendererHandlers();
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
void ImGui_ImplOpenGL3_NewFrame()
|
||||
{
|
||||
if (!g_FontTexture)
|
||||
ImGui_ImplOpenGL3_CreateDeviceObjects();
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL3_Init()?");
|
||||
|
||||
if (!bd->ShaderHandle)
|
||||
if (!ImGui_ImplOpenGL3_CreateDeviceObjects())
|
||||
IM_ASSERT(0 && "ImGui_ImplOpenGL3_CreateDeviceObjects() failed!");
|
||||
}
|
||||
|
||||
// OpenGL3 Render function.
|
||||
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
|
||||
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so.
|
||||
void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
|
||||
{
|
||||
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x);
|
||||
int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y);
|
||||
if (fb_width <= 0 || fb_height <= 0)
|
||||
return;
|
||||
draw_data->ScaleClipRects(io.DisplayFramebufferScale);
|
||||
|
||||
// Backup GL state
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
bool clip_origin_lower_left = true;
|
||||
#ifdef GL_CLIP_ORIGIN
|
||||
if (theGLContext.getMajorVersion() >= 4 && glClipControl != NULL)
|
||||
{
|
||||
GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT)
|
||||
if (last_clip_origin == GL_UPPER_LEFT)
|
||||
clip_origin_lower_left = false;
|
||||
}
|
||||
#endif
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
|
||||
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
|
||||
glcache.Enable(GL_BLEND);
|
||||
@@ -146,13 +236,24 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glcache.Disable(GL_CULL_FACE);
|
||||
glcache.Disable(GL_DEPTH_TEST);
|
||||
glcache.Enable(GL_SCISSOR_TEST);
|
||||
glcache.Disable(GL_STENCIL_TEST);
|
||||
glcache.Enable(GL_SCISSOR_TEST);
|
||||
#ifdef GL_POLYGON_MODE
|
||||
if (glPolygonMode != NULL)
|
||||
if (bd->HasPolygonMode && glPolygonMode != NULL)
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
#endif
|
||||
|
||||
// Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
|
||||
#if defined(GL_CLIP_ORIGIN)
|
||||
bool clip_origin_lower_left = true;
|
||||
if (bd->HasClipOrigin)
|
||||
{
|
||||
GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin);
|
||||
if (current_clip_origin == GL_UPPER_LEFT)
|
||||
clip_origin_lower_left = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setup viewport, orthographic projection matrix
|
||||
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
|
||||
glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);
|
||||
@@ -160,6 +261,9 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
|
||||
float T = draw_data->DisplayPos.y;
|
||||
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
|
||||
#if defined(GL_CLIP_ORIGIN)
|
||||
if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left
|
||||
#endif
|
||||
const float ortho_projection[4][4] =
|
||||
{
|
||||
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
|
||||
@@ -167,64 +271,95 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
{ 0.0f, 0.0f, -1.0f, 0.0f },
|
||||
{ (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f },
|
||||
};
|
||||
glcache.UseProgram(g_ShaderHandle);
|
||||
glUniform1i(g_AttribLocationTex, 0);
|
||||
glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
|
||||
glcache.UseProgram(bd->ShaderHandle);
|
||||
glUniform1i(bd->AttribLocationTex, 0);
|
||||
glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
|
||||
#ifndef GLES2
|
||||
if (theGLContext.getMajorVersion() >= 3 && glBindSampler != NULL)
|
||||
if (bd->HasBindSampler && glBindSampler != NULL)
|
||||
glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise.
|
||||
#endif
|
||||
GLuint vao_handle = 0;
|
||||
#ifndef GLES2
|
||||
if (theGLContext.getMajorVersion() >= 3)
|
||||
{
|
||||
// Recreate the VAO every time
|
||||
// (This is to easily allow multiple GL contexts. VAO are not shared among GL contexts, and we don't track creation/deletion of windows so we don't have an obvious key to use to cache them.)
|
||||
glGenVertexArrays(1, &vao_handle);
|
||||
glBindVertexArray(vao_handle);
|
||||
}
|
||||
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
|
||||
glBindVertexArray(vertex_array_object);
|
||||
#endif
|
||||
glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle);
|
||||
glEnableVertexAttribArray(g_AttribLocationPosition);
|
||||
glEnableVertexAttribArray(g_AttribLocationUV);
|
||||
glEnableVertexAttribArray(g_AttribLocationColor);
|
||||
glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
|
||||
glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv));
|
||||
glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
|
||||
glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle);
|
||||
glEnableVertexAttribArray(bd->AttribLocationVtxPos);
|
||||
glEnableVertexAttribArray(bd->AttribLocationVtxUV);
|
||||
glEnableVertexAttribArray(bd->AttribLocationVtxColor);
|
||||
glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
|
||||
glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv));
|
||||
glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
|
||||
}
|
||||
|
||||
// Draw
|
||||
ImVec2 pos = draw_data->DisplayPos;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
||||
// OpenGL3 Render function.
|
||||
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
|
||||
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so.
|
||||
void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
{
|
||||
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
|
||||
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
|
||||
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
|
||||
if (fb_width <= 0 || fb_height <= 0)
|
||||
return;
|
||||
|
||||
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
|
||||
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
|
||||
if (draw_data->Textures != nullptr)
|
||||
for (ImTextureData* tex : *draw_data->Textures)
|
||||
if (tex->Status != ImTextureStatus_OK)
|
||||
ImGui_ImplOpenGL3_UpdateTexture(tex);
|
||||
|
||||
// Backup GL state
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
// Setup desired GL state
|
||||
// Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
|
||||
// The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
|
||||
GLuint vertex_array_object = 0;
|
||||
#ifndef GLES2
|
||||
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
|
||||
glGenVertexArrays(1, &vertex_array_object);
|
||||
#endif
|
||||
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
|
||||
|
||||
// Will project scissor/clipping rectangles into framebuffer space
|
||||
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
|
||||
ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
|
||||
|
||||
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||
{
|
||||
const ImDrawList* cmd_list = draw_data->CmdLists[n];
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW);
|
||||
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)draw_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)draw_list->VtxBuffer.Data, GL_STREAM_DRAW);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)draw_list->IdxBuffer.Data, GL_STREAM_DRAW);
|
||||
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
|
||||
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback)
|
||||
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback != nullptr)
|
||||
{
|
||||
// User callback (registered via ImDrawList::AddCallback)
|
||||
pcmd->UserCallback(cmd_list, pcmd);
|
||||
// User callback, registered via ImDrawList::AddCallback()
|
||||
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
|
||||
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
|
||||
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
|
||||
else
|
||||
pcmd->UserCallback(draw_list, pcmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImVec4 clip_rect = ImVec4(pcmd->ClipRect.x - pos.x, pcmd->ClipRect.y - pos.y, pcmd->ClipRect.z - pos.x, pcmd->ClipRect.w - pos.y);
|
||||
if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f)
|
||||
{
|
||||
// Apply scissor/clipping rectangle
|
||||
if (clip_origin_lower_left)
|
||||
glcache.Scissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y));
|
||||
else
|
||||
glcache.Scissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT)
|
||||
// Project scissor/clipping rectangles into framebuffer space
|
||||
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
|
||||
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
|
||||
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
||||
continue;
|
||||
|
||||
// Bind texture, Draw
|
||||
glcache.BindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId);
|
||||
glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));
|
||||
}
|
||||
// Apply scissor/clipping rectangle (Y is inverted in OpenGL)
|
||||
glcache.Scissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y));
|
||||
|
||||
// Bind texture, Draw
|
||||
glcache.BindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID());
|
||||
glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,43 +367,77 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
glcache.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
#ifndef GLES2
|
||||
if (vao_handle != 0)
|
||||
glDeleteVertexArrays(1, &vao_handle);
|
||||
if (vertex_array_object != 0)
|
||||
glDeleteVertexArrays(1, &vertex_array_object);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool ImGui_ImplOpenGL3_CreateFontsTexture()
|
||||
static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex)
|
||||
{
|
||||
// Build texture atlas
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
|
||||
|
||||
// Upload texture to graphics system
|
||||
g_FontTexture = glcache.GenTexture();
|
||||
glcache.BindTexture(GL_TEXTURE_2D, g_FontTexture);
|
||||
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
if (theGLContext.getMajorVersion() >= 3)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture;
|
||||
|
||||
return true;
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
if (tex->BackendUserData != (void*)bd)
|
||||
return;
|
||||
GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
|
||||
if (gl_tex_id != 0)
|
||||
glcache.DeleteTextures(1, &gl_tex_id);
|
||||
tex->SetTexID(ImTextureID_Invalid);
|
||||
tex->SetStatus(ImTextureStatus_Destroyed);
|
||||
tex->BackendUserData = nullptr;
|
||||
}
|
||||
|
||||
static void ImGui_ImplOpenGL3_DestroyFontsTexture()
|
||||
void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex)
|
||||
{
|
||||
if (g_FontTexture)
|
||||
if (tex->Status == ImTextureStatus_Destroyed)
|
||||
tex->SetStatus(ImTextureStatus_WantCreate);
|
||||
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
|
||||
if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
glcache.DeleteTextures(1, &g_FontTexture);
|
||||
io.Fonts->TexID = 0;
|
||||
g_FontTexture = 0;
|
||||
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
if (tex->Status == ImTextureStatus_WantCreate)
|
||||
{
|
||||
GLuint gl_texture_id = glcache.GenTexture();
|
||||
glcache.BindTexture(GL_TEXTURE_2D, gl_texture_id);
|
||||
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixels());
|
||||
|
||||
tex->SetTexID((ImTextureID)(intptr_t)gl_texture_id);
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
tex->BackendUserData = (void*)bd;
|
||||
}
|
||||
else if (tex->Status == ImTextureStatus_WantUpdates)
|
||||
{
|
||||
GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
|
||||
glcache.BindTexture(GL_TEXTURE_2D, gl_tex_id);
|
||||
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
|
||||
{
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width);
|
||||
for (auto& r : tex->Updates)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y));
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback for GLES2 / No unpack row length: use a temporary buffer to upload
|
||||
for (auto& r : tex->Updates)
|
||||
{
|
||||
const int src_pitch = r.w * 4; // RGBA
|
||||
bd->TempBuffer.resize(r.h * src_pitch);
|
||||
char* out_p = bd->TempBuffer.Data;
|
||||
for (int y = 0; y < r.h; y++, out_p += src_pitch)
|
||||
memcpy(out_p, (const char*)tex->GetPixelsAt(r.x, r.y + y), src_pitch);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, bd->TempBuffer.Data);
|
||||
}
|
||||
}
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
}
|
||||
else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
|
||||
ImGui_ImplOpenGL3_DestroyTexture(tex);
|
||||
}
|
||||
|
||||
// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
|
||||
@@ -292,11 +461,12 @@ static bool CheckShader(GLuint handle, const char* desc)
|
||||
// If you get an error please report on GitHub. You may try different GL context version or GLSL version.
|
||||
static bool CheckProgram(GLuint handle, const char* desc)
|
||||
{
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
GLint status = 0, log_length = 0;
|
||||
glGetProgramiv(handle, GL_LINK_STATUS, &status);
|
||||
glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
|
||||
if ((GLboolean)status == GL_FALSE)
|
||||
WARN_LOG(RENDERER, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')", desc, g_GlslVersionString);
|
||||
WARN_LOG(RENDERER, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')", desc, bd->GlslVersionString);
|
||||
if (log_length > 0)
|
||||
{
|
||||
ImVector<char> buf;
|
||||
@@ -307,11 +477,13 @@ static bool CheckProgram(GLuint handle, const char* desc)
|
||||
return (GLboolean)status == GL_TRUE;
|
||||
}
|
||||
|
||||
static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
|
||||
bool ImGui_ImplOpenGL3_CreateDeviceObjects()
|
||||
{
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
|
||||
// Parse GLSL version string
|
||||
int glsl_version = 130;
|
||||
sscanf(g_GlslVersionString, "#version %d", &glsl_version);
|
||||
sscanf(bd->GlslVersionString, "#version %d", &glsl_version);
|
||||
|
||||
const GLchar* vertex_shader_glsl_120 =
|
||||
"uniform mat4 ProjMtx;\n"
|
||||
@@ -342,7 +514,7 @@ static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
|
||||
"}\n";
|
||||
|
||||
const GLchar* vertex_shader_glsl_300_es =
|
||||
"precision mediump float;\n"
|
||||
"precision highp float;\n"
|
||||
"layout (location = 0) in vec2 Position;\n"
|
||||
"layout (location = 1) in vec2 UV;\n"
|
||||
"layout (location = 2) in vec4 Color;\n"
|
||||
@@ -414,8 +586,8 @@ static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
|
||||
"}\n";
|
||||
|
||||
// Select shaders matching our GLSL versions
|
||||
const GLchar* vertex_shader = NULL;
|
||||
const GLchar* fragment_shader = NULL;
|
||||
const GLchar* vertex_shader = nullptr;
|
||||
const GLchar* fragment_shader = nullptr;
|
||||
if (glsl_version < 130)
|
||||
{
|
||||
vertex_shader = vertex_shader_glsl_120;
|
||||
@@ -438,52 +610,61 @@ static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
|
||||
}
|
||||
|
||||
// Create shaders
|
||||
const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader };
|
||||
const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader };
|
||||
GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vert_handle, 2, vertex_shader_with_version, NULL);
|
||||
glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
|
||||
glCompileShader(vert_handle);
|
||||
CheckShader(vert_handle, "vertex shader");
|
||||
if (!CheckShader(vert_handle, "vertex shader"))
|
||||
return false;
|
||||
|
||||
const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader };
|
||||
const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };
|
||||
GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(frag_handle, 2, fragment_shader_with_version, NULL);
|
||||
glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
|
||||
glCompileShader(frag_handle);
|
||||
CheckShader(frag_handle, "fragment shader");
|
||||
if (!CheckShader(frag_handle, "fragment shader"))
|
||||
return false;
|
||||
|
||||
g_ShaderHandle = glCreateProgram();
|
||||
glAttachShader(g_ShaderHandle, vert_handle);
|
||||
glAttachShader(g_ShaderHandle, frag_handle);
|
||||
glLinkProgram(g_ShaderHandle);
|
||||
CheckProgram(g_ShaderHandle, "shader program");
|
||||
// Link
|
||||
bd->ShaderHandle = glCreateProgram();
|
||||
glAttachShader(bd->ShaderHandle, vert_handle);
|
||||
glAttachShader(bd->ShaderHandle, frag_handle);
|
||||
glLinkProgram(bd->ShaderHandle);
|
||||
if (!CheckProgram(bd->ShaderHandle, "shader program"))
|
||||
return false;
|
||||
|
||||
glDetachShader(g_ShaderHandle, vert_handle);
|
||||
glDetachShader(g_ShaderHandle, frag_handle);
|
||||
glDetachShader(bd->ShaderHandle, vert_handle);
|
||||
glDetachShader(bd->ShaderHandle, frag_handle);
|
||||
glDeleteShader(vert_handle);
|
||||
glDeleteShader(frag_handle);
|
||||
|
||||
g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture");
|
||||
g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx");
|
||||
g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position");
|
||||
g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV");
|
||||
g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color");
|
||||
bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture");
|
||||
bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx");
|
||||
bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position");
|
||||
bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV");
|
||||
bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color");
|
||||
|
||||
// Create buffers
|
||||
glGenBuffers(1, &g_VboHandle);
|
||||
glGenBuffers(1, &g_ElementsHandle);
|
||||
|
||||
ImGui_ImplOpenGL3_CreateFontsTexture();
|
||||
glGenBuffers(1, &bd->VboHandle);
|
||||
glGenBuffers(1, &bd->ElementsHandle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ImGui_ImplOpenGL3_DestroyDeviceObjects()
|
||||
void ImGui_ImplOpenGL3_DestroyDeviceObjects()
|
||||
{
|
||||
if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle);
|
||||
if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle);
|
||||
g_VboHandle = g_ElementsHandle = 0;
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
if (bd == nullptr) {
|
||||
WARN_LOG(RENDERER, "ImGui_ImplOpenGL3_DestroyDeviceObjects: backend data is null");
|
||||
return;
|
||||
}
|
||||
if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
|
||||
if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
|
||||
if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
|
||||
|
||||
glcache.DeleteProgram(g_ShaderHandle);
|
||||
g_ShaderHandle = 0;
|
||||
|
||||
ImGui_ImplOpenGL3_DestroyFontsTexture();
|
||||
// Destroy all textures
|
||||
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
|
||||
if (tex->RefCount == 1)
|
||||
ImGui_ImplOpenGL3_DestroyTexture(tex);
|
||||
}
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
Regular → Executable
+56
-23
@@ -1,35 +1,68 @@
|
||||
// dear imgui: Renderer for OpenGL3 / OpenGL ES2 / OpenGL ES3 (modern OpenGL with shaders / programmatic pipeline)
|
||||
// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..)
|
||||
// (Note: We are using GL3W as a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc..)
|
||||
// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
|
||||
// - Desktop GL: 2.x 3.x 4.x
|
||||
// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
|
||||
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
|
||||
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!]
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
|
||||
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
|
||||
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
|
||||
// https://github.com/ocornut/imgui
|
||||
// About WebGL/ES:
|
||||
// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
|
||||
// - This is done automatically on iOS, Android and Emscripten targets.
|
||||
// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
|
||||
|
||||
// About OpenGL function loaders:
|
||||
// About OpenGL function loaders: modern OpenGL doesn't have a standard header file and requires individual function pointers to be loaded manually.
|
||||
// Helper libraries are often used for this purpose! Here we are supporting a few common ones: gl3w, glew, glad.
|
||||
// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own.
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// Learn about Dear ImGui:
|
||||
// - FAQ https://dearimgui.com/faq
|
||||
// - Getting Started https://dearimgui.com/getting-started
|
||||
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
|
||||
// - Introduction, links and more at the top of imgui.cpp
|
||||
|
||||
// About GLSL version:
|
||||
// The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string.
|
||||
// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
|
||||
// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
|
||||
// The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string.
|
||||
// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
|
||||
// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
|
||||
|
||||
#pragma once
|
||||
#include "imgui.h" // IMGUI_IMPL_API
|
||||
#ifndef IMGUI_DISABLE
|
||||
|
||||
// Set default OpenGL loader to be gl3w
|
||||
#if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \
|
||||
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \
|
||||
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \
|
||||
&& !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
|
||||
#define IMGUI_IMPL_OPENGL_LOADER_GL3W
|
||||
#endif
|
||||
|
||||
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init();
|
||||
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
|
||||
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
|
||||
|
||||
// (Optional) Called by Init/NewFrame/Shutdown
|
||||
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
|
||||
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
|
||||
|
||||
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
|
||||
IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex);
|
||||
|
||||
// Configuration flags to add in your imconfig file:
|
||||
//#define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten)
|
||||
//#define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (Auto-detected on iOS/Android)
|
||||
|
||||
// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
|
||||
#if !defined(IMGUI_IMPL_OPENGL_ES2) \
|
||||
&& !defined(IMGUI_IMPL_OPENGL_ES3)
|
||||
|
||||
// Try to detect GLES on matching platforms
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))
|
||||
#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es"
|
||||
#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__)
|
||||
#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100"
|
||||
#else
|
||||
// Otherwise imgui_impl_opengl3_loader.h will be used.
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
Regular → Executable
+678
-360
File diff suppressed because it is too large.
Load diff
Regular → Executable
+139
-60
@@ -2,11 +2,10 @@
|
||||
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
|
||||
|
||||
// Implemented features:
|
||||
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions.
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
|
||||
|
||||
// Important: on 32-bit systems, user texture binding is only supported if your imconfig file has '#define ImTextureID ImU64'.
|
||||
// See imgui_impl_vulkan.cpp file for details.
|
||||
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions.
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
|
||||
|
||||
// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification.
|
||||
// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
|
||||
@@ -42,9 +41,20 @@
|
||||
// If you have no idea what this is, leave it alone!
|
||||
//#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES
|
||||
|
||||
// Convenience support for Volk
|
||||
// [Configuration] Convenience support for Volk
|
||||
// (you can also technically use IMGUI_IMPL_VULKAN_NO_PROTOTYPES + wrap Volk via ImGui_ImplVulkan_LoadFunctions().)
|
||||
// (When using Volk from directory outside your include directories list you can specify full path to the volk.h header,
|
||||
// for example when using Volk from VulkanSDK and using include_directories(${Vulkan_INCLUDE_DIRS})' from 'find_package(Vulkan REQUIRED)')
|
||||
//#define IMGUI_IMPL_VULKAN_USE_VOLK
|
||||
//#define IMGUI_IMPL_VULKAN_VOLK_FILENAME <Volk/volk.h>
|
||||
//#define IMGUI_IMPL_VULKAN_VOLK_FILENAME <volk.h> // Default
|
||||
// Reminder: make those changes in your imconfig.h file, not here!
|
||||
|
||||
// Clang/GCC warnings with -Weverything
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
|
||||
#endif
|
||||
|
||||
#if defined(IMGUI_IMPL_VULKAN_NO_PROTOTYPES) && !defined(VK_NO_PROTOTYPES)
|
||||
#define VK_NO_PROTOTYPES
|
||||
@@ -55,7 +65,11 @@
|
||||
|
||||
// Vulkan includes
|
||||
#ifdef IMGUI_IMPL_VULKAN_USE_VOLK
|
||||
#include <Volk/volk.h>
|
||||
#ifdef IMGUI_IMPL_VULKAN_VOLK_FILENAME
|
||||
#include IMGUI_IMPL_VULKAN_VOLK_FILENAME
|
||||
#else
|
||||
#include <volk.h>
|
||||
#endif
|
||||
#else
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
@@ -63,84 +77,131 @@
|
||||
#define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
|
||||
#endif
|
||||
|
||||
// Backend uses a small number of descriptors per font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture().
|
||||
#define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (8) // Minimum per atlas
|
||||
|
||||
// Specify settings to create pipeline and swapchain
|
||||
struct ImGui_ImplVulkan_PipelineInfo
|
||||
{
|
||||
VkRenderPass RenderPass; // Ignored if using dynamic rendering
|
||||
uint32_t Subpass; //
|
||||
VkSampleCountFlagBits MSAASamples = {}; // 0 defaults to VK_SAMPLE_COUNT_1_BIT
|
||||
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
|
||||
VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo; // Optional, valid if .sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR
|
||||
#endif
|
||||
};
|
||||
|
||||
// Initialization data, for ImGui_ImplVulkan_Init()
|
||||
// - VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
|
||||
// and must contain a pool size large enough to hold an ImGui VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptor.
|
||||
// - When using dynamic rendering, set UseDynamicRendering=true and fill PipelineRenderingCreateInfo structure.
|
||||
// [Please zero-clear before use!]
|
||||
// - About descriptor pool:
|
||||
// - A VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
|
||||
// and must contain a pool size large enough to hold a small number of VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptors.
|
||||
// - As an convenience, by setting DescriptorPoolSize > 0 the backend will create one for you.
|
||||
// - About dynamic rendering:
|
||||
// - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure.
|
||||
struct ImGui_ImplVulkan_InitInfo
|
||||
{
|
||||
uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE)
|
||||
VkInstance Instance;
|
||||
VkPhysicalDevice PhysicalDevice;
|
||||
VkDevice Device;
|
||||
uint32_t QueueFamily;
|
||||
VkQueue Queue;
|
||||
VkDescriptorPool DescriptorPool; // See requirements in note above
|
||||
VkRenderPass RenderPass; // Ignored if using dynamic rendering
|
||||
uint32_t MinImageCount; // >= 2
|
||||
uint32_t ImageCount; // >= MinImageCount
|
||||
VkSampleCountFlagBits MSAASamples; // 0 defaults to VK_SAMPLE_COUNT_1_BIT
|
||||
VkDescriptorPool DescriptorPool; // See requirements in note above; ignored if using DescriptorPoolSize > 0
|
||||
uint32_t DescriptorPoolSize; // Optional: set to create internal descriptor pool automatically instead of using DescriptorPool.
|
||||
uint32_t MinImageCount; // >= 2
|
||||
uint32_t ImageCount; // >= MinImageCount
|
||||
VkPipelineCache PipelineCache; // Optional
|
||||
|
||||
// (Optional)
|
||||
VkPipelineCache PipelineCache;
|
||||
uint32_t Subpass;
|
||||
// Pipeline
|
||||
ImGui_ImplVulkan_PipelineInfo PipelineInfoMain; // Infos for Main Viewport (created by app/user)
|
||||
//VkRenderPass RenderPass; // --> Since 2025/09/26: set 'PipelineInfoMain.RenderPass' instead
|
||||
//uint32_t Subpass; // --> Since 2025/09/26: set 'PipelineInfoMain.Subpass' instead
|
||||
//VkSampleCountFlagBits MSAASamples; // --> Since 2025/09/26: set 'PipelineInfoMain.MSAASamples' instead
|
||||
//VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo; // Since 2025/09/26: set 'PipelineInfoMain.PipelineRenderingCreateInfo' instead
|
||||
|
||||
// (Optional) Dynamic Rendering
|
||||
// Need to explicitly enable VK_KHR_dynamic_rendering extension to use this, even for Vulkan 1.3.
|
||||
// Need to explicitly enable VK_KHR_dynamic_rendering extension to use this, even for Vulkan 1.3 + setup PipelineInfoMain.PipelineRenderingCreateInfo.
|
||||
bool UseDynamicRendering;
|
||||
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
|
||||
VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo;
|
||||
#endif
|
||||
|
||||
// (Optional) Allocation, Debugging
|
||||
const VkAllocationCallbacks* Allocator;
|
||||
void (*CheckVkResultFn)(VkResult err);
|
||||
VkDeviceSize MinAllocationSize; // Minimum allocation size. Set to 1024*1024 to satisfy zealous best practices validation layer and waste a little memory.
|
||||
VkDeviceSize MinAllocationSize; // Minimum allocation size. Set to 1024*1024 to satisfy zealous best practices validation layer and waste a little memory.
|
||||
|
||||
// (Optional) Customize default vertex/fragment shaders.
|
||||
// - if .sType == VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO we use specified structs, otherwise we use defaults.
|
||||
// - Shader inputs/outputs need to match ours. Code/data pointed to by the structure needs to survive for whole during of backend usage.
|
||||
VkShaderModuleCreateInfo CustomShaderVertCreateInfo;
|
||||
VkShaderModuleCreateInfo CustomShaderFragCreateInfo;
|
||||
};
|
||||
|
||||
// Called by user code
|
||||
IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE);
|
||||
IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture();
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontsTexture();
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated)
|
||||
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||
IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated)
|
||||
|
||||
// (Advanced) Use e.g. if you need to recreate pipeline without reinitializing the backend (see #8110, #8111)
|
||||
// The main window pipeline will be created by ImGui_ImplVulkan_Init() if possible (== RenderPass xor (UseDynamicRendering && PipelineRenderingCreateInfo->sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR))
|
||||
// Else, the pipeline can be created, or re-created, using ImGui_ImplVulkan_CreateMainPipeline() before rendering.
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_CreateMainPipeline(const ImGui_ImplVulkan_PipelineInfo* info);
|
||||
|
||||
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex);
|
||||
|
||||
// Register a texture (VkDescriptorSet == ImTextureID)
|
||||
// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem
|
||||
// Please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions.
|
||||
IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set);
|
||||
IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set);
|
||||
|
||||
// Optional: load Vulkan functions with a custom function loader
|
||||
// This is only useful with IMGUI_IMPL_VULKAN_NO_PROTOTYPES / VK_NO_PROTOTYPES
|
||||
IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = nullptr);
|
||||
IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = nullptr);
|
||||
|
||||
// [BETA] Selected render state data shared with callbacks.
|
||||
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplVulkan_RenderDrawData() call.
|
||||
// (Please open an issue if you feel you need access to more data)
|
||||
struct ImGui_ImplVulkan_RenderState
|
||||
{
|
||||
VkCommandBuffer CommandBuffer;
|
||||
VkPipeline Pipeline;
|
||||
VkPipelineLayout PipelineLayout;
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Internal / Miscellaneous Vulkan Helpers
|
||||
// (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.)
|
||||
//-------------------------------------------------------------------------
|
||||
// Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.
|
||||
//
|
||||
// You probably do NOT need to use or care about those functions.
|
||||
// WE DO NOT PROVIDE STRONG GUARANTEES OF BACKWARD/FORWARD COMPATIBILITY.
|
||||
// Those functions only exist because:
|
||||
// 1) they facilitate the readability and maintenance of the multiple main.cpp examples files.
|
||||
// 2) the multi-viewport / platform window implementation needs them internally.
|
||||
// Generally we avoid exposing any kind of superfluous high-level helpers in the bindings,
|
||||
// Generally we avoid exposing any kind of superfluous high-level helpers in the backends,
|
||||
// but it is too much code to duplicate everywhere so we exceptionally expose them.
|
||||
//
|
||||
// Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.).
|
||||
// You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work.
|
||||
// (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions)
|
||||
// Your engine/app will likely _already_ have code to setup all that stuff (swap chain,
|
||||
// render pass, frame buffers, etc.). You may read this code if you are curious, but
|
||||
// it is recommended you use your own custom tailored code to do equivalent work.
|
||||
//
|
||||
// The ImGui_ImplVulkanH_XXX functions should NOT interact with any of the state used
|
||||
// by the regular ImGui_ImplVulkan_XXX functions.
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
struct ImGui_ImplVulkanH_Frame;
|
||||
struct ImGui_ImplVulkanH_Window;
|
||||
|
||||
// Helpers
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wnd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wnd, const VkAllocationCallbacks* allocator);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count, VkImageUsageFlags image_usage);
|
||||
IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator);
|
||||
IMGUI_IMPL_API VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space);
|
||||
IMGUI_IMPL_API VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count);
|
||||
IMGUI_IMPL_API VkPhysicalDevice ImGui_ImplVulkanH_SelectPhysicalDevice(VkInstance instance);
|
||||
IMGUI_IMPL_API uint32_t ImGui_ImplVulkanH_SelectQueueFamilyIndex(VkPhysicalDevice physical_device);
|
||||
IMGUI_IMPL_API int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode);
|
||||
|
||||
// Helper structure to hold the data needed by one rendering frame
|
||||
@@ -166,30 +227,48 @@ struct ImGui_ImplVulkanH_FrameSemaphores
|
||||
// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.)
|
||||
struct ImGui_ImplVulkanH_Window
|
||||
{
|
||||
int Width;
|
||||
int Height;
|
||||
VkSwapchainKHR Swapchain;
|
||||
VkSurfaceKHR Surface;
|
||||
VkSurfaceFormatKHR SurfaceFormat;
|
||||
VkPresentModeKHR PresentMode;
|
||||
VkRenderPass RenderPass;
|
||||
VkPipeline Pipeline; // The window pipeline may uses a different VkRenderPass than the one passed in ImGui_ImplVulkan_InitInfo
|
||||
bool UseDynamicRendering;
|
||||
bool ClearEnable;
|
||||
VkClearValue ClearValue;
|
||||
uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount)
|
||||
uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count)
|
||||
uint32_t SemaphoreCount; // Number of simultaneous in-flight frames + 1, to be able to use it in vkAcquireNextImageKHR
|
||||
uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data)
|
||||
ImGui_ImplVulkanH_Frame* Frames;
|
||||
ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores;
|
||||
// Input
|
||||
bool UseDynamicRendering;
|
||||
VkSurfaceKHR Surface; // Surface created and destroyed by caller.
|
||||
VkSurfaceFormatKHR SurfaceFormat;
|
||||
VkPresentModeKHR PresentMode;
|
||||
VkAttachmentDescription AttachmentDesc; // RenderPass creation: main attachment description.
|
||||
VkClearValue ClearValue; // RenderPass creation: clear value when using VK_ATTACHMENT_LOAD_OP_CLEAR.
|
||||
|
||||
// Internal
|
||||
int Width; // Generally same as passed to ImGui_ImplVulkanH_CreateOrResizeWindow()
|
||||
int Height;
|
||||
VkSwapchainKHR Swapchain;
|
||||
VkRenderPass RenderPass;
|
||||
VkPipeline Pipeline; // The window pipeline may uses a different VkRenderPass than the one passed in ImGui_ImplVulkan_InitInfo
|
||||
uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount)
|
||||
uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count)
|
||||
uint32_t SemaphoreCount; // Number of simultaneous in-flight frames + 1, to be able to use it in vkAcquireNextImageKHR
|
||||
uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data)
|
||||
ImVector<ImGui_ImplVulkanH_Frame> Frames;
|
||||
ImVector<ImGui_ImplVulkanH_FrameSemaphores> FrameSemaphores;
|
||||
|
||||
ImGui_ImplVulkanH_Window()
|
||||
{
|
||||
memset((void*)this, 0, sizeof(*this));
|
||||
PresentMode = (VkPresentModeKHR)~0; // Ensure we get an error if user doesn't set this.
|
||||
ClearEnable = true;
|
||||
|
||||
// Parameters to create SwapChain
|
||||
PresentMode = (VkPresentModeKHR)~0; // Ensure we get an error if user doesn't set this.
|
||||
|
||||
// Parameters to create RenderPass
|
||||
AttachmentDesc.format = VK_FORMAT_UNDEFINED; // Will automatically use wd->SurfaceFormat.format.
|
||||
AttachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
AttachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
AttachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
AttachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
AttachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
AttachmentDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
AttachmentDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
Regular → Executable
+28
-12
@@ -15,20 +15,21 @@
|
||||
#pragma once
|
||||
|
||||
//---- Define assertion handler. Defaults to calling assert().
|
||||
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
|
||||
// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
|
||||
// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes.
|
||||
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
|
||||
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
|
||||
|
||||
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
|
||||
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
|
||||
// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
|
||||
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
|
||||
//#define IMGUI_API __declspec( dllexport )
|
||||
//#define IMGUI_API __declspec( dllimport )
|
||||
// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
|
||||
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
|
||||
//#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export
|
||||
//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import
|
||||
//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden
|
||||
|
||||
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
|
||||
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
|
||||
|
||||
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
|
||||
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
|
||||
@@ -42,23 +43,31 @@
|
||||
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
|
||||
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
|
||||
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
|
||||
//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
|
||||
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
|
||||
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
|
||||
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
|
||||
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
|
||||
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
|
||||
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
|
||||
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
|
||||
|
||||
//---- Enable Test Engine / Automation features.
|
||||
//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details.
|
||||
|
||||
//---- Include imgui_user.h at the end of imgui.h as a convenience
|
||||
// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.
|
||||
//#define IMGUI_INCLUDE_IMGUI_USER_H
|
||||
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
|
||||
|
||||
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
|
||||
//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.
|
||||
//#define IMGUI_USE_BGRA_PACKED_COLOR
|
||||
|
||||
//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.
|
||||
//#define IMGUI_USE_LEGACY_CRC32_ADLER
|
||||
|
||||
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
|
||||
//#define IMGUI_USE_WCHAR32
|
||||
#define IMGUI_USE_WCHAR32
|
||||
|
||||
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
|
||||
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
|
||||
@@ -75,13 +84,16 @@
|
||||
|
||||
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
|
||||
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
|
||||
// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience.
|
||||
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
|
||||
//#define IMGUI_ENABLE_FREETYPE
|
||||
#define IMGUI_ENABLE_FREETYPE
|
||||
|
||||
//---- Use FreeType+lunasvg library to render OpenType SVG fonts (SVGinOT)
|
||||
// Requires lunasvg headers to be available in the include path + program to be linked with the lunasvg library (not provided).
|
||||
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
|
||||
// Only works in combination with IMGUI_ENABLE_FREETYPE.
|
||||
// (implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
|
||||
// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
|
||||
// - Both require headers to be available in the include path + program to be linked with the library code (not provided).
|
||||
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
|
||||
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||
//#define IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
|
||||
//---- Use stb_truetype to build and rasterize the font atlas (default)
|
||||
@@ -119,6 +131,10 @@
|
||||
//#define IM_DEBUG_BREAK IM_ASSERT(0)
|
||||
//#define IM_DEBUG_BREAK __debugbreak()
|
||||
|
||||
//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set.
|
||||
// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use)
|
||||
//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS
|
||||
|
||||
//---- Debug Tools: Enable slower asserts
|
||||
//#define IMGUI_DEBUG_PARANOID
|
||||
|
||||
|
||||
Regular → Executable
+4202
-2086
File diff suppressed because it is too large.
Load diff
Regular → Executable
+1378
-560
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,284 @@
|
||||
// Solution adapted from https://github.com/ocornut/imgui/issues/9066
|
||||
|
||||
#include "imgui_cjk.h"
|
||||
#include "imgui_internal.h"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
// From imgui_draw.cpp
|
||||
extern float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* baked, unsigned int codepoint);
|
||||
|
||||
namespace {
|
||||
|
||||
// --- Bitmap helpers ---
|
||||
template <size_t N>
|
||||
struct Bitmap {
|
||||
std::array<uint8_t, N> bits{};
|
||||
|
||||
void set(size_t idx) {
|
||||
bits[idx] = 1;
|
||||
}
|
||||
|
||||
bool test(size_t idx) const {
|
||||
return bits[idx] != 0;
|
||||
}
|
||||
};
|
||||
|
||||
// --- Build bitmaps for dense ranges ---
|
||||
static constexpr size_t RANGE_3000_30FF_SIZE = 0x100; // 256 code points
|
||||
static Bitmap<RANGE_3000_30FF_SIZE> bitmap_3000_30FF{};
|
||||
|
||||
static constexpr size_t RANGE_31F0_31FF_SIZE = 0x10; // 16 code points
|
||||
static Bitmap<RANGE_31F0_31FF_SIZE> bitmap_31F0_31FF{};
|
||||
|
||||
static constexpr size_t RANGE_FF61_FF70_SIZE = 0x10; // 16 code points
|
||||
static Bitmap<RANGE_FF61_FF70_SIZE> bitmap_FF61_FF70{};
|
||||
|
||||
// --- Hash set for scattered values ---
|
||||
static const std::unordered_set<unsigned int> HeadProhibitedScatter = {
|
||||
0x00A2, 0x00B0, 0x2032, 0x2033, 0x2030, 0x2103,
|
||||
0x201D, 0x3009, 0x300B, 0x300D, 0x300F, 0x3011, 0x3015,
|
||||
0xFF09, 0xFF3D, 0xFF5D, 0xFF63
|
||||
// add other scattered ones here
|
||||
};
|
||||
|
||||
// --- Initialization (fill bitmaps) ---
|
||||
struct InitBitmaps {
|
||||
InitBitmaps() {
|
||||
// Fill 0x3000–0x30FF bitmap
|
||||
unsigned int dense_3000_30FF[] = {
|
||||
0x3001, 0x3002, 0x3005, 0x303B, 0x30FB,
|
||||
0x309D, 0x309E, 0x30FD, 0x30FE, 0x30FC,
|
||||
0x30A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9,
|
||||
0x30C3, 0x30E3, 0x30E5, 0x30E7, 0x30EE,
|
||||
0x30F5, 0x30F6
|
||||
};
|
||||
for (auto c : dense_3000_30FF) {
|
||||
bitmap_3000_30FF.set(c - 0x3000);
|
||||
}
|
||||
|
||||
// Fill 0x31F0–0x31FF bitmap
|
||||
for (unsigned int c = 0x31F0; c <= 0x31FF; ++c) {
|
||||
bitmap_31F0_31FF.set(c - 0x31F0);
|
||||
}
|
||||
|
||||
// Fill 0xFF61–0xFF70 bitmap
|
||||
for (unsigned int c = 0xFF61; c <= 0xFF70; ++c) {
|
||||
bitmap_FF61_FF70.set(c - 0xFF61);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Static initializer
|
||||
static InitBitmaps initBitmaps;
|
||||
|
||||
// --- Lookup function ---
|
||||
inline bool ImCharIsHeadProhibitedW(unsigned int c)
|
||||
{
|
||||
if (c >= 0x3000 && c <= 0x30FF) {
|
||||
return bitmap_3000_30FF.test(c - 0x3000);
|
||||
}
|
||||
if (c >= 0x31F0 && c <= 0x31FF) {
|
||||
return bitmap_31F0_31FF.test(c - 0x31F0);
|
||||
}
|
||||
if (c >= 0xFF61 && c <= 0xFF70) {
|
||||
return bitmap_FF61_FF70.test(c - 0xFF61);
|
||||
}
|
||||
return HeadProhibitedScatter.find(c) != HeadProhibitedScatter.end();
|
||||
}
|
||||
|
||||
inline bool ImCharIsHeadProhibitedA(char c) { return c == ' ' || c == '\t' || c == '}' || c == ')' || c == ']' || c == '?' || c == '!' || c == '|' || c == '/' || c == '&' || c == '.' || c == ',' || c == ':' || c == ';';}
|
||||
inline bool ImCharIsHeadProhibited(unsigned int c) { return (c < 128 && ImCharIsHeadProhibitedA((char)c)) || ImCharIsHeadProhibitedW(c); }
|
||||
inline bool ImCharIsTailProhibitedA(unsigned int c) { return c == '(' || c == '[' || c == '{' || c == '+'; }
|
||||
const unsigned int TailProhibitedW[] = { 0x2018, 0x201c, 0x3008, 0x300a, 0x300c, 0x300e, 0x3010, 0x3014, 0xff08, 0xff3b, 0xff5b, 0xff62, 0xa3, 0xa5, 0xff04, 0xffe1, 0xffe5, 0xff0b };
|
||||
inline bool ImCharIsTailProhibitedW(unsigned int c) { for (int i = 0; i < IM_ARRAYSIZE(TailProhibitedW); i++) if (c == TailProhibitedW[i]) return true; return false; }
|
||||
inline bool ImCharIsTailProhibited(unsigned int c) { return (c < 128 && ImCharIsTailProhibitedA(c)) || ImCharIsTailProhibitedW(c); }
|
||||
|
||||
inline bool ImCharIsLineBreakableW(unsigned int c)
|
||||
{
|
||||
return
|
||||
(unsigned)(c - 0x3040) <= (0x9FFF - 0x3040) ||
|
||||
(unsigned)(c - 0x20000) <= (0xDFFFF - 0x20000) ||
|
||||
(unsigned)(c - 0xAC00) <= (0xD7FF - 0xAC00) ||
|
||||
(unsigned)(c - 0xF900) <= (0xFAFF - 0xF900) ||
|
||||
(unsigned)(c - 0x1100) <= (0x11FF - 0x1100) ||
|
||||
(unsigned)(c - 0x2E80) <= (0x2FFF - 0x2E80);
|
||||
}
|
||||
|
||||
const char *CalcWordWrapPositionCJK(ImFont *font, float size, const char *text, const char *text_end, float wrap_width)
|
||||
{
|
||||
if (wrap_width <= 0.0f)
|
||||
return text_end;
|
||||
|
||||
ImFontBaked *baked = font->GetFontBaked(size);
|
||||
const float scale = size / baked->Size;
|
||||
|
||||
float line_width = 0.0f;
|
||||
float word_width = 0.0f;
|
||||
float blank_width = 0.0f;
|
||||
wrap_width /= scale;
|
||||
|
||||
const char *word_end = text;
|
||||
const char *prev_s = NULL;
|
||||
const char *s = NULL;
|
||||
const char *next_s = text;
|
||||
unsigned int prev_c = 0;
|
||||
unsigned int c = 0;
|
||||
unsigned int next_c = 0;
|
||||
|
||||
bool next_char_is_line_break_able = false;
|
||||
bool char_line_is_break_able = false;
|
||||
bool next_char_is_head_prohibited = false;
|
||||
bool char_is_head_prohibited = false;
|
||||
|
||||
#define IM_ADVANCE_WORD() \
|
||||
do \
|
||||
{ \
|
||||
word_end = s; \
|
||||
line_width += word_width + blank_width; \
|
||||
word_width = blank_width = 0.0f; \
|
||||
} while (0)
|
||||
|
||||
IM_ASSERT(text_end != NULL);
|
||||
while (s < text_end)
|
||||
{
|
||||
// prev_s is the END of prev_c, which actually points to c
|
||||
// same for s and next_s.
|
||||
prev_s = s;
|
||||
s = next_s;
|
||||
prev_c = c;
|
||||
c = next_c;
|
||||
char_line_is_break_able = next_char_is_line_break_able;
|
||||
char_is_head_prohibited = next_char_is_head_prohibited;
|
||||
|
||||
next_c = (unsigned int) *next_s;
|
||||
if (next_c < 0x80)
|
||||
next_s = next_s + 1;
|
||||
else
|
||||
next_s = next_s + ImTextCharFromUtf8(&next_c, next_s, text_end);
|
||||
if (next_s > text_end)
|
||||
next_c = 0;
|
||||
|
||||
if (prev_s == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (c < 0x20)
|
||||
{
|
||||
if (c == '\n')
|
||||
return prev_s;
|
||||
if (c == '\r')
|
||||
continue;
|
||||
}
|
||||
// Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);'
|
||||
float char_width = (c < (unsigned int) baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f;
|
||||
if (char_width < 0.0f)
|
||||
char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c);
|
||||
if (ImCharIsBlankW(c))
|
||||
blank_width += char_width;
|
||||
else
|
||||
{
|
||||
word_width += char_width + blank_width;
|
||||
blank_width = 0.0f;
|
||||
}
|
||||
// We ignore blank width at the end of the line (they can be skipped)
|
||||
if (line_width + word_width > wrap_width)
|
||||
{
|
||||
// Words that cannot possibly fit within an entire line will be cut anywhere.
|
||||
if (word_width < wrap_width)
|
||||
s = word_end;
|
||||
else
|
||||
s = prev_s;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!next_c)
|
||||
{
|
||||
IM_ADVANCE_WORD();
|
||||
}
|
||||
else if (c)
|
||||
{
|
||||
next_char_is_line_break_able = ImCharIsLineBreakableW(next_c);
|
||||
next_char_is_head_prohibited = ImCharIsHeadProhibited(next_c);
|
||||
if (prev_c >= '0' && prev_c <= '9' && next_c >= '0' && next_c <= '9' && !next_char_is_line_break_able)
|
||||
continue;
|
||||
if (next_char_is_line_break_able && !next_char_is_head_prohibited && !ImCharIsTailProhibited(c))
|
||||
IM_ADVANCE_WORD();
|
||||
if ((char_is_head_prohibited || char_line_is_break_able) && !next_char_is_head_prohibited)
|
||||
IM_ADVANCE_WORD();
|
||||
}
|
||||
}
|
||||
#undef IM_ADVANCE_WORD
|
||||
|
||||
if (s == text && text < text_end)
|
||||
return s + ImTextCountUtf8BytesFromChar(s, text_end);
|
||||
return (s > text_end) ? text_end : s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ImGui::RenderTextWrappedCJK(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiWindow* window = g.CurrentWindow;
|
||||
|
||||
if (!text_end)
|
||||
text_end = text + strlen(text);
|
||||
|
||||
if (text == text_end)
|
||||
return;
|
||||
|
||||
ImFont* font = g.Font;
|
||||
float fontSize = g.FontSize;
|
||||
|
||||
const char* s = text;
|
||||
ImVec2 current_pos = pos;
|
||||
|
||||
while (s < text_end)
|
||||
{
|
||||
const char* line_end = CalcWordWrapPositionCJK(font, fontSize, s, text_end, wrap_width);
|
||||
|
||||
if (s != line_end)
|
||||
{
|
||||
window->DrawList->AddText(font, fontSize, current_pos, GetColorU32(ImGuiCol_Text), s, line_end, 0.0f);
|
||||
}
|
||||
|
||||
current_pos.y += fontSize;
|
||||
s = ImTextCalcWordWrapNextLineStart(line_end, text_end);
|
||||
}
|
||||
|
||||
if (g.LogEnabled)
|
||||
LogRenderedText(&pos, text, text_end);
|
||||
}
|
||||
|
||||
ImVec2 ImGui::CalcTextSizeCJK(ImFont* font, float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** out_remaining)
|
||||
{
|
||||
if (!text_end)
|
||||
text_end = text_begin + strlen(text_begin);
|
||||
|
||||
if (text_begin == text_end)
|
||||
return ImVec2(0.0f, size);
|
||||
|
||||
ImVec2 text_size(0.0f, 0.0f);
|
||||
const char* s = text_begin;
|
||||
|
||||
while (s < text_end)
|
||||
{
|
||||
const char* line_end = CalcWordWrapPositionCJK(font, size, s, text_end, wrap_width);
|
||||
|
||||
ImVec2 line_size = font->CalcTextSizeA(size, FLT_MAX, 0.0f, s, line_end);
|
||||
text_size.x = ImMax(text_size.x, line_size.x);
|
||||
text_size.y += size;
|
||||
|
||||
s = ImTextCalcWordWrapNextLineStart(line_end, text_end);
|
||||
}
|
||||
|
||||
if (out_remaining)
|
||||
*out_remaining = s;
|
||||
|
||||
return text_size;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include "imgui.h"
|
||||
|
||||
namespace ImGui {
|
||||
// CJK-aware text wrapping function.
|
||||
// Use this instead of ImGui::RenderTextWrapped for better CJK support.
|
||||
void RenderTextWrappedCJK(ImVec2 pos, const char* text, const char* text_end, float wrap_width);
|
||||
ImVec2 CalcTextSizeCJK(ImFont* font, float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** out_remaining = nullptr);
|
||||
}
|
||||
Regular → Executable
+4347
-2156
File diff suppressed because it is too large.
Load diff
Regular → Executable
+3340
-1169
File diff suppressed because it is too large.
Load diff
Regular → Executable
+1064
-681
File diff suppressed because it is too large.
Load diff
@@ -1,13 +1,26 @@
|
||||
// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
|
||||
|
||||
// This is also an example of how you may wrap your own similar types.
|
||||
// TL;DR; this is using the ImGuiInputTextFlags_CallbackResize facility,
|
||||
// which also demonstrated in 'Dear ImGui Demo->Widgets->Text Input->Resize Callback'.
|
||||
|
||||
// Changelog:
|
||||
// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
|
||||
|
||||
// See more C++ related extension (fmt, RAII, syntaxis sugar) on Wiki:
|
||||
// Usage:
|
||||
// {
|
||||
// #include "misc/cpp/imgui_stdlib.h"
|
||||
// #include "misc/cpp/imgui_stdlib.cpp" // <-- If you want to include implementation without messing with your project/build.
|
||||
// [...]
|
||||
// std::string my_string;
|
||||
// ImGui::InputText("my string", &my_string);
|
||||
// }
|
||||
|
||||
// See more C++ related extension (fmt, RAII, syntactic sugar) on Wiki:
|
||||
// https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DISABLE
|
||||
#include "imgui_stdlib.h"
|
||||
|
||||
// Clang warnings with -Weverything
|
||||
@@ -83,3 +96,5 @@ bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string*
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
@@ -1,14 +1,28 @@
|
||||
// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
|
||||
|
||||
// This is also an example of how you may wrap your own similar types.
|
||||
// TL;DR; this is using the ImGuiInputTextFlags_CallbackResize facility,
|
||||
// which also demonstrated in 'Dear ImGui Demo->Widgets->Text Input->Resize Callback'.
|
||||
|
||||
// Changelog:
|
||||
// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
|
||||
|
||||
// Usage:
|
||||
// {
|
||||
// #include "misc/cpp/imgui_stdlib.h"
|
||||
// #include "misc/cpp/imgui_stdlib.cpp" // <-- If you want to include implementation without messing with your project/build.
|
||||
// [...]
|
||||
// std::string my_string;
|
||||
// ImGui::InputText("my string", &my_string);
|
||||
// }
|
||||
|
||||
// See more C++ related extension (fmt, RAII, syntaxis sugar) on Wiki:
|
||||
// https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef IMGUI_DISABLE
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ImGui
|
||||
@@ -19,3 +33,5 @@ namespace ImGui
|
||||
IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr);
|
||||
IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr);
|
||||
}
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
Regular → Executable
+291
-166
File diff suppressed because it is too large.
Load diff
Regular → Executable
+2864
-1077
File diff suppressed because it is too large.
Load diff
Regular → Executable
File mode changed.
Regular → Executable
+160
-74
@@ -3,7 +3,10 @@
|
||||
// Those changes would need to be pushed into nothings/stb:
|
||||
// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
|
||||
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
|
||||
// Grep for [DEAR IMGUI] to find the changes.
|
||||
// - Added name to struct or it may be forward declared in our code.
|
||||
// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
|
||||
// - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
|
||||
// Grep for [DEAR IMGUI] to find some changes.
|
||||
// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
|
||||
|
||||
// stb_textedit.h - v1.14 - public domain - Sean Barrett
|
||||
@@ -37,6 +40,7 @@
|
||||
//
|
||||
// VERSION HISTORY
|
||||
//
|
||||
// !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
|
||||
// 1.14 (2021-07-11) page up/down, various fixes
|
||||
// 1.13 (2019-02-07) fix bug in undo size management
|
||||
// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
|
||||
@@ -139,12 +143,14 @@
|
||||
// with previous char)
|
||||
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
|
||||
// (return type is int, -1 means not valid to insert)
|
||||
// (not supported if you want to use UTF-8, see below)
|
||||
// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
|
||||
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
|
||||
// as manually wordwrapping for end-of-line positioning
|
||||
//
|
||||
// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
|
||||
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
|
||||
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
|
||||
// returns number of characters actually inserted. [DEAR IMGUI]
|
||||
//
|
||||
// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
|
||||
//
|
||||
@@ -176,6 +182,13 @@
|
||||
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
|
||||
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
|
||||
//
|
||||
// To support UTF-8:
|
||||
//
|
||||
// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character
|
||||
// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character
|
||||
// Do NOT define STB_TEXTEDIT_KEYTOTEXT.
|
||||
// Instead, call stb_textedit_text() directly for text contents.
|
||||
//
|
||||
// Keyboard input must be encoded as a single integer value; e.g. a character code
|
||||
// and some bitflags that represent shift states. to simplify the interface, SHIFT must
|
||||
// be a bitflag, so we can test the shifted state of cursor movements to allow selection,
|
||||
@@ -209,6 +222,7 @@
|
||||
// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
|
||||
// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
|
||||
// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)
|
||||
// void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len)
|
||||
//
|
||||
// Each of these functions potentially updates the string and updates the
|
||||
// state.
|
||||
@@ -243,7 +257,14 @@
|
||||
// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
|
||||
// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
|
||||
// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to
|
||||
// anything other type you wante before including.
|
||||
// anything other type you want before including.
|
||||
// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
|
||||
// transformed into text and stb_textedit_text() is automatically called.
|
||||
//
|
||||
// text: (added 2025)
|
||||
// call this to directly send text input the textfield, which is required
|
||||
// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
|
||||
// cannot infer text length.
|
||||
//
|
||||
//
|
||||
// When rendering, you can read the cursor position and selection state from
|
||||
@@ -318,7 +339,7 @@ typedef struct
|
||||
int undo_char_point, redo_char_point;
|
||||
} StbUndoState;
|
||||
|
||||
typedef struct
|
||||
typedef struct STB_TexteditState
|
||||
{
|
||||
/////////////////////
|
||||
//
|
||||
@@ -392,6 +413,16 @@ typedef struct
|
||||
#define IMSTB_TEXTEDIT_memmove memmove
|
||||
#endif
|
||||
|
||||
// [DEAR IMGUI]
|
||||
// Functions must be implemented for UTF8 support
|
||||
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
|
||||
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
|
||||
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
|
||||
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1)
|
||||
#endif
|
||||
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
|
||||
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1)
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -399,7 +430,7 @@ typedef struct
|
||||
//
|
||||
|
||||
// traverse the layout to locate the nearest character to a display position
|
||||
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
|
||||
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line)
|
||||
{
|
||||
StbTexteditRow r;
|
||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
||||
@@ -409,6 +440,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
|
||||
r.x0 = r.x1 = 0;
|
||||
r.ymin = r.ymax = 0;
|
||||
r.num_chars = 0;
|
||||
*out_side_on_line = 0;
|
||||
|
||||
// search rows to find one that straddles 'y'
|
||||
while (i < n) {
|
||||
@@ -428,7 +460,10 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
|
||||
|
||||
// below all text, return 'after' last character
|
||||
if (i >= n)
|
||||
{
|
||||
*out_side_on_line = 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
// check if it's before the beginning of the line
|
||||
if (x < r.x0)
|
||||
@@ -438,13 +473,14 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
|
||||
if (x < r.x1) {
|
||||
// search characters in row for one that straddles 'x'
|
||||
prev_x = r.x0;
|
||||
for (k=0; k < r.num_chars; ++k) {
|
||||
for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
|
||||
float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
|
||||
if (x < prev_x+w) {
|
||||
*out_side_on_line = (k == 0) ? 0 : 1;
|
||||
if (x < prev_x+w/2)
|
||||
return k+i;
|
||||
else
|
||||
return k+i+1;
|
||||
return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k);
|
||||
}
|
||||
prev_x += w;
|
||||
}
|
||||
@@ -452,6 +488,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
|
||||
}
|
||||
|
||||
// if the last character is a newline, return that. otherwise return 'after' the last character
|
||||
*out_side_on_line = 1;
|
||||
if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
|
||||
return i+r.num_chars-1;
|
||||
else
|
||||
@@ -463,6 +500,7 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
|
||||
{
|
||||
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
|
||||
// goes off the top or bottom of the text
|
||||
int side_on_line;
|
||||
if( state->single_line )
|
||||
{
|
||||
StbTexteditRow r;
|
||||
@@ -470,16 +508,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
|
||||
y = r.ymin;
|
||||
}
|
||||
|
||||
state->cursor = stb_text_locate_coord(str, x, y);
|
||||
state->cursor = stb_text_locate_coord(str, x, y, &side_on_line);
|
||||
state->select_start = state->cursor;
|
||||
state->select_end = state->cursor;
|
||||
state->has_preferred_x = 0;
|
||||
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
|
||||
}
|
||||
|
||||
// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
|
||||
static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
|
||||
{
|
||||
int p = 0;
|
||||
int side_on_line;
|
||||
|
||||
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
|
||||
// goes off the top or bottom of the text
|
||||
@@ -493,8 +533,9 @@ static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *sta
|
||||
if (state->select_start == state->select_end)
|
||||
state->select_start = state->cursor;
|
||||
|
||||
p = stb_text_locate_coord(str, x, y);
|
||||
p = stb_text_locate_coord(str, x, y, &side_on_line);
|
||||
state->cursor = state->select_end = p;
|
||||
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -544,6 +585,8 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING
|
||||
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
|
||||
if (n < i + r.num_chars)
|
||||
break;
|
||||
if (str->LastMoveDirectionLR == ImGuiDir_Right && str->Stb->cursor > 0 && str->Stb->cursor == i + r.num_chars && STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] Wrapping point handling
|
||||
break;
|
||||
if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line
|
||||
break; // [DEAR IMGUI]
|
||||
prev_start = i;
|
||||
@@ -563,7 +606,7 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING
|
||||
|
||||
// now scan to find xpos
|
||||
find->x = r.x0;
|
||||
for (i=0; first+i < n; ++i)
|
||||
for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first)
|
||||
find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
|
||||
}
|
||||
|
||||
@@ -640,6 +683,35 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
|
||||
}
|
||||
}
|
||||
|
||||
// [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping.
|
||||
#ifndef STB_TEXTEDIT_MOVELINESTART
|
||||
static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
|
||||
{
|
||||
if (state->single_line)
|
||||
return 0;
|
||||
while (cursor > 0) {
|
||||
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor);
|
||||
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
|
||||
break;
|
||||
cursor = prev;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
#define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start
|
||||
#endif
|
||||
#ifndef STB_TEXTEDIT_MOVELINEEND
|
||||
static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
|
||||
{
|
||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
||||
if (state->single_line)
|
||||
return n;
|
||||
while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE)
|
||||
cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor);
|
||||
return cursor;
|
||||
}
|
||||
#define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end
|
||||
#endif
|
||||
|
||||
#ifdef STB_TEXTEDIT_IS_SPACE
|
||||
static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
|
||||
{
|
||||
@@ -649,9 +721,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
|
||||
#ifndef STB_TEXTEDIT_MOVEWORDLEFT
|
||||
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
|
||||
{
|
||||
--c; // always move at least one character
|
||||
while( c >= 0 && !is_word_boundary( str, c ) )
|
||||
--c;
|
||||
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
|
||||
while (c >= 0 && !is_word_boundary(str, c))
|
||||
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
|
||||
|
||||
if( c < 0 )
|
||||
c = 0;
|
||||
@@ -665,9 +737,9 @@ static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c
|
||||
static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
|
||||
{
|
||||
const int len = STB_TEXTEDIT_STRINGLEN(str);
|
||||
++c; // always move at least one character
|
||||
c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character
|
||||
while( c < len && !is_word_boundary( str, c ) )
|
||||
++c;
|
||||
c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
|
||||
|
||||
if( c > len )
|
||||
c = len;
|
||||
@@ -706,7 +778,8 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
|
||||
stb_textedit_clamp(str, state);
|
||||
stb_textedit_delete_selection(str,state);
|
||||
// try to insert the characters
|
||||
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
|
||||
len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len);
|
||||
if (len) {
|
||||
stb_text_makeundo_insert(state, state->cursor, len);
|
||||
state->cursor += len;
|
||||
state->has_preferred_x = 0;
|
||||
@@ -720,36 +793,47 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
|
||||
#define STB_TEXTEDIT_KEYTYPE int
|
||||
#endif
|
||||
|
||||
// API key: process text input
|
||||
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
|
||||
static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
|
||||
{
|
||||
// can't add newline in single-line mode
|
||||
if (text[0] == '\n' && state->single_line)
|
||||
return;
|
||||
|
||||
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
|
||||
stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
|
||||
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
|
||||
text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
|
||||
if (text_len) {
|
||||
state->cursor += text_len;
|
||||
state->has_preferred_x = 0;
|
||||
}
|
||||
} else {
|
||||
stb_textedit_delete_selection(str, state); // implicitly clamps
|
||||
text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
|
||||
if (text_len) {
|
||||
stb_text_makeundo_insert(state, state->cursor, text_len);
|
||||
state->cursor += text_len;
|
||||
state->has_preferred_x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API key: process a keyboard input
|
||||
static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
|
||||
{
|
||||
retry:
|
||||
switch (key) {
|
||||
default: {
|
||||
#ifdef STB_TEXTEDIT_KEYTOTEXT
|
||||
// This is not suitable for UTF-8 support.
|
||||
int c = STB_TEXTEDIT_KEYTOTEXT(key);
|
||||
if (c > 0) {
|
||||
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE) c;
|
||||
|
||||
// can't add newline in single-line mode
|
||||
if (c == '\n' && state->single_line)
|
||||
break;
|
||||
|
||||
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
|
||||
stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
|
||||
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
|
||||
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
|
||||
++state->cursor;
|
||||
state->has_preferred_x = 0;
|
||||
}
|
||||
} else {
|
||||
stb_textedit_delete_selection(str,state); // implicitly clamps
|
||||
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
|
||||
stb_text_makeundo_insert(state, state->cursor, 1);
|
||||
++state->cursor;
|
||||
state->has_preferred_x = 0;
|
||||
}
|
||||
}
|
||||
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
|
||||
stb_textedit_text(str, state, &ch, 1);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -775,7 +859,7 @@ retry:
|
||||
stb_textedit_move_to_first(state);
|
||||
else
|
||||
if (state->cursor > 0)
|
||||
--state->cursor;
|
||||
state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
|
||||
@@ -784,7 +868,7 @@ retry:
|
||||
if (STB_TEXT_HAS_SELECTION(state))
|
||||
stb_textedit_move_to_last(str, state);
|
||||
else
|
||||
++state->cursor;
|
||||
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
|
||||
stb_textedit_clamp(str, state);
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
@@ -794,7 +878,7 @@ retry:
|
||||
stb_textedit_prep_selection_at_cursor(state);
|
||||
// move selection left
|
||||
if (state->select_end > 0)
|
||||
--state->select_end;
|
||||
state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end);
|
||||
state->cursor = state->select_end;
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
@@ -844,7 +928,7 @@ retry:
|
||||
case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
|
||||
stb_textedit_prep_selection_at_cursor(state);
|
||||
// move selection right
|
||||
++state->select_end;
|
||||
state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end);
|
||||
stb_textedit_clamp(str, state);
|
||||
state->cursor = state->select_end;
|
||||
state->has_preferred_x = 0;
|
||||
@@ -884,15 +968,16 @@ retry:
|
||||
|
||||
// [DEAR IMGUI]
|
||||
// going down while being on the last line shouldn't bring us to that line end
|
||||
if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
|
||||
break;
|
||||
//if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
|
||||
// break;
|
||||
|
||||
// now find character position down a row
|
||||
state->cursor = start;
|
||||
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
||||
x = row.x0;
|
||||
for (i=0; i < row.num_chars; ++i) {
|
||||
for (i=0; i < row.num_chars; ) {
|
||||
float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
|
||||
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
|
||||
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
|
||||
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
|
||||
break;
|
||||
@@ -900,10 +985,13 @@ retry:
|
||||
x += dx;
|
||||
if (x > goal_x)
|
||||
break;
|
||||
++state->cursor;
|
||||
i += next - state->cursor;
|
||||
state->cursor = next;
|
||||
}
|
||||
stb_textedit_clamp(str, state);
|
||||
|
||||
if (state->cursor == find.first_char + find.length)
|
||||
str->LastMoveDirectionLR = ImGuiDir_Left;
|
||||
state->has_preferred_x = 1;
|
||||
state->preferred_x = goal_x;
|
||||
|
||||
@@ -953,8 +1041,9 @@ retry:
|
||||
state->cursor = find.prev_first;
|
||||
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
||||
x = row.x0;
|
||||
for (i=0; i < row.num_chars; ++i) {
|
||||
for (i=0; i < row.num_chars; ) {
|
||||
float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
|
||||
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
|
||||
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
|
||||
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
|
||||
break;
|
||||
@@ -962,10 +1051,15 @@ retry:
|
||||
x += dx;
|
||||
if (x > goal_x)
|
||||
break;
|
||||
++state->cursor;
|
||||
i += next - state->cursor;
|
||||
state->cursor = next;
|
||||
}
|
||||
stb_textedit_clamp(str, state);
|
||||
|
||||
if (state->cursor == find.first_char)
|
||||
str->LastMoveDirectionLR = ImGuiDir_Right;
|
||||
else if (state->cursor == find.prev_first)
|
||||
str->LastMoveDirectionLR = ImGuiDir_Left;
|
||||
state->has_preferred_x = 1;
|
||||
state->preferred_x = goal_x;
|
||||
|
||||
@@ -975,10 +1069,15 @@ retry:
|
||||
// go to previous line
|
||||
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
|
||||
prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
|
||||
while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE)
|
||||
--prev_scan;
|
||||
while (prev_scan > 0)
|
||||
{
|
||||
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan);
|
||||
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
|
||||
break;
|
||||
prev_scan = prev;
|
||||
}
|
||||
find.first_char = find.prev_first;
|
||||
find.prev_first = prev_scan;
|
||||
find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -990,7 +1089,7 @@ retry:
|
||||
else {
|
||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
||||
if (state->cursor < n)
|
||||
stb_textedit_delete(str, state, state->cursor, 1);
|
||||
stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor);
|
||||
}
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
@@ -1002,8 +1101,9 @@ retry:
|
||||
else {
|
||||
stb_textedit_clamp(str, state);
|
||||
if (state->cursor > 0) {
|
||||
stb_textedit_delete(str, state, state->cursor-1, 1);
|
||||
--state->cursor;
|
||||
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
|
||||
stb_textedit_delete(str, state, prev, state->cursor - prev);
|
||||
state->cursor = prev;
|
||||
}
|
||||
}
|
||||
state->has_preferred_x = 0;
|
||||
@@ -1051,10 +1151,7 @@ retry:
|
||||
case STB_TEXTEDIT_K_LINESTART:
|
||||
stb_textedit_clamp(str, state);
|
||||
stb_textedit_move_to_first(state);
|
||||
if (state->single_line)
|
||||
state->cursor = 0;
|
||||
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
|
||||
--state->cursor;
|
||||
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
|
||||
@@ -1062,13 +1159,9 @@ retry:
|
||||
case STB_TEXTEDIT_K_LINEEND2:
|
||||
#endif
|
||||
case STB_TEXTEDIT_K_LINEEND: {
|
||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
||||
stb_textedit_clamp(str, state);
|
||||
stb_textedit_move_to_first(state);
|
||||
if (state->single_line)
|
||||
state->cursor = n;
|
||||
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
|
||||
++state->cursor;
|
||||
stb_textedit_move_to_last(str, state);
|
||||
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
}
|
||||
@@ -1079,10 +1172,7 @@ retry:
|
||||
case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
|
||||
stb_textedit_clamp(str, state);
|
||||
stb_textedit_prep_selection_at_cursor(state);
|
||||
if (state->single_line)
|
||||
state->cursor = 0;
|
||||
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
|
||||
--state->cursor;
|
||||
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
|
||||
state->select_end = state->cursor;
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
@@ -1091,13 +1181,9 @@ retry:
|
||||
case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
|
||||
#endif
|
||||
case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
|
||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
||||
stb_textedit_clamp(str, state);
|
||||
stb_textedit_prep_selection_at_cursor(state);
|
||||
if (state->single_line)
|
||||
state->cursor = n;
|
||||
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
|
||||
++state->cursor;
|
||||
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
|
||||
state->select_end = state->cursor;
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
@@ -1272,7 +1358,7 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
|
||||
// check type of recorded action:
|
||||
if (u.insert_length) {
|
||||
// easy case: was a deletion, so we need to insert n characters
|
||||
STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
|
||||
u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
|
||||
s->undo_char_point -= u.insert_length;
|
||||
}
|
||||
|
||||
@@ -1323,7 +1409,7 @@ static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
|
||||
|
||||
if (r.insert_length) {
|
||||
// easy case: need to insert n characters
|
||||
STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
|
||||
r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
|
||||
s->redo_char_point += r.insert_length;
|
||||
}
|
||||
|
||||
|
||||
Regular → Executable
+2
-2
@@ -4516,8 +4516,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
|
||||
q2[0] = (float)x2;
|
||||
q2[1] = (float)y2;
|
||||
if (equal(q0,q1) || equal(q1,q2)) {
|
||||
x0 = (int)verts[i-1].x;
|
||||
y0 = (int)verts[i-1].y;
|
||||
x0 = (int)verts[i-1].x; //-V1048
|
||||
y0 = (int)verts[i-1].y; //-V1048
|
||||
x1 = (int)verts[i ].x;
|
||||
y1 = (int)verts[i ].y;
|
||||
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
|
||||
|
||||
+844
@@ -0,0 +1,844 @@
|
||||
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
|
||||
// (code)
|
||||
|
||||
// Get the latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
|
||||
// Original code by @vuhdo (Aleksei Skriabin) in 2017, with improvements by @mikesart.
|
||||
// Maintained since 2019 by @ocornut.
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2025/06/11: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support.
|
||||
// 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927)
|
||||
// 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics.
|
||||
// 2023/08/01: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'. (#6591)
|
||||
// 2023/01/04: fixed a packing issue which in some occurrences would prevent large amount of glyphs from being packed correctly.
|
||||
// 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns nullptr.
|
||||
// 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs.
|
||||
// 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a preferred texture format.
|
||||
// 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+).
|
||||
// 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'. renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas().
|
||||
// 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails.
|
||||
// 2019/02/09: added RasterizerFlags::Monochrome flag to disable font anti-aliasing (combine with ::MonoHinting for best results!)
|
||||
// 2019/01/15: added support for imgui allocators + added FreeType only override function SetAllocatorFunctions().
|
||||
// 2019/01/10: re-factored to match big update in STB builder. fixed texture height waste. fixed redundant glyphs when merging. support for glyph padding.
|
||||
// 2018/06/08: added support for ImFontConfig::GlyphMinAdvanceX, GlyphMaxAdvanceX.
|
||||
// 2018/02/04: moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
|
||||
// 2018/01/22: fix for addition of ImFontAtlas::TexUvscale member.
|
||||
// 2017/10/22: minor inconsequential change to match change in master (removed an unnecessary statement).
|
||||
// 2017/09/26: fixes for imgui internal changes.
|
||||
// 2017/08/26: cleanup, optimizations, support for ImFontConfig::RasterizerFlags, ImFontConfig::RasterizerMultiply.
|
||||
// 2017/08/16: imported from https://github.com/Vuhdo/imgui_freetype into http://www.github.com/ocornut/imgui_club, updated for latest changes in ImFontAtlas, minor tweaks.
|
||||
|
||||
// About Gamma Correct Blending:
|
||||
// - FreeType assumes blending in linear space rather than gamma space.
|
||||
// - See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph
|
||||
// - For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
|
||||
// - The default dear imgui styles will be impacted by this change (alpha values will need tweaking).
|
||||
|
||||
// FIXME: cfg.OversampleH, OversampleV are not supported, but generally not necessary with this rasterizer because Hinting makes everything look better.
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DISABLE
|
||||
#include "imgui_freetype.h"
|
||||
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
|
||||
#include <stdint.h>
|
||||
#ifdef _MSC_VER
|
||||
#pragma push_macro("generic")
|
||||
#undef generic
|
||||
#define generic __generic
|
||||
#endif
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H // <freetype/freetype.h>
|
||||
#include FT_MODULE_H // <freetype/ftmodapi.h>
|
||||
#include FT_GLYPH_H // <freetype/ftglyph.h>
|
||||
#include FT_SIZES_H // <freetype/ftsizes.h>
|
||||
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
|
||||
#include FT_TRUETYPE_TABLES_H // <freetype/tttables.h>
|
||||
#include FT_TRUETYPE_TAGS_H // <freetype/tttags.h>
|
||||
#ifdef _MSC_VER
|
||||
#pragma pop_macro("generic")
|
||||
#endif
|
||||
|
||||
// Handle LunaSVG and PlutoSVG
|
||||
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) && defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
|
||||
#error "Cannot enable both IMGUI_ENABLE_FREETYPE_LUNASVG and IMGUI_ENABLE_FREETYPE_PLUTOSVG"
|
||||
#endif
|
||||
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
#include FT_OTSVG_H // <freetype/otsvg.h>
|
||||
#include FT_BBOX_H // <freetype/ftbbox.h>
|
||||
#include <lunasvg.h>
|
||||
#endif
|
||||
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||
#include <plutosvg.h>
|
||||
#endif
|
||||
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined (IMGUI_ENABLE_FREETYPE_PLUTOSVG)
|
||||
#if !((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
|
||||
#error IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG requires FreeType version >= 2.12
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
|
||||
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
|
||||
#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
|
||||
#ifndef __clang__
|
||||
#pragma GCC diagnostic ignored "-Wsubobject-linkage" // warning: 'xxxx' has a field 'xxxx' whose type uses the anonymous namespace
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Data
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Default memory allocators
|
||||
static void* ImGuiFreeTypeDefaultAllocFunc(size_t size, void* user_data) { IM_UNUSED(user_data); return IM_ALLOC(size); }
|
||||
static void ImGuiFreeTypeDefaultFreeFunc(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_FREE(ptr); }
|
||||
|
||||
// Current memory allocators
|
||||
static void* (*GImGuiFreeTypeAllocFunc)(size_t size, void* user_data) = ImGuiFreeTypeDefaultAllocFunc;
|
||||
static void (*GImGuiFreeTypeFreeFunc)(void* ptr, void* user_data) = ImGuiFreeTypeDefaultFreeFunc;
|
||||
static void* GImGuiFreeTypeAllocatorUserData = nullptr;
|
||||
|
||||
// Lunasvg support
|
||||
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* state);
|
||||
static void ImGuiLunasvgPortFree(FT_Pointer* state);
|
||||
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state);
|
||||
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state);
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Code
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
#define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point
|
||||
#define FT_SCALEFACTOR 64.0f // For converting from/to 26.6 factionals
|
||||
|
||||
// Glyph metrics:
|
||||
// --------------
|
||||
//
|
||||
// xmin xmax
|
||||
// | |
|
||||
// |<-------- width -------->|
|
||||
// | |
|
||||
// | +-------------------------+----------------- ymax
|
||||
// | | ggggggggg ggggg | ^ ^
|
||||
// | | g:::::::::ggg::::g | | |
|
||||
// | | g:::::::::::::::::g | | |
|
||||
// | | g::::::ggggg::::::gg | | |
|
||||
// | | g:::::g g:::::g | | |
|
||||
// offsetX -|-------->| g:::::g g:::::g | offsetY |
|
||||
// | | g:::::g g:::::g | | |
|
||||
// | | g::::::g g:::::g | | |
|
||||
// | | g:::::::ggggg:::::g | | |
|
||||
// | | g::::::::::::::::g | | height
|
||||
// | | gg::::::::::::::g | | |
|
||||
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
|
||||
// / | | g:::::g | |
|
||||
// origin | | gggggg g:::::g | |
|
||||
// | | g:::::gg gg:::::g | |
|
||||
// | | g::::::ggg:::::::g | |
|
||||
// | | gg:::::::::::::g | |
|
||||
// | | ggg::::::ggg | |
|
||||
// | | gggggg | v
|
||||
// | +-------------------------+----------------- ymin
|
||||
// | |
|
||||
// |------------- advanceX ----------->|
|
||||
|
||||
// Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US.
|
||||
struct ImGui_ImplFreeType_Data
|
||||
{
|
||||
FT_Library Library;
|
||||
FT_MemoryRec_ MemoryManager;
|
||||
ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
// Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US.
|
||||
struct ImGui_ImplFreeType_FontSrcData
|
||||
{
|
||||
// Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
|
||||
bool InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_user_flags);
|
||||
void CloseFont();
|
||||
ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); }
|
||||
~ImGui_ImplFreeType_FontSrcData() { CloseFont(); }
|
||||
|
||||
// Members
|
||||
FT_Face FtFace;
|
||||
ImGuiFreeTypeLoaderFlags UserFlags; // = ImFontConfig::FontLoaderFlags
|
||||
FT_Int32 LoadFlags;
|
||||
ImFontBaked* BakedLastActivated;
|
||||
};
|
||||
|
||||
// Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE.
|
||||
struct ImGui_ImplFreeType_FontSrcBakedData
|
||||
{
|
||||
FT_Size FtSize; // This represent a FT_Face with a given size.
|
||||
ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_font_loader_flags)
|
||||
{
|
||||
FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)src->FontData, (FT_Long)src->FontDataSize, (FT_Long)src->FontNo, &FtFace);
|
||||
if (error != 0)
|
||||
return false;
|
||||
|
||||
// Reject COLRv1 fonts as they require a dedicated rendering engine (not supported)
|
||||
if (FT_IS_SFNT(FtFace))
|
||||
{
|
||||
FT_ULong len = 0;
|
||||
if (FT_Load_Sfnt_Table(FtFace, TTAG_COLR, 0, nullptr, &len) == 0 && len >= 2)
|
||||
{
|
||||
FT_Byte header[2];
|
||||
FT_ULong headerLen = sizeof(header);
|
||||
if (FT_Load_Sfnt_Table(FtFace, TTAG_COLR, 0, header, &headerLen) == 0 && headerLen >= 2)
|
||||
{
|
||||
int version = (header[0] << 8) | header[1];
|
||||
if (version == 1)
|
||||
{
|
||||
FT_Done_Face(FtFace);
|
||||
FtFace = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE);
|
||||
if (error != 0)
|
||||
return false;
|
||||
|
||||
// Convert to FreeType flags (NB: Bold and Oblique are processed separately)
|
||||
UserFlags = (ImGuiFreeTypeLoaderFlags)(src->FontLoaderFlags | extra_font_loader_flags);
|
||||
|
||||
LoadFlags = 0;
|
||||
if ((UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) == 0)
|
||||
LoadFlags |= FT_LOAD_NO_BITMAP;
|
||||
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoHinting)
|
||||
LoadFlags |= FT_LOAD_NO_HINTING;
|
||||
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoAutoHint)
|
||||
LoadFlags |= FT_LOAD_NO_AUTOHINT;
|
||||
if (UserFlags & ImGuiFreeTypeLoaderFlags_ForceAutoHint)
|
||||
LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
|
||||
if (UserFlags & ImGuiFreeTypeLoaderFlags_LightHinting)
|
||||
LoadFlags |= FT_LOAD_TARGET_LIGHT;
|
||||
else if (UserFlags & ImGuiFreeTypeLoaderFlags_MonoHinting)
|
||||
LoadFlags |= FT_LOAD_TARGET_MONO;
|
||||
else
|
||||
LoadFlags |= FT_LOAD_TARGET_NORMAL;
|
||||
|
||||
if (UserFlags & ImGuiFreeTypeLoaderFlags_LoadColor)
|
||||
LoadFlags |= FT_LOAD_COLOR;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplFreeType_FontSrcData::CloseFont()
|
||||
{
|
||||
if (FtFace)
|
||||
{
|
||||
FT_Done_Face(FtFace);
|
||||
FtFace = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint)
|
||||
{
|
||||
uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint);
|
||||
if (glyph_index == 0)
|
||||
return nullptr;
|
||||
|
||||
// If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts.
|
||||
// - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076
|
||||
// - https://github.com/ocornut/imgui/issues/4567
|
||||
// - https://github.com/ocornut/imgui/issues/4566
|
||||
// You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
|
||||
FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags);
|
||||
if (error)
|
||||
return nullptr;
|
||||
|
||||
// Need an outline for this to work
|
||||
FT_GlyphSlot slot = src_data->FtFace->glyph;
|
||||
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
|
||||
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG);
|
||||
#else
|
||||
#if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
|
||||
IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG && "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG in imconfig.h and install required libraries in order to use this font");
|
||||
#endif
|
||||
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
|
||||
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
|
||||
// Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
|
||||
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bold)
|
||||
FT_GlyphSlot_Embolden(slot);
|
||||
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Oblique)
|
||||
{
|
||||
FT_GlyphSlot_Oblique(slot);
|
||||
//FT_BBox bbox;
|
||||
//FT_Outline_Get_BBox(&slot->outline, &bbox);
|
||||
//slot->metrics.width = bbox.xMax - bbox.xMin;
|
||||
//slot->metrics.height = bbox.yMax - bbox.yMin;
|
||||
}
|
||||
|
||||
return &slot->metrics;
|
||||
}
|
||||
|
||||
static void ImGui_ImplFreeType_BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch)
|
||||
{
|
||||
IM_ASSERT(ft_bitmap != nullptr);
|
||||
const uint32_t w = ft_bitmap->width;
|
||||
const uint32_t h = ft_bitmap->rows;
|
||||
const uint8_t* src = ft_bitmap->buffer;
|
||||
const uint32_t src_pitch = ft_bitmap->pitch;
|
||||
|
||||
switch (ft_bitmap->pixel_mode)
|
||||
{
|
||||
case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel.
|
||||
{
|
||||
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
|
||||
for (uint32_t x = 0; x < w; x++)
|
||||
dst[x] = IM_COL32(255, 255, 255, src[x]);
|
||||
break;
|
||||
}
|
||||
case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB.
|
||||
{
|
||||
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
|
||||
{
|
||||
uint8_t bits = 0;
|
||||
const uint8_t* bits_ptr = src;
|
||||
for (uint32_t x = 0; x < w; x++, bits <<= 1)
|
||||
{
|
||||
if ((x & 7) == 0)
|
||||
bits = *bits_ptr++;
|
||||
dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FT_PIXEL_MODE_BGRA:
|
||||
{
|
||||
// FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
|
||||
#define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u)
|
||||
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
|
||||
for (uint32_t x = 0; x < w; x++)
|
||||
{
|
||||
uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
|
||||
dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a);
|
||||
}
|
||||
#undef DE_MULTIPLY
|
||||
break;
|
||||
}
|
||||
default:
|
||||
IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!");
|
||||
}
|
||||
}
|
||||
|
||||
// FreeType memory allocation callbacks
|
||||
static void* FreeType_Alloc(FT_Memory /*memory*/, long size)
|
||||
{
|
||||
return GImGuiFreeTypeAllocFunc((size_t)size, GImGuiFreeTypeAllocatorUserData);
|
||||
}
|
||||
|
||||
static void FreeType_Free(FT_Memory /*memory*/, void* block)
|
||||
{
|
||||
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
|
||||
}
|
||||
|
||||
static void* FreeType_Realloc(FT_Memory /*memory*/, long cur_size, long new_size, void* block)
|
||||
{
|
||||
// Implement realloc() as we don't ask user to provide it.
|
||||
if (block == nullptr)
|
||||
return GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
|
||||
|
||||
if (new_size == 0)
|
||||
{
|
||||
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (new_size > cur_size)
|
||||
{
|
||||
void* new_block = GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
|
||||
memcpy(new_block, block, (size_t)cur_size);
|
||||
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
|
||||
return new_block;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
static bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas)
|
||||
{
|
||||
IM_ASSERT(atlas->FontLoaderData == nullptr);
|
||||
ImGui_ImplFreeType_Data* bd = IM_NEW(ImGui_ImplFreeType_Data)();
|
||||
|
||||
// FreeType memory management: https://www.freetype.org/freetype2/docs/design/design-4.html
|
||||
bd->MemoryManager.user = nullptr;
|
||||
bd->MemoryManager.alloc = &FreeType_Alloc;
|
||||
bd->MemoryManager.free = &FreeType_Free;
|
||||
bd->MemoryManager.realloc = &FreeType_Realloc;
|
||||
|
||||
// https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library
|
||||
FT_Error error = FT_New_Library(&bd->MemoryManager, &bd->Library);
|
||||
if (error != 0)
|
||||
{
|
||||
IM_DELETE(bd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator.
|
||||
FT_Add_Default_Modules(bd->Library);
|
||||
|
||||
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
// Install svg hooks for FreeType
|
||||
// https://freetype.org/freetype2/docs/reference/ft2-properties.html#svg-hooks
|
||||
// https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html#svg_fonts
|
||||
SVG_RendererHooks hooks = { ImGuiLunasvgPortInit, ImGuiLunasvgPortFree, ImGuiLunasvgPortRender, ImGuiLunasvgPortPresetSlot };
|
||||
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", &hooks);
|
||||
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||
// With plutosvg, use provided hooks
|
||||
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks());
|
||||
#endif // IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||
|
||||
// Store our data
|
||||
atlas->FontLoaderData = (void*)bd;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas)
|
||||
{
|
||||
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
|
||||
IM_ASSERT(bd != nullptr);
|
||||
FT_Done_Library(bd->Library);
|
||||
IM_DELETE(bd);
|
||||
atlas->FontLoaderData = nullptr;
|
||||
}
|
||||
|
||||
static bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src)
|
||||
{
|
||||
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
|
||||
ImGui_ImplFreeType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplFreeType_FontSrcData);
|
||||
IM_ASSERT(src->FontLoaderData == nullptr);
|
||||
src->FontLoaderData = bd_font_data;
|
||||
|
||||
if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeLoaderFlags)atlas->FontLoaderFlags))
|
||||
{
|
||||
IM_DELETE(bd_font_data);
|
||||
src->FontLoaderData = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src)
|
||||
{
|
||||
IM_UNUSED(atlas);
|
||||
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||
IM_DELETE(bd_font_data);
|
||||
src->FontLoaderData = nullptr;
|
||||
}
|
||||
|
||||
static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
|
||||
{
|
||||
IM_UNUSED(atlas);
|
||||
float size = baked->Size;
|
||||
if (src->MergeMode && src->SizePixels != 0.0f)
|
||||
size *= (src->SizePixels / baked->OwnerFont->Sources[0]->SizePixels);
|
||||
size *= src->ExtraSizeScale;
|
||||
|
||||
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||
bd_font_data->BakedLastActivated = baked;
|
||||
|
||||
// We use one FT_Size per (source + baked) combination.
|
||||
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
|
||||
IM_ASSERT(bd_baked_data != nullptr);
|
||||
IM_PLACEMENT_NEW(bd_baked_data) ImGui_ImplFreeType_FontSrcBakedData();
|
||||
|
||||
FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
|
||||
FT_Activate_Size(bd_baked_data->FtSize);
|
||||
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
|
||||
if (((bd_font_data->FtFace->face_flags & FT_FACE_FLAG_FIXED_SIZES) != 0) && ((bd_font_data->FtFace->face_flags & FT_FACE_FLAG_SCALABLE) == 0) && ((bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) != 0))
|
||||
{
|
||||
IM_ASSERT(bd_font_data->FtFace->num_fixed_sizes > 0);
|
||||
|
||||
// Loop over sizes and pick the closest, larger (or better equal) size.
|
||||
int best_index = 0;
|
||||
float best_height = bd_font_data->FtFace->available_sizes[best_index].y_ppem / FT_SCALEFACTOR;
|
||||
for (int i = 1; i < bd_font_data->FtFace->num_fixed_sizes; i++)
|
||||
{
|
||||
const float cur_height = bd_font_data->FtFace->available_sizes[i].y_ppem / FT_SCALEFACTOR;
|
||||
// FIXME: is this overkill?
|
||||
// FIXME: is-float-close with epsilon param would be nice, maybe
|
||||
if (ImFabs(cur_height - size) < 0.001f)
|
||||
{
|
||||
best_index = i;
|
||||
best_height = cur_height;
|
||||
break;
|
||||
}
|
||||
else if (cur_height < size)
|
||||
{
|
||||
if (best_height < cur_height)
|
||||
{
|
||||
best_index = i;
|
||||
best_height = cur_height;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (best_height < size && best_height < cur_height)
|
||||
{
|
||||
best_index = i;
|
||||
best_height = cur_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
FT_Select_Size(bd_font_data->FtFace, best_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
|
||||
// is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me.
|
||||
// FT_Set_Pixel_Sizes() doesn't seem to get us the same result."
|
||||
// (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL)
|
||||
|
||||
FT_Size_RequestRec req;
|
||||
req.type = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
|
||||
req.width = 0;
|
||||
req.height = (uint32_t)(size * FT_SCALEFACTOR * rasterizer_density);
|
||||
req.horiResolution = 0;
|
||||
req.vertResolution = 0;
|
||||
FT_Request_Size(bd_font_data->FtFace, &req);
|
||||
}
|
||||
|
||||
// Output
|
||||
if (src->MergeMode == false)
|
||||
{
|
||||
// Read metrics
|
||||
FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics;
|
||||
const float scale = 1.0f / (rasterizer_density * src->ExtraSizeScale);
|
||||
baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive).
|
||||
baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative).
|
||||
//LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate.
|
||||
//LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent.
|
||||
//MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
|
||||
{
|
||||
IM_UNUSED(atlas);
|
||||
IM_UNUSED(baked);
|
||||
IM_UNUSED(src);
|
||||
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
|
||||
IM_ASSERT(bd_baked_data != nullptr);
|
||||
FT_Done_Size(bd_baked_data->FtSize);
|
||||
bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE()
|
||||
}
|
||||
|
||||
static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x)
|
||||
{
|
||||
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||
uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
|
||||
if (glyph_index == 0)
|
||||
return false;
|
||||
|
||||
if (bd_font_data->BakedLastActivated != baked) // <-- could use id
|
||||
{
|
||||
// Activate current size
|
||||
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
|
||||
FT_Activate_Size(bd_baked_data->FtSize);
|
||||
bd_font_data->BakedLastActivated = baked;
|
||||
}
|
||||
|
||||
const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint);
|
||||
if (metrics == nullptr)
|
||||
return false;
|
||||
|
||||
FT_Face face = bd_font_data->FtFace;
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
|
||||
float bitmap_x_scale = 1.0f;
|
||||
float bitmap_y_scale = 1.0f;
|
||||
if (((face->face_flags & FT_FACE_FLAG_FIXED_SIZES) != 0) && ((face->face_flags & FT_FACE_FLAG_SCALABLE) == 0) && ((bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) != 0))
|
||||
{
|
||||
// FIXME: what if this just means invisible?
|
||||
IM_ASSERT(bd_font_data->FtFace->size->metrics.x_ppem > 0);
|
||||
IM_ASSERT(bd_font_data->FtFace->size->metrics.y_ppem > 0);
|
||||
|
||||
// Scale fixed size bitmap to target size
|
||||
bitmap_x_scale = baked->Size / bd_font_data->FtFace->size->metrics.x_ppem;
|
||||
bitmap_y_scale = baked->Size / bd_font_data->FtFace->size->metrics.y_ppem;
|
||||
}
|
||||
|
||||
// Load metrics only mode
|
||||
const float advance_x = ((slot->advance.x / FT_SCALEFACTOR) * bitmap_x_scale) / rasterizer_density;
|
||||
if (out_advance_x != NULL)
|
||||
{
|
||||
IM_ASSERT(out_glyph == NULL);
|
||||
*out_advance_x = advance_x;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Render glyph into a bitmap (currently held by FreeType)
|
||||
FT_Render_Mode render_mode = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Monochrome) ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL;
|
||||
FT_Error error = FT_Render_Glyph(slot, render_mode);
|
||||
const FT_Bitmap* ft_bitmap = &slot->bitmap;
|
||||
if (error != 0 || ft_bitmap == nullptr)
|
||||
return false;
|
||||
|
||||
const int bitmap_w = (int)ft_bitmap->width;
|
||||
const int bitmap_h = (int)ft_bitmap->rows;
|
||||
const bool is_visible = (bitmap_w != 0 && bitmap_h != 0);
|
||||
|
||||
// Prepare glyph
|
||||
out_glyph->Codepoint = codepoint;
|
||||
out_glyph->AdvanceX = advance_x;
|
||||
|
||||
// Pack and retrieve position inside texture atlas
|
||||
if (is_visible)
|
||||
{
|
||||
const int w = (int)ImCeil(bitmap_w * bitmap_x_scale);
|
||||
const int h = (int)ImCeil(bitmap_h * bitmap_y_scale);
|
||||
const bool rescaling = h != bitmap_h || w != bitmap_w;
|
||||
|
||||
ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h);
|
||||
if (pack_id == ImFontAtlasRectId_Invalid)
|
||||
{
|
||||
// Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?)
|
||||
IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory.");
|
||||
return false;
|
||||
}
|
||||
ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id);
|
||||
|
||||
// Render pixels to our temporary buffer, while making sure we have space for an extra copy used during rescaling.
|
||||
atlas->Builder->TempBuffer.resize(((rescaling ? bitmap_w * bitmap_h : 0) + w * h) * 4);
|
||||
uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data;
|
||||
// Blit (and convert) into the first bitmap_w * bitmap_h * 4 bytes.
|
||||
ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, bitmap_w);
|
||||
|
||||
if (rescaling)
|
||||
{
|
||||
uint32_t* dst_buffer = temp_buffer + bitmap_w * bitmap_h;
|
||||
|
||||
// Perform rescale, from temp_buffer (bitmap_w * bitmap_h) to dst_buffer (w * h)
|
||||
ImFontAtlasTextureBlockResize(temp_buffer, bitmap_w, bitmap_h, dst_buffer, w, h);
|
||||
|
||||
// Redirect to rescaled part of the buffer
|
||||
temp_buffer = dst_buffer;
|
||||
}
|
||||
|
||||
const float ref_size = baked->OwnerFont->Sources[0]->SizePixels;
|
||||
const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f;
|
||||
float font_off_x = ImFloor(src->GlyphOffset.x * offsets_scale + 0.5f); // Snap scaled offset.
|
||||
float font_off_y = ImFloor(src->GlyphOffset.y * offsets_scale + 0.5f) + baked->Ascent;
|
||||
float recip_h = 1.0f / rasterizer_density;
|
||||
float recip_v = 1.0f / rasterizer_density;
|
||||
|
||||
// Register glyph
|
||||
float glyph_off_x = ImCeil((float)face->glyph->bitmap_left * bitmap_x_scale);
|
||||
float glyph_off_y = ImCeil((float)-face->glyph->bitmap_top * bitmap_y_scale);
|
||||
out_glyph->X0 = glyph_off_x * recip_h + font_off_x;
|
||||
out_glyph->Y0 = glyph_off_y * recip_v + font_off_y;
|
||||
out_glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x;
|
||||
out_glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y;
|
||||
out_glyph->Visible = true;
|
||||
out_glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA);
|
||||
out_glyph->PackId = pack_id;
|
||||
ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint)
|
||||
{
|
||||
IM_UNUSED(atlas);
|
||||
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
|
||||
int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
|
||||
return glyph_index != 0;
|
||||
}
|
||||
|
||||
const ImFontLoader* ImGuiFreeType::GetFontLoader()
|
||||
{
|
||||
static ImFontLoader loader;
|
||||
loader.Name = "FreeType";
|
||||
loader.LoaderInit = ImGui_ImplFreeType_LoaderInit;
|
||||
loader.LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown;
|
||||
loader.FontSrcInit = ImGui_ImplFreeType_FontSrcInit;
|
||||
loader.FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy;
|
||||
loader.FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph;
|
||||
loader.FontBakedInit = ImGui_ImplFreeType_FontBakedInit;
|
||||
loader.FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy;
|
||||
loader.FontBakedLoadGlyph = ImGui_ImplFreeType_FontBakedLoadGlyph;
|
||||
loader.FontBakedSrcLoaderDataSize = sizeof(ImGui_ImplFreeType_FontSrcBakedData);
|
||||
return &loader;
|
||||
}
|
||||
|
||||
void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
|
||||
{
|
||||
GImGuiFreeTypeAllocFunc = alloc_func;
|
||||
GImGuiFreeTypeFreeFunc = free_func;
|
||||
GImGuiFreeTypeAllocatorUserData = user_data;
|
||||
}
|
||||
|
||||
bool ImGuiFreeType::DebugEditFontLoaderFlags(unsigned int* p_font_loader_flags)
|
||||
{
|
||||
bool edited = false;
|
||||
edited |= ImGui::CheckboxFlags("NoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoHinting);
|
||||
edited |= ImGui::CheckboxFlags("NoAutoHint", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoAutoHint);
|
||||
edited |= ImGui::CheckboxFlags("ForceAutoHint",p_font_loader_flags, ImGuiFreeTypeLoaderFlags_ForceAutoHint);
|
||||
edited |= ImGui::CheckboxFlags("LightHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LightHinting);
|
||||
edited |= ImGui::CheckboxFlags("MonoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_MonoHinting);
|
||||
edited |= ImGui::CheckboxFlags("Bold", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bold);
|
||||
edited |= ImGui::CheckboxFlags("Oblique", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Oblique);
|
||||
edited |= ImGui::CheckboxFlags("Monochrome", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Monochrome);
|
||||
edited |= ImGui::CheckboxFlags("LoadColor", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LoadColor);
|
||||
edited |= ImGui::CheckboxFlags("Bitmap", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bitmap);
|
||||
return edited;
|
||||
}
|
||||
|
||||
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
// For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
|
||||
// The original code from the demo is licensed under CeCILL-C Free Software License Agreement (https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT)
|
||||
struct LunasvgPortState
|
||||
{
|
||||
FT_Error err = FT_Err_Ok;
|
||||
lunasvg::Matrix matrix;
|
||||
std::unique_ptr<lunasvg::Document> svg = nullptr;
|
||||
};
|
||||
|
||||
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state)
|
||||
{
|
||||
*_state = IM_NEW(LunasvgPortState)();
|
||||
return FT_Err_Ok;
|
||||
}
|
||||
|
||||
static void ImGuiLunasvgPortFree(FT_Pointer* _state)
|
||||
{
|
||||
IM_DELETE(*(LunasvgPortState**)_state);
|
||||
}
|
||||
|
||||
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state)
|
||||
{
|
||||
LunasvgPortState* state = *(LunasvgPortState**)_state;
|
||||
|
||||
// If there was an error while loading the svg in ImGuiLunasvgPortPresetSlot(), the renderer hook still get called, so just returns the error.
|
||||
if (state->err != FT_Err_Ok)
|
||||
return state->err;
|
||||
|
||||
// rows is height, pitch (or stride) equals to width * sizeof(int32)
|
||||
lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch);
|
||||
#if LUNASVG_VERSION_MAJOR >= 3
|
||||
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
|
||||
#else
|
||||
state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value
|
||||
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
|
||||
#endif
|
||||
state->err = FT_Err_Ok;
|
||||
return state->err;
|
||||
}
|
||||
|
||||
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state)
|
||||
{
|
||||
FT_SVG_Document document = (FT_SVG_Document)slot->other;
|
||||
LunasvgPortState* state = *(LunasvgPortState**)_state;
|
||||
FT_Size_Metrics& metrics = document->metrics;
|
||||
|
||||
// This function is called twice, once in the FT_Load_Glyph() and another right before ImGuiLunasvgPortRender().
|
||||
// If it's the latter, don't do anything because it's // already done in the former.
|
||||
if (cache)
|
||||
return state->err;
|
||||
|
||||
state->svg = lunasvg::Document::loadFromData((const char*)document->svg_document, document->svg_document_length);
|
||||
if (state->svg == nullptr)
|
||||
{
|
||||
state->err = FT_Err_Invalid_SVG_Document;
|
||||
return state->err;
|
||||
}
|
||||
|
||||
#if LUNASVG_VERSION_MAJOR >= 3
|
||||
lunasvg::Box box = state->svg->boundingBox();
|
||||
#else
|
||||
lunasvg::Box box = state->svg->box();
|
||||
#endif
|
||||
double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h);
|
||||
double xx = (double)document->transform.xx / (1 << 16);
|
||||
double xy = -(double)document->transform.xy / (1 << 16);
|
||||
double yx = -(double)document->transform.yx / (1 << 16);
|
||||
double yy = (double)document->transform.yy / (1 << 16);
|
||||
double x0 = (double)document->delta.x / 64 * box.w / metrics.x_ppem;
|
||||
double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem;
|
||||
|
||||
#if LUNASVG_VERSION_MAJOR >= 3
|
||||
// Scale, transform and pre-translate the matrix for the rendering step
|
||||
state->matrix = lunasvg::Matrix::translated(-box.x, -box.y);
|
||||
state->matrix.multiply(lunasvg::Matrix(xx, xy, yx, yy, x0, y0));
|
||||
state->matrix.scale(scale, scale);
|
||||
|
||||
// Apply updated transformation to the bounding box
|
||||
box.transform(state->matrix);
|
||||
#else
|
||||
// Scale and transform, we don't translate the svg yet
|
||||
state->matrix.identity();
|
||||
state->matrix.scale(scale, scale);
|
||||
state->matrix.transform(xx, xy, yx, yy, x0, y0);
|
||||
state->svg->setMatrix(state->matrix);
|
||||
|
||||
// Pre-translate the matrix for the rendering step
|
||||
state->matrix.translate(-box.x, -box.y);
|
||||
|
||||
// Get the box again after the transformation
|
||||
box = state->svg->box();
|
||||
#endif
|
||||
|
||||
// Calculate the bitmap size
|
||||
slot->bitmap_left = FT_Int(box.x);
|
||||
slot->bitmap_top = FT_Int(-box.y);
|
||||
slot->bitmap.rows = (unsigned int)(ImCeil((float)box.h));
|
||||
slot->bitmap.width = (unsigned int)(ImCeil((float)box.w));
|
||||
slot->bitmap.pitch = slot->bitmap.width * 4;
|
||||
slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
|
||||
|
||||
// Compute all the bearings and set them correctly. The outline is scaled already, we just need to use the bounding box.
|
||||
double metrics_width = box.w;
|
||||
double metrics_height = box.h;
|
||||
double horiBearingX = box.x;
|
||||
double horiBearingY = -box.y;
|
||||
double vertBearingX = slot->metrics.horiBearingX / 64.0 - slot->metrics.horiAdvance / 64.0 / 2.0;
|
||||
double vertBearingY = (slot->metrics.vertAdvance / 64.0 - slot->metrics.height / 64.0) / 2.0;
|
||||
slot->metrics.width = FT_Pos(IM_ROUND(metrics_width * 64.0)); // Using IM_ROUND() assume width and height are positive
|
||||
slot->metrics.height = FT_Pos(IM_ROUND(metrics_height * 64.0));
|
||||
slot->metrics.horiBearingX = FT_Pos(horiBearingX * 64);
|
||||
slot->metrics.horiBearingY = FT_Pos(horiBearingY * 64);
|
||||
slot->metrics.vertBearingX = FT_Pos(vertBearingX * 64);
|
||||
slot->metrics.vertBearingY = FT_Pos(vertBearingY * 64);
|
||||
|
||||
if (slot->metrics.vertAdvance == 0)
|
||||
slot->metrics.vertAdvance = FT_Pos(metrics_height * 1.2 * 64.0);
|
||||
|
||||
state->err = FT_Err_Ok;
|
||||
return state->err;
|
||||
}
|
||||
|
||||
#endif // #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
|
||||
// (headers)
|
||||
|
||||
#pragma once
|
||||
#include "imgui.h" // IMGUI_API
|
||||
#ifndef IMGUI_DISABLE
|
||||
|
||||
// Usage:
|
||||
// - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to automatically enable support
|
||||
// for imgui_freetype in imgui. It is equivalent to selecting the default loader with:
|
||||
// io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())
|
||||
|
||||
// Optional support for OpenType SVG fonts:
|
||||
// - Add '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG' to use plutosvg (not provided). See #7927.
|
||||
// - Add '#define IMGUI_ENABLE_FREETYPE_LUNASVG' to use lunasvg (not provided). See #6591.
|
||||
|
||||
// Forward declarations
|
||||
struct ImFontAtlas;
|
||||
struct ImFontLoader;
|
||||
|
||||
// Hinting greatly impacts visuals (and glyph sizes).
|
||||
// - By default, hinting is enabled and the font's native hinter is preferred over the auto-hinter.
|
||||
// - When disabled, FreeType generates blurrier glyphs, more or less matches the stb_truetype.h
|
||||
// - The Default hinting mode usually looks good, but may distort glyphs in an unusual way.
|
||||
// - The Light hinting mode generates fuzzier glyphs but better matches Microsoft's rasterizer.
|
||||
// You can set those flags globally in ImFontAtlas::FontLoaderFlags
|
||||
// You can set those flags on a per font basis in ImFontConfig::FontLoaderFlags
|
||||
typedef unsigned int ImGuiFreeTypeLoaderFlags;
|
||||
enum ImGuiFreeTypeLoaderFlags_
|
||||
{
|
||||
ImGuiFreeTypeLoaderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes.
|
||||
ImGuiFreeTypeLoaderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter.
|
||||
ImGuiFreeTypeLoaderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter.
|
||||
ImGuiFreeTypeLoaderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text.
|
||||
ImGuiFreeTypeLoaderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output.
|
||||
ImGuiFreeTypeLoaderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font?
|
||||
ImGuiFreeTypeLoaderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style?
|
||||
ImGuiFreeTypeLoaderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results!
|
||||
ImGuiFreeTypeLoaderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs
|
||||
ImGuiFreeTypeLoaderFlags_Bitmap = 1 << 9, // Enable FreeType bitmap glyphs
|
||||
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
ImGuiFreeTypeBuilderFlags_NoHinting = ImGuiFreeTypeLoaderFlags_NoHinting,
|
||||
ImGuiFreeTypeBuilderFlags_NoAutoHint = ImGuiFreeTypeLoaderFlags_NoAutoHint,
|
||||
ImGuiFreeTypeBuilderFlags_ForceAutoHint = ImGuiFreeTypeLoaderFlags_ForceAutoHint,
|
||||
ImGuiFreeTypeBuilderFlags_LightHinting = ImGuiFreeTypeLoaderFlags_LightHinting,
|
||||
ImGuiFreeTypeBuilderFlags_MonoHinting = ImGuiFreeTypeLoaderFlags_MonoHinting,
|
||||
ImGuiFreeTypeBuilderFlags_Bold = ImGuiFreeTypeLoaderFlags_Bold,
|
||||
ImGuiFreeTypeBuilderFlags_Oblique = ImGuiFreeTypeLoaderFlags_Oblique,
|
||||
ImGuiFreeTypeBuilderFlags_Monochrome = ImGuiFreeTypeLoaderFlags_Monochrome,
|
||||
ImGuiFreeTypeBuilderFlags_LoadColor = ImGuiFreeTypeLoaderFlags_LoadColor,
|
||||
ImGuiFreeTypeBuilderFlags_Bitmap = ImGuiFreeTypeLoaderFlags_Bitmap,
|
||||
#endif
|
||||
};
|
||||
|
||||
// Obsolete names (will be removed)
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
typedef ImGuiFreeTypeLoaderFlags_ ImGuiFreeTypeBuilderFlags_;
|
||||
#endif
|
||||
|
||||
namespace ImGuiFreeType
|
||||
{
|
||||
// This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'.
|
||||
// If you need to dynamically select between multiple builders:
|
||||
// - you can manually assign this builder with 'atlas->SetFontLoader(ImGuiFreeType::GetFontLoader())'
|
||||
// - prefer deep-copying this into your own ImFontLoader instance if you use hot-reloading that messes up static data.
|
||||
IMGUI_API const ImFontLoader* GetFontLoader();
|
||||
|
||||
// Override allocators. By default ImGuiFreeType will use IM_ALLOC()/IM_FREE()
|
||||
// However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired.
|
||||
IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr);
|
||||
|
||||
// Display UI to edit ImFontAtlas::FontLoaderFlags (shared) or ImFontConfig::FontLoaderFlags (single source)
|
||||
IMGUI_API bool DebugEditFontLoaderFlags(ImGuiFreeTypeLoaderFlags* p_font_loader_flags);
|
||||
|
||||
// Obsolete names (will be removed)
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
//IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())' if you need runtime selection.
|
||||
//static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontLoaderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE'
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
+1
-1
Submodule core/deps/libadrenotools updated: 5deac9f1ab...8fae8ce254.
Submodule
+1
Submodule core/deps/libjuice added at 8f0918a716.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,149 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (file_path_io.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <file/file_path.h>
|
||||
#include <compat/strl.h>
|
||||
#include <compat/posix_string.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <string/stdstring.h>
|
||||
#define VFS_FRONTEND
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h> /* stat() is defined here */
|
||||
#endif
|
||||
|
||||
/* TODO/FIXME - globals */
|
||||
static retro_vfs_stat_t path_stat_cb = retro_vfs_stat_impl;
|
||||
static retro_vfs_mkdir_t path_mkdir_cb = retro_vfs_mkdir_impl;
|
||||
|
||||
void path_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
{
|
||||
const struct retro_vfs_interface*
|
||||
vfs_iface = vfs_info->iface;
|
||||
|
||||
path_stat_cb = retro_vfs_stat_impl;
|
||||
path_mkdir_cb = retro_vfs_mkdir_impl;
|
||||
|
||||
if (vfs_info->required_interface_version < PATH_REQUIRED_VFS_VERSION || !vfs_iface)
|
||||
return;
|
||||
|
||||
path_stat_cb = vfs_iface->stat;
|
||||
path_mkdir_cb = vfs_iface->mkdir;
|
||||
}
|
||||
|
||||
int path_stat(const char *path)
|
||||
{
|
||||
return path_stat_cb(path, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* path_is_directory:
|
||||
* @path : path
|
||||
*
|
||||
* Checks if path is a directory.
|
||||
*
|
||||
* @return true if path is a directory, otherwise false.
|
||||
*/
|
||||
bool path_is_directory(const char *path)
|
||||
{
|
||||
return (path_stat_cb(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0;
|
||||
}
|
||||
|
||||
bool path_is_character_special(const char *path)
|
||||
{
|
||||
return (path_stat_cb(path, NULL) & RETRO_VFS_STAT_IS_CHARACTER_SPECIAL) != 0;
|
||||
}
|
||||
|
||||
bool path_is_valid(const char *path)
|
||||
{
|
||||
return (path_stat_cb(path, NULL) & RETRO_VFS_STAT_IS_VALID) != 0;
|
||||
}
|
||||
|
||||
int32_t path_get_size(const char *path)
|
||||
{
|
||||
int32_t filesize = 0;
|
||||
if (path_stat_cb(path, &filesize) != 0)
|
||||
return filesize;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* path_mkdir:
|
||||
* @dir : directory
|
||||
*
|
||||
* Create directory on filesystem.
|
||||
*
|
||||
* Recursive function.
|
||||
*
|
||||
* @return true if directory could be created, otherwise false.
|
||||
**/
|
||||
bool path_mkdir(const char *dir)
|
||||
{
|
||||
bool norecurse = false;
|
||||
char *basedir = NULL;
|
||||
|
||||
if (!(dir && *dir))
|
||||
return false;
|
||||
|
||||
/* Use heap. Real chance of stack
|
||||
* overflow if we recurse too hard. */
|
||||
if (!(basedir = strdup(dir)))
|
||||
return false;
|
||||
|
||||
path_parent_dir(basedir, strlen(basedir));
|
||||
|
||||
if (!*basedir || !strcmp(basedir, dir))
|
||||
{
|
||||
free(basedir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( path_is_directory(basedir)
|
||||
|| path_mkdir(basedir))
|
||||
norecurse = true;
|
||||
|
||||
free(basedir);
|
||||
|
||||
if (norecurse)
|
||||
{
|
||||
int ret = path_mkdir_cb(dir);
|
||||
|
||||
/* Don't treat this as an error. */
|
||||
if (ret == -2 && path_is_directory(dir))
|
||||
return true;
|
||||
else if (ret == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3008,7 +3008,10 @@ static bool glsm_state_ctx_init(glsm_ctx_params_t *params)
|
||||
return false;
|
||||
|
||||
#ifdef HAVE_OPENGLES
|
||||
hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
|
||||
hw_render.context_type = params->context_type != RETRO_HW_CONTEXT_NONE
|
||||
? params->context_type : RETRO_HW_CONTEXT_OPENGLES2;
|
||||
hw_render.version_major = params->major;
|
||||
hw_render.version_minor = params->minor;
|
||||
#else
|
||||
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
|
||||
if (params->context_type != RETRO_HW_CONTEXT_NONE)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010-2019 The RetroArch team
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (file_path.h).
|
||||
@@ -51,6 +51,28 @@ enum
|
||||
RARCH_FILE_UNSUPPORTED
|
||||
};
|
||||
|
||||
struct path_linked_list
|
||||
{
|
||||
char *path;
|
||||
struct path_linked_list *next;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new linked list with one item in it
|
||||
* The path on this item will be set to NULL
|
||||
**/
|
||||
struct path_linked_list* path_linked_list_new(void);
|
||||
|
||||
/* Free the entire linked list */
|
||||
void path_linked_list_free(struct path_linked_list *in_path_linked_list);
|
||||
|
||||
/**
|
||||
* Add a node to the linked list with this path
|
||||
* If the first node's path if it's not yet set,
|
||||
* set this instead
|
||||
**/
|
||||
void path_linked_list_add_path(struct path_linked_list *in_path_linked_list, char *path);
|
||||
|
||||
/**
|
||||
* path_is_compressed_file:
|
||||
* @path : path
|
||||
@@ -81,12 +103,12 @@ bool path_is_compressed_file(const char *path);
|
||||
* path_get_archive_delim:
|
||||
* @path : path
|
||||
*
|
||||
* Gets delimiter of an archive file. Only the first '#'
|
||||
* Find delimiter of an archive file. Only the first '#'
|
||||
* after a compression extension is considered.
|
||||
*
|
||||
* Returns: pointer to the delimiter in the path if it contains
|
||||
* a compressed file, otherwise NULL.
|
||||
*/
|
||||
* @return pointer to the delimiter in the path if it contains
|
||||
* a path inside a compressed file, otherwise NULL.
|
||||
**/
|
||||
const char *path_get_archive_delim(const char *path);
|
||||
|
||||
/**
|
||||
@@ -96,10 +118,28 @@ const char *path_get_archive_delim(const char *path);
|
||||
* Gets extension of file. Only '.'s
|
||||
* after the last slash are considered.
|
||||
*
|
||||
* Returns: extension part from the path.
|
||||
*/
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls string_is_empty()
|
||||
* - calls strrchr
|
||||
*
|
||||
* @return extension part from the path.
|
||||
**/
|
||||
const char *path_get_extension(const char *path);
|
||||
|
||||
/**
|
||||
* path_get_extension_mutable:
|
||||
* @path : path
|
||||
*
|
||||
* Specialized version of path_get_extension(). Return
|
||||
* value is mutable.
|
||||
*
|
||||
* Gets extension of file. Only '.'s
|
||||
* after the last slash are considered.
|
||||
*
|
||||
* @return extension part from the path.
|
||||
**/
|
||||
char *path_get_extension_mutable(const char *path);
|
||||
|
||||
/**
|
||||
* path_remove_extension:
|
||||
* @path : path
|
||||
@@ -108,7 +148,10 @@ const char *path_get_extension(const char *path);
|
||||
* text after and including the last '.'.
|
||||
* Only '.'s after the last slash are considered.
|
||||
*
|
||||
* Returns:
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strrchr
|
||||
*
|
||||
* @return
|
||||
* 1) If path has an extension, returns path with the
|
||||
* extension removed.
|
||||
* 2) If there is no extension, returns NULL.
|
||||
@@ -122,10 +165,24 @@ char *path_remove_extension(char *path);
|
||||
*
|
||||
* Get basename from @path.
|
||||
*
|
||||
* Returns: basename from path.
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls path_get_archive_delim()
|
||||
*
|
||||
* @return basename from path.
|
||||
**/
|
||||
const char *path_basename(const char *path);
|
||||
|
||||
/**
|
||||
* path_basename_nocompression:
|
||||
* @path : path
|
||||
*
|
||||
* Specialized version of path_basename().
|
||||
* Get basename from @path.
|
||||
*
|
||||
* @return basename from path.
|
||||
**/
|
||||
const char *path_basename_nocompression(const char *path);
|
||||
|
||||
/**
|
||||
* path_basedir:
|
||||
* @path : path
|
||||
@@ -133,44 +190,56 @@ const char *path_basename(const char *path);
|
||||
* Extracts base directory by mutating path.
|
||||
* Keeps trailing '/'.
|
||||
**/
|
||||
void path_basedir(char *path);
|
||||
size_t path_basedir(char *path);
|
||||
|
||||
/**
|
||||
* path_parent_dir:
|
||||
* @path : path
|
||||
* @s : path
|
||||
* @len : size of buffer
|
||||
*
|
||||
* Extracts parent directory by mutating path.
|
||||
* Assumes that path is a directory. Keeps trailing '/'.
|
||||
* If the path was already at the root directory, returns empty string
|
||||
**/
|
||||
void path_parent_dir(char *path);
|
||||
size_t path_parent_dir(char *s, size_t len);
|
||||
|
||||
/**
|
||||
* path_resolve_realpath:
|
||||
* @buf : buffer for path
|
||||
* @s : input and output buffer for path
|
||||
* @size : size of buffer
|
||||
* @resolve_symlinks : whether to resolve symlinks or not
|
||||
*
|
||||
* Turns relative paths into absolute paths and
|
||||
* resolves use of "." and ".." in absolute paths.
|
||||
* If relative, rebases on current working dir.
|
||||
* Resolves use of ".", "..", multiple slashes etc in absolute paths.
|
||||
*
|
||||
* Relative paths are rebased on the current working dir.
|
||||
*
|
||||
* @return @s if successful, NULL otherwise.
|
||||
* Note: Not implemented on consoles
|
||||
* Note: Symlinks are only resolved on Unix-likes
|
||||
* Note: The current working dir might not be what you expect,
|
||||
* e.g. on Android it is "/"
|
||||
* Use of fill_pathname_resolve_relative() should be preferred
|
||||
**/
|
||||
void path_resolve_realpath(char *buf, size_t size);
|
||||
char *path_resolve_realpath(char *s, size_t len, bool resolve_symlinks);
|
||||
|
||||
/**
|
||||
* path_relative_to:
|
||||
* @out : buffer to write the relative path to
|
||||
* @s : buffer to write the relative path to
|
||||
* @path : path to be expressed relatively
|
||||
* @base : relative to this
|
||||
* @size : size of output buffer
|
||||
* @len : size of output buffer
|
||||
*
|
||||
* Turns @path into a path relative to @base and writes it to @out.
|
||||
* Turns @path into a path relative to @base and writes it to @s.
|
||||
*
|
||||
* @base is assumed to be a base directory, i.e. a path ending with '/' or '\'.
|
||||
* Both @path and @base are assumed to be absolute paths without "." or "..".
|
||||
*
|
||||
* E.g. path /a/b/e/f.cgp with base /a/b/c/d/ turns into ../../e/f.cgp
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
void path_relative_to(char *out, const char *path, const char *base, size_t size);
|
||||
size_t path_relative_to(char *s, const char *path, const char *base,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* path_is_absolute:
|
||||
@@ -178,261 +247,339 @@ void path_relative_to(char *out, const char *path, const char *base, size_t size
|
||||
*
|
||||
* Checks if @path is an absolute path or a relative path.
|
||||
*
|
||||
* Returns: true if path is absolute, false if path is relative.
|
||||
* @return true if path is absolute, false if path is relative.
|
||||
**/
|
||||
bool path_is_absolute(const char *path);
|
||||
|
||||
/**
|
||||
* fill_pathname:
|
||||
* @out_path : output path
|
||||
* @s : output path
|
||||
* @in_path : input path
|
||||
* @replace : what to replace
|
||||
* @size : buffer size of output path
|
||||
* @len : buffer size of output path
|
||||
*
|
||||
* FIXME: Verify
|
||||
*
|
||||
* Replaces filename extension with 'replace' and outputs result to out_path.
|
||||
* Replaces filename extension with 'replace' and outputs result to s.
|
||||
* The extension here is considered to be the string from the last '.'
|
||||
* to the end.
|
||||
*
|
||||
* Only '.'s after the last slash are considered as extensions.
|
||||
* If no '.' is present, in_path and replace will simply be concatenated.
|
||||
* 'size' is buffer size of 'out_path'.
|
||||
* 'len' is buffer size of 's'.
|
||||
* E.g.: in_path = "/foo/bar/baz/boo.c", replace = ".asm" =>
|
||||
* out_path = "/foo/bar/baz/boo.asm"
|
||||
* s = "/foo/bar/baz/boo.asm"
|
||||
* E.g.: in_path = "/foo/bar/baz/boo.c", replace = "" =>
|
||||
* out_path = "/foo/bar/baz/boo"
|
||||
* s = "/foo/bar/baz/boo"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strlcpy 2x
|
||||
* - calls strrchr
|
||||
* - calls strlcat
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
*/
|
||||
void fill_pathname(char *out_path, const char *in_path,
|
||||
const char *replace, size_t size);
|
||||
size_t fill_pathname(char *s, const char *in_path,
|
||||
const char *replace, size_t len);
|
||||
|
||||
/**
|
||||
* fill_dated_filename:
|
||||
* @out_filename : output filename
|
||||
* @s : output filename
|
||||
* @ext : extension of output filename
|
||||
* @size : buffer size of output filename
|
||||
* @len : buffer size of output filename
|
||||
*
|
||||
* Creates a 'dated' filename prefixed by 'RetroArch', and
|
||||
* concatenates extension (@ext) to it.
|
||||
*
|
||||
* E.g.:
|
||||
* out_filename = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}"
|
||||
* s = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls rtime_localtime()
|
||||
* - Calls strftime
|
||||
* - Calls strlcat
|
||||
*
|
||||
**/
|
||||
void fill_dated_filename(char *out_filename,
|
||||
const char *ext, size_t size);
|
||||
size_t fill_dated_filename(char *s, const char *ext, size_t len);
|
||||
|
||||
/**
|
||||
* fill_str_dated_filename:
|
||||
* @out_filename : output filename
|
||||
* @s : output filename
|
||||
* @in_str : input string
|
||||
* @ext : extension of output filename
|
||||
* @size : buffer size of output filename
|
||||
* @len : buffer size of output filename
|
||||
*
|
||||
* Creates a 'dated' filename prefixed by the string @in_str, and
|
||||
* concatenates extension (@ext) to it.
|
||||
*
|
||||
* E.g.:
|
||||
* out_filename = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}"
|
||||
* s = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls time
|
||||
* - Calls rtime_localtime()
|
||||
* - Calls strlcpy 2x
|
||||
* - Calls string_is_empty()
|
||||
* - Calls strftime
|
||||
* - Calls strlcat
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
void fill_str_dated_filename(char *out_filename,
|
||||
const char *in_str, const char *ext, size_t size);
|
||||
|
||||
/**
|
||||
* fill_pathname_noext:
|
||||
* @out_path : output path
|
||||
* @in_path : input path
|
||||
* @replace : what to replace
|
||||
* @size : buffer size of output path
|
||||
*
|
||||
* Appends a filename extension 'replace' to 'in_path', and outputs
|
||||
* result in 'out_path'.
|
||||
*
|
||||
* Assumes in_path has no extension. If an extension is still
|
||||
* present in 'in_path', it will be ignored.
|
||||
*
|
||||
*/
|
||||
void fill_pathname_noext(char *out_path, const char *in_path,
|
||||
const char *replace, size_t size);
|
||||
size_t fill_str_dated_filename(char *s, const char *in_str, const char *ext, size_t len);
|
||||
|
||||
/**
|
||||
* find_last_slash:
|
||||
* @str : input path
|
||||
* @str : path
|
||||
*
|
||||
* Gets a pointer to the last slash in the input path.
|
||||
* Find last slash in path. Tries to find
|
||||
* a backslash on Windows too which takes precedence
|
||||
* over regular slash.
|
||||
*
|
||||
* Returns: a pointer to the last slash in the input path.
|
||||
* Leaf function.
|
||||
*
|
||||
* @return pointer to last slash/backslash found in @str.
|
||||
**/
|
||||
char *find_last_slash(const char *str);
|
||||
|
||||
/**
|
||||
* fill_pathname_dir:
|
||||
* @in_dir : input directory path
|
||||
* @in_basename : input basename to be appended to @in_dir
|
||||
* @s : input directory path
|
||||
* @in_basename : input basename to be appended to @s
|
||||
* @replace : replacement to be appended to @in_basename
|
||||
* @size : size of buffer
|
||||
* @len : size of buffer
|
||||
*
|
||||
* Appends basename of 'in_basename', to 'in_dir', along with 'replace'.
|
||||
* Appends basename of 'in_basename', to 's', along with 'replace'.
|
||||
* Basename of in_basename is the string after the last '/' or '\\',
|
||||
* i.e the filename without directories.
|
||||
*
|
||||
* If in_basename has no '/' or '\\', the whole 'in_basename' will be used.
|
||||
* 'size' is buffer size of 'in_dir'.
|
||||
* 'len' is buffer size of 's'.
|
||||
*
|
||||
* E.g..: in_dir = "/tmp/some_dir", in_basename = "/some_content/foo.c",
|
||||
* replace = ".asm" => in_dir = "/tmp/some_dir/foo.c.asm"
|
||||
* E.g..: s = "/tmp/some_dir", in_basename = "/some_content/foo.c",
|
||||
* replace = ".asm" => s = "/tmp/some_dir/foo.c.asm"
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls fill_pathname_slash()
|
||||
* - Calls path_basename()
|
||||
* - Calls strlcpy 2x
|
||||
**/
|
||||
void fill_pathname_dir(char *in_dir, const char *in_basename,
|
||||
const char *replace, size_t size);
|
||||
size_t fill_pathname_dir(char *s, const char *in_basename,
|
||||
const char *replace, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_base:
|
||||
* @out : output path
|
||||
* @s : output path
|
||||
* @in_path : input path
|
||||
* @size : size of output path
|
||||
* @len : size of output path
|
||||
*
|
||||
* Copies basename of @in_path into @out_path.
|
||||
* Copies basename of @in_path into @s.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls path_basename()
|
||||
* - Calls strlcpy
|
||||
*
|
||||
* @return length of the string copied into @s
|
||||
**/
|
||||
void fill_pathname_base(char *out_path, const char *in_path, size_t size);
|
||||
|
||||
void fill_pathname_base_noext(char *out_dir,
|
||||
const char *in_path, size_t size);
|
||||
|
||||
void fill_pathname_base_ext(char *out,
|
||||
const char *in_path, const char *ext,
|
||||
size_t size);
|
||||
size_t fill_pathname_base(char *s, const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_basedir:
|
||||
* @out_dir : output directory
|
||||
* @s : output directory
|
||||
* @in_path : input path
|
||||
* @size : size of output directory
|
||||
* @len : size of output directory
|
||||
*
|
||||
* Copies base directory of @in_path into @out_path.
|
||||
* Copies base directory of @in_path into @s.
|
||||
* If in_path is a path without any slashes (relative current directory),
|
||||
* @out_path will get path "./".
|
||||
* @s will get path "./".
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strlcpy
|
||||
* - Calls path_basedir()
|
||||
**/
|
||||
void fill_pathname_basedir(char *out_path, const char *in_path, size_t size);
|
||||
|
||||
void fill_pathname_basedir_noext(char *out_dir,
|
||||
const char *in_path, size_t size);
|
||||
size_t fill_pathname_basedir(char *s, const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_parent_dir_name:
|
||||
* @out_dir : output directory
|
||||
* @s : output string
|
||||
* @in_dir : input directory
|
||||
* @size : size of output directory
|
||||
* @len : size of @s
|
||||
*
|
||||
* Copies only the parent directory name of @in_dir into @out_dir.
|
||||
* Copies only the parent directory name of @in_dir into @s.
|
||||
* The two buffers must not overlap. Removes trailing '/'.
|
||||
* Returns true on success, false if a slash was not found in the path.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strdup
|
||||
* - Can call strlcpy
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
bool fill_pathname_parent_dir_name(char *out_dir,
|
||||
const char *in_dir, size_t size);
|
||||
size_t fill_pathname_parent_dir_name(char *s,
|
||||
const char *in_dir, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_parent_dir:
|
||||
* @out_dir : output directory
|
||||
* @s : output directory
|
||||
* @in_dir : input directory
|
||||
* @size : size of output directory
|
||||
* @len : size of output directory
|
||||
*
|
||||
* Copies parent directory of @in_dir into @out_dir.
|
||||
* Copies parent directory of @in_dir into @s.
|
||||
* Assumes @in_dir is a directory. Keeps trailing '/'.
|
||||
* If the path was already at the root directory, @out_dir will be an empty string.
|
||||
* If the path was already at the root directory, @s will be an empty string.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Can call strlcpy if (@s!= @in_dir)
|
||||
* - Calls strlen if (@s == @in_dir)
|
||||
* - Calls path_parent_dir()
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
void fill_pathname_parent_dir(char *out_dir,
|
||||
const char *in_dir, size_t size);
|
||||
size_t fill_pathname_parent_dir(char *s,
|
||||
const char *in_dir, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_resolve_relative:
|
||||
* @out_path : output path
|
||||
* @s : output path
|
||||
* @in_refpath : input reference path
|
||||
* @in_path : input path
|
||||
* @size : size of @out_path
|
||||
* @len : size of @s
|
||||
*
|
||||
* Joins basedir of @in_refpath together with @in_path.
|
||||
* If @in_path is an absolute path, out_path = in_path.
|
||||
* If @in_path is an absolute path, s = in_path.
|
||||
* E.g.: in_refpath = "/foo/bar/baz.a", in_path = "foobar.cg",
|
||||
* out_path = "/foo/bar/foobar.cg".
|
||||
* s = "/foo/bar/foobar.cg".
|
||||
**/
|
||||
void fill_pathname_resolve_relative(char *out_path, const char *in_refpath,
|
||||
const char *in_path, size_t size);
|
||||
void fill_pathname_resolve_relative(char *s, const char *in_refpath,
|
||||
const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_join:
|
||||
* @out_path : output path
|
||||
* @s : output path
|
||||
* @dir : directory
|
||||
* @path : path
|
||||
* @size : size of output path
|
||||
* @len : size of output path
|
||||
*
|
||||
* Joins a directory (@dir) and path (@path) together.
|
||||
* Makes sure not to get two consecutive slashes
|
||||
* Makes sure not to get two consecutive slashes
|
||||
* between directory and path.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strlcpy at least once
|
||||
* - calls fill_pathname_slash()
|
||||
*
|
||||
* Deprecated. Use fill_pathname_join_special() instead
|
||||
* if you can ensure @dir != @s
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
void fill_pathname_join(char *out_path, const char *dir,
|
||||
const char *path, size_t size);
|
||||
size_t fill_pathname_join(char *s, const char *dir,
|
||||
const char *path, size_t len);
|
||||
|
||||
void fill_pathname_join_special_ext(char *out_path,
|
||||
/**
|
||||
* fill_pathname_join_special:
|
||||
* @s : output path
|
||||
* @dir : directory. Cannot be identical to @s
|
||||
* @path : path
|
||||
* @len : size of output path
|
||||
*
|
||||
*
|
||||
* Specialized version of fill_pathname_join.
|
||||
* Unlike fill_pathname_join(),
|
||||
* @dir and @s CANNOT be identical.
|
||||
*
|
||||
* Joins a directory (@dir) and path (@path) together.
|
||||
* Makes sure not to get two consecutive slashes
|
||||
* between directory and path.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - calls strlcpy 2x
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t fill_pathname_join_special(char *s,
|
||||
const char *dir, const char *path, size_t len);
|
||||
|
||||
size_t fill_pathname_join_special_ext(char *s,
|
||||
const char *dir, const char *path,
|
||||
const char *last, const char *ext,
|
||||
size_t size);
|
||||
|
||||
void fill_pathname_join_concat_noext(char *out_path,
|
||||
const char *dir, const char *path,
|
||||
const char *concat,
|
||||
size_t size);
|
||||
|
||||
void fill_pathname_join_concat(char *out_path,
|
||||
const char *dir, const char *path,
|
||||
const char *concat,
|
||||
size_t size);
|
||||
|
||||
void fill_pathname_join_noext(char *out_path,
|
||||
const char *dir, const char *path, size_t size);
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* fill_pathname_join_delim:
|
||||
* @out_path : output path
|
||||
* @s : output path
|
||||
* @dir : directory
|
||||
* @path : path
|
||||
* @delim : delimiter
|
||||
* @size : size of output path
|
||||
* @len : size of output path
|
||||
*
|
||||
* Joins a directory (@dir) and path (@path) together
|
||||
* using the given delimiter (@delim).
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - can call strlen
|
||||
* - can call strlcpy
|
||||
* - can call strlcat
|
||||
**/
|
||||
void fill_pathname_join_delim(char *out_path, const char *dir,
|
||||
const char *path, const char delim, size_t size);
|
||||
size_t fill_pathname_join_delim(char *s, const char *dir,
|
||||
const char *path, const char delim, size_t len);
|
||||
|
||||
void fill_pathname_join_delim_concat(char *out_path, const char *dir,
|
||||
const char *path, const char delim, const char *concat,
|
||||
size_t size);
|
||||
size_t fill_pathname_expand_special(char *s,
|
||||
const char *in_path, size_t len);
|
||||
|
||||
size_t fill_pathname_abbreviate_special(char *s,
|
||||
const char *in_path, size_t len);
|
||||
|
||||
/**
|
||||
* fill_short_pathname_representation:
|
||||
* @out_rep : output representation
|
||||
* @in_path : input path
|
||||
* @size : size of output representation
|
||||
* fill_pathname_abbreviated_or_relative:
|
||||
*
|
||||
* Generates a short representation of path. It should only
|
||||
* be used for displaying the result; the output representation is not
|
||||
* binding in any meaningful way (for a normal path, this is the same as basename)
|
||||
* In case of more complex URLs, this should cut everything except for
|
||||
* the main image file.
|
||||
* Fills the supplied path with either the abbreviated path or
|
||||
* the relative path, which ever one has less depth / number of slashes
|
||||
*
|
||||
* E.g.: "/path/to/game.img" -> game.img
|
||||
* "/path/to/myarchive.7z#folder/to/game.img" -> game.img
|
||||
*/
|
||||
void fill_short_pathname_representation(char* out_rep,
|
||||
const char *in_path, size_t size);
|
||||
* If lengths of abbreviated and relative paths are the same,
|
||||
* the relative path will be used
|
||||
* @in_path can be an absolute, relative or abbreviated path
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t fill_pathname_abbreviated_or_relative(char *s,
|
||||
const char *in_refpath, const char *in_path, size_t len);
|
||||
|
||||
void fill_short_pathname_representation_noext(char* out_rep,
|
||||
const char *in_path, size_t size);
|
||||
/**
|
||||
* sanitize_path_part:
|
||||
*
|
||||
* @path_part : directory or filename
|
||||
* @len : length of path_part
|
||||
*
|
||||
* Takes single part of a path eg. single filename
|
||||
* or directory, and removes any special chars that are
|
||||
* unavailable.
|
||||
*
|
||||
* @returns newly allocated string that has been sanitized.
|
||||
* Caller is responsible for freeing the returned string.
|
||||
**/
|
||||
char *sanitize_path_part(const char *path_part, size_t len);
|
||||
|
||||
void fill_pathname_expand_special(char *out_path,
|
||||
const char *in_path, size_t size);
|
||||
/**
|
||||
* pathname_conform_slashes_to_os:
|
||||
*
|
||||
* @path : path
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Changes the slashes to the correct kind for the os
|
||||
* So forward slash on linux and backslash on Windows
|
||||
**/
|
||||
void pathname_conform_slashes_to_os(char *s);
|
||||
|
||||
void fill_pathname_abbreviate_special(char *out_path,
|
||||
const char *in_path, size_t size);
|
||||
/**
|
||||
* pathname_make_slashes_portable:
|
||||
* @path : path
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Change all slashes to forward so they are more
|
||||
* portable between Windows and Linux
|
||||
**/
|
||||
void pathname_make_slashes_portable(char *s);
|
||||
|
||||
/**
|
||||
* path_basedir:
|
||||
@@ -441,7 +588,7 @@ void fill_pathname_abbreviate_special(char *out_path,
|
||||
* Extracts base directory by mutating path.
|
||||
* Keeps trailing '/'.
|
||||
**/
|
||||
void path_basedir_wrapper(char *path);
|
||||
void path_basedir_wrapper(char *s);
|
||||
|
||||
/**
|
||||
* path_char_is_slash:
|
||||
@@ -449,12 +596,12 @@ void path_basedir_wrapper(char *path);
|
||||
*
|
||||
* Checks if character (@c) is a slash.
|
||||
*
|
||||
* Returns: true (1) if character is a slash, otherwise false (0).
|
||||
*/
|
||||
* @return true if character is a slash, otherwise false.
|
||||
**/
|
||||
#ifdef _WIN32
|
||||
#define path_char_is_slash(c) (((c) == '/') || ((c) == '\\'))
|
||||
#define PATH_CHAR_IS_SLASH(c) (((c) == '/') || ((c) == '\\'))
|
||||
#else
|
||||
#define path_char_is_slash(c) ((c) == '/')
|
||||
#define PATH_CHAR_IS_SLASH(c) ((c) == '/')
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -462,30 +609,34 @@ void path_basedir_wrapper(char *path);
|
||||
*
|
||||
* Gets the default slash separator.
|
||||
*
|
||||
* Returns: default slash separator.
|
||||
*/
|
||||
* @return default slash separator.
|
||||
**/
|
||||
#ifdef _WIN32
|
||||
#define path_default_slash() "\\"
|
||||
#define path_default_slash_c() '\\'
|
||||
#define PATH_DEFAULT_SLASH() "\\"
|
||||
#define PATH_DEFAULT_SLASH_C() '\\'
|
||||
#else
|
||||
#define path_default_slash() "/"
|
||||
#define path_default_slash_c() '/'
|
||||
#define PATH_DEFAULT_SLASH() "/"
|
||||
#define PATH_DEFAULT_SLASH_C() '/'
|
||||
#endif
|
||||
|
||||
/**
|
||||
* fill_pathname_slash:
|
||||
* @path : path
|
||||
* @size : size of path
|
||||
* @s : path
|
||||
* @len : size of path
|
||||
*
|
||||
* Assumes path is a directory. Appends a slash
|
||||
* if not already there.
|
||||
|
||||
* Hidden non-leaf function cost:
|
||||
* - can call strlcat once if it returns false
|
||||
* - calls strlen
|
||||
**/
|
||||
void fill_pathname_slash(char *path, size_t size);
|
||||
size_t fill_pathname_slash(char *s, size_t len);
|
||||
|
||||
#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
|
||||
void fill_pathname_application_path(char *buf, size_t size);
|
||||
void fill_pathname_application_dir(char *buf, size_t size);
|
||||
void fill_pathname_home_dir(char *buf, size_t size);
|
||||
size_t fill_pathname_application_path(char *s, size_t len);
|
||||
size_t fill_pathname_application_dir(char *s, size_t len);
|
||||
size_t fill_pathname_home_dir(char *s, size_t len);
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -494,7 +645,16 @@ void fill_pathname_home_dir(char *buf, size_t size);
|
||||
*
|
||||
* Create directory on filesystem.
|
||||
*
|
||||
* Returns: true (1) if directory could be created, otherwise false (0).
|
||||
* Recursive function.
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strdup
|
||||
* - Calls path_parent_dir()
|
||||
* - Calls strcmp
|
||||
* - Calls path_is_directory()
|
||||
* - Calls path_mkdir()
|
||||
*
|
||||
* @return true if directory could be created, otherwise false.
|
||||
**/
|
||||
bool path_mkdir(const char *dir);
|
||||
|
||||
@@ -504,10 +664,17 @@ bool path_mkdir(const char *dir);
|
||||
*
|
||||
* Checks if path is a directory.
|
||||
*
|
||||
* Returns: true (1) if path is a directory, otherwise false (0).
|
||||
* @return true if path is a directory, otherwise false.
|
||||
*/
|
||||
bool path_is_directory(const char *path);
|
||||
|
||||
/* Time format strings with AM-PM designation require special
|
||||
* handling due to platform dependence
|
||||
* @return Length of the string written to @s
|
||||
*/
|
||||
size_t strftime_am_pm(char *s, size_t len, const char* format,
|
||||
const void* timeptr);
|
||||
|
||||
bool path_is_character_special(const char *path);
|
||||
|
||||
int path_stat(const char *path);
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (file_stream.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_FILE_STREAM_H
|
||||
#define __LIBRETRO_SDK_FILE_STREAM_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <libretro.h>
|
||||
#include <retro_common_api.h>
|
||||
#include <retro_inline.h>
|
||||
#include <boolean.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
/** @defgroup file_stream File Streams
|
||||
*
|
||||
* All functions in this header will use the VFS interface set in \ref filestream_vfs_init if possible,
|
||||
* or else they will fall back to built-in equivalents.
|
||||
*
|
||||
* @note These functions are modeled after those in the C standard library
|
||||
* (and may even use them internally),
|
||||
* but identical behavior is not guaranteed.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The minimum version of the VFS interface required by the \c filestream functions.
|
||||
*/
|
||||
#define FILESTREAM_REQUIRED_VFS_VERSION 2
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Opaque handle to a file stream.
|
||||
* @warning This is not interchangeable with \c FILE* or \c retro_vfs_file_handle.
|
||||
*/
|
||||
typedef struct RFILE RFILE;
|
||||
|
||||
#define FILESTREAM_REQUIRED_VFS_VERSION 2
|
||||
|
||||
/**
|
||||
* Initializes the \c filestream functions to use the VFS interface provided by the frontend.
|
||||
* Optional; if not called, all \c filestream functions
|
||||
* will use libretro-common's built-in implementations.
|
||||
*
|
||||
* @param vfs_info The VFS interface returned by the frontend.
|
||||
* If \c vfs_info::iface (but \em not \c vfs_info itself) is \c NULL,
|
||||
* then libretro-common's built-in VFS implementation will be used.
|
||||
*/
|
||||
void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info);
|
||||
|
||||
/**
|
||||
* Returns the size of the given file, in bytes.
|
||||
*
|
||||
* @param stream The open file to query.
|
||||
* @return The size of \c stream in bytes,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_size_t
|
||||
*/
|
||||
int64_t filestream_get_size(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Sets the size of the given file,
|
||||
* truncating or extending it as necessary.
|
||||
*
|
||||
* @param stream The file to resize.
|
||||
* @param length The new size of \c stream, in bytes.
|
||||
* @return 0 if the resize was successful,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_truncate_t
|
||||
*/
|
||||
int64_t filestream_truncate(RFILE *stream, int64_t length);
|
||||
|
||||
/**
|
||||
* Opens a file for reading or writing.
|
||||
*
|
||||
* @param path Path to the file to open.
|
||||
* Should be in the format described in \ref GET_VFS_INTERFACE.
|
||||
* @param mode The mode to open the file in.
|
||||
* Should be one or more of the flags in \refitem RETRO_VFS_FILE_ACCESS OR'd together,
|
||||
* and \c RETRO_VFS_FILE_ACCESS_READ or \c RETRO_VFS_FILE_ACCESS_WRITE
|
||||
* (or both) must be included.
|
||||
* @param hints Optional hints to pass to the frontend.
|
||||
*
|
||||
* @return The opened file, or \c NULL if there was an error.
|
||||
* Must be cleaned up with \c filestream_close when no longer needed.
|
||||
* @see retro_vfs_open_t
|
||||
*/
|
||||
RFILE* filestream_open(const char *path, unsigned mode, unsigned hints);
|
||||
|
||||
/**
|
||||
* Sets the current position of the file stream.
|
||||
* Use this to read specific sections of a file.
|
||||
*
|
||||
* @param stream The file to set the stream position of.
|
||||
* @param offset The new stream position, in bytes.
|
||||
* @param seek_position The position to seek from.
|
||||
* Should be one of the values in \refitem RETRO_VFS_SEEK_POSITION.
|
||||
* @return The new stream position in bytes relative to the beginning,
|
||||
* or -1 if there was an error.
|
||||
* @see RETRO_VFS_SEEK_POSITION
|
||||
* @see retro_vfs_seek_t
|
||||
*/
|
||||
int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position);
|
||||
|
||||
/**
|
||||
* Reads data from the given file into a buffer.
|
||||
* If the read is successful,
|
||||
* the file's stream position will advance by the number of bytes read.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param data The buffer in which to store the read data.
|
||||
* @param len The size of \c data, in bytes.
|
||||
* @return The number of bytes read,
|
||||
* or -1 if there was an error.
|
||||
* May be less than \c len, but never more.
|
||||
* @see retro_vfs_read_t
|
||||
*/
|
||||
int64_t filestream_read(RFILE *stream, void *data, int64_t len);
|
||||
|
||||
/**
|
||||
* Writes data from a buffer to the given file.
|
||||
* If the write is successful,
|
||||
* the file's stream position will advance by the number of bytes written.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param data The buffer containing the data to write.
|
||||
* @param len The size of \c data, in bytes.
|
||||
* @return The number of bytes written,
|
||||
* or -1 if there was an error.
|
||||
* May be less than \c len, but never more.
|
||||
* @see retro_vfs_write_t
|
||||
*/
|
||||
int64_t filestream_write(RFILE *stream, const void *data, int64_t len);
|
||||
|
||||
/**
|
||||
* Returns the current position of the given file in bytes.
|
||||
*
|
||||
* @param stream The file to return the stream position for.
|
||||
* @return The current stream position in bytes relative to the beginning,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_tell_t
|
||||
*/
|
||||
int64_t filestream_tell(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Rewinds the given file to the beginning.
|
||||
* Equivalent to <tt>filestream_seek(stream, 0, RETRO_VFS_SEEK_POSITION_START)</tt>.
|
||||
|
||||
* @param stream The file to rewind.
|
||||
* May be \c NULL, in which case this function does nothing.
|
||||
*/
|
||||
void filestream_rewind(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Closes the given file.
|
||||
*
|
||||
* @param stream The file to close.
|
||||
* This should have been created with \c filestream_open.
|
||||
* Behavior is undefined if \c NULL.
|
||||
* @return 0 if the file was closed successfully,
|
||||
* or -1 if there was an error.
|
||||
* @post \c stream is no longer valid and should not be used,
|
||||
* even if this function fails.
|
||||
* @see retro_vfs_close_t
|
||||
*/
|
||||
int filestream_close(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Opens a file, reads its contents into a newly-allocated buffer,
|
||||
* then closes it.
|
||||
*
|
||||
* @param path[in] Path to the file to read.
|
||||
* Should be in the format described in \ref GET_VFS_INTERFACE.
|
||||
* @param buf[out] A pointer to the address of the newly-allocated buffer.
|
||||
* The buffer will contain the entirety of the file at \c path.
|
||||
* Will be allocated with \c malloc and must be freed with \c free.
|
||||
* @param len[out] Pointer to the size of the buffer in bytes.
|
||||
* May be \c NULL, in which case the length is not written.
|
||||
* Value is unspecified if this function fails.
|
||||
* @return 1 if the file was read successfully,
|
||||
* 0 if there was an error.
|
||||
* @see filestream_write_file
|
||||
*/
|
||||
int64_t filestream_read_file(const char *path, void **buf, int64_t *len);
|
||||
|
||||
/**
|
||||
* Reads a line of text from the given file,
|
||||
* up to a given length.
|
||||
*
|
||||
* Will read to the next newline or until the buffer is full,
|
||||
* whichever comes first.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param s The buffer to write the retrieved line to.
|
||||
* Will contain at most \c len - 1 characters
|
||||
* plus a null terminator.
|
||||
* The newline character (if any) will not be included.
|
||||
* The line delimiter must be Unix-style (\c '\n').
|
||||
* Carriage returns (\c '\r') will not be treated specially.
|
||||
* @param len The length of the buffer \c s, in bytes.
|
||||
* @return \s if successful, \c NULL if there was an error.
|
||||
*/
|
||||
char* filestream_gets(RFILE *stream, char *s, size_t len);
|
||||
|
||||
/**
|
||||
* Reads a single character from the given file.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @return The character read, or -1 upon reaching the end of the file.
|
||||
*/
|
||||
int filestream_getc(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Reads formatted text from the given file,
|
||||
* with arguments provided as a standard \c va_list.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param format The string to write, possibly including scanf-compatible format specifiers.
|
||||
* @param args Argument list with zero or more elements
|
||||
* whose values will be updated according to the semantics of \c format.
|
||||
* @return The number of arguments in \c args that were successfully assigned,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/fscanf
|
||||
* @see https://en.cppreference.com/w/c/variadic
|
||||
*/
|
||||
int filestream_vscanf(RFILE *stream, const char* format, va_list *args);
|
||||
|
||||
/**
|
||||
* Reads formatted text from the given file.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @param format The string to write, possibly including scanf-compatible format specifiers.
|
||||
* @param ... Zero or more arguments that will be updated according to the semantics of \c format.
|
||||
* @return The number of arguments in \c ... that were successfully assigned,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/fscanf
|
||||
*/
|
||||
int filestream_scanf(RFILE *stream, const char* format, ...);
|
||||
|
||||
/**
|
||||
* Determines if there's any more data left to read from this file.
|
||||
*
|
||||
* @param stream The file to check the position of.
|
||||
* @return -1 if this stream has reached the end of the file,
|
||||
* 0 if not.
|
||||
*/
|
||||
int filestream_eof(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Writes the entirety of a given buffer to a file at a given path.
|
||||
* Any file that already exists will be overwritten.
|
||||
*
|
||||
* @param path Path to the file that will be written to.
|
||||
* @param data The buffer to write to \c path.
|
||||
* @param size The size of \c data, in bytes.
|
||||
* @return \c true if the file was written successfully,
|
||||
* \c false if there was an error.
|
||||
*/
|
||||
bool filestream_write_file(const char *path, const void *data, int64_t size);
|
||||
|
||||
/**
|
||||
* Writes a single character to the given file.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param c The character to write.
|
||||
* @return The character written,
|
||||
* or -1 if there was an error.
|
||||
* Will return -1 if \c stream is \c NULL.
|
||||
*/
|
||||
int filestream_putc(RFILE *stream, int c);
|
||||
|
||||
/**
|
||||
* Writes formatted text to the given file,
|
||||
* with arguments provided as a standard \c va_list.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param format The string to write, possibly including printf-compatible format specifiers.
|
||||
* @param args A list of arguments to be formatted and inserted in the resulting string.
|
||||
* @return The number of characters written,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/vfprintf
|
||||
* @see https://en.cppreference.com/w/c/variadic
|
||||
*/
|
||||
int filestream_vprintf(RFILE *stream, const char* format, va_list args);
|
||||
|
||||
/**
|
||||
* Writes formatted text to the given file.
|
||||
*
|
||||
* @param stream The file to write to.
|
||||
* @param format The string to write, possibly including printf-compatible format specifiers.
|
||||
* @param ... Zero or more arguments to be formatted and inserted into the resulting string.
|
||||
* @return The number of characters written,
|
||||
* or -1 if there was an error.
|
||||
* @see https://en.cppreference.com/w/c/io/printf
|
||||
*/
|
||||
int filestream_printf(RFILE *stream, const char* format, ...);
|
||||
|
||||
/**
|
||||
* Checks if there was an error in using the given file stream.
|
||||
*
|
||||
* @param stream The file stream to check for errors.
|
||||
* @return \c true if there was an error in using this stream,
|
||||
* \c false if not or if \c stream is \c NULL.
|
||||
*/
|
||||
int filestream_error(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Flushes pending writes to the operating system's file layer.
|
||||
* There is no guarantee that pending writes will be written to disk immediately.
|
||||
*
|
||||
* @param stream The file to flush.
|
||||
* @return 0 if the flush was successful,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_flush_t
|
||||
*/
|
||||
int filestream_flush(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Deletes the file at the given path.
|
||||
* If the file is open by any process,
|
||||
* the behavior is platform-specific.
|
||||
*
|
||||
* @note This function might or might not delete directories recursively,
|
||||
* depending on the platform and the underlying VFS implementation.
|
||||
*
|
||||
* @param path The file to delete.
|
||||
* @return 0 if the file was deleted successfully,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_remove_t
|
||||
*/
|
||||
int filestream_delete(const char *path);
|
||||
|
||||
/**
|
||||
* Moves a file to a new location, with a new name.
|
||||
*
|
||||
* @param old_path Path to the file to rename.
|
||||
* @param new_path The target name and location of the file.
|
||||
* @return 0 if the file was renamed successfully,
|
||||
* or -1 if there was an error.
|
||||
* @see retro_vfs_rename_t
|
||||
*/
|
||||
int filestream_rename(const char *old_path, const char *new_path);
|
||||
|
||||
/**
|
||||
* Copies a file to a new location.
|
||||
*
|
||||
* @param src_path Path to the file to rename.
|
||||
* @param dst_path The target name and location of the file.
|
||||
* @return 0 if the file was copied successfully,
|
||||
* or -1 if there was an error.
|
||||
*/
|
||||
int filestream_copy(const char *src_path, const char *dst_path);
|
||||
|
||||
/**
|
||||
* Compares and verifies files.
|
||||
*
|
||||
* @param src_path Path to the file.
|
||||
* @param dst_path Path to the other file.
|
||||
* @return 0 if the files are equal,
|
||||
* or -1 if there was an error.
|
||||
*/
|
||||
int filestream_cmp(const char *src_path, const char *dst_path);
|
||||
|
||||
/**
|
||||
* Get the path that was used to open a file.
|
||||
*
|
||||
* @param stream The file to get the path of.
|
||||
* @return The path that was used to open \c stream,
|
||||
* or \c NULL if there was an error.
|
||||
* The string is owned by \c stream and must not be modified or freed by the caller.
|
||||
*/
|
||||
const char* filestream_get_path(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Determines if a file exists at the given path.
|
||||
*
|
||||
* @param path The path to check for existence.
|
||||
* @return \c true if a file exists at \c path,
|
||||
* \c false if not or if \c path is \c NULL or empty.
|
||||
*/
|
||||
bool filestream_exists(const char *path);
|
||||
|
||||
/**
|
||||
* Reads a line from the given file into a newly-allocated buffer.
|
||||
*
|
||||
* @param stream The file to read from.
|
||||
* @return Pointer to the line read from \c stream,
|
||||
* or \c NULL if there was an error.
|
||||
* Must be freed with \c free when no longer needed.
|
||||
*/
|
||||
char* filestream_getline(RFILE *stream);
|
||||
|
||||
/**
|
||||
* Returns the open file handle
|
||||
* that was originally returned by the VFS interface.
|
||||
*
|
||||
* @param stream File handle returned by \c filestream_open.
|
||||
* @return The file handle returned by the underlying VFS implementation.
|
||||
*/
|
||||
libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010-2019 The RetroArch team
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (stdstring.h).
|
||||
@@ -35,6 +35,28 @@
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
#define STRLEN_CONST(x) ((sizeof((x))-1))
|
||||
|
||||
#define string_is_not_equal(a, b) !string_is_equal((a), (b))
|
||||
|
||||
#define TOLOWER(c) ((c) | (lr_char_props[(unsigned char)(c)] & 0x20))
|
||||
#define TOUPPER(c) ((c) & ~(lr_char_props[(unsigned char)(c)] & 0x20))
|
||||
|
||||
/* C standard says \f \v are space, but this one disagrees */
|
||||
#define ISSPACE(c) (lr_char_props[(unsigned char)(c)] & 0x80)
|
||||
|
||||
#define ISDIGIT(c) (lr_char_props[(unsigned char)(c)] & 0x40)
|
||||
#define ISALPHA(c) (lr_char_props[(unsigned char)(c)] & 0x20)
|
||||
#define ISLOWER(c) (lr_char_props[(unsigned char)(c)] & 0x04)
|
||||
#define ISUPPER(c) (lr_char_props[(unsigned char)(c)] & 0x02)
|
||||
#define ISALNUM(c) (lr_char_props[(unsigned char)(c)] & 0x60)
|
||||
#define ISUALPHA(c) (lr_char_props[(unsigned char)(c)] & 0x28)
|
||||
#define ISUALNUM(c) (lr_char_props[(unsigned char)(c)] & 0x68)
|
||||
#define IS_XDIGIT(c) (lr_char_props[(unsigned char)(c)] & 0x01)
|
||||
|
||||
/* Deprecated alias, all callers should use string_is_equal_case_insensitive instead */
|
||||
#define string_is_equal_noncase string_is_equal_case_insensitive
|
||||
|
||||
static INLINE bool string_is_empty(const char *data)
|
||||
{
|
||||
return !data || (*data == '\0');
|
||||
@@ -45,31 +67,49 @@ static INLINE bool string_is_equal(const char *a, const char *b)
|
||||
return (a && b) ? !strcmp(a, b) : false;
|
||||
}
|
||||
|
||||
#define STRLEN_CONST(x) ((sizeof((x))-1))
|
||||
|
||||
#define string_is_not_equal(a, b) !string_is_equal((a), (b))
|
||||
|
||||
#define string_add_pair_open(s, size) strlcat((s), " (", (size))
|
||||
#define string_add_pair_close(s, size) strlcat((s), ")", (size))
|
||||
#define string_add_bracket_open(s, size) strlcat((s), "{", (size))
|
||||
#define string_add_bracket_close(s, size) strlcat((s), "}", (size))
|
||||
#define string_add_single_quote(s, size) strlcat((s), "'", (size))
|
||||
#define string_add_quote(s, size) strlcat((s), "\"", (size))
|
||||
#define string_add_colon(s, size) strlcat((s), ":", (size))
|
||||
#define string_add_glob_open(s, size) strlcat((s), "glob('*", (size))
|
||||
#define string_add_glob_close(s, size) strlcat((s), "*')", (size))
|
||||
|
||||
#define string_is_not_equal_fast(a, b, size) (memcmp(a, b, size) != 0)
|
||||
#define string_is_equal_fast(a, b, size) (memcmp(a, b, size) == 0)
|
||||
|
||||
static INLINE void string_add_between_pairs(char *s, const char *str,
|
||||
size_t size)
|
||||
static INLINE bool string_starts_with_size(const char *str, const char *prefix,
|
||||
size_t len)
|
||||
{
|
||||
string_add_pair_open(s, size);
|
||||
strlcat(s, str, size);
|
||||
string_add_pair_close(s, size);
|
||||
return (str && prefix) ? !strncmp(prefix, str, len) : false;
|
||||
}
|
||||
|
||||
static INLINE bool string_starts_with(const char *str, const char *prefix)
|
||||
{
|
||||
return (str && prefix) ? !strncmp(prefix, str, strlen(prefix)) : false;
|
||||
}
|
||||
|
||||
static INLINE bool string_ends_with_size(const char *s, const char *suffix,
|
||||
size_t len, size_t suffix_len)
|
||||
{
|
||||
return (len < suffix_len) ? false :
|
||||
!memcmp(suffix, s + (len - suffix_len), suffix_len);
|
||||
}
|
||||
|
||||
static INLINE bool string_ends_with(const char *s, const char *suffix)
|
||||
{
|
||||
return s && suffix && string_ends_with_size(s, suffix, strlen(s), strlen(suffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* strlen_size:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* @return the length of 'str' (c.f. strlen()), but only
|
||||
* checks the first 'size' characters
|
||||
* - If 'str' is NULL, returns 0
|
||||
* - If 'str' is not NULL and no '\0' character is found
|
||||
* in the first 'size' characters, returns 'size'
|
||||
**/
|
||||
static INLINE size_t strlen_size(const char *str, size_t len)
|
||||
{
|
||||
size_t i = 0;
|
||||
if (str)
|
||||
while (i < len && str[i]) i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
static INLINE bool string_is_equal_case_insensitive(const char *a,
|
||||
const char *b)
|
||||
{
|
||||
@@ -89,22 +129,23 @@ static INLINE bool string_is_equal_case_insensitive(const char *a,
|
||||
return (result == 0);
|
||||
}
|
||||
|
||||
static INLINE bool string_is_equal_noncase(const char *a, const char *b)
|
||||
static INLINE bool string_starts_with_case_insensitive(const char *str,
|
||||
const char *prefix)
|
||||
{
|
||||
int result = 0;
|
||||
const unsigned char *p1 = (const unsigned char*)a;
|
||||
const unsigned char *p2 = (const unsigned char*)b;
|
||||
const unsigned char *p1 = (const unsigned char*)str;
|
||||
const unsigned char *p2 = (const unsigned char*)prefix;
|
||||
|
||||
if (!a || !b)
|
||||
if (!str || !prefix)
|
||||
return false;
|
||||
if (p1 == p2)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
while ((result = tolower (*p1) - tolower (*p2++)) == 0)
|
||||
if (*p1++ == '\0')
|
||||
while ((result = tolower (*p1++) - tolower (*p2)) == 0)
|
||||
if (*p2++ == '\0')
|
||||
break;
|
||||
|
||||
return (result == 0);
|
||||
return (result == 0 || *p2 == '\0');
|
||||
}
|
||||
|
||||
char *string_to_upper(char *s);
|
||||
@@ -113,21 +154,225 @@ char *string_to_lower(char *s);
|
||||
|
||||
char *string_ucwords(char *s);
|
||||
|
||||
char *string_replace_substring(const char *in, const char *pattern,
|
||||
const char *by);
|
||||
char *string_replace_substring(
|
||||
const char *in, size_t in_len,
|
||||
const char *pattern, size_t pattern_len,
|
||||
const char *replacement, size_t replacement_len);
|
||||
|
||||
/* Remove leading whitespaces */
|
||||
/**
|
||||
* string_trim_whitespace_left:
|
||||
*
|
||||
* Remove leading whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace_left(char *const s);
|
||||
|
||||
/* Remove trailing whitespaces */
|
||||
/**
|
||||
* string_trim_whitespace_right:
|
||||
*
|
||||
* Remove trailing whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace_right(char *const s);
|
||||
|
||||
/* Remove leading and trailing whitespaces */
|
||||
/**
|
||||
* string_trim_whitespace:
|
||||
*
|
||||
* Remove leading and trailing whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace(char *const s);
|
||||
|
||||
/* max_lines == 0 means no limit */
|
||||
char *word_wrap(char *buffer, const char *string,
|
||||
int line_width, bool unicode, unsigned max_lines);
|
||||
/**
|
||||
* word_wrap:
|
||||
* @dst : pointer to destination buffer.
|
||||
* @dst_size : size of destination buffer.
|
||||
* @src : pointer to input string.
|
||||
* @src_len : length of @src
|
||||
* @line_width : max number of characters per line.
|
||||
* @wideglyph_width : not used, but is necessary to keep
|
||||
* compatibility with word_wrap_wideglyph().
|
||||
* @max_lines : max lines of destination string.
|
||||
* 0 means no limit.
|
||||
*
|
||||
* Wraps string specified by 'src' to destination buffer
|
||||
* specified by 'dst' and 'dst_size'.
|
||||
* This function assumes that all glyphs in the string
|
||||
* have an on-screen pixel width similar to that of
|
||||
* regular Latin characters - i.e. it will not wrap
|
||||
* correctly any text containing so-called 'wide' Unicode
|
||||
* characters (e.g. CJK languages, emojis, etc.).
|
||||
**/
|
||||
size_t word_wrap(char *dst, size_t dst_size, const char *src, size_t src_len,
|
||||
int line_width, int wideglyph_width, unsigned max_lines);
|
||||
|
||||
/**
|
||||
* word_wrap_wideglyph:
|
||||
* @dst : pointer to destination buffer.
|
||||
* @dst_size : size of destination buffer.
|
||||
* @src : pointer to input string.
|
||||
* @src_len : length of @src
|
||||
* @line_width : max number of characters per line.
|
||||
* @wideglyph_width : effective width of 'wide' Unicode glyphs.
|
||||
* the value here is normalised relative to the
|
||||
* typical on-screen pixel width of a regular
|
||||
* Latin character:
|
||||
* - a regular Latin character is defined to
|
||||
* have an effective width of 100
|
||||
* - wideglyph_width = 100 * (wide_character_pixel_width / latin_character_pixel_width)
|
||||
* - e.g. if 'wide' Unicode characters in 'src'
|
||||
* have an on-screen pixel width twice that of
|
||||
* regular Latin characters, wideglyph_width
|
||||
* would be 200
|
||||
* @max_lines : max lines of destination string.
|
||||
* 0 means no limit.
|
||||
*
|
||||
* Wraps string specified by @src to destination buffer
|
||||
* specified by @dst and @dst_size.
|
||||
* This function assumes that all glyphs in the string
|
||||
* are:
|
||||
* - EITHER 'non-wide' Unicode glyphs, with an on-screen
|
||||
* pixel width similar to that of regular Latin characters
|
||||
* - OR 'wide' Unicode glyphs (e.g. CJK languages, emojis, etc.)
|
||||
* with an on-screen pixel width defined by @wideglyph_width
|
||||
* Note that wrapping may occur in inappropriate locations
|
||||
* if @src string contains 'wide' Unicode characters whose
|
||||
* on-screen pixel width deviates greatly from the set
|
||||
* @wideglyph_width value.
|
||||
**/
|
||||
size_t word_wrap_wideglyph(
|
||||
char *dst, size_t dst_size,
|
||||
const char *src, size_t src_len,
|
||||
int line_width, int wideglyph_width,
|
||||
unsigned max_lines);
|
||||
|
||||
/**
|
||||
* string_tokenize:
|
||||
*
|
||||
* Splits string into tokens separated by @delim
|
||||
* > Returned token string must be free()'d
|
||||
* > Returns NULL if token is not found
|
||||
* > After each call, @str is set to the position after the
|
||||
* last found token
|
||||
* > Tokens *include* empty strings
|
||||
* Usage example:
|
||||
* char *str = "1,2,3,4,5,6,7,,,10,";
|
||||
* char **str_ptr = &str;
|
||||
* char *token = NULL;
|
||||
* while ((token = string_tokenize(str_ptr, ",")))
|
||||
* {
|
||||
* printf("%s\n", token);
|
||||
* free(token);
|
||||
* token = NULL;
|
||||
* }
|
||||
**/
|
||||
char* string_tokenize(char **str, const char *delim);
|
||||
|
||||
/**
|
||||
* string_remove_all_chars:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Removes every instance of character @c from @str
|
||||
**/
|
||||
void string_remove_all_chars(char *str, char c);
|
||||
|
||||
/**
|
||||
* string_replace_all_chars:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
* @find : character to find
|
||||
* @replace : character to replace @find with
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strchr (in a loop)
|
||||
*
|
||||
* Replaces every instance of character @find in @str
|
||||
* with character @replace
|
||||
**/
|
||||
void string_replace_all_chars(char *str, char find, char replace);
|
||||
|
||||
/**
|
||||
* string_to_unsigned:
|
||||
* @str : input string
|
||||
*
|
||||
* Converts string to unsigned integer.
|
||||
*
|
||||
* @return 0 if string is invalid, otherwise > 0
|
||||
**/
|
||||
unsigned string_to_unsigned(const char *str);
|
||||
|
||||
/**
|
||||
* string_hex_to_unsigned:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
*
|
||||
* Converts hexadecimal string to unsigned integer.
|
||||
* Handles optional leading '0x'.
|
||||
*
|
||||
* @return 0 if string is invalid, otherwise > 0
|
||||
**/
|
||||
unsigned string_hex_to_unsigned(const char *str);
|
||||
|
||||
/**
|
||||
* string_count_occurrences_single_character:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Get the total number of occurrences of character @c in @str.
|
||||
*
|
||||
* @return Total number of occurrences of character @c
|
||||
*/
|
||||
int string_count_occurrences_single_character(const char *str, char c);
|
||||
|
||||
/**
|
||||
* string_replace_whitespace_with_single_character:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Replaces all spaces with given character @c.
|
||||
**/
|
||||
void string_replace_whitespace_with_single_character(char *str, char c);
|
||||
|
||||
/**
|
||||
* string_replace_multi_space_with_single_space:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Replaces multiple spaces with a single space in a string.
|
||||
**/
|
||||
void string_replace_multi_space_with_single_space(char *str);
|
||||
|
||||
/**
|
||||
* string_remove_all_whitespace:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Remove all spaces from the given string.
|
||||
**/
|
||||
void string_remove_all_whitespace(char *str_trimmed, const char *str);
|
||||
|
||||
/* Retrieve the last occurance of the given character in a string. */
|
||||
int string_index_last_occurance(const char *str, char c);
|
||||
|
||||
/**
|
||||
* string_find_index_substring_string:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
* @substr : substring to find in @str
|
||||
*
|
||||
* Hidden non-leaf function cost:
|
||||
* - Calls strstr
|
||||
*
|
||||
* Find the position of substring @substr in string @str.
|
||||
**/
|
||||
int string_find_index_substring_string(const char *str, const char *substr);
|
||||
|
||||
/**
|
||||
* string_copy_only_ascii:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Strips non-ASCII characters from a string.
|
||||
**/
|
||||
void string_copy_only_ascii(char *str_stripped, const char *str);
|
||||
|
||||
extern const unsigned char lr_char_props[256];
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rtime.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_RTIME_H__
|
||||
#define __LIBRETRO_SDK_RTIME_H__
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/* TODO/FIXME: Move all generic time handling functions
|
||||
* to this file */
|
||||
|
||||
/**
|
||||
* Must be called before using \c rtime_localtime().
|
||||
* May be called multiple times without ill effects,
|
||||
* but must only be called from the main thread.
|
||||
*/
|
||||
void rtime_init(void);
|
||||
|
||||
/**
|
||||
* Must be called upon program or core termination.
|
||||
* May be called multiple times without ill effects,
|
||||
* but must only be called from the main thread.
|
||||
*/
|
||||
void rtime_deinit(void);
|
||||
|
||||
/**
|
||||
* Thread-safe wrapper around standard \c localtime(),
|
||||
* which by itself is not guaranteed to be thread-safe.
|
||||
* @param timep Pointer to a time_t object to convert.
|
||||
* @param result Pointer to a tm object to store the result in.
|
||||
* @return \c result.
|
||||
* @see https://en.cppreference.com/w/c/chrono/localtime
|
||||
*/
|
||||
struct tm *rtime_localtime(const time_t *timep, struct tm *result);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,700 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (file_stream.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <compat/msvc.h>
|
||||
#endif
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <file/file_path.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <streams/file_stream.h>
|
||||
#define VFS_FRONTEND
|
||||
#include <vfs/vfs_implementation.h>
|
||||
|
||||
#define VFS_ERROR_RETURN_VALUE -1
|
||||
|
||||
struct RFILE
|
||||
{
|
||||
struct retro_vfs_file_handle *hfile;
|
||||
bool err_flag;
|
||||
};
|
||||
|
||||
static retro_vfs_get_path_t filestream_get_path_cb = NULL;
|
||||
static retro_vfs_open_t filestream_open_cb = NULL;
|
||||
static retro_vfs_close_t filestream_close_cb = NULL;
|
||||
static retro_vfs_size_t filestream_size_cb = NULL;
|
||||
static retro_vfs_truncate_t filestream_truncate_cb = NULL;
|
||||
static retro_vfs_tell_t filestream_tell_cb = NULL;
|
||||
static retro_vfs_seek_t filestream_seek_cb = NULL;
|
||||
static retro_vfs_read_t filestream_read_cb = NULL;
|
||||
static retro_vfs_write_t filestream_write_cb = NULL;
|
||||
static retro_vfs_flush_t filestream_flush_cb = NULL;
|
||||
static retro_vfs_remove_t filestream_remove_cb = NULL;
|
||||
static retro_vfs_rename_t filestream_rename_cb = NULL;
|
||||
|
||||
/* VFS Initialization */
|
||||
|
||||
void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
{
|
||||
const struct retro_vfs_interface *
|
||||
vfs_iface = vfs_info->iface;
|
||||
|
||||
filestream_get_path_cb = NULL;
|
||||
filestream_open_cb = NULL;
|
||||
filestream_close_cb = NULL;
|
||||
filestream_tell_cb = NULL;
|
||||
filestream_size_cb = NULL;
|
||||
filestream_truncate_cb = NULL;
|
||||
filestream_seek_cb = NULL;
|
||||
filestream_read_cb = NULL;
|
||||
filestream_write_cb = NULL;
|
||||
filestream_flush_cb = NULL;
|
||||
filestream_remove_cb = NULL;
|
||||
filestream_rename_cb = NULL;
|
||||
|
||||
if (
|
||||
(vfs_info->required_interface_version <
|
||||
FILESTREAM_REQUIRED_VFS_VERSION)
|
||||
|| !vfs_iface)
|
||||
return;
|
||||
|
||||
filestream_get_path_cb = vfs_iface->get_path;
|
||||
filestream_open_cb = vfs_iface->open;
|
||||
filestream_close_cb = vfs_iface->close;
|
||||
filestream_size_cb = vfs_iface->size;
|
||||
filestream_truncate_cb = vfs_iface->truncate;
|
||||
filestream_tell_cb = vfs_iface->tell;
|
||||
filestream_seek_cb = vfs_iface->seek;
|
||||
filestream_read_cb = vfs_iface->read;
|
||||
filestream_write_cb = vfs_iface->write;
|
||||
filestream_flush_cb = vfs_iface->flush;
|
||||
filestream_remove_cb = vfs_iface->remove;
|
||||
filestream_rename_cb = vfs_iface->rename;
|
||||
}
|
||||
|
||||
/* Callback wrappers */
|
||||
bool filestream_exists(const char *path)
|
||||
{
|
||||
RFILE *dummy = NULL;
|
||||
|
||||
if (!path || !*path)
|
||||
return false;
|
||||
if (!(dummy = filestream_open(
|
||||
path,
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
|
||||
return false;
|
||||
|
||||
if (filestream_close(dummy) != 0)
|
||||
free(dummy);
|
||||
|
||||
dummy = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t filestream_get_size(RFILE *stream)
|
||||
{
|
||||
int64_t output;
|
||||
|
||||
if (filestream_size_cb)
|
||||
output = filestream_size_cb(stream->hfile);
|
||||
else
|
||||
output = retro_vfs_file_size_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile);
|
||||
|
||||
if (output == VFS_ERROR_RETURN_VALUE)
|
||||
stream->err_flag = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int64_t filestream_truncate(RFILE *stream, int64_t length)
|
||||
{
|
||||
int64_t output;
|
||||
|
||||
if (filestream_truncate_cb)
|
||||
output = filestream_truncate_cb(stream->hfile, length);
|
||||
else
|
||||
output = retro_vfs_file_truncate_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile, length);
|
||||
|
||||
if (output == VFS_ERROR_RETURN_VALUE)
|
||||
stream->err_flag = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
RFILE* filestream_open(const char *path, unsigned mode, unsigned hints)
|
||||
{
|
||||
struct retro_vfs_file_handle *fp = NULL;
|
||||
RFILE* output = (RFILE*)malloc(sizeof(RFILE));
|
||||
|
||||
if (!output)
|
||||
return NULL;
|
||||
|
||||
if (filestream_open_cb)
|
||||
fp = (struct retro_vfs_file_handle*)
|
||||
filestream_open_cb(path, mode, hints);
|
||||
else
|
||||
fp = (struct retro_vfs_file_handle*)
|
||||
retro_vfs_file_open_impl(path, mode, hints);
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
free(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
output->err_flag = false;
|
||||
output->hfile = fp;
|
||||
return output;
|
||||
}
|
||||
|
||||
char* filestream_gets(RFILE *stream, char *s, size_t len)
|
||||
{
|
||||
int c = 0;
|
||||
char *p = s;
|
||||
if (!stream)
|
||||
return NULL;
|
||||
|
||||
/* get max bytes or up to a newline */
|
||||
|
||||
for (len--; len > 0; len--)
|
||||
{
|
||||
if ((c = filestream_getc(stream)) == EOF)
|
||||
break;
|
||||
*p++ = c;
|
||||
if (c == '\n')
|
||||
break;
|
||||
}
|
||||
*p = 0;
|
||||
|
||||
if (p == s && c == EOF)
|
||||
return NULL;
|
||||
return (s);
|
||||
}
|
||||
|
||||
int filestream_getc(RFILE *stream)
|
||||
{
|
||||
char c = 0;
|
||||
if (stream && filestream_read(stream, &c, 1) == 1)
|
||||
return (int)(unsigned char)c;
|
||||
return EOF;
|
||||
}
|
||||
|
||||
int filestream_vscanf(RFILE *stream, const char* format, va_list *args)
|
||||
{
|
||||
char buf[4096];
|
||||
char subfmt[64];
|
||||
va_list args_copy;
|
||||
const char *bufiter = buf;
|
||||
int ret = 0;
|
||||
int64_t startpos = filestream_tell(stream);
|
||||
int64_t maxlen = filestream_read(stream, buf, sizeof(buf)-1);
|
||||
|
||||
if (maxlen <= 0)
|
||||
return EOF;
|
||||
|
||||
buf[maxlen] = '\0';
|
||||
|
||||
/* Have to copy the input va_list here
|
||||
* > Calling va_arg() on 'args' directly would
|
||||
* cause the va_list to have an indeterminate value
|
||||
* in the function calling filestream_vscanf(),
|
||||
* leading to unexpected behaviour */
|
||||
#ifdef __va_copy
|
||||
__va_copy(args_copy, *args);
|
||||
#else
|
||||
va_copy(args_copy, *args);
|
||||
#endif
|
||||
|
||||
while (*format)
|
||||
{
|
||||
if (*format == '%')
|
||||
{
|
||||
int sublen = 0;
|
||||
char* subfmtiter = subfmt;
|
||||
bool asterisk = false;
|
||||
|
||||
*subfmtiter++ = *format++; /* '%' */
|
||||
|
||||
/* %[*][width][length]specifier */
|
||||
|
||||
if (*format == '*')
|
||||
{
|
||||
asterisk = true;
|
||||
*subfmtiter++ = *format++;
|
||||
}
|
||||
|
||||
while (ISDIGIT((unsigned char)*format))
|
||||
*subfmtiter++ = *format++; /* width */
|
||||
|
||||
/* length */
|
||||
if (*format == 'h' || *format == 'l')
|
||||
{
|
||||
if (format[1] == format[0])
|
||||
*subfmtiter++ = *format++;
|
||||
*subfmtiter++ = *format++;
|
||||
}
|
||||
else if (
|
||||
*format == 'j' ||
|
||||
*format == 'z' ||
|
||||
*format == 't' ||
|
||||
*format == 'L')
|
||||
{
|
||||
*subfmtiter++ = *format++;
|
||||
}
|
||||
|
||||
/* specifier - always a single character (except ]) */
|
||||
if (*format == '[')
|
||||
{
|
||||
while (*format != ']')
|
||||
*subfmtiter++ = *format++;
|
||||
*subfmtiter++ = *format++;
|
||||
}
|
||||
else
|
||||
*subfmtiter++ = *format++;
|
||||
|
||||
*subfmtiter++ = '%';
|
||||
*subfmtiter++ = 'n';
|
||||
*subfmtiter++ = '\0';
|
||||
|
||||
if (sizeof(void*) != sizeof(long*))
|
||||
abort(); /* all pointers must have the same size */
|
||||
|
||||
if (asterisk)
|
||||
{
|
||||
int v = sscanf(bufiter, subfmt, &sublen);
|
||||
if (v == EOF)
|
||||
return EOF;
|
||||
if (v != 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
int v = sscanf(bufiter, subfmt, va_arg(args_copy, void*), &sublen);
|
||||
if (v == EOF)
|
||||
return EOF;
|
||||
if (v != 1)
|
||||
break;
|
||||
}
|
||||
|
||||
ret++;
|
||||
bufiter += sublen;
|
||||
}
|
||||
else if (isspace((unsigned char)*format))
|
||||
{
|
||||
while (isspace((unsigned char)*bufiter))
|
||||
bufiter++;
|
||||
format++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*bufiter != *format)
|
||||
break;
|
||||
bufiter++;
|
||||
format++;
|
||||
}
|
||||
}
|
||||
|
||||
va_end(args_copy);
|
||||
filestream_seek(stream, startpos + (bufiter - buf),
|
||||
RETRO_VFS_SEEK_POSITION_START);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int filestream_scanf(RFILE *stream, const char* format, ...)
|
||||
{
|
||||
int ret;
|
||||
va_list vl;
|
||||
va_start(vl, format);
|
||||
ret = filestream_vscanf(stream, format, &vl);
|
||||
va_end(vl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position)
|
||||
{
|
||||
int64_t output;
|
||||
|
||||
if (filestream_seek_cb)
|
||||
output = filestream_seek_cb(stream->hfile, offset, seek_position);
|
||||
else
|
||||
output = retro_vfs_file_seek_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile,
|
||||
offset, seek_position);
|
||||
|
||||
if (output == VFS_ERROR_RETURN_VALUE)
|
||||
stream->err_flag = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int filestream_eof(RFILE *stream)
|
||||
{
|
||||
return filestream_tell(stream) == filestream_get_size(stream) ? EOF : 0;
|
||||
}
|
||||
|
||||
int64_t filestream_tell(RFILE *stream)
|
||||
{
|
||||
int64_t output;
|
||||
|
||||
if (filestream_size_cb)
|
||||
output = filestream_tell_cb(stream->hfile);
|
||||
else
|
||||
output = retro_vfs_file_tell_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile);
|
||||
|
||||
if (output == VFS_ERROR_RETURN_VALUE)
|
||||
stream->err_flag = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void filestream_rewind(RFILE *stream)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
filestream_seek(stream, 0L, RETRO_VFS_SEEK_POSITION_START);
|
||||
stream->err_flag = false;
|
||||
}
|
||||
|
||||
int64_t filestream_read(RFILE *stream, void *s, int64_t len)
|
||||
{
|
||||
int64_t output;
|
||||
|
||||
if (filestream_read_cb)
|
||||
output = filestream_read_cb(stream->hfile, s, len);
|
||||
else
|
||||
output = retro_vfs_file_read_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile, s, len);
|
||||
|
||||
if (output == VFS_ERROR_RETURN_VALUE)
|
||||
stream->err_flag = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int filestream_flush(RFILE *stream)
|
||||
{
|
||||
int output;
|
||||
|
||||
if (filestream_flush_cb)
|
||||
output = filestream_flush_cb(stream->hfile);
|
||||
else
|
||||
output = retro_vfs_file_flush_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile);
|
||||
|
||||
if (output == VFS_ERROR_RETURN_VALUE)
|
||||
stream->err_flag = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int filestream_delete(const char *path)
|
||||
{
|
||||
if (filestream_remove_cb)
|
||||
return filestream_remove_cb(path);
|
||||
|
||||
return retro_vfs_file_remove_impl(path);
|
||||
}
|
||||
|
||||
int filestream_rename(const char *old_path, const char *new_path)
|
||||
{
|
||||
if (filestream_rename_cb)
|
||||
return filestream_rename_cb(old_path, new_path);
|
||||
|
||||
return retro_vfs_file_rename_impl(old_path, new_path);
|
||||
}
|
||||
|
||||
int filestream_copy(const char *src, const char *dst)
|
||||
{
|
||||
char buf[256] = {0};
|
||||
int64_t n = 0;
|
||||
int ret = 0;
|
||||
char path_dst[PATH_MAX_LENGTH] = {0};
|
||||
|
||||
RFILE *fp_src = filestream_open(src, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
RFILE *fp_dst = filestream_open(dst, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!fp_src || !fp_dst)
|
||||
ret = -1;
|
||||
|
||||
if (ret < 0)
|
||||
goto close;
|
||||
|
||||
snprintf(path_dst, sizeof(path_dst), "%s", dst);
|
||||
path_basedir(path_dst);
|
||||
|
||||
if (!path_is_directory(path_dst))
|
||||
path_mkdir(path_dst);
|
||||
|
||||
while ((n = filestream_read(fp_src, buf, sizeof(buf))) > 0 && ret == 0)
|
||||
{
|
||||
if (filestream_write(fp_dst, buf, n) != n)
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
close:
|
||||
if (fp_src)
|
||||
filestream_close(fp_src);
|
||||
if (fp_dst)
|
||||
filestream_close(fp_dst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int filestream_cmp(const char *src, const char *dst)
|
||||
{
|
||||
int ret = 0;
|
||||
RFILE *fp_src = filestream_open(src, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
RFILE *fp_dst = filestream_open(dst, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!fp_src || !fp_dst || filestream_get_size(fp_src) != filestream_get_size(fp_dst))
|
||||
ret = -1;
|
||||
|
||||
if (ret >= 0)
|
||||
{
|
||||
char buf_src[256] = {0};
|
||||
char buf_dst[256] = {0};
|
||||
while ((filestream_read(fp_src, buf_src, sizeof(buf_src))) > 0 && ret == 0)
|
||||
{
|
||||
filestream_read(fp_dst, buf_dst, sizeof(buf_dst));
|
||||
ret = memcmp(buf_src, buf_dst, sizeof(buf_src));
|
||||
}
|
||||
}
|
||||
|
||||
if (fp_src)
|
||||
{
|
||||
filestream_close(fp_src);
|
||||
fp_src = NULL;
|
||||
}
|
||||
if (fp_dst)
|
||||
{
|
||||
filestream_close(fp_dst);
|
||||
fp_dst = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char* filestream_get_path(RFILE *stream)
|
||||
{
|
||||
if (filestream_get_path_cb)
|
||||
return filestream_get_path_cb(stream->hfile);
|
||||
|
||||
return retro_vfs_file_get_path_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile);
|
||||
}
|
||||
|
||||
int64_t filestream_write(RFILE *stream, const void *s, int64_t len)
|
||||
{
|
||||
int64_t output;
|
||||
|
||||
if (filestream_write_cb)
|
||||
output = filestream_write_cb(stream->hfile, s, len);
|
||||
else
|
||||
output = retro_vfs_file_write_impl(
|
||||
(libretro_vfs_implementation_file*)stream->hfile, s, len);
|
||||
|
||||
if (output == VFS_ERROR_RETURN_VALUE)
|
||||
stream->err_flag = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int filestream_putc(RFILE *stream, int c)
|
||||
{
|
||||
char c_char = (char)c;
|
||||
if (!stream)
|
||||
return EOF;
|
||||
return filestream_write(stream, &c_char, 1) == 1
|
||||
? (int)(unsigned char)c
|
||||
: EOF;
|
||||
}
|
||||
|
||||
int filestream_vprintf(RFILE *stream, const char* format, va_list args)
|
||||
{
|
||||
static char buffer[8 * 1024];
|
||||
int _len = vsnprintf(buffer, sizeof(buffer),
|
||||
format, args);
|
||||
if (_len < 0)
|
||||
return -1;
|
||||
else if (_len == 0)
|
||||
return 0;
|
||||
return (int)filestream_write(stream, buffer, _len);
|
||||
}
|
||||
|
||||
int filestream_printf(RFILE *stream, const char* format, ...)
|
||||
{
|
||||
va_list vl;
|
||||
int ret;
|
||||
va_start(vl, format);
|
||||
ret = filestream_vprintf(stream, format, vl);
|
||||
va_end(vl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int filestream_error(RFILE *stream)
|
||||
{
|
||||
return (stream && stream->err_flag);
|
||||
}
|
||||
|
||||
int filestream_close(RFILE *stream)
|
||||
{
|
||||
int output;
|
||||
struct retro_vfs_file_handle* fp = stream->hfile;
|
||||
|
||||
if (filestream_close_cb)
|
||||
output = filestream_close_cb(fp);
|
||||
else
|
||||
output = retro_vfs_file_close_impl(
|
||||
(libretro_vfs_implementation_file*)fp);
|
||||
|
||||
if (output == 0)
|
||||
free(stream);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int64_t filestream_read_file(const char *path, void **buf, int64_t *len)
|
||||
{
|
||||
int64_t ret = 0;
|
||||
int64_t content_buf_size = 0;
|
||||
void *content_buf = NULL;
|
||||
RFILE *file = filestream_open(path,
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
*buf = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((content_buf_size = filestream_get_size(file)) < 0)
|
||||
goto error;
|
||||
|
||||
if (!(content_buf = malloc((size_t)(content_buf_size + 1))))
|
||||
goto error;
|
||||
if ((int64_t)(uint64_t)(content_buf_size + 1) != (content_buf_size + 1))
|
||||
goto error;
|
||||
|
||||
if ((ret = filestream_read(file, content_buf, (int64_t)content_buf_size)) <
|
||||
0)
|
||||
goto error;
|
||||
|
||||
if (filestream_close(file) != 0)
|
||||
free(file);
|
||||
|
||||
*buf = content_buf;
|
||||
|
||||
/* Allow for easy reading of strings to be safe.
|
||||
* Will only work with sane character formatting (Unix). */
|
||||
((char*)content_buf)[ret] = '\0';
|
||||
|
||||
if (len)
|
||||
*len = ret;
|
||||
|
||||
return 1;
|
||||
|
||||
error:
|
||||
if (filestream_close(file) != 0)
|
||||
free(file);
|
||||
if (content_buf)
|
||||
free(content_buf);
|
||||
if (len)
|
||||
*len = -1;
|
||||
*buf = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool filestream_write_file(const char *path, const void *data, int64_t size)
|
||||
{
|
||||
int64_t ret = 0;
|
||||
RFILE *file = filestream_open(path,
|
||||
RETRO_VFS_FILE_ACCESS_WRITE,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!file)
|
||||
return false;
|
||||
ret = filestream_write(file, data, size);
|
||||
if (filestream_close(file) != 0)
|
||||
free(file);
|
||||
return (ret == size);
|
||||
}
|
||||
|
||||
char *filestream_getline(RFILE *stream)
|
||||
{
|
||||
char *newline_tmp = NULL;
|
||||
size_t cur_size = 8;
|
||||
size_t idx = 0;
|
||||
int in = 0;
|
||||
char *newline = (char*)malloc(9);
|
||||
|
||||
if (!stream || !newline)
|
||||
{
|
||||
if (newline)
|
||||
free(newline);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
in = filestream_getc(stream);
|
||||
|
||||
while (in != EOF && in != '\n')
|
||||
{
|
||||
if (idx == cur_size)
|
||||
{
|
||||
cur_size *= 2;
|
||||
|
||||
if (!(newline_tmp = (char*)realloc(newline, cur_size + 1)))
|
||||
{
|
||||
free(newline);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
newline = newline_tmp;
|
||||
}
|
||||
|
||||
newline[idx++] = in;
|
||||
in = filestream_getc(stream);
|
||||
}
|
||||
|
||||
newline[idx] = '\0';
|
||||
return newline;
|
||||
}
|
||||
|
||||
libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream)
|
||||
{
|
||||
return (libretro_vfs_implementation_file*)stream->hfile;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010-2018 The RetroArch team
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (stdstring.c).
|
||||
@@ -22,10 +22,32 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <compat/strl.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <encodings/utf.h>
|
||||
|
||||
const uint8_t lr_char_props[256] = {
|
||||
/*x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x80,0x00,0x00, /* 0x */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 1x */
|
||||
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 2x !"#$%&'()*+,-./ */
|
||||
0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x00,0x00,0x00,0x00,0x00,0x00, /* 3x 0123456789:;<=>? */
|
||||
0x00,0x23,0x23,0x23,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, /* 4x @ABCDEFGHIJKLMNO */
|
||||
0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x08, /* 5x PQRSTUVWXYZ[\]^_ */
|
||||
0x00,0x25,0x25,0x25,0x25,0x25,0x25,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, /* 6x `abcdefghijklmno */
|
||||
0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00, /* 7x pqrstuvwxyz{|}~ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8x */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 9x */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ax */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Bx */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Cx */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Dx */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ex */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Fx */
|
||||
};
|
||||
|
||||
char *string_to_upper(char *s)
|
||||
{
|
||||
char *cs = (char *)s;
|
||||
@@ -45,9 +67,13 @@ char *string_to_lower(char *s)
|
||||
char *string_ucwords(char *s)
|
||||
{
|
||||
char *cs = (char *)s;
|
||||
|
||||
if (!s || *s == '\0')
|
||||
return s;
|
||||
|
||||
for ( ; *cs != '\0'; cs++)
|
||||
{
|
||||
if (*cs == ' ')
|
||||
if (*cs == ' ' && *(cs + 1) != '\0')
|
||||
*(cs+1) = toupper((unsigned char)*(cs+1));
|
||||
}
|
||||
|
||||
@@ -55,10 +81,13 @@ char *string_ucwords(char *s)
|
||||
return s;
|
||||
}
|
||||
|
||||
char *string_replace_substring(const char *in,
|
||||
const char *pattern, const char *replacement)
|
||||
char *string_replace_substring(
|
||||
const char *in, size_t in_len,
|
||||
const char *pattern, size_t pattern_len,
|
||||
const char *replacement, size_t replacement_len)
|
||||
{
|
||||
size_t numhits, pattern_len, replacement_len, outlen;
|
||||
size_t _len;
|
||||
size_t numhits = 0;
|
||||
const char *inat = NULL;
|
||||
const char *inprev = NULL;
|
||||
char *out = NULL;
|
||||
@@ -69,9 +98,6 @@ char *string_replace_substring(const char *in,
|
||||
if (!pattern || !replacement)
|
||||
return strdup(in);
|
||||
|
||||
pattern_len = strlen(pattern);
|
||||
replacement_len = strlen(replacement);
|
||||
numhits = 0;
|
||||
inat = in;
|
||||
|
||||
while ((inat = strstr(inat, pattern)))
|
||||
@@ -80,10 +106,9 @@ char *string_replace_substring(const char *in,
|
||||
numhits++;
|
||||
}
|
||||
|
||||
outlen = strlen(in) - pattern_len*numhits + replacement_len*numhits;
|
||||
out = (char *)malloc(outlen+1);
|
||||
_len = in_len - pattern_len * numhits + replacement_len*numhits;
|
||||
|
||||
if (!out)
|
||||
if (!(out = (char *)malloc(_len + 1)))
|
||||
return NULL;
|
||||
|
||||
outat = out;
|
||||
@@ -96,56 +121,69 @@ char *string_replace_substring(const char *in,
|
||||
outat += inat-inprev;
|
||||
memcpy(outat, replacement, replacement_len);
|
||||
outat += replacement_len;
|
||||
inat += pattern_len;
|
||||
inat += pattern_len;
|
||||
inprev = inat;
|
||||
}
|
||||
strcpy(outat, inprev);
|
||||
|
||||
_len = (in + in_len) - inprev;
|
||||
strlcpy(outat, inprev, _len + 1);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Remove leading whitespaces */
|
||||
/**
|
||||
* string_trim_whitespace_left:
|
||||
*
|
||||
* Remove leading whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace_left(char *const s)
|
||||
{
|
||||
if(s && *s)
|
||||
if (s && *s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
char *current = s;
|
||||
|
||||
while(*current && isspace((unsigned char)*current))
|
||||
{
|
||||
while (*current && ISSPACE((unsigned char)*current))
|
||||
++current;
|
||||
--len;
|
||||
}
|
||||
|
||||
if(s != current)
|
||||
memmove(s, current, len + 1);
|
||||
if (s != current)
|
||||
{
|
||||
size_t _len = strlen(current);
|
||||
memmove(s, current, _len + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Remove trailing whitespaces */
|
||||
/**
|
||||
* string_trim_whitespace_right:
|
||||
*
|
||||
* Remove trailing whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace_right(char *const s)
|
||||
{
|
||||
if(s && *s)
|
||||
if (s && *s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
char *current = s + len - 1;
|
||||
size_t _len = strlen(s);
|
||||
char *current = s + _len - 1;
|
||||
|
||||
while(current != s && isspace((unsigned char)*current))
|
||||
while (current != s && ISSPACE((unsigned char)*current))
|
||||
{
|
||||
--current;
|
||||
--len;
|
||||
--_len;
|
||||
}
|
||||
|
||||
current[isspace((unsigned char)*current) ? 0 : 1] = '\0';
|
||||
current[ISSPACE((unsigned char)*current) ? 0 : 1] = '\0';
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Remove leading and trailing whitespaces */
|
||||
/**
|
||||
* string_trim_whitespace:
|
||||
*
|
||||
* Remove leading and trailing whitespaces
|
||||
**/
|
||||
char *string_trim_whitespace(char *const s)
|
||||
{
|
||||
string_trim_whitespace_right(s); /* order matters */
|
||||
@@ -154,86 +192,534 @@ char *string_trim_whitespace(char *const s)
|
||||
return s;
|
||||
}
|
||||
|
||||
char *word_wrap(char* buffer, const char *string, int line_width, bool unicode, unsigned max_lines)
|
||||
/**
|
||||
* word_wrap:
|
||||
* @s : pointer to destination buffer.
|
||||
* @len : size of destination buffer.
|
||||
* @src : pointer to input string.
|
||||
* @line_width : max number of characters per line.
|
||||
* @wideglyph_width : not used, but is necessary to keep
|
||||
* compatibility with word_wrap_wideglyph().
|
||||
* @max_lines : max lines of destination string.
|
||||
* 0 means no limit.
|
||||
*
|
||||
* Wraps string specified by @src to destination buffer
|
||||
* specified by @s and @len.
|
||||
* This function assumes that all glyphs in the string
|
||||
* have an on-screen pixel width similar to that of
|
||||
* regular Latin characters - i.e. it will not wrap
|
||||
* correctly any text containing so-called 'wide' Unicode
|
||||
* characters (e.g. CJK languages, emojis, etc.).
|
||||
**/
|
||||
size_t word_wrap(
|
||||
char *s, size_t len,
|
||||
const char *src, size_t src_len,
|
||||
int line_width, int wideglyph_width, unsigned max_lines)
|
||||
{
|
||||
unsigned i = 0;
|
||||
unsigned len = (unsigned)strlen(string);
|
||||
unsigned lines = 1;
|
||||
char *last_space = NULL;
|
||||
unsigned counter = 0;
|
||||
unsigned lines = 1;
|
||||
const char *src_end = src + src_len;
|
||||
char *s_start = s;
|
||||
size_t _len = len;
|
||||
|
||||
while (i < len)
|
||||
/* Prevent buffer overflow */
|
||||
if (len < src_len + 1)
|
||||
return 0;
|
||||
|
||||
/* Early return if src string length is less
|
||||
* than line width */
|
||||
if (src_len < (size_t)line_width)
|
||||
return strlcpy(s, src, len);
|
||||
|
||||
while (*src != '\0')
|
||||
{
|
||||
unsigned counter;
|
||||
int pos = (int)(&buffer[i] - buffer);
|
||||
unsigned char_len = (unsigned)(utf8skip(src, 1) - src);
|
||||
counter++;
|
||||
|
||||
/* copy string until the end of the line is reached */
|
||||
for (counter = 1; counter <= (unsigned)line_width; counter++)
|
||||
if (*src == ' ')
|
||||
last_space = s; /* Remember the location of the whitespace */
|
||||
else if (*src == '\n')
|
||||
{
|
||||
const char *character;
|
||||
unsigned char_len;
|
||||
unsigned j = i;
|
||||
/* If newlines embedded in the input,
|
||||
* reset the index */
|
||||
lines++;
|
||||
counter = 0;
|
||||
|
||||
/* check if end of string reached */
|
||||
if (i == len)
|
||||
/* Early return if remaining src string
|
||||
* length is less than line width */
|
||||
if (src_end - src <= line_width)
|
||||
{
|
||||
buffer[i] = 0;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
character = utf8skip(&string[i], 1);
|
||||
char_len = (unsigned)(character - &string[i]);
|
||||
|
||||
if (!unicode)
|
||||
counter += char_len - 1;
|
||||
|
||||
do
|
||||
{
|
||||
buffer[i] = string[i];
|
||||
char_len--;
|
||||
i++;
|
||||
} while(char_len);
|
||||
|
||||
/* check for newlines embedded in the original input
|
||||
* and reset the index */
|
||||
if (buffer[j] == '\n')
|
||||
{
|
||||
lines++;
|
||||
counter = 1;
|
||||
_len = len - (size_t)(s - s_start);
|
||||
return strlcpy(s, src, _len);
|
||||
}
|
||||
}
|
||||
|
||||
/* check for whitespace */
|
||||
if (string[i] == ' ')
|
||||
while (char_len--)
|
||||
*s++ = *src++;
|
||||
|
||||
if (counter >= (unsigned)line_width)
|
||||
{
|
||||
if ((max_lines == 0 || lines < max_lines))
|
||||
counter = 0;
|
||||
|
||||
if (last_space && (max_lines == 0 || lines < max_lines))
|
||||
{
|
||||
buffer[i] = '\n';
|
||||
i++;
|
||||
/* Replace nearest (previous) whitespace
|
||||
* with newline character */
|
||||
*last_space = '\n';
|
||||
lines++;
|
||||
|
||||
src -= s - last_space - 1;
|
||||
s = last_space + 1;
|
||||
last_space = NULL;
|
||||
|
||||
/* Early return if remaining src string
|
||||
* length is less than line width */
|
||||
if (src_end - src < line_width)
|
||||
{
|
||||
_len = len - (size_t)(s - s_start);
|
||||
return strlcpy(s, src, _len);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int k;
|
||||
|
||||
/* check for nearest whitespace back in string */
|
||||
for (k = i; k > 0; k--)
|
||||
{
|
||||
if (string[k] != ' ' || (max_lines != 0 && lines >= max_lines))
|
||||
continue;
|
||||
|
||||
buffer[k] = '\n';
|
||||
/* set string index back to character after this one */
|
||||
i = k + 1;
|
||||
lines++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (&buffer[i] - buffer == pos)
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[i] = 0;
|
||||
*s = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
/**
|
||||
* word_wrap_wideglyph:
|
||||
* @dst : pointer to destination buffer.
|
||||
* @len : size of destination buffer.
|
||||
* @src : pointer to input string.
|
||||
* @line_width : max number of characters per line.
|
||||
* @wideglyph_width : effective width of 'wide' Unicode glyphs.
|
||||
* the value here is normalised relative to the
|
||||
* typical on-screen pixel width of a regular
|
||||
* Latin character:
|
||||
* - a regular Latin character is defined to
|
||||
* have an effective width of 100
|
||||
* - wideglyph_width = 100 * (wide_character_pixel_width / latin_character_pixel_width)
|
||||
* - e.g. if 'wide' Unicode characters in 'src'
|
||||
* have an on-screen pixel width twice that of
|
||||
* regular Latin characters, wideglyph_width
|
||||
* would be 200
|
||||
* @max_lines : max lines of destination string.
|
||||
* 0 means no limit.
|
||||
*
|
||||
* Wraps string specified by @src to destination buffer
|
||||
* specified by @dst and @len.
|
||||
* This function assumes that all glyphs in the string
|
||||
* are:
|
||||
* - EITHER 'non-wide' Unicode glyphs, with an on-screen
|
||||
* pixel width similar to that of regular Latin characters
|
||||
* - OR 'wide' Unicode glyphs (e.g. CJK languages, emojis, etc.)
|
||||
* with an on-screen pixel width defined by @wideglyph_width
|
||||
* Note that wrapping may occur in inappropriate locations
|
||||
* if @src string contains 'wide' Unicode characters whose
|
||||
* on-screen pixel width deviates greatly from the set
|
||||
* @wideglyph_width value.
|
||||
**/
|
||||
size_t word_wrap_wideglyph(char *s, size_t len,
|
||||
const char *src, size_t src_len, int line_width,
|
||||
int wideglyph_width, unsigned max_lines)
|
||||
{
|
||||
char *lastspace = NULL;
|
||||
char *lastwideglyph = NULL;
|
||||
const char *src_end = src + src_len;
|
||||
char *s_start = s;
|
||||
unsigned lines = 1;
|
||||
/* 'line_width' means max numbers of characters per line,
|
||||
* but this metric is only meaningful when dealing with
|
||||
* 'regular' glyphs that have an on-screen pixel width
|
||||
* similar to that of regular Latin characters.
|
||||
* When handing so-called 'wide' Unicode glyphs, it is
|
||||
* necessary to consider the actual on-screen pixel width
|
||||
* of each character.
|
||||
* In order to do this, we create a distinction between
|
||||
* regular Latin 'non-wide' glyphs and 'wide' glyphs, and
|
||||
* normalise all values relative to the on-screen pixel
|
||||
* width of regular Latin characters:
|
||||
* - Regular 'non-wide' glyphs have a normalised width of 100
|
||||
* - 'line_width' is therefore normalised to 100 * (width_in_characters)
|
||||
* - 'wide' glyphs have a normalised width of
|
||||
* 100 * (wide_character_pixel_width / latin_character_pixel_width)
|
||||
* - When a character is detected, the position in the current
|
||||
* line is incremented by the regular normalised width of 100
|
||||
* - If that character is then determined to be a 'wide'
|
||||
* glyph, the position in the current line is further incremented
|
||||
* by the difference between the normalised 'wide' and 'non-wide'
|
||||
* width values */
|
||||
unsigned counter_normalized = 0;
|
||||
int line_width_normalized = line_width * 100;
|
||||
int additional_counter_normalized = wideglyph_width - 100;
|
||||
|
||||
/* Early return if src string length is less
|
||||
* than line width */
|
||||
if (src_end - src < line_width)
|
||||
return strlcpy(s, src, len);
|
||||
|
||||
while (*src != '\0')
|
||||
{
|
||||
size_t remaining;
|
||||
unsigned char_len = (unsigned)(utf8skip(src, 1) - src);
|
||||
counter_normalized += 100;
|
||||
|
||||
/* Prevent buffer overflow */
|
||||
if (char_len >= len)
|
||||
break;
|
||||
|
||||
if (*src == ' ')
|
||||
lastspace = s; /* Remember the location of the whitespace */
|
||||
else if (*src == '\n')
|
||||
{
|
||||
/* If newlines embedded in the input,
|
||||
* reset the index */
|
||||
lines++;
|
||||
counter_normalized = 0;
|
||||
|
||||
/* Early return if remaining src string
|
||||
* length is less than line width */
|
||||
if (src_end - src <= line_width)
|
||||
{
|
||||
remaining = len - (size_t)(s - s_start);
|
||||
return strlcpy(s, src, remaining);
|
||||
}
|
||||
}
|
||||
else if (char_len >= 3)
|
||||
{
|
||||
/* Remember the location of the first byte
|
||||
* whose length as UTF-8 >= 3*/
|
||||
lastwideglyph = s;
|
||||
counter_normalized += additional_counter_normalized;
|
||||
}
|
||||
|
||||
len -= char_len;
|
||||
while (char_len--)
|
||||
*s++ = *src++;
|
||||
|
||||
if (counter_normalized >= (unsigned)line_width_normalized)
|
||||
{
|
||||
counter_normalized = 0;
|
||||
|
||||
if (max_lines != 0 && lines >= max_lines)
|
||||
continue;
|
||||
else if (lastwideglyph
|
||||
&& (!lastspace || lastwideglyph > lastspace))
|
||||
{
|
||||
/* Insert newline character */
|
||||
*lastwideglyph = '\n';
|
||||
lines++;
|
||||
src -= s - lastwideglyph;
|
||||
s = lastwideglyph + 1;
|
||||
lastwideglyph = NULL;
|
||||
|
||||
/* Early return if remaining src string
|
||||
* length is less than line width */
|
||||
if (src_end - src <= line_width)
|
||||
{
|
||||
remaining = len - (size_t)(s - s_start);
|
||||
return strlcpy(s, src, remaining);
|
||||
}
|
||||
}
|
||||
else if (lastspace)
|
||||
{
|
||||
/* Replace nearest (previous) whitespace
|
||||
* with newline character */
|
||||
*lastspace = '\n';
|
||||
lines++;
|
||||
src -= s - lastspace - 1;
|
||||
s = lastspace + 1;
|
||||
lastspace = NULL;
|
||||
|
||||
/* Early return if remaining src string
|
||||
* length is less than line width */
|
||||
if (src_end - src < line_width)
|
||||
{
|
||||
remaining = len - (size_t)(s - s_start);
|
||||
return strlcpy(s, src, remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*s = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_tokenize:
|
||||
*
|
||||
* Splits string into tokens separated by @delim
|
||||
* > Returned token string must be free()'d
|
||||
* > Returns NULL if token is not found
|
||||
* > After each call, @str is set to the position after the
|
||||
* last found token
|
||||
* > Tokens *include* empty strings
|
||||
* Usage example:
|
||||
* char *str = "1,2,3,4,5,6,7,,,10,";
|
||||
* char **str_ptr = &str;
|
||||
* char *token = NULL;
|
||||
* while ((token = string_tokenize(str_ptr, ",")))
|
||||
* {
|
||||
* printf("%s\n", token);
|
||||
* free(token);
|
||||
* token = NULL;
|
||||
* }
|
||||
**/
|
||||
char* string_tokenize(char **str, const char *delim)
|
||||
{
|
||||
/* Taken from https://codereview.stackexchange.com/questions/216956/strtok-function-thread-safe-supports-empty-tokens-doesnt-change-string# */
|
||||
char *str_ptr = NULL;
|
||||
char *delim_ptr = NULL;
|
||||
char *token = NULL;
|
||||
size_t _len = 0;
|
||||
size_t delim_len = 0;
|
||||
|
||||
/* Sanity checks */
|
||||
if (!str || string_is_empty(delim))
|
||||
return NULL;
|
||||
|
||||
/* Note: we don't check string_is_empty() here,
|
||||
* empty strings are valid */
|
||||
if (!(str_ptr = *str))
|
||||
return NULL;
|
||||
|
||||
delim_len = strlen(delim);
|
||||
|
||||
/* Search for delimiter */
|
||||
if ((delim_ptr = strstr(str_ptr, delim)))
|
||||
_len = delim_ptr - str_ptr;
|
||||
else
|
||||
_len = strlen(str_ptr);
|
||||
|
||||
/* Allocate token string */
|
||||
if (!(token = (char *)malloc((_len + 1) * sizeof(char))))
|
||||
return NULL;
|
||||
|
||||
/* Copy token */
|
||||
strlcpy(token, str_ptr, (_len + 1) * sizeof(char));
|
||||
token[_len] = '\0';
|
||||
/* Update input string pointer */
|
||||
*str = delim_ptr ? delim_ptr + delim_len : NULL;
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_remove_all_chars:
|
||||
* @s : input string (must be non-NULL, otherwise UB)
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Removes every instance of character @c from @s
|
||||
**/
|
||||
void string_remove_all_chars(char *s, char c)
|
||||
{
|
||||
char *read_ptr = s;
|
||||
char *write_ptr = s;
|
||||
|
||||
while (*read_ptr != '\0')
|
||||
{
|
||||
/* Only write if the character is not the one to remove */
|
||||
if (*read_ptr != c)
|
||||
*write_ptr++ = *read_ptr;
|
||||
read_ptr++;
|
||||
}
|
||||
|
||||
*write_ptr = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* string_replace_all_chars:
|
||||
* @s : input string (must be non-NULL, otherwise UB)
|
||||
* @find : character to find
|
||||
* @replace : character to replace @find with
|
||||
*
|
||||
* Replaces every instance of character @find in @s
|
||||
* with character @replace
|
||||
**/
|
||||
void string_replace_all_chars(char *s, char find, char replace)
|
||||
{
|
||||
char *str_ptr = s;
|
||||
while ((str_ptr = strchr(str_ptr, find)))
|
||||
*str_ptr++ = replace;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_to_unsigned:
|
||||
* @str : input string
|
||||
*
|
||||
* Converts string to unsigned integer.
|
||||
*
|
||||
* @return 0 if string is invalid, otherwise > 0
|
||||
**/
|
||||
unsigned string_to_unsigned(const char *str)
|
||||
{
|
||||
const char *ptr = NULL;
|
||||
|
||||
if (string_is_empty(str))
|
||||
return 0;
|
||||
|
||||
for (ptr = str; *ptr != '\0'; ptr++)
|
||||
{
|
||||
if (!ISDIGIT((unsigned char)*ptr))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (unsigned)strtoul(str, NULL, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* string_hex_to_unsigned:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
*
|
||||
* Converts hexadecimal string to unsigned integer.
|
||||
* Handles optional leading '0x'.
|
||||
*
|
||||
* @return 0 if string is invalid, otherwise > 0
|
||||
**/
|
||||
unsigned string_hex_to_unsigned(const char *str)
|
||||
{
|
||||
const char *hex_str = str;
|
||||
const char *ptr = NULL;
|
||||
|
||||
if (string_is_empty(str))
|
||||
return 0;
|
||||
|
||||
/* Remove leading '0x', if present */
|
||||
if ( str[0] == '0'
|
||||
&& (str[1] == 'x' || str[1] == 'X'))
|
||||
{
|
||||
hex_str = str + 2;
|
||||
if (string_is_empty(hex_str))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check for valid characters */
|
||||
for (ptr = hex_str; *ptr != '\0'; ptr++)
|
||||
{
|
||||
if (!isxdigit((unsigned char)*ptr))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (unsigned)strtoul(hex_str, NULL, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* string_count_occurrences_single_character:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Get the total number of occurrences of character @c in @str.
|
||||
*
|
||||
* @return Total number of occurrences of character @c
|
||||
*/
|
||||
int string_count_occurrences_single_character(const char *str, char c)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (; *str; str++)
|
||||
if (*str == c)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_replace_whitespace_with_single_character:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Replaces all spaces with given character @c.
|
||||
**/
|
||||
/* FIX: Added unsigned char cast to ISSPACE argument for
|
||||
* correct behaviour with high-byte / signed char inputs. */
|
||||
void string_replace_whitespace_with_single_character(char *s, char c)
|
||||
{
|
||||
for (; *s; s++)
|
||||
if (ISSPACE((unsigned char)*s))
|
||||
*s = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_replace_multi_space_with_single_space:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Replaces multiple spaces with a single space in a string.
|
||||
**/
|
||||
/* FIX: Added unsigned char cast to ISSPACE arguments. */
|
||||
void string_replace_multi_space_with_single_space(char *s)
|
||||
{
|
||||
char *str_trimmed = s;
|
||||
bool prev_is_space = false;
|
||||
bool curr_is_space = false;
|
||||
|
||||
for (; *s; s++)
|
||||
{
|
||||
curr_is_space = ISSPACE((unsigned char)*s);
|
||||
if (prev_is_space && curr_is_space)
|
||||
continue;
|
||||
*str_trimmed++ = *s;
|
||||
prev_is_space = curr_is_space;
|
||||
}
|
||||
*str_trimmed = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* string_remove_all_whitespace:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Remove all spaces from the given string.
|
||||
**/
|
||||
/* FIX: Added unsigned char cast to ISSPACE argument. */
|
||||
void string_remove_all_whitespace(char *s, const char *str)
|
||||
{
|
||||
for (; *str; str++)
|
||||
if (!ISSPACE((unsigned char)*str))
|
||||
*s++ = *str;
|
||||
*s = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last occurrence of the given character in a string.
|
||||
*/
|
||||
int string_index_last_occurance(const char *str, char c)
|
||||
{
|
||||
const char *pos = strrchr(str, c);
|
||||
if (pos)
|
||||
return (int)(pos - str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_find_index_substring_string:
|
||||
* @str : input string (must be non-NULL, otherwise UB)
|
||||
* @substr : substring to find in @str
|
||||
*
|
||||
* Find the position of substring @substr in string @str.
|
||||
**/
|
||||
int string_find_index_substring_string(const char *str, const char *substr)
|
||||
{
|
||||
const char *pos = strstr(str, substr);
|
||||
if (pos)
|
||||
return (int)(pos - str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_copy_only_ascii:
|
||||
*
|
||||
* Leaf function.
|
||||
*
|
||||
* Strips non-ASCII characters from a string.
|
||||
**/
|
||||
void string_copy_only_ascii(char *s, const char *str)
|
||||
{
|
||||
for (; *str; str++)
|
||||
if (*str > 0x1F && *str < 0x7F)
|
||||
*s++ = *str;
|
||||
*s = '\0';
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rtime.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <time/rtime.h>
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
/* TODO/FIXME - global */
|
||||
slock_t *rtime_localtime_lock = NULL;
|
||||
#endif
|
||||
|
||||
/* Must be called before using rtime_localtime() */
|
||||
void rtime_init(void)
|
||||
{
|
||||
rtime_deinit();
|
||||
#ifdef HAVE_THREADS
|
||||
if (!rtime_localtime_lock)
|
||||
rtime_localtime_lock = slock_new();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Must be called upon program termination */
|
||||
void rtime_deinit(void)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
if (rtime_localtime_lock)
|
||||
{
|
||||
slock_free(rtime_localtime_lock);
|
||||
rtime_localtime_lock = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Thread-safe wrapper for localtime() */
|
||||
struct tm *rtime_localtime(const time_t *timep, struct tm *result)
|
||||
{
|
||||
struct tm *time_info = NULL;
|
||||
|
||||
/* Lock mutex */
|
||||
#ifdef HAVE_THREADS
|
||||
slock_lock(rtime_localtime_lock);
|
||||
#endif
|
||||
|
||||
time_info = localtime(timep);
|
||||
if (time_info)
|
||||
memcpy(result, time_info, sizeof(struct tm));
|
||||
|
||||
/* Unlock mutex */
|
||||
#ifdef HAVE_THREADS
|
||||
slock_unlock(rtime_localtime_lock);
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
+1
-1
Submodule core/deps/oboe updated: b15f5e39c0...a81bb9f87d.
@@ -4,7 +4,7 @@
|
||||
|
||||
*********************************************************************/
|
||||
#define MAX_BLOCK_SIZE 1600
|
||||
#define MAX_BLOCK_COUNT 16
|
||||
#define MAX_BLOCK_COUNT 1024 /* 16 blocks (~1,000 timers) ran out with a few dozen connections a second */
|
||||
|
||||
#define DECLARE_HEAP(type, orderby) \
|
||||
struct heap_ ## type { \
|
||||
|
||||
Loaded 100 of 773 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user