Compare commits
1013 commits
Author | SHA1 | Date | |
---|---|---|---|
b261c59379 | |||
ffd2e82749 | |||
d6f6d1273e | |||
43a573f71b | |||
25d2b5519f | |||
2620d3e928 | |||
81b0396f49 | |||
894f5e91b6 | |||
34eccfe547 | |||
b71f454076 | |||
bd3a1c03e0 | |||
2b7601d9b9 | |||
|
d4aa32a34c | ||
|
48d30b6b19 | ||
|
eac88a340d | ||
|
044640cad0 | ||
|
2ea14ee2de | ||
|
a3774705e9 | ||
|
37445c4e35 | ||
|
661de9972d | ||
|
49885bbecb | ||
|
7640ce8383 | ||
|
500c2e50b6 | ||
|
cdd360bdfe | ||
|
2d4f60452f | ||
|
5a63f25b89 | ||
|
545d6f9f4f | ||
|
592a26332f | ||
|
10a5bc1da7 | ||
|
bda732cce4 | ||
|
29bc469bc1 | ||
|
0357249317 | ||
|
afc1477332 | ||
|
27904182c4 | ||
|
de2071476e | ||
|
01cbc4c43e | ||
|
a6b5590744 | ||
|
00382da4a2 | ||
|
bb1d5ac792 | ||
|
f4986708d8 | ||
|
bbb5783fa6 | ||
|
9006e39eaa | ||
|
165c4532fc | ||
|
3d247e6ee9 | ||
|
124de54aa9 | ||
|
ccc0285930 | ||
|
fa876ccdd9 | ||
|
c3a8259302 | ||
|
d101a6c200 | ||
|
a87e709503 | ||
|
375029a2c3 | ||
|
6e630d038b | ||
|
6e67d1247d | ||
|
4bd23597a7 | ||
|
048391981d | ||
|
2464b9dc67 | ||
|
c3fb1a7713 | ||
|
cfb38aa08f | ||
|
25d0d8a05c | ||
|
db34cd326f | ||
|
c32105ff63 | ||
|
4f723d900b | ||
|
058726746f | ||
|
0419c2ca80 | ||
|
4a6315014e | ||
|
65f9843ad8 | ||
|
71285eebb6 | ||
|
2a908c1889 | ||
|
4f3c92bee4 | ||
|
ab358975ad | ||
|
364cfcbade | ||
|
586f094393 | ||
|
6c6409b625 | ||
|
26a729309f | ||
|
d88ab1199d | ||
|
c70377fcaf | ||
|
2abca0c0b2 | ||
|
3348d4420e | ||
|
726b05cdbe | ||
|
0264538f4b | ||
|
f557566824 | ||
|
e8fe81c7cc | ||
|
d0601d156d | ||
|
c2a255303d | ||
|
4a51835525 | ||
|
f03c09db76 | ||
|
1f1a0f0c3f | ||
|
fa60480875 | ||
|
8de697f3f6 | ||
|
dadba55b73 | ||
|
89d9031752 | ||
|
d6e9a58a15 | ||
|
b48893fab9 | ||
|
89842321b8 | ||
|
9006bcf354 | ||
|
d865d5c252 | ||
|
9ec45b1dab | ||
|
e5456916fb | ||
|
d6aa407076 | ||
|
86122aa5f5 | ||
|
913081649e | ||
|
9590d7c4f9 | ||
|
151ef94382 | ||
|
c758e6884c | ||
|
b80f7c5f50 | ||
|
28e5703ab5 | ||
|
6dd4390c0b | ||
|
abe7d664c5 | ||
|
b5b2958be1 | ||
|
9d8d1110ab | ||
|
49d0b7535d | ||
|
aa7f3277d1 | ||
|
36e1c5a135 | ||
|
b740255979 | ||
|
ad6ca9c940 | ||
|
6e42274033 | ||
|
9439b1b400 | ||
|
79668e49e1 | ||
|
0ae58c5815 | ||
|
a6eea044af | ||
|
36ab6a8b4f | ||
|
1f1ef6c8a3 | ||
|
97c3472e6e | ||
|
e08363f141 | ||
|
24a135e042 | ||
|
76317c2df1 | ||
|
d46c90a82c | ||
|
a16b2b2d35 | ||
|
02740cee80 | ||
|
30dbfad59c | ||
|
7037e20c0f | ||
|
741849a026 | ||
|
117502b774 | ||
|
cca65baf97 | ||
|
eb74e28585 | ||
|
34a0baa65e | ||
|
fad9da0ec2 | ||
|
f61d2073e5 | ||
|
f080599718 | ||
|
6175dc8a55 | ||
|
8fd124e3ae | ||
|
759cb5ac64 | ||
|
ea828795c3 | ||
|
7c81aafad3 | ||
|
0493cade10 | ||
|
682916415c | ||
|
7839aa0330 | ||
|
3128fdb449 | ||
|
3c09d75573 | ||
|
8da9f845a8 | ||
|
8c36ead724 | ||
|
1a302935f0 | ||
|
09343371d5 | ||
|
d72e91dd2a | ||
|
8db8423634 | ||
|
052ccac8d0 | ||
|
ca05f42721 | ||
|
b4aa5fff1e | ||
|
b36c17b198 | ||
|
94de9e9e00 | ||
|
f9c9832262 | ||
|
78dd08e329 | ||
|
70cee11c79 | ||
|
ae9e934193 | ||
|
ae36dcbe30 | ||
|
783cf233a5 | ||
|
0daa52f9f8 | ||
|
6e872d4bf1 | ||
|
e8bcb72771 | ||
|
c7bf4f5a39 | ||
|
b349ec405d | ||
|
4eb070c653 | ||
|
b81c3c78b3 | ||
|
48e2668bbc | ||
|
35452965c8 | ||
|
60878c5a4f | ||
|
448de8f6c7 | ||
|
232d0e62d3 | ||
|
496a132211 | ||
|
17131932d4 | ||
|
2f80b15ba4 | ||
|
b16084baf3 | ||
|
4063198e34 | ||
|
ed98c6ce01 | ||
|
4129a78254 | ||
|
3f6116431f | ||
|
b4f6d64034 | ||
|
030d6d5a62 | ||
|
3904cf58b2 | ||
|
51b6604142 | ||
|
fff8fe91a9 | ||
|
6acc688800 | ||
|
56779456b5 | ||
|
28ecb6b512 | ||
|
1d2d0a1a5f | ||
|
0d88767320 | ||
|
53f1bf52f2 | ||
|
b795c05b4f | ||
|
580be748dd | ||
|
f21b4b8b10 | ||
|
8640f82323 | ||
|
c6b9cb1ee1 | ||
|
e5b431a908 | ||
|
ad40662985 | ||
|
1315d8050f | ||
|
fc92b7006a | ||
|
b31d134258 | ||
|
34ad2d9a23 | ||
|
75e2abbe6e | ||
|
cd0a18de1d | ||
|
7ae208a2e5 | ||
|
db8997d652 | ||
|
edf8978f17 | ||
|
911741e411 | ||
|
2a376f8826 | ||
|
6cadd8dc36 | ||
|
3617b0d5c5 | ||
|
c3e8bb94a2 | ||
|
0b3e57d934 | ||
|
24971328ba | ||
|
abc4a4e1d3 | ||
|
087f0f7942 | ||
|
4205bc852f | ||
|
d68b503d0d | ||
|
db71bb1a4c | ||
|
a8057a5850 | ||
|
8d07bb1506 | ||
|
dcae60dede | ||
|
629afb1055 | ||
|
46333f06e7 | ||
|
4adde75a13 | ||
|
a302227470 | ||
|
9a77ed4597 | ||
|
e2634210ca | ||
|
01c5c91fbd | ||
|
8d26fdb2b8 | ||
|
a8c41b79fb | ||
|
2d60d802a2 | ||
|
dfae404334 | ||
|
cfab12cd6f | ||
|
39cf60f45a | ||
|
9b3a3e7913 | ||
|
825cb59e62 | ||
|
83ab755144 | ||
|
db2f9be009 | ||
|
96e3f37b76 | ||
|
a059fad539 | ||
|
1c94fd7150 | ||
|
d8ebc8dc9f | ||
|
5c96069a3f | ||
|
77a293a4bc | ||
|
0f753d0bfb | ||
|
c1276a3f12 | ||
|
d5c3e7047e | ||
|
919fe84cfe | ||
|
d4568180c5 | ||
|
b9c199bce3 | ||
|
bde3eb92b0 | ||
|
7b09bacb01 | ||
|
09da101cbb | ||
|
535ceaadc6 | ||
|
e9bcdb25ff | ||
|
c0c5c2a7cb | ||
|
ce54d08bc2 | ||
|
9af407a089 | ||
|
16c70e4cde | ||
|
8a14c7e974 | ||
|
6a3ba0bd72 | ||
|
283a74d7e7 | ||
|
1ac7710b07 | ||
|
b65e67ed67 | ||
|
54403f74c6 | ||
|
8b77575fbd | ||
|
c48cbfb46c | ||
|
a7e2ca580c | ||
|
6753e62abd | ||
|
a626fc5b50 | ||
|
8d21b68af0 | ||
|
329c19c8d9 | ||
|
9d4f293a3c | ||
|
c36cbe5a31 | ||
|
8ab76a9235 | ||
|
8852154a6d | ||
|
c01487d0f4 | ||
|
1f14d06790 | ||
|
9393f04d3d | ||
|
b78f20d915 | ||
|
ffa5a35ba8 | ||
|
10e769735a | ||
|
87f232d990 | ||
|
36df9170bc | ||
|
94a20126b7 | ||
|
44ddf798f2 | ||
|
50599e9535 | ||
|
22a9c64010 | ||
|
2f19a3980f | ||
|
d2d241cf9c | ||
|
577a3dfa4b | ||
|
d5f2fbbb78 | ||
|
7f3ecac9b1 | ||
|
80222caa0d | ||
|
f5c929d2a2 | ||
|
2eec57ad1b | ||
|
c50bef8cb2 | ||
|
df0d6bdb95 | ||
|
8fd0b0f5a6 | ||
|
1c9beb246b | ||
|
669c7124c2 | ||
|
6243d17e93 | ||
|
10fcf305b1 | ||
|
eeaefee89a | ||
|
e3c4fe6ebf | ||
|
5dd8e39787 | ||
|
982ba0922d | ||
|
f1b9232933 | ||
|
9aefc20d24 | ||
|
9aba397034 | ||
|
d7cf271a41 | ||
|
5966fa5138 | ||
|
b1e431d17d | ||
|
633e2e62ad | ||
|
cf13ce7eb3 | ||
|
e4a91e084d | ||
|
bcd56ad8a3 | ||
|
512594cca7 | ||
|
9aa7a9df95 | ||
|
be8eee37b5 | ||
|
bbb698ece1 | ||
|
4683883c2b | ||
|
4c51309e3b | ||
|
6b6a238bcf | ||
|
99907c9a89 | ||
|
73db44ac73 | ||
|
e6bfe89722 | ||
|
947ae1b7a4 | ||
|
ee48c0d567 | ||
|
6a75299826 | ||
|
4c8b74332c | ||
|
7a8a162d78 | ||
|
f144cb037f | ||
|
dd683e8e48 | ||
|
e40d16c2d7 | ||
|
da1648706d | ||
|
783ccb9060 | ||
|
362c9877d4 | ||
|
29821c9e7c | ||
|
25dc50d83c | ||
|
953571a433 | ||
|
bb15de8e99 | ||
|
4002193584 | ||
|
cdf5ca8419 | ||
|
f44428352c | ||
|
90cc5732f9 | ||
|
341f1aaac1 | ||
|
a2f89c78db | ||
|
b56ccf8eb4 | ||
|
11c44233b4 | ||
|
8dc4c042a2 | ||
|
3e03e3ba57 | ||
|
fec69e1f72 | ||
|
64e329c054 | ||
|
488adf2313 | ||
|
177858b055 | ||
|
fd2c690ea5 | ||
|
ff82eb4a61 | ||
|
165830d488 | ||
|
5b24df0fc9 | ||
|
3d8ac1bee3 | ||
|
cbf9b078c6 | ||
|
a583fd75f3 | ||
|
7025bcd457 | ||
|
24c105ffd5 | ||
|
f795147611 | ||
|
458bbf4634 | ||
|
f7848852c6 | ||
|
3e768c4d78 | ||
|
199a344e37 | ||
|
ea4891f1f6 | ||
|
4fca44ed6f | ||
|
d029befe6a | ||
|
c09827f332 | ||
|
5a3618912f | ||
|
0f66cd6f85 | ||
|
49cae397d6 | ||
|
5cb5108409 | ||
|
32c200d65c | ||
|
5230a6a558 | ||
|
7152b82a45 | ||
|
405ea13ba2 | ||
|
f7c6dfc1b9 | ||
|
db874d37be | ||
|
665245bf7b | ||
|
d99d35013d | ||
|
dd6455ceb9 | ||
|
883cbdd404 | ||
|
8499ccb692 | ||
|
03329bba84 | ||
|
ddd1c4f301 | ||
|
ec56d2ee00 | ||
|
8d8782946b | ||
|
462f02e77f | ||
|
0459fd3ba9 | ||
|
c416eee9df | ||
|
6066a52592 | ||
|
56511affa2 | ||
|
3b917e93be | ||
|
9366956c49 | ||
|
c81b4ab1bf | ||
|
fdfbb795d1 | ||
|
d9c6541f73 | ||
|
799ffc7604 | ||
|
b78a2224d0 | ||
|
4137238cd7 | ||
|
956427bea8 | ||
|
3e5a6aa520 | ||
|
07900d5c38 | ||
|
20092dccb9 | ||
|
cb166dc47f | ||
|
282e024c37 | ||
|
a75833847e | ||
|
3051fbe3fe | ||
|
d912026f3e | ||
|
b204dc5362 | ||
|
1905590b27 | ||
|
c2fab20d0c | ||
|
dc9bd91f84 | ||
|
7865fc3457 | ||
|
c8ad71ed07 | ||
|
1b3c3f6711 | ||
|
4ae655caab | ||
|
0b692ee7f8 | ||
|
f3d5cb3f10 | ||
|
cfc7b719fe | ||
|
93bebd3eb0 | ||
|
53a6928991 | ||
|
a3381bfcb6 | ||
|
989410c17d | ||
|
05b2d5b2e7 | ||
|
ad5b6628b5 | ||
|
ea0a3cb5ba | ||
|
5594c4fbc8 | ||
|
bb2fce9e72 | ||
|
cc55d9e681 | ||
|
3215f2aff3 | ||
|
e376b787cd | ||
|
be1772bc37 | ||
|
f61e73b639 | ||
|
217e9ab2cc | ||
|
d3e25773dc | ||
|
75b64a019d | ||
|
a3bb062260 | ||
|
094e09a6d1 | ||
|
fee6720fb4 | ||
|
620fa10454 | ||
|
e80fee86c5 | ||
|
ad56874b0f | ||
|
6ca8aedcf2 | ||
|
54a3bbf773 | ||
|
59c581b807 | ||
|
8a41ed4d73 | ||
|
4e357b59bd | ||
|
d06462beb0 | ||
|
4fb1d3b75e | ||
|
8e36c2c7b3 | ||
|
133285ba1c | ||
|
0105e626a3 | ||
|
9ff3544535 | ||
|
0aec7061c3 | ||
|
e387a8aaac | ||
|
ae33493da7 | ||
|
57b2c670ab | ||
|
545c175a76 | ||
|
ed27650368 | ||
|
0e574fad7b | ||
|
1edec39462 | ||
|
3bf4e5648f | ||
|
faca71ace7 | ||
|
b3abd6962e | ||
|
cdf8573202 | ||
|
a1ba1d2cd1 | ||
|
247b51b2ab | ||
|
577a997003 | ||
|
32d4c32524 | ||
|
3a063463ca | ||
|
04fe254521 | ||
|
3d79b7983d | ||
|
38ecf366d6 | ||
|
87ddb8033f | ||
|
2c9e4561df | ||
|
84b6afe758 | ||
|
7d6439fefd | ||
|
c4f3296336 | ||
|
675d1316a9 | ||
|
093e4d7146 | ||
|
e0fd978d05 | ||
|
5bdc62eff3 | ||
|
9068d47a44 | ||
|
2e654a0fcc | ||
|
05321679f8 | ||
|
175491f250 | ||
|
1a5ec9ec0f | ||
|
9b90335c63 | ||
|
947b39763f | ||
|
bea7bac3ab | ||
|
2fecd3bd1c | ||
|
f4af643500 | ||
|
e85eb32a0a | ||
|
cb2f9b6177 | ||
|
ad41cae876 | ||
|
a1f67585d9 | ||
|
eae49b3f41 | ||
|
2b46f136fc | ||
|
7a8e5bee57 | ||
|
ba6aaf6775 | ||
|
10eeeda581 | ||
|
e6532deea3 | ||
|
986cf9b896 | ||
|
5de5af3fe8 | ||
|
5c7eaea49e | ||
|
3d10369044 | ||
|
f28a806253 | ||
|
9dcb33aba5 | ||
|
db7e0625bd | ||
|
758e231565 | ||
|
a04425bc67 | ||
|
62c8b61a6b | ||
|
1b40c217f4 | ||
|
449e3d2d68 | ||
|
254ea8dbf6 | ||
|
c2f4d8ed34 | ||
|
e461847a49 | ||
|
ab655cda40 | ||
|
66b62ecca1 | ||
|
94b7d66096 | ||
|
54232b988b | ||
|
120cb8be50 | ||
|
cec0c10286 | ||
|
b95bcadfe9 | ||
|
7f441060f2 | ||
|
40b838aeb9 | ||
|
c176b3d2ff | ||
|
edb93cbaa0 | ||
|
f6224a5a3b | ||
|
fb708ca7b8 | ||
|
4fc2ee18f2 | ||
|
81664c60aa | ||
|
092a74f5b3 | ||
|
1d07215f78 | ||
|
74cb18a7a1 | ||
|
6cb2e0bbb0 | ||
|
eb55d011ca | ||
|
887e88f89c | ||
|
3dd23a7034 | ||
|
9a41db13d8 | ||
|
c33ae2b26e | ||
|
5029b91850 | ||
|
f867dc0479 | ||
|
8cd74adc21 | ||
|
3f5b2bd396 | ||
|
9164611a96 | ||
|
1f6538f0b0 | ||
|
131120d6fc | ||
|
2b6093861c | ||
|
ad1382ec36 | ||
|
861c87efd4 | ||
|
37298f0d5e | ||
|
299c907be6 | ||
|
56a095f0e2 | ||
|
586493e8c6 | ||
|
e43a08c5a1 | ||
|
dc030fe607 | ||
|
2b3c189b07 | ||
|
74e7e1a00f | ||
|
cf6ddba626 | ||
|
f919a3ba6f | ||
|
59772cda6b | ||
|
40b37ccccb | ||
|
ab08dd7de7 | ||
|
600d6cf00e | ||
|
a5734cac10 | ||
|
1b6127dbb6 | ||
|
a0c3fcf416 | ||
|
312a618196 | ||
|
338719d82b | ||
|
156d1421e0 | ||
|
9fabc71464 | ||
|
8e6fc89384 | ||
|
90368927e8 | ||
|
9cd8b22647 | ||
|
97e219ac47 | ||
|
18dd63e7eb | ||
|
cadb72442a | ||
|
78200c7a2c | ||
|
e2ea9c5f22 | ||
|
e883e1b38c | ||
|
a121981e5a | ||
|
7067ad150e | ||
|
b59e7605e9 | ||
|
dec8b54a9e | ||
|
6d4dd11f06 | ||
|
48146f4c34 | ||
|
ad335e2a5c | ||
|
7a9ed38326 | ||
|
31d87f5a9c | ||
|
8c71b946b4 | ||
|
e58b547d59 | ||
|
0511128fea | ||
|
7add40d913 | ||
|
c7f8bf1280 | ||
|
70969a2d03 | ||
|
e7e848917b | ||
|
78a7c98c3e | ||
|
4840b7b0ac | ||
|
169188b2b4 | ||
|
67df88d9a2 | ||
|
cbe10854b9 | ||
|
1588f1cb05 | ||
|
f13816262d | ||
|
ef114f9e64 | ||
|
6a16d416a4 | ||
|
314c647daf | ||
|
b360d27f22 | ||
|
892c6e383f | ||
|
4ff7741a41 | ||
|
c7a8c628e4 | ||
|
aa473958d0 | ||
|
0dda605c36 | ||
|
c2105bfe1f | ||
|
66ab6dde9d | ||
|
d28ac063db | ||
|
3ba2cd6d98 | ||
|
da37dfe17c | ||
|
b5630539df | ||
|
7037582907 | ||
|
daa57cc1a0 | ||
|
d314546b11 | ||
|
e8972aa824 | ||
|
4866867d06 | ||
|
d4fa3f6a2c | ||
|
a72d19b386 | ||
|
b100ed1ae0 | ||
|
9320db8ab7 | ||
|
bc029dee97 | ||
|
1400828452 | ||
|
aac2c5139a | ||
|
ddea5b139e | ||
|
080198f88c | ||
|
4ba10089c3 | ||
|
3e355288cb | ||
|
e279fc7a5d | ||
|
f4eba20295 | ||
|
dab1735a69 | ||
|
bc74b5fbfb | ||
|
65effc121d | ||
|
bb8269052a | ||
|
9982ceb36a | ||
|
24621373dd | ||
|
01c059873f | ||
|
274ef6a796 | ||
|
697a5b5c88 | ||
|
8b081725ce | ||
|
ae0194d5b1 | ||
|
039bfc04c3 | ||
|
13edefa6e0 | ||
|
ffddc18fe6 | ||
|
806a369624 | ||
|
14fdc2520b | ||
|
d9f29257f9 | ||
|
317e048d5a | ||
|
a6cb08714d | ||
|
8446794828 | ||
|
388e6f5916 | ||
|
38163b4abf | ||
|
450c2e3622 | ||
|
7010d9f6e1 | ||
|
60beb9aa58 | ||
|
2ebcb856a3 | ||
|
ce0db1f74f | ||
|
a310d50da4 | ||
|
9515e7de2d | ||
|
5e2025d36c | ||
|
9afb9c631f | ||
|
b7fb13576f | ||
|
9c442468f5 | ||
|
22125c9566 | ||
|
40d3e822e9 | ||
|
a5eaf8a011 | ||
|
ac6bc48f60 | ||
|
7204abce03 | ||
|
c9b4eb9aab | ||
|
86ad2130a9 | ||
|
ad1b6774ae | ||
|
a29e781de0 | ||
|
fb776b3c7f | ||
|
16d17a3ab3 | ||
|
df01fafd06 | ||
|
0c0ed92164 | ||
|
36e774ab6e | ||
|
38908c7d6d | ||
|
74cab50cee | ||
|
0539fd9a69 | ||
|
3cfea2da07 | ||
|
5dee29fbf9 | ||
|
04ef0bf377 | ||
|
259428a946 | ||
|
fd9b2184bf | ||
|
5536050b71 | ||
|
ebfa58293d | ||
|
98d1f74fba | ||
|
ca96fa232f | ||
|
a2e7191604 | ||
|
85b1f872f0 | ||
|
46f056f38e | ||
|
de24c4e90a | ||
|
ee2e80aae4 | ||
|
507e316dfc | ||
|
aa60179992 | ||
|
1d5710151d | ||
|
b01bfe8b49 | ||
|
8502d4b0d9 | ||
|
9282c5ddb9 | ||
|
7acac5cd22 | ||
|
a7e75078bb | ||
|
457fc184d9 | ||
|
42a3294791 | ||
|
059820847a | ||
|
12a4180fc7 | ||
|
66bb1b9905 | ||
|
0706697b13 | ||
|
fc88f17dc5 | ||
|
b7e5bb8656 | ||
|
54117536fd | ||
|
92d421bc68 | ||
|
4f466d95a1 | ||
|
9eb21251cb | ||
|
c3dedef7c9 | ||
|
93990b0055 | ||
|
3b0043af6d | ||
|
582b29a545 | ||
|
9c21b12176 | ||
|
e315bade5a | ||
|
f3bff96968 | ||
|
c299920fc1 | ||
|
3f39bc8f7b | ||
|
51497eebc1 | ||
|
efec61caba | ||
|
2d1021c629 | ||
|
0191652565 | ||
|
9ef028fac6 | ||
|
68b19b332f | ||
|
24e0b17135 | ||
|
849482ea65 | ||
|
2b40967589 | ||
|
ea044268de | ||
|
e49275ee9d | ||
|
2462743edd | ||
|
20852fcb2a | ||
|
900c9ab40b | ||
|
f50d303765 | ||
|
20a01a5ea9 | ||
|
874f4be666 | ||
|
84332ada39 | ||
|
187739f913 | ||
|
45721b3fc5 | ||
|
35765e23e4 | ||
|
77490c6d48 | ||
|
eb3c11c672 | ||
|
7c90b040ef | ||
|
177f345861 | ||
|
f2d06cb18d | ||
|
01e55a380f | ||
|
bad18cafbf | ||
|
e8022bf6a0 | ||
|
76a1663282 | ||
|
c2baf8dc83 | ||
|
31340919de | ||
|
54057134cb | ||
|
2820e811f8 | ||
|
ee09f828ee | ||
|
19c9193412 | ||
|
f66e46ec1d | ||
|
00df2f93a5 | ||
|
5ad8fb3e1b | ||
|
9ca7485f52 | ||
|
151c6c92f9 | ||
|
17b43a9ded | ||
|
256815eff9 | ||
|
7c61888447 | ||
|
43f0b140a6 | ||
|
35a81f8009 | ||
|
7529f5c659 | ||
|
725510f238 | ||
|
6f0a71a330 | ||
|
4792c2eeed | ||
|
d18269fb2d | ||
|
9c7abcab91 | ||
|
add4e526a9 | ||
|
4d0af5d3ca | ||
|
54d418725e | ||
|
1da46e89e1 | ||
|
342697e983 | ||
|
e638f63f6e | ||
|
7d7e46f7ec | ||
|
60ef2c5d26 | ||
|
d481b3c534 | ||
|
ced20877c2 | ||
|
8fc3747229 | ||
|
c4a03e0434 | ||
|
5166731e5a | ||
|
0778812560 | ||
|
54a1bb4991 | ||
|
730e35150a | ||
|
fe63225065 | ||
|
5572f937f0 | ||
|
7c65e0f972 | ||
|
92c257d1e5 | ||
|
3632a06c4b | ||
|
f139a88cdb | ||
|
98b8e6841f | ||
|
8a90b15a36 | ||
|
2529309e9e | ||
|
41a0b2f5d5 | ||
|
e8a443020a | ||
|
720ff1606f | ||
|
250e481104 | ||
|
bb645d82e1 | ||
|
bb573c63ef | ||
|
d44bc6c019 | ||
|
0aad738d0e | ||
|
74d49aebbc | ||
|
dcea8cde98 | ||
|
4a64bba6c8 | ||
|
906d28117c | ||
|
715321ad4a | ||
|
9ce6592c5e | ||
|
6c28c71e7f | ||
|
c669c101e2 | ||
|
ba801e4e13 | ||
|
d8f2976830 | ||
|
7c5b3a643b | ||
|
0a6de645e0 | ||
|
52bd4ba3d8 | ||
|
197c84d556 | ||
|
fef75d878a | ||
|
0783c4ec1d | ||
|
907474c489 | ||
|
ed82f9d9d6 | ||
|
39812b3c36 | ||
|
4d2febfb42 | ||
|
850ceff343 | ||
|
1776fc8aec | ||
|
d9f1bfb12a | ||
|
78ef2cc23c | ||
|
f3ad4093c7 | ||
|
aada654e60 | ||
|
69ec0e7db0 | ||
|
90f0a6d6c4 | ||
|
bf6d4510a6 | ||
|
020fb5c0cb | ||
|
224fcbce78 | ||
|
a51118cf35 | ||
|
f6719554e6 | ||
|
a3061f55d6 | ||
|
e04d5cac04 | ||
|
598cb2e70e | ||
|
cbceb5c79f | ||
|
f1aa6d2e25 | ||
|
d6abf1a93a | ||
|
4373e1ac61 | ||
|
7ce15903bf | ||
|
7f2310150b | ||
|
2ee0a01959 | ||
|
e68898daf4 | ||
|
5a737e988b | ||
|
629bbfb757 | ||
|
0a087e40ae | ||
|
ee2c70c389 | ||
|
4f88325d3a | ||
|
b697be894d | ||
|
d3abd9f2ac | ||
|
16beff078d | ||
|
d0bb3f5815 | ||
|
14c5c820c1 | ||
|
edc1c366fe | ||
|
daadd42b63 | ||
|
ae25b518a7 | ||
|
f20a463c36 | ||
|
aacf9528a2 | ||
|
6b649bee1a | ||
|
f0b4b8e43d | ||
|
56fccbe621 | ||
|
a8e1db3e58 | ||
|
b29b69647d | ||
|
5efc8ca461 | ||
|
cdd59a1e12 | ||
|
a4ddef6826 | ||
|
afa366259e | ||
|
5659dde498 | ||
|
cde7b12555 | ||
|
ba8221d3ee | ||
|
2df7a7c67b | ||
|
91050fc3bd | ||
|
7a478fb545 | ||
|
9b58ce1a42 | ||
|
04f7492e6b | ||
|
eeaaede75e | ||
|
9218eb671e | ||
|
30ac85b9c0 | ||
|
1b70ca4979 | ||
|
6985ef40e2 | ||
|
da7ac10987 | ||
|
a27585d2a6 | ||
|
7a6ad9f363 | ||
|
21156bb606 | ||
|
c1631a1abf | ||
|
c0623f9134 | ||
|
b4098867bb | ||
|
154923a039 | ||
|
b5e5199eeb | ||
|
ea99fb0584 | ||
|
0c9bec8af3 | ||
|
59e570d8e2 | ||
|
269cb6cfd1 | ||
|
89618601b1 | ||
|
0eaeaf60f8 | ||
|
611c661d00 | ||
|
ae4be77382 | ||
|
d4fad7e8eb | ||
|
3a620f8b92 | ||
|
7c4f50c4fa | ||
|
bcf57a4e19 | ||
|
8dd454af4d | ||
|
3231f86f87 | ||
|
3733da1af7 | ||
|
03de883889 | ||
|
00360bbe64 | ||
|
348ba1ecd9 | ||
|
6ec2408e44 | ||
|
1334d461b4 | ||
|
0f8f7da2b8 | ||
|
8f92e63f45 | ||
|
4873e1f020 | ||
|
8b458e61fb | ||
|
2258d91208 | ||
|
86cc85bf8f | ||
|
246e66d9be | ||
|
5d00a41d66 | ||
|
520de12f58 | ||
|
17d75813a7 | ||
|
1c7f124977 | ||
|
3eca669004 | ||
|
416b461291 | ||
|
138ff8f94d | ||
|
b9c4789198 | ||
|
140b5bf7d4 | ||
|
1a75e9f0de | ||
|
10339131fd | ||
|
d699dc98b8 | ||
|
c1b7d96dd1 | ||
|
3da7d4ed55 | ||
|
8295c686e9 | ||
|
18e0d3b6b6 | ||
|
b1644c01e5 | ||
|
c6789ece7b | ||
|
ede695bcd6 | ||
|
9c21b59827 | ||
|
ba67de8d9a | ||
|
57934c1bc9 | ||
|
1033c98028 | ||
|
b3dc584fb0 | ||
|
1192f7b584 | ||
|
fe0730c4d3 | ||
|
c9fb116464 | ||
|
facf9aec25 | ||
|
0c37816c28 | ||
|
ce124e0aa0 | ||
|
91678645a7 | ||
|
c9a6d7688e | ||
|
eba2cea5ca | ||
|
e68748a158 | ||
|
a43cf5cf8a | ||
|
6996c5fc47 | ||
|
df53415d98 | ||
|
863a9d3671 | ||
|
b63f232ebe | ||
|
a79a4b4a4c | ||
|
26e377e3a1 | ||
|
d8d2cc2412 | ||
|
ced5c39401 | ||
|
1d12b788ea | ||
|
ca7542f39a | ||
|
f42d7e9fcc | ||
|
8515b20327 | ||
|
0ee3225026 | ||
|
05a55c0735 | ||
|
2aae3592c4 | ||
|
ae4c010564 | ||
|
2b60a1e0a7 | ||
|
97e672cf4d | ||
|
50b84e06fe | ||
|
e7ddcd6b82 | ||
|
c198c24a22 | ||
|
5c4d499e14 | ||
|
ab831ed2db | ||
|
64d7994c71 | ||
|
031fcab519 | ||
|
f625e36f5c | ||
|
017dc307b2 | ||
|
aa57d072bc | ||
|
4ccfa0cc56 | ||
|
1fccde2892 | ||
|
ce0ced0c44 | ||
|
26f4532cb8 |
251 changed files with 22365 additions and 119 deletions
180
.github/workflows/ppa-release.yml
vendored
Normal file
180
.github/workflows/ppa-release.yml
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
build_rpm:
|
||||
env:
|
||||
REPO_URL: ${{ github.server_url }}/audetto/AppleWin
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os_name: fedora
|
||||
os_codename: '34'
|
||||
build_type: 'QT'
|
||||
|
||||
- os_name: fedora
|
||||
os_codename: '35'
|
||||
build_type: 'QT'
|
||||
|
||||
container: ${{ matrix.os_name }}:${{ matrix.os_codename }}
|
||||
|
||||
steps:
|
||||
- run: dnf -y update && dnf -y install git ca-certificates curl gpg rsync file && dnf clean all
|
||||
|
||||
- name: Import GPG key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v4
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.PACKAGE_SIGNING_KEY }}
|
||||
passphrase: ''
|
||||
|
||||
- run: git config --global --add safe.directory /__w/AppleWin/AppleWin && git init && git remote add origin "$REPO_URL" && git fetch origin ${{ github.ref }} && git checkout ${{ github.ref_name }} && git submodule init && git submodule update
|
||||
|
||||
- run: CPACK_TYPE=RPM INSTALL_DEPS=${{ matrix.build_type }} bash ./source/linux/build.sh
|
||||
|
||||
- run: cd build/packages && rm -rf ./_CPack_Packages
|
||||
- run: cd build/packages && dpkg-scanpackages --multiversion . > Packages
|
||||
- run: cd build/packages && gzip -k -f Packages
|
||||
|
||||
- run: cd build/packages && apt-ftparchive release . > Release
|
||||
- run: cd build/packages && gpg --default-key "${{ secrets.PACKAGE_SIGNING_EMAIL }}" -abs -o - Release > Release.gpg
|
||||
- run: cd build/packages && gpg --default-key "${{ secrets.PACKAGE_SIGNING_EMAIL }}" --clearsign -o - Release > InRelease
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os_name }}-${{ matrix.os_codename }}
|
||||
path: ./build/packages/**/*
|
||||
|
||||
|
||||
|
||||
build_deb:
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
REPO_URL: ${{ github.server_url }}/audetto/AppleWin
|
||||
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os_name: debian
|
||||
os_codename: bullseye
|
||||
build_type: 'QT'
|
||||
has_backports: backports
|
||||
|
||||
- os_name: debian
|
||||
os_codename: bookworm
|
||||
build_type: 'QT'
|
||||
|
||||
|
||||
- os_name: ubuntu
|
||||
os_codename: bionic
|
||||
is_lts: lts
|
||||
build_type: 'WITHOUT_QT'
|
||||
|
||||
- os_name: ubuntu
|
||||
os_codename: focal
|
||||
is_lts: lts
|
||||
build_type: 'QT'
|
||||
|
||||
- os_name: ubuntu
|
||||
os_codename: hirsute
|
||||
build_type: 'QT'
|
||||
|
||||
- os_name: ubuntu
|
||||
os_codename: impish
|
||||
build_type: 'QT'
|
||||
|
||||
container: ${{ matrix.os_name }}:${{ matrix.os_codename }}
|
||||
|
||||
steps:
|
||||
- run: apt-get update && apt-get install -y --no-install-recommends git ca-certificates apt-transport-https curl gpg gpg-agent lsb-release dpkg-dev apt-utils rsync file
|
||||
|
||||
- name: Import GPG key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v4
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.PACKAGE_SIGNING_KEY }}
|
||||
passphrase: ''
|
||||
|
||||
- if: matrix.has_backports == 'backports'
|
||||
run: echo 'deb http://deb.debian.org/debian ${{ matrix.os_codename }}-backports main' >> /etc/apt/sources.list
|
||||
|
||||
- if: matrix.is_lts == 'lts'
|
||||
run: curl -L https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
|
||||
- if: matrix.is_lts == 'lts'
|
||||
run: echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/${{ matrix.os_name }}/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/kitware.list >/dev/null
|
||||
|
||||
- run: git config --global --add safe.directory /__w/AppleWin/AppleWin && git init && git remote add origin "$REPO_URL" && git fetch origin ${{ github.ref }} && git checkout ${{ github.ref_name }} && git submodule init && git submodule update
|
||||
|
||||
- run: INSTALL_DEPS=${{ matrix.build_type }} bash ./source/linux/build.sh
|
||||
|
||||
- run: cd build/packages && rm -rf ./_CPack_Packages
|
||||
- run: cd build/packages && dpkg-scanpackages --multiversion . > Packages
|
||||
- run: cd build/packages && gzip -k -f Packages
|
||||
|
||||
- run: cd build/packages && apt-ftparchive release . > Release
|
||||
- run: cd build/packages && gpg --default-key "${{ secrets.PACKAGE_SIGNING_EMAIL }}" -abs -o - Release > Release.gpg
|
||||
- run: cd build/packages && gpg --default-key "${{ secrets.PACKAGE_SIGNING_EMAIL }}" --clearsign -o - Release > InRelease
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os_name }}-${{ matrix.os_codename }}
|
||||
path: ./build/packages/**/*
|
||||
|
||||
deploy:
|
||||
needs:
|
||||
- build_deb
|
||||
- build_rpm
|
||||
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- os_name: fedora
|
||||
os_codename: '34'
|
||||
- os_name: fedora
|
||||
os_codename: '35'
|
||||
|
||||
- os_name: debian
|
||||
os_codename: bullseye
|
||||
- os_name: debian
|
||||
os_codename: bookworm
|
||||
|
||||
- os_name: ubuntu
|
||||
os_codename: bionic
|
||||
- os_name: ubuntu
|
||||
os_codename: focal
|
||||
- os_name: ubuntu
|
||||
os_codename: hirsute
|
||||
- os_name: ubuntu
|
||||
os_codename: impish
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: audetto/AppleWin
|
||||
fetch-depth: 1
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os_name }}-${{ matrix.os_codename }}
|
||||
path: packages
|
||||
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@v4.3.0
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: packages
|
||||
target-folder: packages/${{ matrix.os_name }}/dists/${{ matrix.os_codename }}
|
65
.gitignore
vendored
65
.gitignore
vendored
|
@ -183,3 +183,68 @@ UpgradeLog*.htm
|
|||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# Linux
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
*.so
|
||||
*.a
|
||||
Disks/
|
||||
|
||||
# Qt
|
||||
# C++ objects and libs
|
||||
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.a
|
||||
*.la
|
||||
*.lai
|
||||
*.so
|
||||
*.dll
|
||||
*.dylib
|
||||
|
||||
# Qt-es
|
||||
|
||||
object_script.*.Release
|
||||
object_script.*.Debug
|
||||
*_plugin_import.cpp
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.qbs.user
|
||||
*.qbs.user.*
|
||||
*.moc
|
||||
moc_*.cpp
|
||||
moc_*.h
|
||||
qrc_*.cpp
|
||||
ui_*.h
|
||||
*.qmlc
|
||||
*.jsc
|
||||
Makefile*
|
||||
*build-*
|
||||
|
||||
# Qt unit tests
|
||||
target_wrapper.*
|
||||
|
||||
# QtCreator
|
||||
|
||||
*.autosave
|
||||
|
||||
# QtCtreator Qml
|
||||
*.qmlproject.user
|
||||
*.qmlproject.user.*
|
||||
|
||||
# QtCtreator CMake
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# VSCode
|
||||
.vscode
|
||||
|
|
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
[submodule "source/frontends/qt/QHexView"]
|
||||
path = source/frontends/qt/QHexView
|
||||
url = ../../Dax89/QHexView.git
|
||||
[submodule "source/frontends/sdl/imgui/imgui"]
|
||||
path = source/frontends/sdl/imgui/imgui
|
||||
url = ../../ocornut/imgui
|
||||
[submodule "source/frontends/sdl/imgui/imgui_club"]
|
||||
path = source/frontends/sdl/imgui/imgui_club
|
||||
url = ../../ocornut/imgui_club
|
38
.travis.yml
Normal file
38
.travis.yml
Normal file
|
@ -0,0 +1,38 @@
|
|||
language: cpp
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
||||
# disabled as too expensive
|
||||
# - name: "AppleWin on Windows"
|
||||
# os: windows
|
||||
# script: ./CIBuild.bat
|
||||
|
||||
- name: "AppleWin on Linux"
|
||||
os: linux
|
||||
dist: focal
|
||||
arch: amd64
|
||||
|
||||
script: source/linux/build.sh
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- cmake
|
||||
- libyaml-dev
|
||||
- libminizip-dev
|
||||
- qtbase5-dev
|
||||
- qtmultimedia5-dev
|
||||
- libqt5gamepad5-dev
|
||||
- libboost-program-options-dev
|
||||
- libncurses-dev
|
||||
- libevdev-dev
|
||||
- libsdl2-dev
|
||||
- libsdl2-image-dev
|
||||
- libgles-dev
|
||||
- libpcap-dev
|
||||
- libslirp-dev
|
|
@ -114,6 +114,7 @@
|
|||
<ClInclude Include="source\StrFormat.h" />
|
||||
<ClInclude Include="source\SynchronousEventManager.h" />
|
||||
<ClInclude Include="source\Tape.h" />
|
||||
<ClInclude Include="source\Tfe\IPRaw.h" />
|
||||
<ClInclude Include="source\Tfe\NetworkBackend.h" />
|
||||
<ClInclude Include="source\Tfe\Bpf.h" />
|
||||
<ClInclude Include="source\Tfe\Ip6_misc.h" />
|
||||
|
@ -222,6 +223,7 @@
|
|||
<ClCompile Include="source\StrFormat.cpp" />
|
||||
<ClCompile Include="source\SynchronousEventManager.cpp" />
|
||||
<ClCompile Include="source\Tape.cpp" />
|
||||
<ClCompile Include="source\Tfe\IPRaw.cpp" />
|
||||
<ClCompile Include="source\Tfe\NetworkBackend.cpp" />
|
||||
<ClCompile Include="source\Tfe\PCapBackend.cpp" />
|
||||
<ClCompile Include="source\Tfe\tfearch.cpp">
|
||||
|
@ -467,7 +469,7 @@
|
|||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalManifestDependencies>"type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"</AdditionalManifestDependencies>
|
||||
<MinimumRequiredVersion>5.01</MinimumRequiredVersion>
|
||||
</Link>
|
||||
|
@ -495,7 +497,7 @@
|
|||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalManifestDependencies>"type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"</AdditionalManifestDependencies>
|
||||
<MinimumRequiredVersion>5.01</MinimumRequiredVersion>
|
||||
</Link>
|
||||
|
@ -522,7 +524,7 @@
|
|||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalManifestDependencies>"type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"</AdditionalManifestDependencies>
|
||||
<MinimumRequiredVersion>5.01</MinimumRequiredVersion>
|
||||
</Link>
|
||||
|
@ -553,7 +555,7 @@
|
|||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
<AdditionalManifestDependencies>"type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"</AdditionalManifestDependencies>
|
||||
<MinimumRequiredVersion>5.01</MinimumRequiredVersion>
|
||||
|
@ -586,7 +588,7 @@
|
|||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
<AdditionalManifestDependencies>"type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"</AdditionalManifestDependencies>
|
||||
<MinimumRequiredVersion>5.01</MinimumRequiredVersion>
|
||||
|
@ -619,7 +621,7 @@
|
|||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
<AdditionalManifestDependencies>"type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"</AdditionalManifestDependencies>
|
||||
<MinimumRequiredVersion>5.01</MinimumRequiredVersion>
|
||||
|
|
|
@ -259,6 +259,9 @@
|
|||
<ClCompile Include="source\Uthernet2.cpp">
|
||||
<Filter>Source Files\Uthernet</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="source\Tfe\IPRaw.cpp">
|
||||
<Filter>Source Files\Uthernet</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="source\CommonVICE\6510core.h">
|
||||
|
@ -594,6 +597,9 @@
|
|||
<ClInclude Include="source\Uthernet2.h">
|
||||
<Filter>Source Files\Uthernet</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="source\Tfe\IPRaw.h">
|
||||
<Filter>Source Files\Uthernet</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="resource\Applewin.bmp">
|
||||
|
|
10
CIBuild.bat
Normal file
10
CIBuild.bat
Normal file
|
@ -0,0 +1,10 @@
|
|||
rem Commands to build AppleWin on travis
|
||||
|
||||
setlocal
|
||||
|
||||
choco install visualstudio2019community
|
||||
choco install visualstudio2019-workload-nativedesktop
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsMSBuildCmd.bat"
|
||||
|
||||
MSBuild.exe /p:Configuration=Release AppleWinExpress2019.sln
|
94
CMakeLists.txt
Normal file
94
CMakeLists.txt
Normal file
|
@ -0,0 +1,94 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(applewin HOMEPAGE_URL "https://github.com/audetto/AppleWin")
|
||||
|
||||
option(BUILD_APPLEN "build ncurses frontend")
|
||||
option(BUILD_QAPPLE "build Qt5 frontend")
|
||||
option(BUILD_SA2 "build SDL2 frontend")
|
||||
option(BUILD_LIBRETRO "build libretro core")
|
||||
|
||||
if (NOT (BUILD_APPLEN OR BUILD_QAPPLE OR BUILD_SA2 OR BUILD_LIBRETRO))
|
||||
message(NOTICE "Building everything by default")
|
||||
set(BUILD_APPLEN ON)
|
||||
set(BUILD_QAPPLE ON)
|
||||
set(BUILD_SA2 ON)
|
||||
set(BUILD_LIBRETRO ON)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_compile_options(-Werror=return-type)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
add_compile_options(-Werror=format -Wno-error=format-overflow -Wno-error=format-truncation -Wno-psabi)
|
||||
endif()
|
||||
|
||||
MESSAGE("CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||
MESSAGE("CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
|
||||
MESSAGE("CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
MESSAGE("CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}")
|
||||
MESSAGE("CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
|
||||
|
||||
# this only affects common2, the others are already build with fPIC by default
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
execute_process(COMMAND uname -n
|
||||
OUTPUT_VARIABLE UNAME
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if(${UNAME} STREQUAL raspberrypi)
|
||||
# it is too slow and might cause out of memory issues
|
||||
# more forensic is required
|
||||
MESSAGE(NOTICE "Raspberry Pi detected: IPO disabled")
|
||||
else()
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported()
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE)
|
||||
endif()
|
||||
|
||||
include_directories(source)
|
||||
|
||||
add_subdirectory(source)
|
||||
add_subdirectory(source/linux/libwindows)
|
||||
add_subdirectory(test/TestCPU6502)
|
||||
|
||||
if (BUILD_LIBRETRO OR BUILD_APPLEN OR BUILD_SA2)
|
||||
add_subdirectory(source/frontends/common2)
|
||||
endif()
|
||||
|
||||
if (BUILD_APPLEN)
|
||||
add_subdirectory(source/frontends/ncurses)
|
||||
endif()
|
||||
|
||||
if (BUILD_QAPPLE)
|
||||
add_subdirectory(source/frontends/qt)
|
||||
endif()
|
||||
|
||||
if (BUILD_SA2)
|
||||
add_subdirectory(source/frontends/sdl)
|
||||
endif()
|
||||
|
||||
if (BUILD_LIBRETRO)
|
||||
add_subdirectory(source/frontends/libretro)
|
||||
endif()
|
||||
|
||||
file(STRINGS resource/version.h VERSION_FILE LIMIT_COUNT 1)
|
||||
string(REGEX MATCH "#define APPLEWIN_VERSION (.*)" _ ${VERSION_FILE})
|
||||
string(REPLACE "," "." VERSION ${CMAKE_MATCH_1})
|
||||
|
||||
set(CPACK_PACKAGE_VERSION ${VERSION})
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Apple ][ emulator for Linux")
|
||||
set(CPACK_PACKAGE_CONTACT "audetto <mariofutire@gmail.com>")
|
||||
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS "ON")
|
||||
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS "ON")
|
||||
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "Applications/Emulators")
|
||||
# RPM dependencies are automatic
|
||||
|
||||
include(CPack)
|
|
@ -15,7 +15,7 @@ Peripheral cards and add-on hardware supported:
|
|||
- Apple IIe Extended 80-Column Text Card and RamWorks III (8MB)
|
||||
- RGB cards: Apple's Extended 80-Column Text/AppleColor Adaptor Card, 'Le Chat Mauve' Féline and Eve.
|
||||
- CP/M SoftCard
|
||||
- Uthernet I (ethernet card)
|
||||
- Uthernet I and II (ethernet cards)
|
||||
- Language Card and Saturn 64/128K for Apple II/II+
|
||||
- 4Play and SNES MAX joystick cards
|
||||
- VidHD card (functionality limited to IIgs' Super Hi-Res video modes)
|
||||
|
|
|
@ -8,6 +8,19 @@ https://github.com/AppleWin/AppleWin/issues/new
|
|||
|
||||
Tom Charlesworth
|
||||
|
||||
|
||||
1.30.9.0 - 23 Mar 2022
|
||||
----------------------
|
||||
. [Change #518] Support Uthernet II card in slot 3. [audetto]
|
||||
- EG. Use with Contiki, A2osX, ii-vision, a2stream etc.
|
||||
- Support for W5100 modes: TCP, UDP, IPRAW and MACRAW (no support for PPPoE mode, interrupts and SPI).
|
||||
. [Bug #1066] Fix for save-states where (eg) disk image name contains '#' character.
|
||||
. [Bug #1017] Fix for printer interface where character got output twice.
|
||||
. [PR #1031 + others] Internal: refactor string output handling. [kiyolee]
|
||||
. Change: default install of AppleWin now sets slot 3 as empty (was Uthernet I card)
|
||||
. Fix 6522 bug: IFR.T2 was always set when counter.b15=1
|
||||
|
||||
|
||||
1.30.8.0 - 8 Feb 2022
|
||||
---------------------
|
||||
. [Bug #1023] WOZ support: Tweak to track sync support.
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
X 2002:2003
|
||||
Released post 1.30.7.0
|
||||
|
||||
2.9.1.0 Added: Bookmarks now have their own indicator (a number with a box around it) and replace the ":" seperator. Updated Debug_Font.bmp
|
||||
2.9.1.0 Added: Bookmarks now have their own indicator (a number with a box around it) and replace the ":" separator. Updated Debug_Font.bmp
|
||||
|
||||
.18 Fixed: Resetting bookmarks wasn't setting the total bookmarks back to zero.
|
||||
.17 Fixed: If all bookmarks were used then setting a new one wouldn't update an existing one to the new address.
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<p style="MARGIN-LEFT: 40px">Bob Sander-Cederlof: Applesoft Symbols (<a href="http://www.txbobsc.com/scsc/scdocumentor/index.html">http://www.txbobsc.com/scsc/scdocumentor/</a> S-C DocuMentor: Applesoft)</p>
|
||||
<p style="MARGIN-LEFT: 40px">David Schmidt: Updates to this help file</p>
|
||||
<p style="MARGIN-LEFT: 40px">Mike Harvey, Founder & Editor of Nibble Magazine: For providing us Apple fans the pleasure of eagerly awaiting each next month's issue to learn about the Apple! (<a href="http://www.nibblemagazine.com/">http://www.nibblemagazine.com/</a>)</p>
|
||||
<p style="MARGIN-LEFT: 40px">Andrea Odetti: working on making the source code more portable</p>
|
||||
<p style="MARGIN-LEFT: 40px">Andrea Odetti: working on making the source code more portable & Uthernet II card support</p>
|
||||
<p style="MARGIN-LEFT: 40px">Iván Izaguirre: Taiwanese Copam Base64A Apple II clone</p>
|
||||
<p style="MARGIN-LEFT: 40px">Arnaud C: debugger suggestions and help with 6502/6522/video timing issues</p>
|
||||
<p style="MARGIN-LEFT: 40px">Cyril Lambin: RGB card/monitor rendering, debugger improvements</p>
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
<strong>Ethernet Settings...:</strong><br>
|
||||
This allows to choose which network interface card (NIC) you want to
|
||||
use with the Uthernet card.<br>
|
||||
use with the Uthernet or Uthernet II card.<br>
|
||||
<br>
|
||||
|
||||
<strong>Emulation Speed Control:</strong><br>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<li>Parallel Printer card</li>
|
||||
<li>Super Serial card</li>
|
||||
<li>No-Slot clock</li>
|
||||
<li>Uthernet card</li>
|
||||
<li>Uthernet & Uthernet II cards</li>
|
||||
<li>4Play & SNES MAX joystick cards</li>
|
||||
<li>VidHD card</li>
|
||||
</ul>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<li><a href="sound.html">Sound</a>
|
||||
<li><a href="clock.html">Clock</a>
|
||||
<li><a href="card-ssc.html">Super Serial card</a>
|
||||
<li><a href="uthernet.html">Uthernet network card</a>
|
||||
<li><a href="uthernet.html">Uthernet network cards</a>
|
||||
<li><a href="configuration.html">AppleWin Configuration</a>
|
||||
<li><a href="dbg-toc-intro.html">Using the Debugger</a>
|
||||
<li><a href="resources.html">Resources</a></li>
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Uthernet network card</title>
|
||||
<title>Uthernet network cards</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
</head>
|
||||
<body style="FONT-FAMILY: verdana; BACKGROUND-COLOR: rgb(255,255,255)" alink="#008000"
|
||||
link="#008000" vlink="#008000">
|
||||
<h2 style="COLOR: rgb(0,128,0)">Uthernet network card</h2>
|
||||
<h2 style="COLOR: rgb(0,128,0)">Uthernet network cards</h2>
|
||||
<hr size="4">
|
||||
<p style="FONT-WEIGHT: bold">Overview:
|
||||
</p>
|
||||
<P>The Uthernet network card coupled with the Contiki OS allows you to browse the
|
||||
<P>The Uthernet network cards coupled with the Contiki OS allow you to browse the
|
||||
internet on your Apple.</P>
|
||||
<P style="FONT-WEIGHT: bold">Acknowledgment:
|
||||
</P>
|
||||
<P>Uthernet (TFE) support in Applewin was made possible by implementing the GPL
|
||||
<P>Uthernet (TFE) support in AppleWin was made possible by implementing the GPL
|
||||
source written by Spiro Trikaliotis for the Vice emulator - <A href="http://vice-emu.sourceforge.net/index.html#developers">
|
||||
http://vice-emu.sourceforge.net/index.html#developers</A></P>
|
||||
<P><A href="https://a2retrosystems.com/">Uthernet II</A> support in AppleWin has been contributed by Andrea (audetto) Odetti.</P>
|
||||
<P style="FONT-WEIGHT: bold">Details:
|
||||
</P>
|
||||
<P>To enable ethernet support in AppleWin you must first download and install
|
||||
|
@ -35,15 +36,15 @@
|
|||
<P>After AppleWin starts, select the settings icon and then select the ethernet
|
||||
settings button.
|
||||
</P>
|
||||
<P>Uthernet will be disabled. Select Uthernet from the list of available ethernet
|
||||
emulations (currently the only one).
|
||||
<P>Uthernet will be disabled. Select Uthernet or Uthernet II from the list of available ethernet
|
||||
emulations.
|
||||
</P>
|
||||
<P>Select the ethernet interface you want to work with. This must be a physical
|
||||
ethernet interface.
|
||||
</P>
|
||||
<P>If you have more than one interface you may need to select them in turn in order
|
||||
to get the text description for each interface vs what Npcap likes to use for
|
||||
a reference. Select Ok. and then close AppleWin.
|
||||
a reference.
|
||||
</P>
|
||||
<P><span style="font-weight: bold;">Note:</span> Wireless does not work
|
||||
with WinPcap (but see <A href="uthernet-wifi-workaround.html">WiFi Workaround</A>).
|
||||
|
@ -56,7 +57,7 @@
|
|||
also grab a copy of the Uthernet/Contiki getting started guide <A href="http://www.a2retrosystems.com/a2UtherManual.pdf">
|
||||
http://www.a2retrosystems.com/a2UtherManual.pdf</A>
|
||||
</P>
|
||||
<P>When you run AppleWin again, select the contiki80pri.dsk image. Boot AppleWin.
|
||||
<P>Select the contiki80pri.dsk image. Boot AppleWin.
|
||||
</P>
|
||||
<P>Once Contiki is loaded then press Enter to clear the welcome screen and press
|
||||
ESC for a menu.
|
||||
|
@ -87,5 +88,14 @@
|
|||
if you are still having difficulty then you should refer to the VICE network
|
||||
support page for additional information - <A href="http://vicekb.trikaliotis.net/13-005.shtml">
|
||||
http://vicekb.trikaliotis.net/13-005.shtml</A></P>
|
||||
</body>
|
||||
<P style="FONT-WEIGHT: bold">Uthernet II:
|
||||
</P>
|
||||
<P>Most features of the Uthernet II are emulated, with the following caveats:
|
||||
<ul>
|
||||
<li>PPPoE, interrupts and SPI are not implemented</li>
|
||||
<li>server side is not well tested</li>
|
||||
<li>after loading a save-state file, TCP and UDP sockets are closed</li>
|
||||
</ul>
|
||||
</P>
|
||||
</body>
|
||||
</html>
|
||||
|
|
176
linux.md
Normal file
176
linux.md
Normal file
|
@ -0,0 +1,176 @@
|
|||
# Linux
|
||||
|
||||
## Structure
|
||||
|
||||
There are 4 projects
|
||||
|
||||
* libapple: the core emulator files
|
||||
* applen: a frontend based on ncurses
|
||||
* qapple: Qt frontend
|
||||
* sa2: SDL2 frontend
|
||||
* libra2: a libretro core
|
||||
|
||||
The main goal is to reuse the AppleWin source files without changes: only where really necessary the AppleWin source files have
|
||||
been modified.
|
||||
|
||||
## What works
|
||||
|
||||
Almost everything works, except the serial port, SNES-MAX and FourPlay.
|
||||
|
||||
The UI has been rewritten in Qt or ImGui.
|
||||
|
||||
The rest works very well.
|
||||
Uthernet I is supported via `libpcap`, but it requires elevated capabilities:
|
||||
|
||||
`sudo setcap cap_net_raw=ep ./sa2`
|
||||
|
||||
Unfortunately, this must be reapplied after every build.
|
||||
|
||||
Most of the debugger now works (in the ImGui version).
|
||||
|
||||
## New features
|
||||
|
||||
Uthernet II is supported too and by default uses `libslirp` which does *not* require elevated capabilities. Use the ImGui settings to enable it.
|
||||
|
||||
`libslirp` is not packaged on Raspberry Pi OS. `libpcap` will be used instead, unless the user manually compiles and installs [libslirp](https://gitlab.freedesktop.org/slirp/libslirp).
|
||||
|
||||
Audio files can be read via the cassette interface (SDL Version). Just drop a `wav` file into the emulator. Tested with all the formats from [asciiexpress](https://asciiexpress.net/).
|
||||
|
||||
## Executables
|
||||
|
||||
### sa2
|
||||
|
||||
This is your best choice, in particular the ImGui version.
|
||||
|
||||
TL;DR: just run ``sa2``
|
||||
|
||||
See [sa2](source/frontends/sdl/README.md) for more details.
|
||||
|
||||
### applen
|
||||
|
||||
Frontend based on ncurses, with a ASCII art graphic mode.
|
||||
|
||||
Keyboard shortcuts
|
||||
|
||||
* ``F2``: reset the machine
|
||||
* ``F3``: terminate the emulator
|
||||
* ``F11``, ``F12``: Save, Load Snapshot
|
||||
* ``ALT-RIGHT``: wider hi res graphis
|
||||
* ``ALT-LEFT``: narrower hi res graphics
|
||||
* ``ALT-UP``: vertical hi res (smaller)
|
||||
* ``ALT-DOWN``: vertical hi res (bigger)
|
||||
|
||||
In order to properly appreciate the wider hi res graphics, open a big terminal window and choose a small font size.
|
||||
Try ``CTRL-`` as well if ``ALT-`` does not work: terminals do not report a consistent keycode for these combinations.
|
||||
|
||||
The joystick uses evdev (``--device-name /dev/input/by-id/id_of_device``).
|
||||
|
||||
### qapple
|
||||
|
||||
This is based on Qt.
|
||||
|
||||
* keyboard shortcuts are listed in the menu entries
|
||||
* joystick: it uses QtGamepad
|
||||
* emulator runs in the main UI thread
|
||||
* Qt timers are very coarse: the emulator needs to dynamically adapt the cycles to execute
|
||||
* the app runs at 60FPS with correction for uneven timer deltas.
|
||||
* full speed when disk spins execute up to 5 ms real wall clock of emulator code (then returns to Qt)
|
||||
* audio is supported and there are a few configuration options to tune the latency (default very conservative 200ms)
|
||||
* Open Apple and Solid Apple can be emulated using AltGr and Menu (unfortunately, Alt does not work well)
|
||||
* ``yaml`` files can be dropped to restore a saved state
|
||||
|
||||
### ra2
|
||||
|
||||
There is an initial [libretro](https://docs.libretro.com/development/cores/developing-cores/) core.
|
||||
|
||||
A retropad can be plugged in port 1 (with or without analog stick).
|
||||
|
||||
Keyboard emulation
|
||||
|
||||
* ``JOYPAD_R``: equivalent to ``F9`` to cycle video types
|
||||
* ``JOYPAD_L``: equivalent to ``CTRL-SHIFT-F6`` to cycle 50% scan lines
|
||||
* ``START``: equivalent to ``F2`` to reset the machine
|
||||
|
||||
In order to have a better experience with the keyboard, one should probably enable *Game Focus Mode* (normally Scroll-Lock) to disable hotkeys.
|
||||
|
||||
Video works, but the vertical flip is done in software.
|
||||
|
||||
Audio (speaker) works.
|
||||
|
||||
Easiest way to run from the ``build`` folder:
|
||||
``retroarch -L source/frontends/libretro/applewin_libretro.so ../bin/MASTER.DSK``
|
||||
|
||||
## Build
|
||||
|
||||
The project can be built using cmake from the top level directory.
|
||||
|
||||
qapple can be managed from Qt Creator as well and the 2 have coexisted so far, but YMMV.
|
||||
|
||||
### Checkout
|
||||
|
||||
```
|
||||
git clone https://github.com/audetto/AppleWin.git --recursive
|
||||
cd AppleWin
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=RELEASE ..
|
||||
make
|
||||
```
|
||||
|
||||
### Frontend selection
|
||||
|
||||
There are 4 `cmake` variables to selectively enable frontends: `BUILD_APPLEN`, `BUILD_QAPPLE`, `BUILD_SA2` and `BUILD_LIBRETRO`.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
cmake -DBUILD_SA2=ON -DBUILD_LIBRETRO=ON ..
|
||||
```
|
||||
|
||||
or use `cmake-gui` (if none is selected, they are all built).
|
||||
|
||||
### Fedora
|
||||
|
||||
On Fedora 35, from a fresh installation, install all packages from [fedora.list.txt](source/linux/fedora.list.txt).
|
||||
|
||||
### Raspberry Pi OS, Ubuntu and other Debian distributions
|
||||
|
||||
Install all packages from [raspbian.list.txt](source/linux/raspbian.list.txt).
|
||||
|
||||
You can use `sudo apt-get -y install $(cat raspbian.list.txt)` for an automated installation.
|
||||
|
||||
See [Travis](.travis.yml) CI too.
|
||||
|
||||
### Packaging
|
||||
|
||||
It is possible to create `.deb` and `.rpm` packages using `cpack`. Use `cpack -G DEB` or `cpack -G RPM` from the build folder. It is best to build packages for the running system.
|
||||
|
||||
## Speed
|
||||
|
||||
### Fedora
|
||||
|
||||
Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz
|
||||
|
||||
Full update = 582 MHz
|
||||
|
||||
| Video Stype | Video update |
|
||||
| :--- | ---: |
|
||||
| RGB Monitor | 39 |
|
||||
| NTSC Monitor | 27 |
|
||||
| Color TV | 25 |
|
||||
| B&W TV | 27 |
|
||||
| Amber Monitor | 31 |
|
||||
|
||||
### Raspbian
|
||||
|
||||
Pi 3B+
|
||||
|
||||
Full update = 54 MHz
|
||||
|
||||
| Video Stype | Video update |
|
||||
| :--- | ---: |
|
||||
| RGB Monitor | 5.3 |
|
||||
| NTSC Monitor | 3.6 |
|
||||
| Color TV | 2.6 |
|
||||
| B&W TV | 2.9 |
|
||||
| Amber Monitor | 4.5 |
|
|
@ -1,4 +1,4 @@
|
|||
#define APPLEWIN_VERSION 1,30,8,0
|
||||
#define APPLEWIN_VERSION 1,30,9,0
|
||||
|
||||
#define xstr(a) str(a)
|
||||
#define str(a) #a
|
||||
|
|
265
source/CMakeLists.txt
Normal file
265
source/CMakeLists.txt
Normal file
|
@ -0,0 +1,265 @@
|
|||
include(FindPkgConfig)
|
||||
include(FindZLIB)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
pkg_search_module(YAML REQUIRED yaml-0.1)
|
||||
pkg_search_module(MINIZIP REQUIRED minizip)
|
||||
pkg_search_module(SLIRP slirp)
|
||||
|
||||
if ("${SLIRP_FOUND}" STREQUAL "")
|
||||
message(WARNING "'libslirp' not found. Will use 'libpcap' instead")
|
||||
endif()
|
||||
|
||||
pkg_search_module(PCAP libpcap)
|
||||
if ("${PCAP_FOUND}" STREQUAL "")
|
||||
# old versions of pcap do not work with pkg-config
|
||||
# this is necessary on Rapsberri Pi OS
|
||||
execute_process(COMMAND pcap-config --cflags
|
||||
OUTPUT_VARIABLE PCAP_INCLUDE_DIRS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE STATUS)
|
||||
if ("${STATUS}" STREQUAL "0")
|
||||
message("Found 'libpcap' via pcap-config")
|
||||
else()
|
||||
message(FATAL_ERROR "Cannot locate 'libpcap-dev'")
|
||||
endif()
|
||||
execute_process(COMMAND pcap-config --libs
|
||||
OUTPUT_VARIABLE PCAP_LIBRARIES
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
set(SOURCE_FILES
|
||||
Tfe/tfearch.cpp
|
||||
Tfe/tfesupp.cpp
|
||||
Tfe/NetworkBackend.cpp
|
||||
Tfe/PCapBackend.cpp
|
||||
Tfe/IPRaw.cpp
|
||||
|
||||
Debugger/Debug.cpp
|
||||
Debugger/Debugger_Help.cpp
|
||||
Debugger/Debugger_Color.cpp
|
||||
Debugger/Debugger_Disassembler.cpp
|
||||
Debugger/Debugger_Symbols.cpp
|
||||
Debugger/Debugger_DisassemblerData.cpp
|
||||
Debugger/Debugger_Console.cpp
|
||||
Debugger/Debugger_Assembler.cpp
|
||||
Debugger/Debugger_Parser.cpp
|
||||
Debugger/Debugger_Range.cpp
|
||||
Debugger/Debugger_Commands.cpp
|
||||
Debugger/Util_MemoryTextFile.cpp
|
||||
|
||||
Uthernet1.cpp
|
||||
Uthernet2.cpp
|
||||
StrFormat.cpp
|
||||
6522.cpp
|
||||
VidHD.cpp
|
||||
SSI263.cpp
|
||||
Speaker.cpp
|
||||
SoundCore.cpp
|
||||
AY8910.cpp
|
||||
Mockingboard.cpp
|
||||
Pravets.cpp
|
||||
YamlHelper.cpp
|
||||
Log.cpp
|
||||
Disk.cpp
|
||||
DiskFormatTrack.cpp
|
||||
DiskImage.cpp
|
||||
DiskImageHelper.cpp
|
||||
Harddisk.cpp
|
||||
Memory.cpp
|
||||
CPU.cpp
|
||||
6821.cpp
|
||||
NoSlotClock.cpp
|
||||
SAM.cpp
|
||||
z80emu.cpp
|
||||
ParallelPrinter.cpp
|
||||
MouseInterface.cpp
|
||||
LanguageCard.cpp
|
||||
RGBMonitor.cpp
|
||||
NTSC.cpp
|
||||
NTSC_CharSet.cpp
|
||||
Card.cpp
|
||||
CardManager.cpp
|
||||
Disk2CardManager.cpp
|
||||
Riff.cpp
|
||||
SaveState.cpp
|
||||
SynchronousEventManager.cpp
|
||||
Video.cpp
|
||||
Core.cpp
|
||||
Utilities.cpp
|
||||
FrameBase.cpp
|
||||
CmdLine.cpp
|
||||
|
||||
Configuration/PropertySheetHelper.cpp
|
||||
|
||||
linux/resources.cpp
|
||||
linux/benchmark.cpp
|
||||
linux/paddle.cpp
|
||||
linux/version.cpp
|
||||
linux/registry.cpp
|
||||
linux/keyboard.cpp
|
||||
linux/linuxframe.cpp
|
||||
linux/context.cpp
|
||||
linux/tape.cpp
|
||||
linux/network/slirp2.cpp
|
||||
|
||||
linux/duplicates/Debugger_Display.cpp
|
||||
linux/duplicates/Debugger_Win32.cpp
|
||||
linux/duplicates/Joystick.cpp
|
||||
linux/duplicates/SerialComms.cpp
|
||||
linux/duplicates/PropertySheet.cpp
|
||||
linux/duplicates/Registry.cpp
|
||||
linux/duplicates/FourPlay.cpp
|
||||
linux/duplicates/SNESMAX.cpp
|
||||
linux/duplicates/Keyboard.cpp
|
||||
|
||||
Z80VICE/z80.cpp
|
||||
Z80VICE/z80mem.cpp
|
||||
Z80VICE/daa.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
Tfe/tfearch.h
|
||||
Tfe/tfesupp.h
|
||||
Tfe/NetworkBackend.h
|
||||
Tfe/PCapBackend.h
|
||||
Tfe/IPRaw.h
|
||||
|
||||
Uthernet1.h
|
||||
Uthernet2.h
|
||||
W5100.h
|
||||
6522.h
|
||||
VidHD.h
|
||||
SSI263.h
|
||||
SSI263Phonemes.h
|
||||
Speaker.h
|
||||
SoundCore.h
|
||||
AY8910.h
|
||||
Mockingboard.h
|
||||
Pravets.h
|
||||
Tape.h
|
||||
YamlHelper.h
|
||||
Log.h
|
||||
Disk.h
|
||||
DiskFormatTrack.h
|
||||
DiskImage.h
|
||||
DiskImageHelper.h
|
||||
Harddisk.h
|
||||
Memory.h
|
||||
CPU.h
|
||||
6821.h
|
||||
NoSlotClock.h
|
||||
SAM.h
|
||||
z80emu.h
|
||||
ParallelPrinter.h
|
||||
MouseInterface.h
|
||||
LanguageCard.h
|
||||
RGBMonitor.h
|
||||
NTSC.h
|
||||
NTSC_CharSet.h
|
||||
Card.h
|
||||
CardManager.h
|
||||
Disk2CardManager.h
|
||||
Riff.h
|
||||
SaveState.h
|
||||
SynchronousEventManager.h
|
||||
Video.h
|
||||
Core.h
|
||||
Utilities.h
|
||||
FrameBase.h
|
||||
FourPlay.h
|
||||
SNESMAX.h
|
||||
|
||||
Common.h
|
||||
DiskDefs.h
|
||||
DiskLog.h
|
||||
Interface.h
|
||||
SaveState_Structs_common.h
|
||||
SaveState_Structs_v1.h
|
||||
|
||||
Debugger/Debug.h
|
||||
Debugger/DebugDefs.h
|
||||
Debugger/Debugger_Color.h
|
||||
Debugger/Debugger_Console.h
|
||||
Debugger/Debugger_Disassembler.h
|
||||
Debugger/Debugger_DisassemblerData.h
|
||||
Debugger/Debugger_Display.h
|
||||
Debugger/Debugger_Help.h
|
||||
Debugger/Debugger_Parser.h
|
||||
Debugger/Debugger_Range.h
|
||||
Debugger/Debugger_Symbols.h
|
||||
Debugger/Debugger_Types.h
|
||||
Debugger/Debugger_Win32.h
|
||||
Debugger/Util_MemoryTextFile.h
|
||||
Debugger/Util_Text.h
|
||||
|
||||
Configuration/PropertySheetHelper.h
|
||||
|
||||
linux/resources.h
|
||||
linux/linuxinterface.h
|
||||
linux/benchmark.h
|
||||
linux/paddle.h
|
||||
linux/version.h
|
||||
linux/registry.h
|
||||
linux/keyboard.h
|
||||
linux/linuxframe.h
|
||||
linux/tape.h
|
||||
linux/network/slirp2.h
|
||||
|
||||
Z80VICE/z80.h
|
||||
Z80VICE/z80mem.h
|
||||
Z80VICE/z80regs.h
|
||||
Z80VICE/daa.h
|
||||
)
|
||||
|
||||
# we used to generate a shared object, but it turns out there are more cons than pros
|
||||
add_library(appleii STATIC
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
)
|
||||
|
||||
target_include_directories(appleii PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR} # for config.h
|
||||
${YAML_INCLUDE_DIRS}
|
||||
${PCAP_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${SLIRP_INCLUDE_DIRS}
|
||||
Debugger
|
||||
)
|
||||
|
||||
# this one appears in header files
|
||||
target_include_directories(appleii PUBLIC
|
||||
${MINIZIP_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(appleii PRIVATE
|
||||
${YAML_LIBRARIES}
|
||||
${MINIZIP_LIBRARIES}
|
||||
${PCAP_LIBRARIES}
|
||||
${SLIRP_LIBRARIES}
|
||||
ZLIB::ZLIB
|
||||
)
|
||||
|
||||
target_link_libraries(appleii PUBLIC
|
||||
windows
|
||||
)
|
||||
|
||||
target_link_directories(appleii PRIVATE
|
||||
${YAML_LIBRARY_DIRS}
|
||||
${MINIZIP_LIBRARY_DIRS}
|
||||
${PCAP_LIBRARY_DIRS}
|
||||
${SLIRP_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_compile_options(appleii PUBLIC
|
||||
-Wno-multichar
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
TARGET appleii POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/bin/*.SYM ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
|
||||
configure_file(linux/config.h.in linux/config.h)
|
|
@ -3798,16 +3798,16 @@ Update_t CmdConfigSetDebugDir (int nArgs)
|
|||
if ((nSubDirLen == 3) && (sSubDir == UP_DIR)) // Up directory "..\" in the subpath?
|
||||
{
|
||||
size_t nCurrentLen = g_sCurrentDir.size();
|
||||
size_t nLastSeperator = g_sCurrentDir.rfind( '\\', nCurrentLen - 2 );
|
||||
size_t nLastSeparator = g_sCurrentDir.rfind( '\\', nCurrentLen - 2 );
|
||||
|
||||
if (nLastSeperator != std::string::npos)
|
||||
if (nLastSeparator != std::string::npos)
|
||||
{
|
||||
#if _DEBUG
|
||||
LogOutput( "Last: %" SIZE_T_FMT "\n", nLastSeperator );
|
||||
LogOutput( "Last: %" SIZE_T_FMT "\n", nLastSeparator );
|
||||
LogOutput( "%s\n", g_sCurrentDir.c_str() );
|
||||
LogOutput( "%*s%s\n", int(nLastSeperator), "", "^" );
|
||||
LogOutput( "%*s%s\n", int(nLastSeparator), "", "^" );
|
||||
#endif
|
||||
std::string sCurrentDir = g_sCurrentDir.substr( 0, nLastSeperator + 1 ); // Path always has trailing slash so include it
|
||||
std::string sCurrentDir = g_sCurrentDir.substr( 0, nLastSeparator + 1 ); // Path always has trailing slash so include it
|
||||
g_sCurrentDir = sCurrentDir;
|
||||
}
|
||||
}
|
||||
|
@ -5509,7 +5509,7 @@ Update_t _CmdMemorySearch (int nArgs, bool bTextIsAscii = true )
|
|||
|
||||
// if (eRange == RANGE_MISSING_ARG_2)
|
||||
if (! Range_CalcEndLen( eRange, nAddressStart, nAddress2, nAddressEnd, nAddressLen))
|
||||
return ConsoleDisplayError( "Error: Missing address seperator (comma or colon)" );
|
||||
return ConsoleDisplayError( "Error: Missing address separator (comma or colon)" );
|
||||
|
||||
int iArgFirstByte = 4;
|
||||
int iArg;
|
||||
|
@ -7812,7 +7812,7 @@ int ParseInput ( LPTSTR pConsoleInput, bool bCook )
|
|||
{
|
||||
int nArg = 0;
|
||||
|
||||
// TODO: need to check for non-quoted command seperator ';', and buffer input
|
||||
// TODO: need to check for non-quoted command separator ';', and buffer input
|
||||
RemoveWhiteSpaceReverse( pConsoleInput );
|
||||
|
||||
ArgsClear();
|
||||
|
@ -7871,24 +7871,24 @@ void ProfileLineReset()
|
|||
//===========================================================================
|
||||
void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
||||
{
|
||||
char sSeperator7[ 32 ] = "\t";
|
||||
char sSeperator2[ 32 ] = "\t";
|
||||
char sSeperator1[ 32 ] = "\t";
|
||||
char sSeparator7[ 32 ] = "\t";
|
||||
char sSeparator2[ 32 ] = "\t";
|
||||
char sSeparator1[ 32 ] = "\t";
|
||||
char sOpcode [ 8 ]; // 2 chars for opcode in hex, plus quotes on either side
|
||||
char sAddress[MAX_OPMODE_NAME];
|
||||
|
||||
if (eFormatMode == PROFILE_FORMAT_COMMA)
|
||||
{
|
||||
sSeperator7[0] = ',';
|
||||
sSeperator2[0] = ',';
|
||||
sSeperator1[0] = ',';
|
||||
sSeparator7[0] = ',';
|
||||
sSeparator2[0] = ',';
|
||||
sSeparator1[0] = ',';
|
||||
}
|
||||
else
|
||||
if (eFormatMode == PROFILE_FORMAT_SPACE)
|
||||
{
|
||||
sprintf( sSeperator7, " " ); // 7
|
||||
sprintf( sSeperator2, " " ); // 2
|
||||
sprintf( sSeperator1, " " ); // 1
|
||||
sprintf( sSeparator7, " " ); // 7
|
||||
sprintf( sSeparator2, " " ); // 2
|
||||
sprintf( sSeparator1, " " ); // 1
|
||||
}
|
||||
|
||||
ProfileLineReset();
|
||||
|
@ -7946,11 +7946,11 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
if (bExport) // Export = SeperateColumns
|
||||
sprintf( pText
|
||||
, "\"Percent\"" DELIM "\"Count\"" DELIM "\"Opcode\"" DELIM "\"Mnemonic\"" DELIM "\"Addressing Mode\"\n"
|
||||
, sSeperator7, sSeperator2, sSeperator1, sSeperator1 );
|
||||
, sSeparator7, sSeparator2, sSeparator1, sSeparator1 );
|
||||
else
|
||||
sprintf( pText
|
||||
, "Percent" DELIM "Count" DELIM "Mnemonic" DELIM "Addressing Mode\n"
|
||||
, sSeperator7, sSeperator2, sSeperator1 );
|
||||
, sSeparator7, sSeparator2, sSeparator1 );
|
||||
|
||||
pText = ProfileLinePush();
|
||||
|
||||
|
@ -7998,13 +7998,13 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
, pColorNumber
|
||||
, nPercent
|
||||
, pColorOperator
|
||||
, sSeperator2
|
||||
, sSeparator2
|
||||
, pColorNumber
|
||||
, static_cast<unsigned int>(nCount), sSeperator2
|
||||
, static_cast<unsigned int>(nCount), sSeparator2
|
||||
, pColorOpcode
|
||||
, sOpcode, sSeperator2
|
||||
, sOpcode, sSeparator2
|
||||
, pColorMnemonic
|
||||
, g_aOpcodes[ nOpcode ].sMnemonic, sSeperator2
|
||||
, g_aOpcodes[ nOpcode ].sMnemonic, sSeparator2
|
||||
, pColorOpmode
|
||||
, sAddress
|
||||
);
|
||||
|
@ -8016,7 +8016,7 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
|
||||
sprintf( pText
|
||||
, "Total: " DELIM "%s%9u\n"
|
||||
, sSeperator2
|
||||
, sSeparator2
|
||||
, pColorTotal
|
||||
, static_cast<unsigned int>(nOpcodeTotal) );
|
||||
pText = ProfileLinePush();
|
||||
|
@ -8030,12 +8030,12 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
// Note: 2 extra dummy columns are inserted to keep Addressing Mode in same column
|
||||
sprintf( pText
|
||||
, "\"Percent\"" DELIM "\"Count\"" DELIM DELIM DELIM "\"Addressing Mode\"\n"
|
||||
, sSeperator7, sSeperator2, sSeperator2, sSeperator2 );
|
||||
, sSeparator7, sSeparator2, sSeparator2, sSeparator2 );
|
||||
else
|
||||
{
|
||||
sprintf( pText
|
||||
, "Percent" DELIM "Count" DELIM "Addressing Mode\n"
|
||||
, sSeperator7, sSeperator2 );
|
||||
, sSeparator7, sSeparator2 );
|
||||
}
|
||||
pText = ProfileLinePush();
|
||||
|
||||
|
@ -8060,7 +8060,7 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
if (bExport)
|
||||
{
|
||||
// Note: 2 extra dummy columns are inserted to keep Addressing Mode in same column
|
||||
sprintf( sAddress, "%s%s\"%s\"", sSeperator1, sSeperator1, g_aOpmodes[ nOpmode ].m_sName );
|
||||
sprintf( sAddress, "%s%s\"%s\"", sSeparator1, sSeparator1, g_aOpmodes[ nOpmode ].m_sName );
|
||||
}
|
||||
else // not qouted if dumping to console
|
||||
{
|
||||
|
@ -8073,9 +8073,9 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
, pColorNumber
|
||||
, nPercent
|
||||
, pColorOperator
|
||||
, sSeperator2
|
||||
, sSeparator2
|
||||
, pColorNumber
|
||||
, static_cast<unsigned int>(nCount), sSeperator2
|
||||
, static_cast<unsigned int>(nCount), sSeparator2
|
||||
, pColorOpmode
|
||||
, sAddress
|
||||
);
|
||||
|
@ -8087,7 +8087,7 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
|
||||
sprintf( pText
|
||||
, "Total: " DELIM "%s%9u\n"
|
||||
, sSeperator2
|
||||
, sSeparator2
|
||||
, pColorTotal
|
||||
, static_cast<unsigned int>(nOpmodeTotal) );
|
||||
pText = ProfileLinePush();
|
||||
|
@ -8098,7 +8098,7 @@ void ProfileFormat( bool bExport, ProfileFormat_e eFormatMode )
|
|||
unsigned int cycles = static_cast<unsigned int>(g_nCumulativeCycles - g_nProfileBeginCycles);
|
||||
sprintf( pText
|
||||
, "Cycles: " DELIM "%s%9u\n"
|
||||
, sSeperator2
|
||||
, sSeparator2
|
||||
, pColorNumber
|
||||
, cycles );
|
||||
pText = ProfileLinePush();
|
||||
|
|
|
@ -617,7 +617,7 @@ void FormatNopcodeBytes(WORD nBaseAddress, DisasmLine_t& line_)
|
|||
//===========================================================================
|
||||
void FormatDisassemblyLine(const DisasmLine_t& line, char* sDisassembly, const int nBufferSize)
|
||||
{
|
||||
//> Address Seperator Opcodes Label Mnemonic Target [Immediate] [Branch]
|
||||
//> Address Separator Opcodes Label Mnemonic Target [Immediate] [Branch]
|
||||
//
|
||||
// Data Disassembler
|
||||
// Label Directive [Immediate]
|
||||
|
|
|
@ -1344,7 +1344,7 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress )
|
|||
nOpbyte = line.nOpbyte;
|
||||
|
||||
// sAddress, sOpcodes, sTarget, sTargetOffset, nTargetOffset, sTargetPointer, sTargetValue, sImmediate, nImmediate, sBranch );
|
||||
//> Address Seperator Opcodes Label Mnemonic Target [Immediate] [Branch]
|
||||
//> Address Separator Opcodes Label Mnemonic Target [Immediate] [Branch]
|
||||
//
|
||||
//> xxxx: xx xx xx LABEL MNEMONIC 'E' =
|
||||
//> ^ ^ ^ ^ ^
|
||||
|
@ -1515,7 +1515,7 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress )
|
|||
}
|
||||
|
||||
|
||||
// Address Seperator
|
||||
// Address Separator
|
||||
if (! bCursorLine)
|
||||
DebuggerSetColorFG( DebuggerGetColor( FG_DISASM_OPERATOR ) );
|
||||
|
||||
|
|
|
@ -249,8 +249,8 @@ void Help_Operators()
|
|||
ConsolePrintFormat( " %s#%s Designate number in hex" , CHC_USAGE, CHC_DEFAULT );
|
||||
// ConsoleBufferPush( " Operators: (Range)" );
|
||||
ConsolePrintFormat( " Operators: (%sRange%s)" , CHC_USAGE, CHC_DEFAULT );
|
||||
ConsolePrintFormat( " %s,%s range seperator (2nd address is relative)", CHC_USAGE, CHC_DEFAULT );
|
||||
ConsolePrintFormat( " %s:%s range seperator (2nd address is absolute)", CHC_USAGE, CHC_DEFAULT );
|
||||
ConsolePrintFormat( " %s,%s range separator (2nd address is relative)", CHC_USAGE, CHC_DEFAULT );
|
||||
ConsolePrintFormat( " %s:%s range separator (2nd address is absolute)", CHC_USAGE, CHC_DEFAULT );
|
||||
// ConsolePrintFormat( " Operators: (Misc)" );
|
||||
ConsolePrintFormat( " Operators: (%sMisc%s)" , CHC_USAGE, CHC_DEFAULT );
|
||||
ConsolePrintFormat( " %s//%s comment until end of line" , CHC_USAGE, CHC_DEFAULT );
|
||||
|
|
|
@ -286,7 +286,7 @@ int ArgsGet ( TCHAR * pInput )
|
|||
|
||||
if (iTokenSrc == TOKEN_SEMI)
|
||||
{
|
||||
// TODO - command seperator, must handle non-quoted though!
|
||||
// TODO - command separator, must handle non-quoted though!
|
||||
}
|
||||
|
||||
if (iTokenSrc == TOKEN_QUOTE_DOUBLE)
|
||||
|
|
|
@ -1271,7 +1271,7 @@ const DisasmData_t* pDisasmData; // If != NULL then bytes are marked up as data
|
|||
, TOKEN_PLUS // + Delta Argument1 += Argument2
|
||||
, TOKEN_QUOTE_SINGLE // '
|
||||
, TOKEN_QUOTE_DOUBLE // "
|
||||
, TOKEN_SEMI // ; Command Seperator
|
||||
, TOKEN_SEMI // ; Command Separator
|
||||
, TOKEN_SPACE // Token Delimiter
|
||||
, TOKEN_STAR // *
|
||||
// , TOKEN_TAB // '\t'
|
||||
|
|
|
@ -443,7 +443,7 @@ bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
|
|||
}
|
||||
|
||||
// "Memory Bankxx"
|
||||
std::string memName = GetSnapshotMemStructName() + StrFormat("%02X", uBank);
|
||||
std::string memName = GetSnapshotMemStructName() + ByteToHexStr(uBank);
|
||||
|
||||
if (!yamlLoadHelper.GetSubMap(memName))
|
||||
throw std::runtime_error("Memory: Missing map name: " + memName);
|
||||
|
|
|
@ -2443,7 +2443,7 @@ static void MemLoadSnapshotAuxCommon(YamlLoadHelper& yamlLoadHelper, const std::
|
|||
}
|
||||
|
||||
// "Auxiliary Memory Bankxx"
|
||||
std::string auxMemName = MemGetSnapshotAuxMemStructName() + StrFormat("%02X", uBank-1);
|
||||
std::string auxMemName = MemGetSnapshotAuxMemStructName() + ByteToHexStr(uBank-1);
|
||||
|
||||
if (!yamlLoadHelper.GetSubMap(auxMemName))
|
||||
throw std::runtime_error("Memory: Missing map name: " + auxMemName);
|
||||
|
|
|
@ -86,7 +86,7 @@ void SSI_Output(void)
|
|||
LogOutput("SSI: ");
|
||||
for (int i = 0; i <= 4; i++)
|
||||
{
|
||||
std::string r = (ssiRegs[i] >= 0) ? StrFormat("%02X", ssiRegs[i]) : "--";
|
||||
std::string r = (ssiRegs[i] >= 0) ? ByteToHexStr(ssiRegs[i]) : "--";
|
||||
LogOutput("%s ", r.c_str());
|
||||
ssiRegs[i] = -1;
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ typedef UINT64 uint64_t;
|
|||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include "windows.h"
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#if defined(_MSC_VER) && _MSC_VER < 1600
|
||||
#include <basetsd.h>
|
||||
typedef UINT8 uint8_t;
|
||||
typedef UINT16 uint16_t;
|
||||
#else
|
||||
#include <cstdint>
|
||||
#endif
|
||||
|
@ -19,10 +20,59 @@ typedef UINT8 uint8_t;
|
|||
std::string StrFormat(const char* format, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2);
|
||||
std::string StrFormatV(const char* format, va_list va);
|
||||
|
||||
namespace {
|
||||
|
||||
const char g_aHexDigits[16] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||
};
|
||||
|
||||
// No buffer overflow check or null termination. Use with caution.
|
||||
inline char* StrBufferAppendByteAsHex(char* cp, uint8_t n)
|
||||
{
|
||||
*cp++ = g_aHexDigits[(n >> 4) & 0x0f];
|
||||
*cp++ = g_aHexDigits[(n >> 0) & 0x0f];
|
||||
return cp;
|
||||
}
|
||||
|
||||
// No buffer overflow check or null termination. Use with caution.
|
||||
inline char* StrBufferAppendWordAsHex(char* cp, uint16_t n)
|
||||
{
|
||||
*cp++ = g_aHexDigits[(n >> 12) & 0x0f];
|
||||
*cp++ = g_aHexDigits[(n >> 8) & 0x0f];
|
||||
*cp++ = g_aHexDigits[(n >> 4) & 0x0f];
|
||||
*cp++ = g_aHexDigits[(n >> 0) & 0x0f];
|
||||
return cp;
|
||||
}
|
||||
|
||||
inline std::string& StrAppendByteAsHex(std::string& s, uint8_t n)
|
||||
{
|
||||
const char szHex[] = "0123456789ABCDEF";
|
||||
s += szHex[(n >> 4) & 0x0f];
|
||||
s += szHex[n & 0x0f];
|
||||
const char hex[2] = { g_aHexDigits[(n >> 4) & 0x0f],
|
||||
g_aHexDigits[(n >> 0) & 0x0f] };
|
||||
return s.append(hex, 2);
|
||||
}
|
||||
|
||||
inline std::string& StrAppendWordAsHex(std::string& s, uint16_t n)
|
||||
{
|
||||
const char hex[4] = { g_aHexDigits[(n >> 12) & 0x0f],
|
||||
g_aHexDigits[(n >> 8) & 0x0f],
|
||||
g_aHexDigits[(n >> 4) & 0x0f],
|
||||
g_aHexDigits[(n >> 0) & 0x0f] };
|
||||
return s.append(hex, 4);
|
||||
}
|
||||
|
||||
inline std::string ByteToHexStr(uint8_t n)
|
||||
{
|
||||
std::string s;
|
||||
StrAppendByteAsHex(s, n);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string WordToHexStr(uint16_t n)
|
||||
{
|
||||
std::string s;
|
||||
StrAppendWordAsHex(s, n);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
162
source/Tfe/IPRaw.cpp
Normal file
162
source/Tfe/IPRaw.cpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
AppleWin : An Apple //e emulator for Windows
|
||||
|
||||
Copyright (C) 2022, Andrea Odetti
|
||||
|
||||
AppleWin 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.
|
||||
|
||||
AppleWin 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 AppleWin; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "StdAfx.h"
|
||||
|
||||
#include "IPRaw.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#define IPV4 0x04
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1) // Ensure struct is packed
|
||||
struct IP4Header
|
||||
{
|
||||
uint8_t ihl : 4;
|
||||
uint8_t version : 4;
|
||||
uint8_t tos;
|
||||
uint16_t len;
|
||||
uint16_t id;
|
||||
uint16_t flags : 3;
|
||||
uint16_t fragmentOffset : 13;
|
||||
uint8_t ttl;
|
||||
uint8_t proto;
|
||||
uint16_t checksum;
|
||||
uint32_t sourceAddress;
|
||||
uint32_t destinationAddress;
|
||||
};
|
||||
|
||||
struct ETH2Frame
|
||||
{
|
||||
uint8_t destinationMac[6];
|
||||
uint8_t sourceMac[6];
|
||||
uint16_t type;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
uint32_t sum_every_16bits(const void *addr, int count)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
const uint16_t *ptr = reinterpret_cast<const uint16_t *>(addr);
|
||||
|
||||
while (count > 1)
|
||||
{
|
||||
/* This is the inner loop */
|
||||
sum += *ptr++;
|
||||
count -= 2;
|
||||
}
|
||||
|
||||
/* Add left-over byte, if any */
|
||||
if (count > 0)
|
||||
sum += *reinterpret_cast<const uint8_t *>(ptr);
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
uint16_t checksum(const void *addr, int count)
|
||||
{
|
||||
/* Compute Internet Checksum for "count" bytes
|
||||
* beginning at location "addr".
|
||||
* Taken from https://tools.ietf.org/html/rfc1071
|
||||
*/
|
||||
uint32_t sum = sum_every_16bits(addr, count);
|
||||
|
||||
/* Fold 32-bit sum to 16 bits */
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
}
|
||||
|
||||
// get the minimum size of a ETH Frame that contains a IP payload
|
||||
// 34 = 14 bytes for ETH2 + 20 bytes IPv4 (minimum)
|
||||
int getIPMinimumSize()
|
||||
{
|
||||
const int minimumSize = sizeof(ETH2Frame) + sizeof(IP4Header) + 0; // 0 len
|
||||
return minimumSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<uint8_t> createETH2Frame(const std::vector<uint8_t> &data,
|
||||
const MACAddress *sourceMac, const MACAddress *destinationMac,
|
||||
const uint8_t ttl, const uint8_t tos, const uint8_t protocol,
|
||||
const uint32_t sourceAddress, const uint32_t destinationAddress)
|
||||
{
|
||||
const size_t total = sizeof(ETH2Frame) + sizeof(IP4Header) + data.size();
|
||||
std::vector<uint8_t> frame(total);
|
||||
ETH2Frame *eth2frame = reinterpret_cast<ETH2Frame *>(frame.data() + 0);
|
||||
memcpy(eth2frame->destinationMac, destinationMac, sizeof(eth2frame->destinationMac));
|
||||
memcpy(eth2frame->sourceMac, sourceMac, sizeof(eth2frame->destinationMac));
|
||||
eth2frame->type = htons(0x0800);
|
||||
IP4Header *ip4header = reinterpret_cast<IP4Header *>(frame.data() + sizeof(ETH2Frame));
|
||||
|
||||
ip4header->version = IPV4;
|
||||
ip4header->ihl = 0x05; // minimum size = 20 bytes, without any extra option
|
||||
ip4header->tos = tos;
|
||||
ip4header->len = htons(static_cast<uint16_t>(sizeof(IP4Header) + data.size()));
|
||||
ip4header->id = 0;
|
||||
ip4header->fragmentOffset = 0;
|
||||
ip4header->ttl = ttl;
|
||||
ip4header->proto = protocol;
|
||||
ip4header->sourceAddress = sourceAddress;
|
||||
ip4header->destinationAddress = destinationAddress;
|
||||
ip4header->checksum = checksum(ip4header, sizeof(IP4Header));
|
||||
|
||||
memcpy(frame.data() + sizeof(ETH2Frame) + sizeof(IP4Header), data.data(), data.size());
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void getIPPayload(const int lengthOfFrame, const uint8_t *frame,
|
||||
size_t &lengthOfPayload, const uint8_t *&payload, uint32_t &destination, uint8_t &protocol)
|
||||
{
|
||||
const int minimumSize = getIPMinimumSize();
|
||||
if (lengthOfFrame > minimumSize)
|
||||
{
|
||||
const ETH2Frame *eth2Frame = reinterpret_cast<const ETH2Frame *>(frame);
|
||||
const IP4Header *ip4header = reinterpret_cast<const IP4Header *>(frame + sizeof(ETH2Frame));
|
||||
if (eth2Frame->type == htons(0x0800) && ip4header->version == IPV4)
|
||||
{
|
||||
const uint16_t ipv4HeaderSize = ip4header->ihl * 4;
|
||||
const uint16_t ipPacketSize = ntohs(ip4header->len);
|
||||
const int expectedSize = sizeof(ETH2Frame) + ipPacketSize;
|
||||
if (ipPacketSize > ipv4HeaderSize && lengthOfFrame >= expectedSize)
|
||||
{
|
||||
protocol = ip4header->proto;
|
||||
payload = frame + sizeof(ETH2Frame) + ipv4HeaderSize;
|
||||
lengthOfPayload = ipPacketSize - ipv4HeaderSize;
|
||||
destination = ip4header->destinationAddress;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// not a good packet
|
||||
protocol = 0xFF; // reserved protocol
|
||||
payload = nullptr;
|
||||
lengthOfPayload = 0;
|
||||
destination = 0;
|
||||
}
|
11
source/Tfe/IPRaw.h
Normal file
11
source/Tfe/IPRaw.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
struct MACAddress;
|
||||
|
||||
std::vector<uint8_t> createETH2Frame(const std::vector<uint8_t> &data,
|
||||
const MACAddress *sourceMac, const MACAddress *destinationMac,
|
||||
const uint8_t ttl, const uint8_t tos, const uint8_t protocol,
|
||||
const uint32_t sourceAddress, const uint32_t destinationAddress);
|
||||
|
||||
void getIPPayload(const int lengthOfFrame, const uint8_t *frame,
|
||||
size_t &lengthOfPayload, const uint8_t *&payload, uint32_t &destination, uint8_t &protocol);
|
|
@ -6,6 +6,14 @@
|
|||
#define MAX_RXLENGTH 1518
|
||||
#define MIN_RXLENGTH 64
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1) // Ensure struct is packed
|
||||
struct MACAddress
|
||||
{
|
||||
uint8_t address[6];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class NetworkBackend
|
||||
{
|
||||
public:
|
||||
|
@ -26,6 +34,9 @@ public:
|
|||
// process pending packets
|
||||
virtual void update(const ULONG nExecutedCycles) = 0;
|
||||
|
||||
// get MAC for IPRAW (it is only supposed to handle addresses on the local network)
|
||||
virtual void getMACAddress(const uint32_t address, MACAddress & mac) = 0;
|
||||
|
||||
// if the backend is usable
|
||||
virtual bool isValid() = 0;
|
||||
};
|
||||
|
|
|
@ -25,6 +25,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||
#include "../Common.h"
|
||||
#include "../Registry.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <iphlpapi.h>
|
||||
#endif
|
||||
|
||||
std::string PCapBackend::tfe_interface;
|
||||
|
||||
PCapBackend::PCapBackend(const std::string & pcapInterface)
|
||||
|
@ -71,6 +75,16 @@ void PCapBackend::update(const ULONG /* nExecutedCycles */)
|
|||
// nothing to do
|
||||
}
|
||||
|
||||
void PCapBackend::getMACAddress(const uint32_t address, MACAddress & mac)
|
||||
{
|
||||
// this is only expected to be called for IP addresses on the same network
|
||||
#ifdef _MSC_VER
|
||||
const DWORD dwSourceAddress = INADDR_ANY;
|
||||
ULONG len = sizeof(MACAddress::address);
|
||||
SendARP(address, dwSourceAddress, mac.address, &len);
|
||||
#endif
|
||||
}
|
||||
|
||||
int PCapBackend::tfe_enumadapter_open(void)
|
||||
{
|
||||
return tfe_arch_enumadapter_open();
|
||||
|
|
|
@ -29,6 +29,9 @@ public:
|
|||
// process pending packets
|
||||
virtual bool isValid();
|
||||
|
||||
// get MAC for IPRAW (it is only supposed to handle addresses on the local network)
|
||||
virtual void getMACAddress(const uint32_t address, MACAddress & mac);
|
||||
|
||||
static void tfe_SetRegistryInterface(UINT slot, const std::string& name);
|
||||
static void get_disabled_state(int * param);
|
||||
|
||||
|
|
|
@ -433,7 +433,7 @@ void TfePcapPacketHandler(u_char *param, const struct pcap_pkthdr *header, const
|
|||
/* determine the count of bytes which has been returned,
|
||||
* but make sure not to overrun the buffer
|
||||
*/
|
||||
pinternal->rxlength = min(pinternal->size, header->caplen);
|
||||
pinternal->rxlength = std::min(pinternal->size, header->caplen);
|
||||
|
||||
memcpy(pinternal->buffer, pkt_data, pinternal->rxlength);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||
#include "Interface.h"
|
||||
#include "Tfe/NetworkBackend.h"
|
||||
#include "Tfe/PCapBackend.h"
|
||||
#include "Tfe/IPRaw.h"
|
||||
#include "W5100.h"
|
||||
|
||||
// Linux uses EINPROGRESS while Windows returns WSAEWOULDBLOCK
|
||||
|
@ -73,6 +74,9 @@ typedef int socklen_t;
|
|||
#define SOCK_NONBLOCK O_NONBLOCK
|
||||
#endif
|
||||
|
||||
// Dest MAC + Source MAC + Ether Type
|
||||
#define ETH_MINIMUM_SIZE (6 + 6 + 2)
|
||||
|
||||
// #define U2_LOG_VERBOSE
|
||||
// #define U2_LOG_TRAFFIC
|
||||
// #define U2_LOG_STATE
|
||||
|
@ -95,6 +99,12 @@ namespace
|
|||
return host;
|
||||
}
|
||||
|
||||
uint32_t readAddress(const uint8_t *ptr)
|
||||
{
|
||||
const uint32_t address = *reinterpret_cast<const uint32_t *>(ptr);
|
||||
return address;
|
||||
}
|
||||
|
||||
uint8_t getIByte(const uint16_t value, const size_t shift)
|
||||
{
|
||||
return (value >> shift) & 0xFF;
|
||||
|
@ -141,6 +151,13 @@ namespace
|
|||
writeData(socket, memory, data, len);
|
||||
}
|
||||
|
||||
void writeDataIPRaw(Socket &socket, std::vector<uint8_t> &memory, const uint8_t *data, const size_t len, const uint32_t destination)
|
||||
{
|
||||
writeAny(socket, memory, destination);
|
||||
write16(socket, memory, static_cast<uint16_t>(len));
|
||||
writeData(socket, memory, data, len);
|
||||
}
|
||||
|
||||
void writeDataForProtocol(Socket &socket, std::vector<uint8_t> &memory, const uint8_t *data, const size_t len, const sockaddr_in &source)
|
||||
{
|
||||
if (socket.sn_sr == W5100_SN_SR_SOCK_UDP)
|
||||
|
@ -199,31 +216,46 @@ Socket::~Socket()
|
|||
clearFD();
|
||||
}
|
||||
|
||||
bool Socket::isOpen() const
|
||||
{
|
||||
return (myFD != INVALID_SOCKET) &&
|
||||
((sn_sr == W5100_SN_SR_ESTABLISHED) || (sn_sr == W5100_SN_SR_SOCK_UDP));
|
||||
}
|
||||
|
||||
void Socket::process()
|
||||
{
|
||||
if (myFD != INVALID_SOCKET && sn_sr == W5100_SN_SR_SOCK_INIT && (myErrno == SOCK_EINPROGRESS || myErrno == SOCK_EWOULDBLOCK))
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
FD_SET writefds;
|
||||
FD_SET writefds, exceptfds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_ZERO(&exceptfds);
|
||||
FD_SET(myFD, &writefds);
|
||||
FD_SET(myFD, &exceptfds);
|
||||
const timeval timeout = {0, 0};
|
||||
if (select(0, NULL, &writefds, NULL, &timeout) > 0)
|
||||
if (select(0, NULL, &writefds, &exceptfds, &timeout) > 0)
|
||||
#else
|
||||
pollfd pfd = {.fd = myFD, .events = POLLOUT};
|
||||
if (poll(&pfd, 1, 0) > 0)
|
||||
#endif
|
||||
{
|
||||
int err = 0;
|
||||
socklen_t elen = sizeof err;
|
||||
getsockopt(myFD, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&err), &elen);
|
||||
socklen_t elen = sizeof(err);
|
||||
const int res = getsockopt(myFD, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&err), &elen);
|
||||
|
||||
if (err == 0)
|
||||
if (res == 0 && err == 0)
|
||||
{
|
||||
myErrno = 0;
|
||||
sn_sr = W5100_SN_SR_ESTABLISHED;
|
||||
#ifdef U2_LOG_STATE
|
||||
LogFileOutput("U2: TCP[]: Connected\n");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
clearFD();
|
||||
#ifdef U2_LOG_STATE
|
||||
LogFileOutput("U2: TCP[]: Connection error: %d - %" ERROR_FMT "\n", res, STRERROR(err));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -266,12 +298,18 @@ bool Socket::LoadSnapshot(YamlLoadHelper &yamlLoadHelper)
|
|||
|
||||
// transmit and receive sizes are restored from the card common registers
|
||||
|
||||
if (sn_sr != W5100_SN_SR_SOCK_MACRAW)
|
||||
switch (sn_sr)
|
||||
{
|
||||
case W5100_SN_SR_SOCK_MACRAW:
|
||||
case W5100_SN_SR_SOCK_IPRAW:
|
||||
// we can restore RAW sockets
|
||||
break;
|
||||
default:
|
||||
// no point in restoring a broken UDP or TCP connection
|
||||
// just reset the socket
|
||||
sn_sr = W5100_SN_SR_CLOSED;
|
||||
// for the same reason there is no point in saving myFD and myErrno
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -434,7 +472,7 @@ void Uthernet2::updateRSR(const size_t i)
|
|||
socket.sn_rx_rsr = dataPresent;
|
||||
}
|
||||
|
||||
int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data)
|
||||
int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data, PacketDestination & packetDestination)
|
||||
{
|
||||
const uint8_t * mac = myMemory.data() + W5100_SHAR0;
|
||||
|
||||
|
@ -442,15 +480,9 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_
|
|||
int len;
|
||||
while ((len = myNetworkBackend->receive(size, data)) > 0)
|
||||
{
|
||||
// minimum valid Ethernet frame is actually 64 bytes
|
||||
// 12 is the minimum to ensure valid MAC Address logging later
|
||||
if (len >= 12)
|
||||
// smaller frames are not good anyway
|
||||
if (len >= ETH_MINIMUM_SIZE)
|
||||
{
|
||||
if (acceptAll)
|
||||
{
|
||||
return len;
|
||||
}
|
||||
|
||||
if (data[0] == mac[0] &&
|
||||
data[1] == mac[1] &&
|
||||
data[2] == mac[2] &&
|
||||
|
@ -458,6 +490,7 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_
|
|||
data[4] == mac[4] &&
|
||||
data[5] == mac[5])
|
||||
{
|
||||
packetDestination = HOST;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
@ -468,8 +501,16 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_
|
|||
data[4] == 0xFF &&
|
||||
data[5] == 0xFF)
|
||||
{
|
||||
packetDestination = BROADCAST;
|
||||
return len;
|
||||
}
|
||||
|
||||
if (acceptAll)
|
||||
{
|
||||
packetDestination = OTHER;
|
||||
return len;
|
||||
}
|
||||
|
||||
}
|
||||
// skip this frame and try with another one
|
||||
}
|
||||
|
@ -477,34 +518,110 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_
|
|||
return len;
|
||||
}
|
||||
|
||||
void Uthernet2::receiveOnePacketMacRaw(const size_t i)
|
||||
void Uthernet2::receiveOnePacketRaw()
|
||||
{
|
||||
bool acceptAll = false;
|
||||
int macRawSocket = -1; // to which IPRaw soccket to send to (can only be 0)
|
||||
|
||||
Socket & socket0 = mySockets[0];
|
||||
if (socket0.sn_sr == W5100_SN_SR_SOCK_MACRAW)
|
||||
{
|
||||
macRawSocket = 0; // the only MAC Raw socket is open, packet will go there as a fallback
|
||||
const uint8_t mr = myMemory[socket0.registerAddress + W5100_SN_MR];
|
||||
|
||||
// see if MAC RAW filters or not
|
||||
const bool filterMAC = mr & W5100_SN_MR_MF;
|
||||
if (!filterMAC)
|
||||
{
|
||||
acceptAll = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t buffer[MAX_RXLENGTH];
|
||||
PacketDestination packetDestination;
|
||||
const int len = receiveForMacAddress(acceptAll, sizeof(buffer), buffer, packetDestination);
|
||||
if (len > 0)
|
||||
{
|
||||
const uint8_t * payload;
|
||||
size_t lengthOfPayload;
|
||||
uint32_t destination;
|
||||
uint8_t packetProtocol;
|
||||
getIPPayload(len, buffer, lengthOfPayload, payload, destination, packetProtocol);
|
||||
|
||||
// see if there is a IPRAW socket that should accept thi spacket
|
||||
int ipRawSocket = -1;
|
||||
if (packetDestination != OTHER) // IPRaw always filters (HOST or BROADCAST, never OTHER)
|
||||
{
|
||||
for (size_t i = 0; i < mySockets.size(); ++i)
|
||||
{
|
||||
Socket & socket = mySockets[i];
|
||||
|
||||
if (socket.sn_sr == W5100_SN_SR_SOCK_IPRAW)
|
||||
{
|
||||
// IP only accepts by protocol & always filters MAC
|
||||
const uint8_t socketProtocol = myMemory[socket.registerAddress + W5100_SN_PROTO];
|
||||
if (payload && packetProtocol == socketProtocol)
|
||||
{
|
||||
ipRawSocket = i;
|
||||
break; // a valid IPRAW socket has been found
|
||||
}
|
||||
}
|
||||
// we should probably check for UDP & TCP sockets and filter these packets too
|
||||
}
|
||||
}
|
||||
|
||||
// priority to IPRAW
|
||||
if (ipRawSocket >= 0)
|
||||
{
|
||||
receiveOnePacketIPRaw(ipRawSocket, lengthOfPayload, payload, destination, packetProtocol, len);
|
||||
}
|
||||
// fallback to MACRAW (if open)
|
||||
else if (macRawSocket >= 0)
|
||||
{
|
||||
receiveOnePacketMacRaw(macRawSocket, len, buffer);
|
||||
}
|
||||
// else packet is dropped
|
||||
}
|
||||
}
|
||||
|
||||
void Uthernet2::receiveOnePacketMacRaw(const size_t i, const int size, uint8_t * data)
|
||||
{
|
||||
Socket &socket = mySockets[i];
|
||||
|
||||
uint8_t buffer[MAX_RXLENGTH];
|
||||
|
||||
const uint8_t mr = myMemory[socket.registerAddress + W5100_SN_MR];
|
||||
const bool filterMAC = mr & W5100_SN_MR_MF;
|
||||
|
||||
const int len = receiveForMacAddress(!filterMAC, sizeof(buffer), buffer);
|
||||
if (len > 0)
|
||||
if (socket.isThereRoomFor(size, sizeof(uint16_t)))
|
||||
{
|
||||
// we know the packet is at least 12 bytes, and logging is ok
|
||||
if (socket.isThereRoomFor(len, sizeof(uint16_t)))
|
||||
{
|
||||
writeDataMacRaw(socket, myMemory, buffer, len);
|
||||
writeDataMacRaw(socket, myMemory, data, size);
|
||||
#ifdef U2_LOG_TRAFFIC
|
||||
LogFileOutput("U2: Read MACRAW[%" SIZE_T_FMT "]: " MAC_FMT " -> " MAC_FMT ": +%d -> %d bytes\n", i, MAC_SOURCE(buffer), MAC_DEST(buffer),
|
||||
len, socket.sn_rx_rsr);
|
||||
LogFileOutput("U2: Read MACRAW[%" SIZE_T_FMT "]: " MAC_FMT " -> " MAC_FMT ": +%d -> %d bytes\n", i, MAC_SOURCE(data), MAC_DEST(data),
|
||||
size, socket.sn_rx_rsr);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// drop it
|
||||
}
|
||||
else
|
||||
{
|
||||
// drop it
|
||||
#ifdef U2_LOG_TRAFFIC
|
||||
LogFileOutput("U2: Skip MACRAW[%" SIZE_T_FMT "]: %d bytes\n", i, len);
|
||||
LogFileOutput("U2: Skip MACRAW[%" SIZE_T_FMT "]: %d bytes\n", i, size);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Uthernet2::receiveOnePacketIPRaw(const size_t i, const size_t lengthOfPayload, const uint8_t * payload, const uint32_t destination, const uint8_t protocol, const int len)
|
||||
{
|
||||
Socket &socket = mySockets[i];
|
||||
|
||||
if (socket.isThereRoomFor(lengthOfPayload, 4 + sizeof(uint16_t)))
|
||||
{
|
||||
writeDataIPRaw(socket, myMemory, payload, lengthOfPayload, destination);
|
||||
#ifdef U2_LOG_TRAFFIC
|
||||
LogFileOutput("U2: Read IPRAW[%" SIZE_T_FMT "]: +%" SIZE_T_FMT " (%d) -> %d bytes\n", i, lengthOfPayload, len, socket.sn_rx_rsr);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// drop it
|
||||
#ifdef U2_LOG_TRAFFIC
|
||||
LogFileOutput("U2: Skip IPRAW[%" SIZE_T_FMT "]: %" SIZE_T_FMT " (%d) bytes \n", i, lengthOfPayload, len);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,7 +629,7 @@ void Uthernet2::receiveOnePacketMacRaw(const size_t i)
|
|||
void Uthernet2::receiveOnePacketFromSocket(const size_t i)
|
||||
{
|
||||
Socket &socket = mySockets[i];
|
||||
if (socket.myFD != INVALID_SOCKET)
|
||||
if (socket.isOpen())
|
||||
{
|
||||
const uint16_t freeRoom = socket.getFreeRoom();
|
||||
if (freeRoom > 32) // avoid meaningless reads
|
||||
|
@ -557,7 +674,8 @@ void Uthernet2::receiveOnePacket(const size_t i)
|
|||
switch (socket.sn_sr)
|
||||
{
|
||||
case W5100_SN_SR_SOCK_MACRAW:
|
||||
receiveOnePacketMacRaw(i);
|
||||
case W5100_SN_SR_SOCK_IPRAW:
|
||||
receiveOnePacketRaw();
|
||||
break;
|
||||
case W5100_SN_SR_ESTABLISHED:
|
||||
case W5100_SN_SR_SOCK_UDP:
|
||||
|
@ -572,6 +690,29 @@ void Uthernet2::receiveOnePacket(const size_t i)
|
|||
};
|
||||
}
|
||||
|
||||
void Uthernet2::sendDataIPRaw(const size_t i, std::vector<uint8_t> &payload)
|
||||
{
|
||||
const Socket &socket = mySockets[i];
|
||||
|
||||
const uint8_t ttl = myMemory[socket.registerAddress + W5100_SN_TTL];
|
||||
const uint8_t tos = myMemory[socket.registerAddress + W5100_SN_TOS];
|
||||
const uint8_t protocol = myMemory[socket.registerAddress + W5100_SN_PROTO];
|
||||
const uint32_t source = readAddress(myMemory.data() + W5100_SIPR0);
|
||||
const uint32_t destination = readAddress(myMemory.data() + socket.registerAddress + W5100_SN_DIPR0);
|
||||
|
||||
const MACAddress * sourceMac = reinterpret_cast<const MACAddress *>(myMemory.data() + W5100_SHAR0);
|
||||
const MACAddress * destinationMac;
|
||||
getMACAddress(destination, destinationMac);
|
||||
|
||||
std::vector<uint8_t> packet = createETH2Frame(payload, sourceMac, destinationMac, ttl, tos, protocol, source, destination);
|
||||
|
||||
#ifdef U2_LOG_TRAFFIC
|
||||
LogFileOutput("U2: Send IPRAW[%" SIZE_T_FMT "]: %" SIZE_T_FMT " (%" SIZE_T_FMT ") bytes\n", i, payload.size(), packet.size());
|
||||
#endif
|
||||
|
||||
myNetworkBackend->transmit(packet.size(), packet.data());
|
||||
}
|
||||
|
||||
void Uthernet2::sendDataMacRaw(const size_t i, std::vector<uint8_t> &packet) const
|
||||
{
|
||||
#ifdef U2_LOG_TRAFFIC
|
||||
|
@ -592,7 +733,7 @@ void Uthernet2::sendDataMacRaw(const size_t i, std::vector<uint8_t> &packet) con
|
|||
void Uthernet2::sendDataToSocket(const size_t i, std::vector<uint8_t> &data)
|
||||
{
|
||||
Socket &socket = mySockets[i];
|
||||
if (socket.myFD != INVALID_SOCKET)
|
||||
if (socket.isOpen())
|
||||
{
|
||||
sockaddr_in destination = {};
|
||||
destination.sin_family = AF_INET;
|
||||
|
@ -656,6 +797,9 @@ void Uthernet2::sendData(const size_t i)
|
|||
case W5100_SN_SR_SOCK_MACRAW:
|
||||
sendDataMacRaw(i, data);
|
||||
break;
|
||||
case W5100_SN_SR_SOCK_IPRAW:
|
||||
sendDataIPRaw(i, data);
|
||||
break;
|
||||
case W5100_SN_SR_ESTABLISHED:
|
||||
case W5100_SN_SR_SOCK_UDP:
|
||||
sendDataToSocket(i, data);
|
||||
|
@ -680,7 +824,7 @@ void Uthernet2::resetRXTXBuffers(const size_t i)
|
|||
myMemory[socket.registerAddress + W5100_SN_RX_RD1] = 0x00;
|
||||
}
|
||||
|
||||
void Uthernet2::openSystemSocket(const size_t i, const int type, const int protocol, const int state)
|
||||
void Uthernet2::openSystemSocket(const size_t i, const int type, const int protocol, const int status)
|
||||
{
|
||||
Socket &s = mySockets[i];
|
||||
#ifdef _MSC_VER
|
||||
|
@ -691,7 +835,7 @@ void Uthernet2::openSystemSocket(const size_t i, const int type, const int proto
|
|||
if (fd == INVALID_SOCKET)
|
||||
{
|
||||
#ifdef U2_LOG_STATE
|
||||
const char *proto = state == W5100_SN_SR_SOCK_UDP ? "UDP" : "TCP";
|
||||
const char *proto = (status == W5100_SN_SR_SOCK_UDP) ? "UDP" : "TCP";
|
||||
LogFileOutput("U2: %s[%" SIZE_T_FMT "]: socket error: %" ERROR_FMT "\n", proto, i, STRERROR(sock_error()));
|
||||
#endif
|
||||
s.clearFD();
|
||||
|
@ -702,7 +846,7 @@ void Uthernet2::openSystemSocket(const size_t i, const int type, const int proto
|
|||
u_long on = 1;
|
||||
ioctlsocket(fd, FIONBIO, &on);
|
||||
#endif
|
||||
s.setFD(fd, state);
|
||||
s.setFD(fd, status);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -854,7 +998,7 @@ uint8_t Uthernet2::readSocketRegister(const uint16_t address)
|
|||
break;
|
||||
default:
|
||||
#ifdef U2_LOG_UNKNOWN
|
||||
LogFileOutput("U2: Get unknown socket register[%" SIZE_T_FMT "]: %04x\n", i, address);
|
||||
LogFileOutput("U2: Get unknown socket register[%d]: %04x\n", i, address);
|
||||
#endif
|
||||
value = myMemory[address];
|
||||
break;
|
||||
|
@ -988,7 +1132,7 @@ void Uthernet2::writeSocketRegister(const uint16_t address, const uint8_t value)
|
|||
break;
|
||||
#ifdef U2_LOG_UNKNOWN
|
||||
default:
|
||||
LogFileOutput("U2: Set unknown socket register[%" SIZE_T_FMT "]: %04x\n", i, address);
|
||||
LogFileOutput("U2: Set unknown socket register[%d]: %04x\n", i, address);
|
||||
break;
|
||||
#endif
|
||||
};
|
||||
|
@ -1073,6 +1217,7 @@ void Uthernet2::Reset(const bool powerCycle)
|
|||
// dataAddress is NOT reset, see page 10 of Uthernet II
|
||||
myDataAddress = 0;
|
||||
myNetworkBackend = GetFrame().CreateNetworkBackend();
|
||||
myARPCache.clear();
|
||||
}
|
||||
|
||||
mySockets.clear();
|
||||
|
@ -1084,14 +1229,25 @@ void Uthernet2::Reset(const bool powerCycle)
|
|||
{
|
||||
resetRXTXBuffers(i);
|
||||
mySockets[i].clearFD();
|
||||
mySockets[i].registerAddress = static_cast<uint16_t>(W5100_S0_BASE + (i << 8));
|
||||
const uint16_t registerAddress = static_cast<uint16_t>(W5100_S0_BASE + (i << 8));
|
||||
mySockets[i].registerAddress = registerAddress;
|
||||
|
||||
myMemory[registerAddress + W5100_SN_DHAR0] = 0xFF;
|
||||
myMemory[registerAddress + W5100_SN_DHAR1] = 0xFF;
|
||||
myMemory[registerAddress + W5100_SN_DHAR2] = 0xFF;
|
||||
myMemory[registerAddress + W5100_SN_DHAR3] = 0xFF;
|
||||
myMemory[registerAddress + W5100_SN_DHAR4] = 0xFF;
|
||||
myMemory[registerAddress + W5100_SN_DHAR5] = 0xFF;
|
||||
myMemory[registerAddress + W5100_SN_TTL] = 0x80;
|
||||
}
|
||||
|
||||
// initial values
|
||||
myMemory[W5100_RTR0] = 0x07;
|
||||
myMemory[W5100_RTR1] = 0xD0;
|
||||
myMemory[W5100_RTR0] = 0x07;
|
||||
myMemory[W5100_RTR1] = 0xD0;
|
||||
myMemory[W5100_RCR] = 0x08;
|
||||
setRXSizes(W5100_RMSR, 0x55);
|
||||
setTXSizes(W5100_TMSR, 0x55);
|
||||
myMemory[W5100_PTIMER] = 0x28;
|
||||
}
|
||||
|
||||
BYTE Uthernet2::IO_C0(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles)
|
||||
|
@ -1162,6 +1318,46 @@ void Uthernet2::InitializeIO(LPBYTE pCxRomPeripheral)
|
|||
RegisterIoHandler(m_slot, u2_C0, u2_C0, nullptr, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
void Uthernet2::getMACAddress(const uint32_t address, const MACAddress * & mac)
|
||||
{
|
||||
const auto it = myARPCache.find(address);
|
||||
if (it != myARPCache.end())
|
||||
{
|
||||
mac = &it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
MACAddress & macAddr = myARPCache[address];
|
||||
const uint32_t source = readAddress(myMemory.data() + W5100_SIPR0);
|
||||
|
||||
if (address == source)
|
||||
{
|
||||
const uint8_t * sourceMac = myMemory.data() + W5100_SHAR0;
|
||||
memcpy(macAddr.address, sourceMac, sizeof(macAddr.address));
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(macAddr.address, 0xFF, sizeof(macAddr.address)); // fallback to broadcast
|
||||
if (address != INADDR_BROADCAST)
|
||||
{
|
||||
const uint32_t subnet = readAddress(myMemory.data() + W5100_SUBR0);
|
||||
if ((address & subnet) == (source & subnet))
|
||||
{
|
||||
// same network: send ARP request
|
||||
myNetworkBackend->getMACAddress(address, macAddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32_t gateway = readAddress(myMemory.data() + W5100_GAR0);
|
||||
// different network: go via gateway
|
||||
myNetworkBackend->getMACAddress(gateway, macAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
mac = &macAddr;
|
||||
}
|
||||
}
|
||||
|
||||
void Uthernet2::Update(const ULONG nExecutedCycles)
|
||||
{
|
||||
myNetworkBackend->update(nExecutedCycles);
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
#include "Card.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
class NetworkBackend;
|
||||
struct MACAddress;
|
||||
|
||||
struct Socket
|
||||
{
|
||||
|
@ -28,6 +30,7 @@ struct Socket
|
|||
socket_t myFD;
|
||||
int myErrno;
|
||||
|
||||
bool isOpen() const;
|
||||
void clearFD();
|
||||
void setFD(const socket_t fd, const int status);
|
||||
void process();
|
||||
|
@ -47,6 +50,7 @@ struct Socket
|
|||
* Documentation from
|
||||
* http://dserver.macgui.com/Uthernet%20II%20manual%2017%20Nov%2018.pdf
|
||||
* https://www.wiznet.io/wp-content/uploads/wiznethome/Chip/W5100/Document/W5100_DS_V128E.pdf
|
||||
* https://www.wiznet.io/wp-content/uploads/wiznethome/Chip/W5100/Document/3150Aplus_5100_ES_V260E.pdf
|
||||
*/
|
||||
|
||||
class Uthernet2 : public Card
|
||||
|
@ -54,6 +58,8 @@ class Uthernet2 : public Card
|
|||
public:
|
||||
static const std::string& GetSnapshotCardName();
|
||||
|
||||
enum PacketDestination { HOST, BROADCAST, OTHER };
|
||||
|
||||
Uthernet2(UINT slot);
|
||||
|
||||
virtual void Destroy(void) {}
|
||||
|
@ -72,6 +78,13 @@ private:
|
|||
uint16_t myDataAddress;
|
||||
std::shared_ptr<NetworkBackend> myNetworkBackend;
|
||||
|
||||
// the real Uthernet II card does not have a ARP Cache
|
||||
// but in the interest of speeding up the emulator
|
||||
// we introduce one
|
||||
std::map<uint32_t, MACAddress> myARPCache;
|
||||
|
||||
void getMACAddress(const uint32_t address, const MACAddress * & mac);
|
||||
|
||||
void setSocketModeRegister(const size_t i, const uint16_t address, const uint8_t value);
|
||||
void setTXSizes(const uint16_t address, uint8_t value);
|
||||
void setRXSizes(const uint16_t address, uint8_t value);
|
||||
|
@ -79,11 +92,14 @@ private:
|
|||
uint8_t getTXFreeSizeRegister(const size_t i, const size_t shift) const;
|
||||
uint8_t getRXDataSizeRegister(const size_t i, const size_t shift) const;
|
||||
|
||||
void receiveOnePacketMacRaw(const size_t i);
|
||||
void receiveOnePacketRaw();
|
||||
void receiveOnePacketIPRaw(const size_t i, const size_t lengthOfPayload, const uint8_t * payload, const uint32_t destination, const uint8_t protocol, const int len);
|
||||
void receiveOnePacketMacRaw(const size_t i, const int size, uint8_t * data);
|
||||
void receiveOnePacketFromSocket(const size_t i);
|
||||
void receiveOnePacket(const size_t i);
|
||||
int receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data);
|
||||
int receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data, PacketDestination & packetDestination);
|
||||
|
||||
void sendDataIPRaw(const size_t i, std::vector<uint8_t> &data);
|
||||
void sendDataMacRaw(const size_t i, std::vector<uint8_t> &data) const;
|
||||
void sendDataToSocket(const size_t i, std::vector<uint8_t> &data);
|
||||
void sendData(const size_t i);
|
||||
|
@ -91,7 +107,7 @@ private:
|
|||
void resetRXTXBuffers(const size_t i);
|
||||
void updateRSR(const size_t i);
|
||||
|
||||
void openSystemSocket(const size_t i, const int type, const int protocol, const int state);
|
||||
void openSystemSocket(const size_t i, const int type, const int protocol, const int status);
|
||||
void openSocket(const size_t i);
|
||||
void closeSocket(const size_t i);
|
||||
void connectSocket(const size_t i);
|
||||
|
|
|
@ -196,11 +196,13 @@ void LoadConfiguration(bool loadImages)
|
|||
if(REGLOAD(TEXT(REGVALUE_THE_FREEZES_F8_ROM), &dwTmp))
|
||||
GetPropertySheet().SetTheFreezesF8Rom(dwTmp);
|
||||
|
||||
if(REGLOAD(TEXT(REGVALUE_SPKR_VOLUME), &dwTmp))
|
||||
SpkrSetVolume(dwTmp, GetPropertySheet().GetVolumeMax());
|
||||
dwTmp = 70;
|
||||
REGLOAD(TEXT(REGVALUE_SPKR_VOLUME), &dwTmp);
|
||||
SpkrSetVolume(dwTmp, GetPropertySheet().GetVolumeMax());
|
||||
|
||||
if(REGLOAD(TEXT(REGVALUE_MB_VOLUME), &dwTmp))
|
||||
MB_SetVolume(dwTmp, GetPropertySheet().GetVolumeMax());
|
||||
dwTmp = 70;
|
||||
REGLOAD(TEXT(REGVALUE_MB_VOLUME), &dwTmp);
|
||||
MB_SetVolume(dwTmp, GetPropertySheet().GetVolumeMax());
|
||||
|
||||
if(REGLOAD(TEXT(REGVALUE_SAVE_STATE_ON_EXIT), &dwTmp))
|
||||
g_bSaveStateOnExit = dwTmp ? true : false;
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
#define W5100_SIPR3 0x0012
|
||||
#define W5100_RTR0 0x0017
|
||||
#define W5100_RTR1 0x0018
|
||||
#define W5100_RCR 0x0019
|
||||
#define W5100_RMSR 0x001A
|
||||
#define W5100_TMSR 0x001B
|
||||
#define W5100_PTIMER 0x0028
|
||||
#define W5100_UPORT1 0x002F
|
||||
#define W5100_S0_BASE 0x0400
|
||||
#define W5100_S3_MAX 0x07FF
|
||||
|
@ -58,6 +60,12 @@
|
|||
#define W5100_SN_SR 0x03
|
||||
#define W5100_SN_PORT0 0x04
|
||||
#define W5100_SN_PORT1 0x05
|
||||
#define W5100_SN_DHAR0 0x06
|
||||
#define W5100_SN_DHAR1 0x07
|
||||
#define W5100_SN_DHAR2 0x08
|
||||
#define W5100_SN_DHAR3 0x09
|
||||
#define W5100_SN_DHAR4 0x0A
|
||||
#define W5100_SN_DHAR5 0x0B
|
||||
#define W5100_SN_DIPR0 0x0C
|
||||
#define W5100_SN_DIPR1 0x0D
|
||||
#define W5100_SN_DIPR2 0x0E
|
||||
|
|
55
source/frontends/common2/CMakeLists.txt
Normal file
55
source/frontends/common2/CMakeLists.txt
Normal file
|
@ -0,0 +1,55 @@
|
|||
include(GNUInstallDirs)
|
||||
|
||||
set(SOURCE_FILES
|
||||
commonframe.cpp
|
||||
gnuframe.cpp
|
||||
fileregistry.cpp
|
||||
ptreeregistry.cpp
|
||||
programoptions.cpp
|
||||
utils.cpp
|
||||
timer.cpp
|
||||
speed.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
commonframe.h
|
||||
gnuframe.h
|
||||
fileregistry.h
|
||||
ptreeregistry.h
|
||||
programoptions.h
|
||||
utils.h
|
||||
timer.h
|
||||
speed.h
|
||||
)
|
||||
|
||||
add_library(common2 STATIC
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
)
|
||||
|
||||
find_package(Boost REQUIRED
|
||||
COMPONENTS program_options
|
||||
)
|
||||
|
||||
target_include_directories(common2 PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(common2 PRIVATE
|
||||
Boost::program_options
|
||||
appleii
|
||||
windows
|
||||
)
|
||||
|
||||
file(RELATIVE_PATH ROOT_PATH ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR})
|
||||
if ("${ROOT_PATH}" STREQUAL "")
|
||||
# if the 2 paths are the same
|
||||
set(ROOT_PATH "./")
|
||||
endif()
|
||||
file(RELATIVE_PATH SHARE_PATH ${CMAKE_INSTALL_FULL_BINDIR} ${CMAKE_INSTALL_FULL_DATADIR}/applewin)
|
||||
|
||||
configure_file(config.h.in config.h)
|
||||
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/resource
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/applewin)
|
64
source/frontends/common2/commonframe.cpp
Normal file
64
source/frontends/common2/commonframe.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/common2/commonframe.h"
|
||||
#include "frontends/common2/utils.h"
|
||||
#include "linux/resources.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "SaveState.h"
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
void CommonFrame::LoadSnapshot()
|
||||
{
|
||||
Snapshot_LoadState();
|
||||
}
|
||||
|
||||
BYTE* CommonFrame::GetResource(WORD id, LPCSTR lpType, DWORD expectedSize)
|
||||
{
|
||||
myResource.clear();
|
||||
|
||||
const std::string & filename = getResourceName(id);
|
||||
const std::string path = getResourcePath(filename);
|
||||
|
||||
const int fd = open(path.c_str(), O_RDONLY);
|
||||
|
||||
if (fd != -1)
|
||||
{
|
||||
struct stat stdbuf;
|
||||
if ((fstat(fd, &stdbuf) == 0) && S_ISREG(stdbuf.st_mode))
|
||||
{
|
||||
const off_t size = stdbuf.st_size;
|
||||
std::vector<BYTE> data(size);
|
||||
const ssize_t rd = read(fd, data.data(), size);
|
||||
if (rd == expectedSize)
|
||||
{
|
||||
std::swap(myResource, data);
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
if (myResource.empty())
|
||||
{
|
||||
LogFileOutput("FindResource: could not load resource %s\n", filename.c_str());
|
||||
}
|
||||
|
||||
return myResource.data();
|
||||
}
|
||||
|
||||
std::string CommonFrame::getBitmapFilename(const std::string & resource)
|
||||
{
|
||||
if (resource == "CHARSET40") return "CHARSET4.BMP";
|
||||
if (resource == "CHARSET82") return "CHARSET82.bmp";
|
||||
if (resource == "CHARSET8M") return "CHARSET8M.bmp";
|
||||
if (resource == "CHARSET8C") return "CHARSET8C.bmp";
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
}
|
24
source/frontends/common2/commonframe.h
Normal file
24
source/frontends/common2/commonframe.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "linux/linuxframe.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
class CommonFrame : public LinuxFrame
|
||||
{
|
||||
public:
|
||||
BYTE* GetResource(WORD id, LPCSTR lpType, DWORD expectedSize) override;
|
||||
virtual void LoadSnapshot();
|
||||
|
||||
protected:
|
||||
virtual std::string getResourcePath(const std::string & filename) = 0;
|
||||
|
||||
static std::string getBitmapFilename(const std::string & resource);
|
||||
|
||||
std::vector<BYTE> myResource;
|
||||
};
|
||||
|
||||
}
|
6
source/frontends/common2/config.h.in
Normal file
6
source/frontends/common2/config.h.in
Normal file
|
@ -0,0 +1,6 @@
|
|||
// relative path from executable to resources
|
||||
#cmakedefine ROOT_PATH "@ROOT_PATH@"
|
||||
#cmakedefine SHARE_PATH "@SHARE_PATH@"
|
||||
|
||||
// this one is a bit of a hack, until resources are embedded in the retro core
|
||||
#cmakedefine CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@"
|
137
source/frontends/common2/fileregistry.cpp
Normal file
137
source/frontends/common2/fileregistry.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#include "StdAfx.h"
|
||||
|
||||
#include "frontends/common2/fileregistry.h"
|
||||
#include "frontends/common2/ptreeregistry.h"
|
||||
#include "frontends/common2/programoptions.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "windows.h"
|
||||
#include "frontends/qt/applicationname.h"
|
||||
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void parseOption(const std::string & s, std::string & path, std::string & value)
|
||||
{
|
||||
const size_t pos = s.find('=');
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Invalid option format: " + s + ", expected: section.key=value");
|
||||
}
|
||||
path = s.substr(0, pos);
|
||||
std::replace(path.begin(), path.end(), '_', ' ');
|
||||
value = s.substr(pos + 1);
|
||||
std::replace(value.begin(), value.end(), '_', ' ');
|
||||
}
|
||||
|
||||
class Configuration : public common2::PTreeRegistry
|
||||
{
|
||||
public:
|
||||
Configuration(const std::string & filename, const bool saveOnExit);
|
||||
~Configuration();
|
||||
|
||||
void addExtraOptions(const std::vector<std::string> & options);
|
||||
|
||||
private:
|
||||
const std::string myFilename;
|
||||
bool mySaveOnExit;
|
||||
};
|
||||
|
||||
Configuration::Configuration(const std::string & filename, const bool saveOnExit) : myFilename(filename), mySaveOnExit(saveOnExit)
|
||||
{
|
||||
if (GetFileAttributes(myFilename.c_str()) != INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
boost::property_tree::ini_parser::read_ini(myFilename, myINI);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFileOutput("Registry: configuration file '%s' not found\n", filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Configuration::~Configuration()
|
||||
{
|
||||
if (mySaveOnExit)
|
||||
{
|
||||
try
|
||||
{
|
||||
boost::property_tree::ini_parser::write_ini(myFilename, myINI);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
LogFileOutput("Registry: cannot save settings to '%s': %s\n", myFilename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::addExtraOptions(const std::vector<std::string> & options)
|
||||
{
|
||||
for (const std::string & option : options)
|
||||
{
|
||||
std::string path, value;
|
||||
parseOption(option, path, value);
|
||||
myINI.put(path, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
std::string GetHomeDir()
|
||||
{
|
||||
const char* homeDir = getenv("HOME");
|
||||
if (!homeDir)
|
||||
{
|
||||
throw std::runtime_error("${HOME} not set, cannot locate configuration file");
|
||||
}
|
||||
|
||||
return std::string(homeDir);
|
||||
}
|
||||
|
||||
std::string GetConfigFile(const std::string & filename)
|
||||
{
|
||||
const std::string dir = GetHomeDir() + "/.applewin";
|
||||
const int status = mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
if (!status || (errno == EEXIST))
|
||||
{
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char * s = strerror(errno);
|
||||
LogFileOutput("No registry. Cannot create %s in %s: %s\n", filename.c_str(), dir.c_str(), s);
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Registry> CreateFileRegistry(const EmulatorOptions & options)
|
||||
{
|
||||
const std::string homeDir = GetHomeDir();
|
||||
|
||||
std::string filename;
|
||||
bool saveOnExit;
|
||||
|
||||
if (options.useQtIni)
|
||||
{
|
||||
filename = homeDir + "/.config/" + ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
||||
saveOnExit = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
filename = options.configurationFile;
|
||||
saveOnExit = true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Configuration> config(new Configuration(filename, saveOnExit));
|
||||
config->addExtraOptions(options.registryOptions);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
17
source/frontends/common2/fileregistry.h
Normal file
17
source/frontends/common2/fileregistry.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
class Registry;
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
struct EmulatorOptions;
|
||||
|
||||
std::string GetConfigFile(const std::string & filename);
|
||||
std::shared_ptr<Registry> CreateFileRegistry(const EmulatorOptions & options);
|
||||
std::string GetHomeDir();
|
||||
|
||||
}
|
99
source/frontends/common2/gnuframe.cpp
Normal file
99
source/frontends/common2/gnuframe.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/common2/gnuframe.h"
|
||||
#include "frontends/common2/fileregistry.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "mach-o/dyld.h"
|
||||
#endif
|
||||
|
||||
#include "Core.h"
|
||||
#include "config.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool dirExists(const std::string & folder)
|
||||
{
|
||||
struct stat stdbuf;
|
||||
|
||||
if (stat(folder.c_str(), &stdbuf) == 0 && S_ISDIR(stdbuf.st_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string getResourceFolder(const std::string & target)
|
||||
{
|
||||
std::vector<std::string> paths;
|
||||
|
||||
char self[1024] = {0};
|
||||
|
||||
#ifdef __APPLE__
|
||||
uint32_t size = sizeof(self);
|
||||
const int ch = _NSGetExecutablePath(self, &size);
|
||||
#else
|
||||
const int ch = readlink("/proc/self/exe", self, sizeof(self));
|
||||
#endif
|
||||
|
||||
if (ch != -1)
|
||||
{
|
||||
const char * path = dirname(self);
|
||||
|
||||
// case 1: run from the build folder
|
||||
paths.emplace_back(std::string(path) + '/'+ ROOT_PATH);
|
||||
// case 2: run from the installation folder
|
||||
paths.emplace_back(std::string(path) + '/'+ SHARE_PATH);
|
||||
}
|
||||
|
||||
// case 3: use the source folder
|
||||
paths.emplace_back(CMAKE_SOURCE_DIR);
|
||||
|
||||
for (const std::string & path : paths)
|
||||
{
|
||||
char * real = realpath(path.c_str(), nullptr);
|
||||
if (real)
|
||||
{
|
||||
const std::string resourcePath = std::string(real) + target;
|
||||
free(real);
|
||||
if (dirExists(resourcePath))
|
||||
{
|
||||
return resourcePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Cannot found the resource path: " + target);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
GNUFrame::GNUFrame()
|
||||
: myHomeDir(GetHomeDir())
|
||||
, myResourceFolder(getResourceFolder("/resource/"))
|
||||
{
|
||||
// should this go down to LinuxFrame (maybe Initialisation?)
|
||||
g_sProgramDir = getResourceFolder("/bin/");
|
||||
}
|
||||
|
||||
std::string GNUFrame::getResourcePath(const std::string & filename)
|
||||
{
|
||||
return myResourceFolder + filename;
|
||||
}
|
||||
|
||||
std::string GNUFrame::Video_GetScreenShotFolder() const
|
||||
{
|
||||
return myHomeDir + "/Pictures/";
|
||||
}
|
||||
|
||||
}
|
22
source/frontends/common2/gnuframe.h
Normal file
22
source/frontends/common2/gnuframe.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/common2/commonframe.h"
|
||||
#include <string>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
class GNUFrame : public virtual CommonFrame
|
||||
{
|
||||
public:
|
||||
GNUFrame();
|
||||
|
||||
std::string Video_GetScreenShotFolder() const override;
|
||||
std::string getResourcePath(const std::string & filename) override;
|
||||
|
||||
private:
|
||||
const std::string myHomeDir;
|
||||
const std::string myResourceFolder;
|
||||
};
|
||||
|
||||
}
|
252
source/frontends/common2/programoptions.cpp
Normal file
252
source/frontends/common2/programoptions.cpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
#include "frontends/common2/programoptions.h"
|
||||
#include "frontends/common2/fileregistry.h"
|
||||
#include "linux/version.h"
|
||||
#include "linux/paddle.h"
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include "Memory.h"
|
||||
#include "Log.h"
|
||||
#include "Disk.h"
|
||||
#include "Utilities.h"
|
||||
#include "Core.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void parseGeometry(const std::string & s, common2::Geometry & geometry)
|
||||
{
|
||||
std::smatch m;
|
||||
if (std::regex_match(s, m, std::regex("^(\\d+)x(\\d+)(\\+(\\d+)\\+(\\d+))?$")))
|
||||
{
|
||||
const size_t groups = m.size();
|
||||
if (groups == 6)
|
||||
{
|
||||
geometry.width = std::stoi(m.str(1));
|
||||
geometry.height = std::stoi(m.str(2));
|
||||
if (!m.str(3).empty())
|
||||
{
|
||||
geometry.x = std::stoi(m.str(4));
|
||||
geometry.y = std::stoi(m.str(5));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Invalid sizes: " + s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
EmulatorOptions::EmulatorOptions()
|
||||
{
|
||||
memclear = g_nMemoryClearType;
|
||||
configurationFile = GetConfigFile("applewin.conf");
|
||||
}
|
||||
|
||||
bool getEmulatorOptions(int argc, const char * argv [], const std::string & edition, EmulatorOptions & options)
|
||||
{
|
||||
const std::string name = "Apple Emulator for " + edition + " (based on AppleWin " + getVersion() + ")";
|
||||
po::options_description desc(name);
|
||||
desc.add_options()
|
||||
("help,h", "Print this help message")
|
||||
;
|
||||
|
||||
po::options_description configDesc("configuration");
|
||||
configDesc.add_options()
|
||||
("conf", po::value<std::string>()->default_value(options.configurationFile), "Select configuration file")
|
||||
("registry,r", po::value<std::vector<std::string>>(), "Registry options section.path=value")
|
||||
("qt-ini,q", "Use Qt ini file (read only)")
|
||||
;
|
||||
desc.add(configDesc);
|
||||
|
||||
po::options_description diskDesc("Disk");
|
||||
diskDesc.add_options()
|
||||
("d1,1", po::value<std::string>(), "Disk in 1st drive")
|
||||
("d2,2", po::value<std::string>(), "Disk in 2nd drive")
|
||||
;
|
||||
desc.add(diskDesc);
|
||||
|
||||
po::options_description snapshotDesc("Snapshot");
|
||||
snapshotDesc.add_options()
|
||||
("state-filename,f", po::value<std::string>(), "Set snapshot filename")
|
||||
("load-state,s", po::value<std::string>(), "Load snapshot from file")
|
||||
;
|
||||
desc.add(snapshotDesc);
|
||||
|
||||
po::options_description memoryDesc("Memory");
|
||||
memoryDesc.add_options()
|
||||
("memclear", po::value<int>()->default_value(options.memclear), "Memory initialization pattern [0..7]")
|
||||
;
|
||||
desc.add(memoryDesc);
|
||||
|
||||
po::options_description emulatorDesc("Emulator");
|
||||
emulatorDesc.add_options()
|
||||
("log", "Log to AppleWin.log")
|
||||
("headless", "Headless: disable video (freewheel)")
|
||||
("fixed-speed", "Fixed (non-adaptive) speed")
|
||||
("ntsc,nt", "NTSC: execute NTSC code")
|
||||
("benchmark,b", "Benchmark emulator")
|
||||
("rom", po::value<std::string>(), "Custom 12k/16k ROM")
|
||||
("f8rom", po::value<std::string>(), "Custom 2k ROM")
|
||||
;
|
||||
desc.add(emulatorDesc);
|
||||
|
||||
po::options_description sdlDesc("SDL");
|
||||
sdlDesc.add_options()
|
||||
("sdl-driver", po::value<int>()->default_value(options.sdlDriver), "SDL driver")
|
||||
("gl-swap", po::value<int>()->default_value(options.glSwapInterval), "SDL_GL_SwapInterval")
|
||||
("no-imgui", "Plain SDL2 renderer")
|
||||
("geometry", po::value<std::string>(), "WxH[+X+Y]")
|
||||
;
|
||||
desc.add(sdlDesc);
|
||||
|
||||
po::options_description paddleDesc("Paddle");
|
||||
paddleDesc.add_options()
|
||||
("no-squaring", "Gamepad range is (already) a square")
|
||||
("device-name", po::value<std::string>(), "Gamepad device name")
|
||||
;
|
||||
desc.add(paddleDesc);
|
||||
|
||||
po::variables_map vm;
|
||||
try
|
||||
{
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
|
||||
if (vm.count("help"))
|
||||
{
|
||||
std::cout << desc << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
options.configurationFile = vm["conf"].as<std::string>();
|
||||
options.useQtIni = vm.count("qt-ini");
|
||||
options.sdlDriver = vm["sdl-driver"].as<int>();
|
||||
options.glSwapInterval = vm["gl-swap"].as<int>();
|
||||
options.imgui = vm.count("no-imgui") == 0;
|
||||
|
||||
if (vm.count("registry"))
|
||||
{
|
||||
options.registryOptions = vm["registry"].as<std::vector<std::string> >();
|
||||
}
|
||||
|
||||
if (vm.count("d1"))
|
||||
{
|
||||
options.disk1 = vm["d1"].as<std::string>();
|
||||
}
|
||||
|
||||
if (vm.count("d2"))
|
||||
{
|
||||
options.disk2 = vm["d2"].as<std::string>();
|
||||
}
|
||||
|
||||
if (vm.count("load-state"))
|
||||
{
|
||||
options.snapshotFilename = vm["load-state"].as<std::string>();
|
||||
options.loadSnapshot = true;
|
||||
}
|
||||
|
||||
if (vm.count("state-filename"))
|
||||
{
|
||||
options.snapshotFilename = vm["state-filename"].as<std::string>();
|
||||
options.loadSnapshot = false;
|
||||
}
|
||||
|
||||
if (vm.count("rom"))
|
||||
{
|
||||
options.customRom = vm["rom"].as<std::string>();
|
||||
}
|
||||
|
||||
if (vm.count("f8rom"))
|
||||
{
|
||||
options.customRomF8 = vm["f8rom"].as<std::string>();
|
||||
}
|
||||
|
||||
const int memclear = vm["memclear"].as<int>();
|
||||
if (memclear >=0 && memclear < NUM_MIP)
|
||||
options.memclear = memclear;
|
||||
|
||||
options.benchmark = vm.count("benchmark") > 0;
|
||||
options.headless = vm.count("headless") > 0;
|
||||
options.log = vm.count("log") > 0;
|
||||
options.ntsc = vm.count("ntsc") > 0;
|
||||
options.fixedSpeed = vm.count("fixed-speed") > 0;
|
||||
|
||||
options.paddleSquaring = vm.count("no-squaring") == 0;
|
||||
if (vm.count("device-name"))
|
||||
{
|
||||
options.paddleDeviceName = vm["device-name"].as<std::string>();
|
||||
}
|
||||
|
||||
if (vm.count("geometry"))
|
||||
{
|
||||
options.geometry.empty = false;
|
||||
parseGeometry(vm["geometry"].as<std::string>(), options.geometry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (const po::error& e)
|
||||
{
|
||||
std::cerr << "ERROR: " << e.what() << std::endl << desc << std::endl;
|
||||
return false;
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
std::cerr << "ERROR: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void applyOptions(const EmulatorOptions & options)
|
||||
{
|
||||
g_nMemoryClearType = options.memclear;
|
||||
|
||||
LPCSTR szImageName_drive[NUM_DRIVES] = {nullptr, nullptr};
|
||||
bool driveConnected[NUM_DRIVES] = {true, true};
|
||||
|
||||
if (!options.disk1.empty())
|
||||
{
|
||||
szImageName_drive[DRIVE_1] = options.disk1.c_str();
|
||||
}
|
||||
|
||||
if (!options.disk2.empty())
|
||||
{
|
||||
szImageName_drive[DRIVE_2] = options.disk2.c_str();
|
||||
}
|
||||
|
||||
bool bBoot;
|
||||
InsertFloppyDisks(SLOT6, szImageName_drive, driveConnected, bBoot);
|
||||
|
||||
if (!options.customRom.empty())
|
||||
{
|
||||
CloseHandle(g_hCustomRom);
|
||||
g_hCustomRom = CreateFile(options.customRom.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
|
||||
if (g_hCustomRom == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
LogFileOutput("Init: Failed to load Custom ROM: %s\n", options.customRom.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.customRomF8.empty())
|
||||
{
|
||||
CloseHandle(g_hCustomRomF8);
|
||||
g_hCustomRomF8 = CreateFile(options.customRomF8.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
|
||||
if (g_hCustomRomF8 == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
LogFileOutput("Init: Failed to load custom F8 ROM: %s\n", options.customRomF8.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Paddle::setSquaring(options.paddleSquaring);
|
||||
}
|
||||
|
||||
}
|
64
source/frontends/common2/programoptions.h
Normal file
64
source/frontends/common2/programoptions.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
struct Geometry
|
||||
{
|
||||
bool empty = true;
|
||||
int width;
|
||||
int height;
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
|
||||
struct EmulatorOptions
|
||||
{
|
||||
EmulatorOptions();
|
||||
|
||||
std::string disk1;
|
||||
std::string disk2;
|
||||
|
||||
std::string snapshotFilename;
|
||||
bool loadSnapshot = false;
|
||||
|
||||
int memclear;
|
||||
|
||||
bool log = false;
|
||||
|
||||
bool benchmark = false;
|
||||
bool headless = false;
|
||||
bool ntsc = false; // only for applen
|
||||
|
||||
bool paddleSquaring = true; // turn the x/y range to a square
|
||||
// on my PC it is something like
|
||||
// "/dev/input/by-id/usb-©Microsoft_Corporation_Controller_1BBE3DB-event-joystick"
|
||||
std::string paddleDeviceName;
|
||||
|
||||
std::string configurationFile;
|
||||
bool useQtIni = false; // use Qt .ini file (read only)
|
||||
|
||||
bool run = true; // false if options include "-h"
|
||||
|
||||
bool fixedSpeed = false; // default adaptive
|
||||
|
||||
int sdlDriver = -1; // default = -1 to let SDL choose
|
||||
bool imgui = true; // use imgui renderer
|
||||
Geometry geometry; // must be initialised with defaults
|
||||
int glSwapInterval = 1; // SDL_GL_SetSwapInterval
|
||||
|
||||
std::string customRomF8;
|
||||
std::string customRom;
|
||||
|
||||
std::vector<std::string> registryOptions;
|
||||
};
|
||||
|
||||
bool getEmulatorOptions(int argc, const char * argv [], const std::string & edition, EmulatorOptions & options);
|
||||
|
||||
void applyOptions(const EmulatorOptions & options);
|
||||
|
||||
}
|
82
source/frontends/common2/ptreeregistry.cpp
Normal file
82
source/frontends/common2/ptreeregistry.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include "frontends/common2/ptreeregistry.h"
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string decodeKey(const std::string & key)
|
||||
{
|
||||
std::string result = key;
|
||||
// quick implementation, just to make it work.
|
||||
// is there a library function available somewhere?
|
||||
boost::algorithm::replace_all(result, "%20", " ");
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
bool PTreeRegistry::KeyQtEncodedLess::operator()(const std::string & lhs, const std::string & rhs) const
|
||||
{
|
||||
const std::string key1 = decodeKey(lhs);
|
||||
const std::string key2 = decodeKey(rhs);
|
||||
return key1 < key2;
|
||||
}
|
||||
|
||||
std::string PTreeRegistry::getString(const std::string & section, const std::string & key) const
|
||||
{
|
||||
return getValue<std::string>(section, key);
|
||||
}
|
||||
|
||||
DWORD PTreeRegistry::getDWord(const std::string & section, const std::string & key) const
|
||||
{
|
||||
return getValue<DWORD>(section, key);
|
||||
}
|
||||
|
||||
bool PTreeRegistry::getBool(const std::string & section, const std::string & key) const
|
||||
{
|
||||
return getValue<bool>(section, key);
|
||||
}
|
||||
|
||||
void PTreeRegistry::putString(const std::string & section, const std::string & key, const std::string & value)
|
||||
{
|
||||
putValue(section, key, value);
|
||||
}
|
||||
|
||||
void PTreeRegistry::putDWord(const std::string & section, const std::string & key, const DWORD value)
|
||||
{
|
||||
putValue(section, key, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T PTreeRegistry::getValue(const std::string & section, const std::string & key) const
|
||||
{
|
||||
const std::string path = section + "." + key;
|
||||
const T value = myINI.get<T>(path);
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PTreeRegistry::putValue(const std::string & section, const std::string & key, const T & value)
|
||||
{
|
||||
const std::string path = section + "." + key;
|
||||
myINI.put(path, value);
|
||||
}
|
||||
|
||||
std::map<std::string, std::map<std::string, std::string>> PTreeRegistry::getAllValues() const
|
||||
{
|
||||
std::map<std::string, std::map<std::string, std::string>> values;
|
||||
for (const auto & it1 : myINI)
|
||||
{
|
||||
for (const auto & it2 : it1.second)
|
||||
{
|
||||
values[it1.first][it2.first] = it2.second.get_value<std::string>();
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
42
source/frontends/common2/ptreeregistry.h
Normal file
42
source/frontends/common2/ptreeregistry.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "linux/registry.h"
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
class PTreeRegistry : public Registry
|
||||
{
|
||||
public:
|
||||
struct KeyQtEncodedLess
|
||||
{
|
||||
// this function is used to make the Qt registry compatible with sa2 and napple
|
||||
// it is here, in the base class PTreeRegistry simply because it makes things easier
|
||||
// KeyQtEncodedLess goes in the typedef init_t below
|
||||
bool operator()(const std::string & lhs, const std::string & rhs) const;
|
||||
};
|
||||
|
||||
typedef boost::property_tree::basic_ptree<std::string, std::string, KeyQtEncodedLess> ini_t;
|
||||
|
||||
std::string getString(const std::string & section, const std::string & key) const override;
|
||||
DWORD getDWord(const std::string & section, const std::string & key) const override;
|
||||
bool getBool(const std::string & section, const std::string & key) const override;
|
||||
|
||||
void putString(const std::string & section, const std::string & key, const std::string & value) override;
|
||||
void putDWord(const std::string & section, const std::string & key, const DWORD value) override;
|
||||
|
||||
template<typename T>
|
||||
T getValue(const std::string & section, const std::string & key) const;
|
||||
|
||||
template<typename T>
|
||||
void putValue(const std::string & section, const std::string & key, const T & value);
|
||||
|
||||
std::map<std::string, std::map<std::string, std::string>> getAllValues() const override;
|
||||
|
||||
protected:
|
||||
ini_t myINI;
|
||||
};
|
||||
|
||||
}
|
59
source/frontends/common2/speed.cpp
Normal file
59
source/frontends/common2/speed.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#include "frontends/common2/speed.h"
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include "CPU.h"
|
||||
#include "Core.h"
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
Speed::Speed(const bool fixedSpeed)
|
||||
: myFixedSpeed(fixedSpeed)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void Speed::reset()
|
||||
{
|
||||
myStartTime = std::chrono::steady_clock::now();
|
||||
myStartCycles = g_nCumulativeCycles;
|
||||
}
|
||||
|
||||
uint64_t Speed::getCyclesAtFixedSpeed(const size_t microseconds) const
|
||||
{
|
||||
const uint64_t cycles = static_cast<uint64_t>(microseconds * g_fCurrentCLK6502 * 1.0e-6);
|
||||
return cycles;
|
||||
}
|
||||
|
||||
uint64_t Speed::getCyclesTillNext(const size_t microseconds) const
|
||||
{
|
||||
if (myFixedSpeed || g_bFullSpeed)
|
||||
{
|
||||
return getCyclesAtFixedSpeed(microseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint64_t currentCycles = g_nCumulativeCycles;
|
||||
const auto currentTime = std::chrono::steady_clock::now();
|
||||
|
||||
const auto currentDelta = std::chrono::duration_cast<std::chrono::microseconds>(currentTime - myStartTime).count();
|
||||
// target the next time we will be called
|
||||
const auto targetDeltaInMicros = currentDelta + microseconds;
|
||||
|
||||
const uint64_t targetCycles = static_cast<uint64_t>(targetDeltaInMicros * g_fCurrentCLK6502 * 1.0e-6) + myStartCycles;
|
||||
if (targetCycles > currentCycles)
|
||||
{
|
||||
// number of cycles to fill this period
|
||||
const uint64_t cyclesToExecute = targetCycles - currentCycles;
|
||||
return cyclesToExecute;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we got ahead, nothing to do this time
|
||||
// CpuExecute will still execute 1 instruction, which does not cause any issues
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
source/frontends/common2/speed.h
Normal file
27
source/frontends/common2/speed.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
class Speed
|
||||
{
|
||||
public:
|
||||
Speed(const bool fixedSpeed);
|
||||
|
||||
void reset();
|
||||
|
||||
// calculate the number of cycles to execute in the current period
|
||||
// assuming the next call will happen in x microseconds
|
||||
uint64_t getCyclesTillNext(const size_t microseconds) const;
|
||||
uint64_t getCyclesAtFixedSpeed(const size_t microseconds) const;
|
||||
|
||||
private:
|
||||
|
||||
const bool myFixedSpeed;
|
||||
std::chrono::time_point<std::chrono::steady_clock> myStartTime;
|
||||
uint64_t myStartCycles;
|
||||
};
|
||||
|
||||
}
|
53
source/frontends/common2/timer.cpp
Normal file
53
source/frontends/common2/timer.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include "frontends/common2/timer.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
Timer::Timer()
|
||||
: mySum(0)
|
||||
, mySum2(0)
|
||||
, myN(0)
|
||||
{
|
||||
tic();
|
||||
}
|
||||
|
||||
void Timer::tic()
|
||||
{
|
||||
myT0 = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Timer::toc()
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto micros = std::chrono::duration_cast<std::chrono::microseconds>(now - myT0).count();
|
||||
const double s = micros * 0.000001;
|
||||
mySum += s;
|
||||
mySum2 += s * s;
|
||||
++myN;
|
||||
myT0 = now;
|
||||
}
|
||||
|
||||
double Timer::getTimeInSeconds() const
|
||||
{
|
||||
return mySum;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Timer & timer)
|
||||
{
|
||||
const int width = 10;
|
||||
const double m1 = timer.mySum / timer.myN;
|
||||
const double m2 = timer.mySum2 / timer.myN;
|
||||
const double std = std::sqrt(std::max(0.0, m2 - m1 * m1));
|
||||
const double scale = 1000;
|
||||
os << "total = " << std::setw(width) << timer.mySum * scale << " ms";
|
||||
os << ", mean = " << std::setw(width) << m1 * scale << " ms";
|
||||
os << ", std = " << std::setw(width) << std * scale << " ms";
|
||||
os << ", n = " << std::setw(6) << timer.myN;
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
29
source/frontends/common2/timer.h
Normal file
29
source/frontends/common2/timer.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include <chrono>
|
||||
#include <iosfwd>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
Timer();
|
||||
void tic();
|
||||
void toc();
|
||||
|
||||
double getTimeInSeconds() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Timer & timer);
|
||||
|
||||
private:
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> myT0;
|
||||
|
||||
double mySum;
|
||||
double mySum2;
|
||||
int myN;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Timer & timer);
|
||||
|
||||
}
|
68
source/frontends/common2/utils.cpp
Normal file
68
source/frontends/common2/utils.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/common2/utils.h"
|
||||
#include "frontends/common2/programoptions.h"
|
||||
|
||||
#include "SaveState.h"
|
||||
#include "Registry.h"
|
||||
|
||||
#include <libgen.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
|
||||
void setSnapshotFilename(const std::string & filename)
|
||||
{
|
||||
if (!filename.empty())
|
||||
{
|
||||
// it is unbelievably hard to convert a path to absolute
|
||||
// unless the file exists
|
||||
char * temp = strdup(filename.c_str());
|
||||
const char * dir = dirname(temp);
|
||||
// dir points inside temp!
|
||||
chdir(dir);
|
||||
Snapshot_SetFilename(filename);
|
||||
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void loadGeometryFromRegistry(const std::string §ion, Geometry & geometry)
|
||||
{
|
||||
if (geometry.empty) // otherwise it was user provided
|
||||
{
|
||||
const std::string path = section + "\\geometry";
|
||||
const auto loadValue = [&path](const char * name, int & dest)
|
||||
{
|
||||
DWORD value;
|
||||
if (RegLoadValue(path.c_str(), name, TRUE, &value))
|
||||
{
|
||||
// DWORD and int have the same size
|
||||
// but if they did not, this would be necessary
|
||||
typedef std::make_signed<DWORD>::type signed_t;
|
||||
dest = static_cast<signed_t>(value);
|
||||
}
|
||||
};
|
||||
|
||||
loadValue("width", geometry.width);
|
||||
loadValue("height", geometry.height);
|
||||
loadValue("x", geometry.x);
|
||||
loadValue("y", geometry.y);
|
||||
}
|
||||
}
|
||||
|
||||
void saveGeometryToRegistry(const std::string §ion, const Geometry & geometry)
|
||||
{
|
||||
const std::string path = section + "\\geometry";
|
||||
const auto saveValue = [&path](const char * name, const int source)
|
||||
{
|
||||
// this seems to already do the right thing for negative numbers
|
||||
RegSaveValue(path.c_str(), name, TRUE, source);
|
||||
};
|
||||
saveValue("width", geometry.width);
|
||||
saveValue("height", geometry.height);
|
||||
saveValue("x", geometry.x);
|
||||
saveValue("y", geometry.y);
|
||||
}
|
||||
|
||||
}
|
13
source/frontends/common2/utils.h
Normal file
13
source/frontends/common2/utils.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace common2
|
||||
{
|
||||
struct Geometry;
|
||||
|
||||
void setSnapshotFilename(const std::string & filename);
|
||||
|
||||
void loadGeometryFromRegistry(const std::string §ion, Geometry & geometry);
|
||||
void saveGeometryToRegistry(const std::string §ion, const Geometry & geometry);
|
||||
}
|
51
source/frontends/libretro/CMakeLists.txt
Normal file
51
source/frontends/libretro/CMakeLists.txt
Normal file
|
@ -0,0 +1,51 @@
|
|||
set(SOURCE_FILES
|
||||
environment.cpp
|
||||
libretro.cpp
|
||||
rdirectsound.cpp
|
||||
game.cpp
|
||||
joypadbase.cpp
|
||||
joypad.cpp
|
||||
mouse.cpp
|
||||
analog.cpp
|
||||
rdirectsound.cpp
|
||||
retroregistry.cpp
|
||||
retroframe.cpp
|
||||
diskcontrol.cpp
|
||||
serialisation.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
libretro-common/include/libretro.h
|
||||
environment.h
|
||||
rdirectsound.h
|
||||
game.h
|
||||
joypadbase.h
|
||||
joypad.h
|
||||
mouse.h
|
||||
analog.h
|
||||
rdirectsound.h
|
||||
retroregistry.h
|
||||
retroframe.h
|
||||
diskcontrol.h
|
||||
serialisation.h
|
||||
)
|
||||
|
||||
add_library(applewin_libretro SHARED
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
)
|
||||
|
||||
target_compile_features(applewin_libretro PUBLIC cxx_std_17)
|
||||
|
||||
target_include_directories(applewin_libretro PRIVATE
|
||||
libretro-common/include
|
||||
)
|
||||
|
||||
target_link_libraries(applewin_libretro PRIVATE
|
||||
appleii
|
||||
common2
|
||||
windows
|
||||
)
|
||||
|
||||
# just call it "applewin_libretro.so" as per libretro standard
|
||||
set_target_properties(applewin_libretro PROPERTIES PREFIX "")
|
28
source/frontends/libretro/analog.cpp
Normal file
28
source/frontends/libretro/analog.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include "frontends/libretro/analog.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include "libretro.h"
|
||||
|
||||
#define AXIS_MIN -32768
|
||||
#define AXIS_MAX 32767
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
Analog::Analog(unsigned device)
|
||||
: JoypadBase(device)
|
||||
, myAxisCodes(2)
|
||||
{
|
||||
myAxisCodes[0] = std::make_pair(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
||||
myAxisCodes[1] = std::make_pair(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
|
||||
}
|
||||
|
||||
double Analog::getAxis(int i) const
|
||||
{
|
||||
const auto & code = myAxisCodes[i];
|
||||
const int value = input_state_cb(0, myDevice, code.first, code.second);
|
||||
const double axis = 2.0 * double(value - AXIS_MIN) / double(AXIS_MAX - AXIS_MIN) - 1.0;
|
||||
return axis;
|
||||
}
|
||||
|
||||
}
|
21
source/frontends/libretro/analog.h
Normal file
21
source/frontends/libretro/analog.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/libretro/joypadbase.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class Analog : public JoypadBase
|
||||
{
|
||||
public:
|
||||
Analog(unsigned device);
|
||||
|
||||
double getAxis(int i) const override;
|
||||
|
||||
private:
|
||||
std::vector<std::pair<unsigned, unsigned> > myAxisCodes;
|
||||
};
|
||||
|
||||
}
|
45
source/frontends/libretro/buffer.h
Normal file
45
source/frontends/libretro/buffer.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
template<typename C> // so it works with both char * and const char *
|
||||
class Buffer
|
||||
{
|
||||
public:
|
||||
Buffer(C * const begin, size_t const size) : myPtr(begin), myEnd(begin + size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & get()
|
||||
{
|
||||
C * c = myPtr;
|
||||
advance(sizeof(T));
|
||||
return *reinterpret_cast<T *>(c);
|
||||
}
|
||||
|
||||
void get(size_t const size, C * & begin, C * & end)
|
||||
{
|
||||
begin = myPtr;
|
||||
advance(size);
|
||||
end = myPtr;
|
||||
}
|
||||
|
||||
private:
|
||||
C * myPtr;
|
||||
C * const myEnd;
|
||||
|
||||
void advance(size_t const size)
|
||||
{
|
||||
if (myPtr + size > myEnd)
|
||||
{
|
||||
throw std::runtime_error("Buffer: out of bounds");
|
||||
}
|
||||
myPtr += size;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
289
source/frontends/libretro/diskcontrol.cpp
Normal file
289
source/frontends/libretro/diskcontrol.cpp
Normal file
|
@ -0,0 +1,289 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/libretro/diskcontrol.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include "Core.h"
|
||||
#include "CardManager.h"
|
||||
#include "Disk.h"
|
||||
#include "Harddisk.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
DiskControl::DiskControl() : myEjected(false), myIndex(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool DiskControl::insertDisk(const std::string & filename)
|
||||
{
|
||||
if (insertFloppyDisk(filename))
|
||||
{
|
||||
myIndex = 0;
|
||||
myImages.clear();
|
||||
myImages.push_back(filename);
|
||||
myEjected = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return insertHardDisk(filename);
|
||||
}
|
||||
|
||||
bool DiskControl::insertPlaylist(const std::string & filename)
|
||||
{
|
||||
const std::filesystem::path path(filename);
|
||||
std::ifstream playlist(path);
|
||||
if (!playlist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
myImages.clear();
|
||||
const std::filesystem::path parent = path.parent_path();
|
||||
|
||||
std::string line;
|
||||
while (std::getline(playlist, line))
|
||||
{
|
||||
// should we trim initial spaces?
|
||||
if (!line.empty() && line[0] != '#')
|
||||
{
|
||||
std::filesystem::path image(line);
|
||||
if (image.is_relative())
|
||||
{
|
||||
image = parent / image;
|
||||
}
|
||||
myImages.push_back(image);
|
||||
}
|
||||
}
|
||||
|
||||
// if we have an initial disk image, let's try to honour it
|
||||
if (!ourInitialPath.empty() && ourInitialIndex < myImages.size() && myImages[ourInitialIndex] == ourInitialPath)
|
||||
{
|
||||
myIndex = ourInitialIndex;
|
||||
// do we need to reset for next time?
|
||||
ourInitialPath.clear();
|
||||
ourInitialIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// insert the first image
|
||||
myIndex = 0;
|
||||
}
|
||||
|
||||
// this is safe even if myImages is empty
|
||||
myEjected = true;
|
||||
return setEjectedState(false);
|
||||
}
|
||||
|
||||
bool DiskControl::insertFloppyDisk(const std::string & filename) const
|
||||
{
|
||||
CardManager & cardManager = GetCardMgr();
|
||||
|
||||
Disk2InterfaceCard * disk2Card = dynamic_cast<Disk2InterfaceCard*>(cardManager.GetObj(SLOT6));
|
||||
if (disk2Card)
|
||||
{
|
||||
const ImageError_e error = disk2Card->InsertDisk(DRIVE_1, filename.c_str(), IMAGE_FORCE_WRITE_PROTECTED, IMAGE_DONT_CREATE);
|
||||
|
||||
if (error == eIMAGE_ERROR_NONE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiskControl::insertHardDisk(const std::string & filename) const
|
||||
{
|
||||
CardManager & cardManager = GetCardMgr();
|
||||
|
||||
if (cardManager.QuerySlot(SLOT7) != CT_GenericHDD)
|
||||
{
|
||||
cardManager.Insert(SLOT7, CT_GenericHDD);
|
||||
}
|
||||
|
||||
HarddiskInterfaceCard * harddiskCard = dynamic_cast<HarddiskInterfaceCard*>(cardManager.GetObj(SLOT7));
|
||||
if (harddiskCard)
|
||||
{
|
||||
BOOL bRes = harddiskCard->Insert(HARDDISK_1, filename);
|
||||
return bRes == TRUE;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiskControl::getEjectedState() const
|
||||
{
|
||||
return myEjected;
|
||||
}
|
||||
|
||||
bool DiskControl::setEjectedState(bool ejected)
|
||||
{
|
||||
if (myEjected == ejected)
|
||||
{
|
||||
return true; // or false?
|
||||
}
|
||||
|
||||
CardManager & cardManager = GetCardMgr();
|
||||
Disk2InterfaceCard * disk2Card = dynamic_cast<Disk2InterfaceCard*>(cardManager.GetObj(SLOT6));
|
||||
|
||||
bool result = false;
|
||||
if (disk2Card && myIndex < myImages.size())
|
||||
{
|
||||
if (ejected)
|
||||
{
|
||||
disk2Card->EjectDisk(DRIVE_1);
|
||||
result = true;
|
||||
myEjected = ejected;
|
||||
}
|
||||
else
|
||||
{
|
||||
// inserted
|
||||
result = insertFloppyDisk(myImages[myIndex]);
|
||||
myEjected = !result;
|
||||
ra2::log_cb(RETRO_LOG_INFO, "Insert new disk: %s -> %d\n", myImages[myIndex].c_str(), result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DiskControl::getImageIndex() const
|
||||
{
|
||||
return myIndex;
|
||||
}
|
||||
|
||||
bool DiskControl::setImageIndex(size_t index)
|
||||
{
|
||||
if (myEjected)
|
||||
{
|
||||
myIndex = index;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t DiskControl::getNumImages() const
|
||||
{
|
||||
return myImages.size();
|
||||
}
|
||||
|
||||
bool DiskControl::replaceImageIndex(size_t index, const std::string & path)
|
||||
{
|
||||
if (myEjected && myIndex < myImages.size())
|
||||
{
|
||||
myImages[index] = path;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DiskControl::removeImageIndex(size_t index)
|
||||
{
|
||||
if (myEjected && myIndex < myImages.size())
|
||||
{
|
||||
myImages.erase(myImages.begin() + index);
|
||||
if (myImages.empty() || myIndex == index)
|
||||
{
|
||||
myIndex = myImages.size();
|
||||
}
|
||||
else if (myIndex > index)
|
||||
{
|
||||
--myIndex;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DiskControl::addImageIndex()
|
||||
{
|
||||
myImages.push_back(std::string());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskControl::getImagePath(unsigned index, char *path, size_t len) const
|
||||
{
|
||||
if (index < myImages.size())
|
||||
{
|
||||
strncpy(path, myImages[index].c_str(), len);
|
||||
path[len - 1] = 0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DiskControl::getImageLabel(unsigned index, char *label, size_t len) const
|
||||
{
|
||||
if (index < myImages.size())
|
||||
{
|
||||
const std::string filename = myImages[index].filename();
|
||||
strncpy(label, filename.c_str(), len);
|
||||
label[len - 1] = 0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DiskControl::serialise(Buffer<char> & buffer) const
|
||||
{
|
||||
buffer.get<bool>() = myEjected;
|
||||
buffer.get<size_t>() = myIndex;
|
||||
buffer.get<size_t>() = myImages.size();
|
||||
|
||||
for (std::string const & image : myImages)
|
||||
{
|
||||
size_t const size = image.size();
|
||||
buffer.get<size_t>() = size;
|
||||
char * begin, * end;
|
||||
buffer.get(size, begin, end);
|
||||
memcpy(begin, image.data(), end - begin);
|
||||
}
|
||||
}
|
||||
|
||||
void DiskControl::deserialise(Buffer<char const> & buffer)
|
||||
{
|
||||
myEjected = buffer.get<bool const>();
|
||||
myIndex = buffer.get<size_t const>();
|
||||
size_t const numberOfImages = buffer.get<size_t const>();
|
||||
myImages.clear();
|
||||
myImages.resize(numberOfImages);
|
||||
|
||||
for (size_t i = 0; i < numberOfImages; ++i)
|
||||
{
|
||||
size_t const size = buffer.get<size_t const>();
|
||||
char const * begin, * end;
|
||||
buffer.get(size, begin, end);
|
||||
myImages[i].assign(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned DiskControl::ourInitialIndex = 0;
|
||||
std::string DiskControl::ourInitialPath;
|
||||
void DiskControl::setInitialPath(unsigned index, const char *path)
|
||||
{
|
||||
if (path && *path)
|
||||
{
|
||||
ourInitialIndex = index;
|
||||
ourInitialPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
51
source/frontends/libretro/diskcontrol.h
Normal file
51
source/frontends/libretro/diskcontrol.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "frontends/libretro/buffer.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class DiskControl
|
||||
{
|
||||
public:
|
||||
DiskControl();
|
||||
|
||||
bool getEjectedState() const;
|
||||
bool setEjectedState(bool state);
|
||||
|
||||
size_t getImageIndex() const;
|
||||
bool setImageIndex(size_t index);
|
||||
|
||||
size_t getNumImages() const;
|
||||
|
||||
bool replaceImageIndex(size_t index, const std::string & path);
|
||||
bool removeImageIndex(size_t index);
|
||||
bool addImageIndex();
|
||||
|
||||
// these 2 functions update the images for the Disc Control Interface
|
||||
bool insertDisk(const std::string & filename);
|
||||
bool insertPlaylist(const std::string & filename);
|
||||
|
||||
bool getImagePath(unsigned index, char *path, size_t len) const;
|
||||
bool getImageLabel(unsigned index, char *label, size_t len) const;
|
||||
|
||||
static void setInitialPath(unsigned index, const char *path);
|
||||
|
||||
void serialise(Buffer<char> & buffer) const;
|
||||
void deserialise(Buffer<char const> & buffer);
|
||||
|
||||
private:
|
||||
std::vector<std::filesystem::path> myImages;
|
||||
|
||||
bool myEjected;
|
||||
size_t myIndex;
|
||||
|
||||
bool insertFloppyDisk(const std::string & filename) const;
|
||||
bool insertHardDisk(const std::string & filename) const;
|
||||
|
||||
static unsigned ourInitialIndex;
|
||||
static std::string ourInitialPath;
|
||||
};
|
||||
|
||||
}
|
36
source/frontends/libretro/environment.cpp
Normal file
36
source/frontends/libretro/environment.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
void fallback_log(enum retro_log_level level, const char *fmt, ...)
|
||||
{
|
||||
(void)level;
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
vfprintf(stderr, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
retro_log_callback logging;
|
||||
retro_log_printf_t log_cb = fallback_log;
|
||||
retro_input_poll_t input_poll_cb;
|
||||
retro_input_state_t input_state_cb;
|
||||
|
||||
retro_environment_t environ_cb;
|
||||
retro_video_refresh_t video_cb;
|
||||
retro_audio_sample_t audio_cb;
|
||||
retro_audio_sample_batch_t audio_batch_cb;
|
||||
|
||||
void display_message(const std::string & message)
|
||||
{
|
||||
retro_message rmsg;
|
||||
rmsg.frames = 180;
|
||||
rmsg.msg = message.c_str();
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &rmsg);
|
||||
}
|
||||
|
||||
}
|
22
source/frontends/libretro/environment.h
Normal file
22
source/frontends/libretro/environment.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "libretro.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
void fallback_log(enum retro_log_level level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
extern retro_log_callback logging;
|
||||
extern retro_log_printf_t log_cb __attribute__((format(printf, 2, 3)));
|
||||
extern retro_input_poll_t input_poll_cb;
|
||||
extern retro_input_state_t input_state_cb;
|
||||
extern retro_environment_t environ_cb;
|
||||
extern retro_video_refresh_t video_cb;
|
||||
extern retro_audio_sample_t audio_cb;
|
||||
extern retro_audio_sample_batch_t audio_batch_cb;
|
||||
|
||||
#define MAX_PADS 1
|
||||
|
||||
void display_message(const std::string & message);
|
||||
|
||||
}
|
273
source/frontends/libretro/game.cpp
Normal file
273
source/frontends/libretro/game.cpp
Normal file
|
@ -0,0 +1,273 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/libretro/game.h"
|
||||
#include "frontends/libretro/retroregistry.h"
|
||||
#include "frontends/libretro/retroframe.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "CardManager.h"
|
||||
#include "Core.h"
|
||||
#include "Mockingboard.h"
|
||||
#include "Speaker.h"
|
||||
#include "Log.h"
|
||||
#include "CPU.h"
|
||||
#include "NTSC.h"
|
||||
#include "Utilities.h"
|
||||
#include "Interface.h"
|
||||
|
||||
#include "linux/keyboard.h"
|
||||
#include "linux/registry.h"
|
||||
#include "linux/paddle.h"
|
||||
#include "linux/context.h"
|
||||
#include "frontends/common2/utils.h"
|
||||
|
||||
#include "libretro.h"
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
unsigned Game::ourInputDevices[MAX_PADS] = {RETRO_DEVICE_NONE};
|
||||
|
||||
Game::Game()
|
||||
: mySpeed(true) // fixed speed
|
||||
, myButtonStates(RETRO_DEVICE_ID_JOYPAD_R3 + 1)
|
||||
{
|
||||
myLoggerContext.reset(new LoggerContext(true));
|
||||
myRegistryContext.reset(new RegistryContext(CreateRetroRegistry()));
|
||||
myFrame.reset(new ra2::RetroFrame());
|
||||
|
||||
SetFrame(myFrame);
|
||||
myFrame->Begin();
|
||||
|
||||
Video & video = GetVideo();
|
||||
// should the user be allowed to tweak 0.75
|
||||
myMouse[0] = {0.0, 0.75 / video.GetFrameBufferBorderlessWidth(), RETRO_DEVICE_ID_MOUSE_X};
|
||||
myMouse[1] = {0.0, 0.75 / video.GetFrameBufferBorderlessHeight(), RETRO_DEVICE_ID_MOUSE_Y};
|
||||
}
|
||||
|
||||
Game::~Game()
|
||||
{
|
||||
myFrame->End();
|
||||
myFrame.reset();
|
||||
SetFrame(myFrame);
|
||||
}
|
||||
|
||||
retro_usec_t Game::ourFrameTime = 0;
|
||||
|
||||
void Game::executeOneFrame()
|
||||
{
|
||||
if (g_nAppMode == MODE_RUNNING)
|
||||
{
|
||||
const bool bVideoUpdate = true;
|
||||
const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();
|
||||
|
||||
const uint64_t cyclesToExecute = mySpeed.getCyclesTillNext(ourFrameTime);
|
||||
const DWORD executedCycles = CpuExecute(cyclesToExecute, bVideoUpdate);
|
||||
|
||||
g_dwCyclesThisFrame = (g_dwCyclesThisFrame + executedCycles) % dwClksPerFrame;
|
||||
GetCardMgr().Update(executedCycles);
|
||||
SpkrUpdate(executedCycles);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::processInputEvents()
|
||||
{
|
||||
input_poll_cb();
|
||||
keyboardEmulation();
|
||||
mouseEmulation();
|
||||
}
|
||||
|
||||
void Game::keyboardCallback(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
processKeyDown(keycode, character, key_modifiers);
|
||||
}
|
||||
else
|
||||
{
|
||||
processKeyUp(keycode, character, key_modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::frameTimeCallback(retro_usec_t usec)
|
||||
{
|
||||
ourFrameTime = usec;
|
||||
}
|
||||
|
||||
void Game::processKeyDown(unsigned keycode, uint32_t character, uint16_t key_modifiers)
|
||||
{
|
||||
BYTE ch = 0;
|
||||
switch (keycode)
|
||||
{
|
||||
case RETROK_RETURN:
|
||||
{
|
||||
ch = 0x0d;
|
||||
break;
|
||||
}
|
||||
case RETROK_BACKSPACE: // same as AppleWin
|
||||
case RETROK_LEFT:
|
||||
{
|
||||
ch = 0x08;
|
||||
break;
|
||||
}
|
||||
case RETROK_RIGHT:
|
||||
{
|
||||
ch = 0x15;
|
||||
break;
|
||||
}
|
||||
case RETROK_UP:
|
||||
{
|
||||
ch = 0x0b;
|
||||
break;
|
||||
}
|
||||
case RETROK_DOWN:
|
||||
{
|
||||
ch = 0x0a;
|
||||
break;
|
||||
}
|
||||
case RETROK_DELETE:
|
||||
{
|
||||
ch = 0x7f;
|
||||
break;
|
||||
}
|
||||
case RETROK_ESCAPE:
|
||||
{
|
||||
ch = 0x1b;
|
||||
break;
|
||||
}
|
||||
case RETROK_TAB:
|
||||
{
|
||||
ch = 0x09;
|
||||
break;
|
||||
}
|
||||
case RETROK_LALT:
|
||||
{
|
||||
Paddle::setButtonPressed(Paddle::ourOpenApple);
|
||||
break;
|
||||
}
|
||||
case RETROK_RALT:
|
||||
{
|
||||
Paddle::setButtonPressed(Paddle::ourSolidApple);
|
||||
break;
|
||||
}
|
||||
case RETROK_a ... RETROK_z:
|
||||
{
|
||||
ch = (keycode - RETROK_a) + 0x01;
|
||||
if (key_modifiers & RETROKMOD_CTRL)
|
||||
{
|
||||
// ok
|
||||
}
|
||||
else if (key_modifiers & RETROKMOD_SHIFT)
|
||||
{
|
||||
ch += 0x60;
|
||||
}
|
||||
else
|
||||
{
|
||||
ch += 0x40;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ch)
|
||||
{
|
||||
switch (character) {
|
||||
case 0x20 ... 0x40:
|
||||
case 0x5b ... 0x60:
|
||||
case 0x7b ... 0x7e:
|
||||
{
|
||||
// not the letters
|
||||
// this is very simple, but one cannot handle CRTL-key combination.
|
||||
ch = character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ch)
|
||||
{
|
||||
addKeyToBuffer(ch);
|
||||
// log_cb(RETRO_LOG_INFO, "RA2: %s - %02x\n", __FUNCTION__, ch);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::processKeyUp(unsigned keycode, uint32_t character, uint16_t key_modifiers)
|
||||
{
|
||||
switch (keycode)
|
||||
{
|
||||
case RETROK_LALT:
|
||||
{
|
||||
Paddle::setButtonReleased(Paddle::ourOpenApple);
|
||||
break;
|
||||
}
|
||||
case RETROK_RALT:
|
||||
{
|
||||
Paddle::setButtonReleased(Paddle::ourSolidApple);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Game::checkButtonPressed(unsigned id)
|
||||
{
|
||||
// pressed if it is down now, but was up before
|
||||
const int value = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, id);
|
||||
const bool pressed = (value != 0) && myButtonStates[id] == 0;
|
||||
|
||||
// update to avoid multiple fires
|
||||
myButtonStates[id] = value;
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
|
||||
void Game::keyboardEmulation()
|
||||
{
|
||||
if (ourInputDevices[0] != RETRO_DEVICE_NONE)
|
||||
{
|
||||
if (checkButtonPressed(RETRO_DEVICE_ID_JOYPAD_R))
|
||||
{
|
||||
myFrame->CycleVideoType();
|
||||
}
|
||||
if (checkButtonPressed(RETRO_DEVICE_ID_JOYPAD_L))
|
||||
{
|
||||
myFrame->Cycle50ScanLines();
|
||||
}
|
||||
if (checkButtonPressed(RETRO_DEVICE_ID_JOYPAD_START))
|
||||
{
|
||||
ResetMachineState();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill(myButtonStates.begin(), myButtonStates.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::mouseEmulation()
|
||||
{
|
||||
for (auto & mouse : myMouse)
|
||||
{
|
||||
const int16_t x = input_state_cb(0, RETRO_DEVICE_MOUSE, 0, mouse.id);
|
||||
mouse.position += x * mouse.multiplier;
|
||||
mouse.position = std::min(1.0, std::max(mouse.position, -1.0));
|
||||
}
|
||||
}
|
||||
|
||||
double Game::getMousePosition(int i) const
|
||||
{
|
||||
return myMouse[i].position;
|
||||
}
|
||||
|
||||
bool Game::loadSnapshot(const std::string & path)
|
||||
{
|
||||
common2::setSnapshotFilename(path);
|
||||
myFrame->LoadSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
DiskControl & Game::getDiskControl()
|
||||
{
|
||||
return myDiskControl;
|
||||
}
|
||||
|
||||
}
|
70
source/frontends/libretro/game.h
Normal file
70
source/frontends/libretro/game.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/common2/speed.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
#include "frontends/libretro/diskcontrol.h"
|
||||
|
||||
#include "linux/context.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class RetroFrame;
|
||||
|
||||
class Game
|
||||
{
|
||||
public:
|
||||
Game();
|
||||
~Game();
|
||||
|
||||
bool loadSnapshot(const std::string & path);
|
||||
|
||||
void executeOneFrame();
|
||||
void processInputEvents();
|
||||
|
||||
void drawVideoBuffer();
|
||||
|
||||
double getMousePosition(int i) const;
|
||||
|
||||
DiskControl & getDiskControl();
|
||||
|
||||
static void keyboardCallback(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers);
|
||||
|
||||
static void frameTimeCallback(retro_usec_t usec);
|
||||
static constexpr size_t FPS = 60;
|
||||
static unsigned ourInputDevices[MAX_PADS];
|
||||
static retro_usec_t ourFrameTime;
|
||||
|
||||
private:
|
||||
// keep them in this order!
|
||||
std::shared_ptr<LoggerContext> myLoggerContext;
|
||||
std::shared_ptr<RegistryContext> myRegistryContext;
|
||||
std::shared_ptr<RetroFrame> myFrame;
|
||||
|
||||
common2::Speed mySpeed; // fixed speed
|
||||
|
||||
std::vector<int> myButtonStates;
|
||||
|
||||
struct MousePosition_t
|
||||
{
|
||||
double position; // -1 to 1
|
||||
double multiplier;
|
||||
unsigned id;
|
||||
};
|
||||
|
||||
MousePosition_t myMouse[2];
|
||||
|
||||
DiskControl myDiskControl;
|
||||
|
||||
bool checkButtonPressed(unsigned id);
|
||||
void keyboardEmulation();
|
||||
void mouseEmulation();
|
||||
|
||||
static void processKeyDown(unsigned keycode, uint32_t character, uint16_t key_modifiers);
|
||||
static void processKeyUp(unsigned keycode, uint32_t character, uint16_t key_modifiers);
|
||||
};
|
||||
|
||||
}
|
33
source/frontends/libretro/joypad.cpp
Normal file
33
source/frontends/libretro/joypad.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include "frontends/libretro/joypad.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include "libretro.h"
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
Joypad::Joypad(unsigned device)
|
||||
: JoypadBase(device)
|
||||
, myAxisCodes(2)
|
||||
{
|
||||
myAxisCodes[0][RETRO_DEVICE_ID_JOYPAD_LEFT] = -1.0;
|
||||
myAxisCodes[0][RETRO_DEVICE_ID_JOYPAD_RIGHT] = 1.0;
|
||||
myAxisCodes[1][RETRO_DEVICE_ID_JOYPAD_UP] = -1.0;
|
||||
myAxisCodes[1][RETRO_DEVICE_ID_JOYPAD_DOWN] = 1.0;
|
||||
}
|
||||
|
||||
double Joypad::getAxis(int i) const
|
||||
{
|
||||
for (const auto & axis : myAxisCodes[i])
|
||||
{
|
||||
const int value = input_state_cb(0, myDevice, 0, axis.first);
|
||||
if (value)
|
||||
{
|
||||
return axis.second;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
}
|
22
source/frontends/libretro/joypad.h
Normal file
22
source/frontends/libretro/joypad.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/libretro/joypadbase.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class Joypad : public JoypadBase
|
||||
{
|
||||
public:
|
||||
Joypad(unsigned device);
|
||||
|
||||
double getAxis(int i) const override;
|
||||
|
||||
private:
|
||||
std::vector<std::map<unsigned, double> > myAxisCodes;
|
||||
};
|
||||
|
||||
}
|
25
source/frontends/libretro/joypadbase.cpp
Normal file
25
source/frontends/libretro/joypadbase.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "frontends/libretro/joypadbase.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include "libretro.h"
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
ControllerBase::ControllerBase(unsigned device, const std::vector<unsigned> & buttonCodes)
|
||||
: myDevice(device), myButtonCodes(buttonCodes)
|
||||
{
|
||||
}
|
||||
|
||||
bool ControllerBase::getButton(int i) const
|
||||
{
|
||||
const int value = input_state_cb(0, myDevice, 0, myButtonCodes[i]);
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
JoypadBase::JoypadBase(unsigned device) : ControllerBase(device, {RETRO_DEVICE_ID_JOYPAD_A, RETRO_DEVICE_ID_JOYPAD_B})
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
32
source/frontends/libretro/joypadbase.h
Normal file
32
source/frontends/libretro/joypadbase.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "linux/paddle.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class ControllerBase : public Paddle
|
||||
{
|
||||
public:
|
||||
ControllerBase(unsigned device, const std::vector<unsigned> & buttonCodes);
|
||||
|
||||
bool getButton(int i) const override;
|
||||
|
||||
protected:
|
||||
const unsigned myDevice;
|
||||
|
||||
private:
|
||||
const std::vector<unsigned> myButtonCodes;
|
||||
};
|
||||
|
||||
class JoypadBase : public ControllerBase
|
||||
{
|
||||
public:
|
||||
JoypadBase(unsigned device);
|
||||
};
|
||||
|
||||
|
||||
}
|
3890
source/frontends/libretro/libretro-common/include/libretro.h
Normal file
3890
source/frontends/libretro/libretro-common/include/libretro.h
Normal file
File diff suppressed because it is too large
Load diff
438
source/frontends/libretro/libretro.cpp
Normal file
438
source/frontends/libretro/libretro.cpp
Normal file
|
@ -0,0 +1,438 @@
|
|||
#include "libretro.h"
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include "Common.h"
|
||||
#include "Video.h"
|
||||
#include "Interface.h"
|
||||
#include "Memory.h"
|
||||
#include "Utilities.h"
|
||||
#include "Debugger/DebugDefs.h"
|
||||
|
||||
#include "linux/version.h"
|
||||
#include "linux/paddle.h"
|
||||
|
||||
#include "frontends/libretro/game.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
#include "frontends/libretro/rdirectsound.h"
|
||||
#include "frontends/libretro/retroregistry.h"
|
||||
#include "frontends/libretro/joypad.h"
|
||||
#include "frontends/libretro/analog.h"
|
||||
#include "frontends/libretro/mouse.h"
|
||||
#include "frontends/libretro/serialisation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::unique_ptr<ra2::Game> ourGame;
|
||||
|
||||
bool endsWith(const std::string & value, const std::string & ending)
|
||||
{
|
||||
if (ending.size() > value.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
}
|
||||
|
||||
bool retro_set_eject_state(bool ejected)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, ejected);
|
||||
return ourGame->getDiskControl().setEjectedState(ejected);
|
||||
}
|
||||
|
||||
bool retro_get_eject_state()
|
||||
{
|
||||
return ourGame->getDiskControl().getEjectedState();
|
||||
}
|
||||
|
||||
unsigned retro_get_image_index()
|
||||
{
|
||||
return ourGame->getDiskControl().getImageIndex();
|
||||
}
|
||||
|
||||
bool retro_set_image_index(unsigned index)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index);
|
||||
return ourGame->getDiskControl().setImageIndex(index);
|
||||
}
|
||||
|
||||
unsigned retro_get_num_images()
|
||||
{
|
||||
return ourGame->getDiskControl().getNumImages();
|
||||
}
|
||||
|
||||
bool retro_replace_image_index(unsigned index, const struct retro_game_info *info)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%s)\n", __FUNCTION__, info->path);
|
||||
if (info->path)
|
||||
{
|
||||
return ourGame->getDiskControl().replaceImageIndex(index, info->path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ourGame->getDiskControl().removeImageIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
bool retro_add_image_index()
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
return ourGame->getDiskControl().addImageIndex();
|
||||
}
|
||||
|
||||
bool retro_set_initial_image(unsigned index, const char *path)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d) = %s\n", __FUNCTION__, index, path);
|
||||
ra2::DiskControl::setInitialPath(index, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool retro_get_image_path(unsigned index, char *path, size_t len)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index);
|
||||
return ourGame->getDiskControl().getImagePath(index, path, len);
|
||||
}
|
||||
|
||||
bool retro_get_image_label(unsigned index, char *label, size_t len)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index);
|
||||
return ourGame->getDiskControl().getImageLabel(index, label, len);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void retro_init(void)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
void retro_deinit(void)
|
||||
{
|
||||
ourGame.reset();
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
unsigned retro_api_version(void)
|
||||
{
|
||||
return RETRO_API_VERSION;
|
||||
}
|
||||
|
||||
void retro_set_controller_port_device(unsigned port, unsigned device)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s, Plugging device %u into port %u.\n", __FUNCTION__, device, port);
|
||||
if (port == 0)
|
||||
{
|
||||
ra2::Game::ourInputDevices[port] = device;
|
||||
|
||||
switch (device)
|
||||
{
|
||||
case RETRO_DEVICE_NONE:
|
||||
Paddle::instance.reset();
|
||||
break;
|
||||
case RETRO_DEVICE_JOYPAD:
|
||||
Paddle::instance.reset(new ra2::Joypad(device));
|
||||
Paddle::setSquaring(false);
|
||||
break;
|
||||
case RETRO_DEVICE_ANALOG:
|
||||
Paddle::instance.reset(new ra2::Analog(device));
|
||||
Paddle::setSquaring(true);
|
||||
break;
|
||||
case RETRO_DEVICE_MOUSE:
|
||||
Paddle::instance.reset(new ra2::Mouse(device, &ourGame));
|
||||
Paddle::setSquaring(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void retro_get_system_info(retro_system_info *info)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
static std::string version = getVersion();
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->library_name = "AppleWin";
|
||||
info->library_version = version.c_str();
|
||||
info->need_fullpath = true;
|
||||
info->valid_extensions = "bin|do|dsk|nib|po|gz|woz|zip|2mg|2img|iie|apl|hdv|yaml|m3u";
|
||||
}
|
||||
|
||||
void retro_get_system_av_info(retro_system_av_info *info)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
|
||||
Video & video = GetVideo();
|
||||
|
||||
info->geometry.base_width = video.GetFrameBufferBorderlessWidth();
|
||||
info->geometry.base_height = video.GetFrameBufferBorderlessHeight();
|
||||
info->geometry.max_width = video.GetFrameBufferBorderlessWidth();
|
||||
info->geometry.max_height = video.GetFrameBufferBorderlessHeight();
|
||||
info->geometry.aspect_ratio = 0;
|
||||
|
||||
info->timing.fps = ra2::Game::FPS;
|
||||
info->timing.sample_rate = SPKR_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t cb)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
ra2::environ_cb = cb;
|
||||
|
||||
if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &ra2::logging))
|
||||
ra2::log_cb = ra2::logging.log;
|
||||
else
|
||||
ra2::log_cb = ra2::fallback_log;
|
||||
|
||||
static const struct retro_controller_description controllers[] =
|
||||
{
|
||||
{ "Standard Joypad", RETRO_DEVICE_JOYPAD },
|
||||
{ "Analog Joypad", RETRO_DEVICE_ANALOG },
|
||||
{ "Mouse", RETRO_DEVICE_MOUSE },
|
||||
};
|
||||
|
||||
static const struct retro_controller_info ports[] =
|
||||
{
|
||||
{ controllers, sizeof(controllers) / sizeof(controllers[0]) },
|
||||
{ nullptr, 0 }
|
||||
};
|
||||
|
||||
cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
|
||||
|
||||
retro_keyboard_callback keyboardCallback = {&ra2::Game::keyboardCallback};
|
||||
cb(RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK, &keyboardCallback);
|
||||
|
||||
retro_audio_buffer_status_callback audioCallback = {&ra2::bufferStatusCallback};
|
||||
cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &audioCallback);
|
||||
|
||||
retro_frame_time_callback timeCallback = {&ra2::Game::frameTimeCallback, 1000000 / ra2::Game::FPS};
|
||||
cb(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &timeCallback);
|
||||
|
||||
// see retro_get_memory_data() below
|
||||
bool achievements = true;
|
||||
cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements);
|
||||
|
||||
unsigned dciVersion = 0;
|
||||
if (cb(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &dciVersion) && (dciVersion >= 1))
|
||||
{
|
||||
retro_disk_control_ext_callback diskControlExtCallback = {
|
||||
&retro_set_eject_state, &retro_get_eject_state,
|
||||
&retro_get_image_index, &retro_set_image_index,
|
||||
&retro_get_num_images, &retro_replace_image_index,
|
||||
&retro_add_image_index, nullptr,
|
||||
&retro_get_image_path, &retro_get_image_label
|
||||
};
|
||||
// intentionally skip retro_set_initial_image
|
||||
// as we do always want to restart with the first disk of a playlist
|
||||
// which (normally) is the only bootable floppy of a game
|
||||
cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &diskControlExtCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
retro_disk_control_callback diskControlCallback = {
|
||||
&retro_set_eject_state, &retro_get_eject_state,
|
||||
&retro_get_image_index, &retro_set_image_index,
|
||||
&retro_get_num_images, &retro_replace_image_index,
|
||||
&retro_add_image_index
|
||||
};
|
||||
cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &diskControlCallback);
|
||||
}
|
||||
|
||||
ra2::SetupRetroVariables();
|
||||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t cb)
|
||||
{
|
||||
ra2::audio_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
|
||||
{
|
||||
ra2::audio_batch_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_poll(retro_input_poll_t cb)
|
||||
{
|
||||
ra2::input_poll_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_state(retro_input_state_t cb)
|
||||
{
|
||||
ra2::input_state_cb = cb;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t cb)
|
||||
{
|
||||
ra2::video_cb = cb;
|
||||
}
|
||||
|
||||
void retro_run(void)
|
||||
{
|
||||
ourGame->processInputEvents();
|
||||
ourGame->executeOneFrame();
|
||||
GetFrame().VideoPresentScreen();
|
||||
const size_t ms = (1000 + 60 - 1) / 60; // round up
|
||||
ra2::writeAudio(ms);
|
||||
}
|
||||
|
||||
bool retro_load_game(const retro_game_info *info)
|
||||
{
|
||||
ourGame.reset();
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
|
||||
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||
if (!ra2::environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<ra2::Game> game(new ra2::Game());
|
||||
|
||||
const std::string snapshotEnding = ".aws.yaml";
|
||||
const std::string playlistEnding = ".m3u";
|
||||
|
||||
bool ok;
|
||||
|
||||
if (info->path && *info->path)
|
||||
{
|
||||
const std::string gamePath = info->path;
|
||||
if (endsWith(gamePath, snapshotEnding))
|
||||
{
|
||||
ok = game->loadSnapshot(gamePath);
|
||||
}
|
||||
else if (endsWith(gamePath, playlistEnding))
|
||||
{
|
||||
ok = game->getDiskControl().insertPlaylist(gamePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = game->getDiskControl().insertDisk(gamePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = false;
|
||||
}
|
||||
|
||||
ra2::log_cb(RETRO_LOG_INFO, "Game path: %s -> %d\n", info->path, ok);
|
||||
|
||||
if (ok)
|
||||
{
|
||||
ra2::display_message("Enable Game Focus Mode for better keyboard handling");
|
||||
std::swap(ourGame, game);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "Exception: %s\n", e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void retro_unload_game(void)
|
||||
{
|
||||
ourGame.reset();
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
unsigned retro_get_region(void)
|
||||
{
|
||||
return RETRO_REGION_NTSC;
|
||||
}
|
||||
|
||||
void retro_reset(void)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
ResetMachineState();
|
||||
}
|
||||
|
||||
bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t retro_serialize_size(void)
|
||||
{
|
||||
try
|
||||
{
|
||||
const size_t size = ra2::RetroSerialisation::getSize();
|
||||
return size;
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s - %s\n", __FUNCTION__, e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool retro_serialize(void *data, size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
ra2::RetroSerialisation::serialise(data, size, ourGame->getDiskControl());
|
||||
return true;
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s - %s\n", __FUNCTION__, e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool retro_unserialize(const void *data, size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
ra2::RetroSerialisation::deserialise(data, size, ourGame->getDiskControl());
|
||||
return true;
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s - %s\n", __FUNCTION__, e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void retro_cheat_reset(void)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
void retro_cheat_set(unsigned index, bool enabled, const char *code)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
void *retro_get_memory_data(unsigned id)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, id);
|
||||
switch (id & RETRO_MEMORY_MASK)
|
||||
{
|
||||
case RETRO_MEMORY_SYSTEM_RAM:
|
||||
return MemGetBankPtr(0);
|
||||
default:
|
||||
return nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
size_t retro_get_memory_size(unsigned id)
|
||||
{
|
||||
ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, id);
|
||||
switch (id & RETRO_MEMORY_MASK)
|
||||
{
|
||||
case RETRO_MEMORY_SYSTEM_RAM:
|
||||
return _6502_MEM_LEN;
|
||||
default:
|
||||
return 0;
|
||||
};
|
||||
}
|
21
source/frontends/libretro/mouse.cpp
Normal file
21
source/frontends/libretro/mouse.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "frontends/libretro/mouse.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
#include "frontends/libretro/game.h"
|
||||
|
||||
#include "libretro.h"
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
Mouse::Mouse(unsigned device, const std::unique_ptr<Game> * game)
|
||||
: ControllerBase(device, {RETRO_DEVICE_ID_MOUSE_LEFT, RETRO_DEVICE_ID_MOUSE_RIGHT})
|
||||
, myGame(game)
|
||||
{
|
||||
}
|
||||
|
||||
double Mouse::getAxis(int i) const
|
||||
{
|
||||
return *myGame ? (*myGame)->getMousePosition(i) : 0.0;
|
||||
}
|
||||
|
||||
}
|
23
source/frontends/libretro/mouse.h
Normal file
23
source/frontends/libretro/mouse.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/libretro/joypadbase.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class Game;
|
||||
|
||||
class Mouse : public ControllerBase
|
||||
{
|
||||
public:
|
||||
Mouse(unsigned device, const std::unique_ptr<Game> * game);
|
||||
|
||||
double getAxis(int i) const override;
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Game> * myGame;
|
||||
};
|
||||
|
||||
}
|
206
source/frontends/libretro/rdirectsound.cpp
Normal file
206
source/frontends/libretro/rdirectsound.cpp
Normal file
|
@ -0,0 +1,206 @@
|
|||
#include "frontends/libretro/rdirectsound.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include "windows.h"
|
||||
#include "linux/linuxinterface.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// we can only run 1 generator at a time
|
||||
// 1 is for speaker (2 would be Mockingboard)
|
||||
const size_t ourChannels = 1;
|
||||
|
||||
class DirectSoundGenerator
|
||||
{
|
||||
public:
|
||||
DirectSoundGenerator(IDirectSoundBuffer * buffer);
|
||||
|
||||
void writeAudio(const size_t ms);
|
||||
void playSilence(const size_t ms);
|
||||
|
||||
bool isRunning() const;
|
||||
size_t getNumberOfChannels() const;
|
||||
|
||||
private:
|
||||
IDirectSoundBuffer * myBuffer;
|
||||
|
||||
std::vector<int16_t> myMixerBuffer;
|
||||
|
||||
size_t myBytesPerSecond;
|
||||
|
||||
void mixBuffer(const void * ptr, const size_t size);
|
||||
};
|
||||
|
||||
std::unordered_map<IDirectSoundBuffer *, std::shared_ptr<DirectSoundGenerator>> activeSoundGenerators;
|
||||
|
||||
std::shared_ptr<DirectSoundGenerator> findRunningGenerator(const size_t channels)
|
||||
{
|
||||
for (auto & it : activeSoundGenerators)
|
||||
{
|
||||
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
|
||||
if (generator->isRunning() && generator->getNumberOfChannels() == channels)
|
||||
{
|
||||
return generator;
|
||||
}
|
||||
}
|
||||
return std::shared_ptr<DirectSoundGenerator>();
|
||||
}
|
||||
|
||||
DirectSoundGenerator::DirectSoundGenerator(IDirectSoundBuffer * buffer)
|
||||
: myBuffer(buffer)
|
||||
{
|
||||
myBytesPerSecond = myBuffer->channels * myBuffer->sampleRate * sizeof(int16_t);
|
||||
}
|
||||
|
||||
bool DirectSoundGenerator::isRunning() const
|
||||
{
|
||||
DWORD dwStatus;
|
||||
myBuffer->GetStatus(&dwStatus);
|
||||
if (dwStatus & DSBSTATUS_PLAYING)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t DirectSoundGenerator::getNumberOfChannels() const
|
||||
{
|
||||
return myBuffer->channels;
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::mixBuffer(const void * ptr, const size_t size)
|
||||
{
|
||||
const int16_t frames = size / (sizeof(int16_t) * myBuffer->channels);
|
||||
const int16_t * data = static_cast<const int16_t *>(ptr);
|
||||
|
||||
if (myBuffer->channels == 2)
|
||||
{
|
||||
myMixerBuffer.assign(data, data + frames * myBuffer->channels);
|
||||
}
|
||||
else
|
||||
{
|
||||
myMixerBuffer.resize(2 * frames);
|
||||
for (int16_t i = 0; i < frames; ++i)
|
||||
{
|
||||
myMixerBuffer[i * 2] = data[i];
|
||||
myMixerBuffer[i * 2 + 1] = data[i];
|
||||
}
|
||||
}
|
||||
|
||||
const double logVolume = myBuffer->GetLogarithmicVolume();
|
||||
// same formula as QAudio::convertVolume()
|
||||
const double linVolume = logVolume > 0.99 ? 1.0 : -std::log(1.0 - logVolume) / std::log(100.0);
|
||||
const int16_t rvolume = int16_t(linVolume * 128);
|
||||
|
||||
for (int16_t & sample : myMixerBuffer)
|
||||
{
|
||||
sample = (sample * rvolume) / 128;
|
||||
}
|
||||
|
||||
ra2::audio_batch_cb(myMixerBuffer.data(), frames);
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::playSilence(const size_t ms)
|
||||
{
|
||||
if (!isRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t frames = ms * myBuffer->sampleRate / 1000;
|
||||
myMixerBuffer.resize(2 * frames);
|
||||
std::fill(myMixerBuffer.begin(), myMixerBuffer.end(), 0);
|
||||
ra2::audio_batch_cb(myMixerBuffer.data(), frames);
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::writeAudio(const size_t ms)
|
||||
{
|
||||
// this is autostart as we only do for the palying buffers
|
||||
// and AW might activate one later
|
||||
if (!isRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t frames = ms * myBuffer->sampleRate / 1000;
|
||||
const size_t bytesToRead = frames * myBuffer->channels * sizeof(int16_t);
|
||||
|
||||
LPVOID lpvAudioPtr1, lpvAudioPtr2;
|
||||
DWORD dwAudioBytes1, dwAudioBytes2;
|
||||
myBuffer->Read(bytesToRead, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2);
|
||||
|
||||
if (lpvAudioPtr1 && dwAudioBytes1)
|
||||
{
|
||||
mixBuffer(lpvAudioPtr1, dwAudioBytes1);
|
||||
}
|
||||
if (lpvAudioPtr2 && dwAudioBytes2)
|
||||
{
|
||||
mixBuffer(lpvAudioPtr2, dwAudioBytes2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void registerSoundBuffer(IDirectSoundBuffer * buffer)
|
||||
{
|
||||
const std::shared_ptr<DirectSoundGenerator> generator = std::make_shared<DirectSoundGenerator>(buffer);
|
||||
activeSoundGenerators[buffer] = generator;
|
||||
}
|
||||
|
||||
void unregisterSoundBuffer(IDirectSoundBuffer * buffer)
|
||||
{
|
||||
const auto it = activeSoundGenerators.find(buffer);
|
||||
if (it != activeSoundGenerators.end())
|
||||
{
|
||||
activeSoundGenerators.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
void writeAudio(const size_t ms)
|
||||
{
|
||||
const auto generator = findRunningGenerator(ourChannels);
|
||||
if (generator)
|
||||
{
|
||||
generator->writeAudio(ms);
|
||||
}
|
||||
}
|
||||
|
||||
void playSilence(const size_t ms)
|
||||
{
|
||||
const auto generator = findRunningGenerator(ourChannels);
|
||||
if (generator)
|
||||
{
|
||||
generator->playSilence(ms);
|
||||
}
|
||||
}
|
||||
|
||||
void bufferStatusCallback(bool active, unsigned occupancy, bool underrun_likely)
|
||||
{
|
||||
if (active)
|
||||
{
|
||||
// I am not sure this is any useful
|
||||
static unsigned lastOccupancy = 0;
|
||||
const int diff = std::abs(int(lastOccupancy) - int(occupancy));
|
||||
if (diff >= 5)
|
||||
{
|
||||
// this is very verbose
|
||||
// log_cb(RETRO_LOG_INFO, "RA2: %s occupancy = %d, underrun_likely = %d\n", __FUNCTION__, occupancy, underrun_likely);
|
||||
lastOccupancy = occupancy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
9
source/frontends/libretro/rdirectsound.h
Normal file
9
source/frontends/libretro/rdirectsound.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
void writeAudio(const size_t ms);
|
||||
void bufferStatusCallback(bool active, unsigned occupancy, bool underrun_likely);
|
||||
}
|
182
source/frontends/libretro/retroframe.cpp
Normal file
182
source/frontends/libretro/retroframe.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/libretro/retroframe.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include "Interface.h"
|
||||
#include "Core.h"
|
||||
#include "Utilities.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void readFileToBuffer(const std::string & filename, std::vector<char> & buffer)
|
||||
{
|
||||
std::ifstream file(filename.c_str(), std::ios::binary | std::ios::ate);
|
||||
const std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
buffer.resize(size);
|
||||
file.read(buffer.data(), size);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T getAs(const std::vector<char> & buffer, const size_t offset)
|
||||
{
|
||||
if (offset + sizeof(T) > buffer.size())
|
||||
{
|
||||
throw std::runtime_error("Invalid bitmap");
|
||||
}
|
||||
const T * ptr = reinterpret_cast<const T *>(buffer.data() + offset);
|
||||
return * ptr;
|
||||
}
|
||||
|
||||
// libretro cannot parse BMP with 1 bpp
|
||||
// simple version implemented here
|
||||
bool getBitmapData(const std::vector<char> & buffer, int32_t & width, int32_t & height, uint16_t & bpp, const char * & data, uint32_t & size)
|
||||
{
|
||||
if (buffer.size() < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer[0] != 0x42 || buffer[1] != 0x4D)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t fileSize = getAs<uint32_t>(buffer, 2);
|
||||
if (fileSize != buffer.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t offset = getAs<uint32_t>(buffer, 10);
|
||||
const uint32_t header = getAs<uint32_t>(buffer, 14);
|
||||
if (header != 40)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
width = getAs<int32_t>(buffer, 18);
|
||||
height = getAs<int32_t>(buffer, 22);
|
||||
bpp = getAs<uint16_t>(buffer, 28);
|
||||
const uint32_t imageSize = getAs<uint32_t>(buffer, 34);
|
||||
if (offset + imageSize > fileSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
data = buffer.data() + offset;
|
||||
size = imageSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
RetroFrame::RetroFrame()
|
||||
{
|
||||
}
|
||||
|
||||
void RetroFrame::FrameRefreshStatus(int drawflags)
|
||||
{
|
||||
if (drawflags & DRAW_TITLE)
|
||||
{
|
||||
GetAppleWindowTitle();
|
||||
display_message(g_pAppTitle.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void RetroFrame::VideoPresentScreen()
|
||||
{
|
||||
// this should not be necessary
|
||||
// either libretro handles it
|
||||
// or we should change AW
|
||||
// but for now, there is no alternative
|
||||
for (size_t row = 0; row < myHeight; ++row)
|
||||
{
|
||||
const uint8_t * src = myFrameBuffer + row * myPitch;
|
||||
uint8_t * dst = myVideoBuffer.data() + (myHeight - row - 1) * myPitch;
|
||||
memcpy(dst, src, myPitch);
|
||||
}
|
||||
|
||||
video_cb(myVideoBuffer.data() + myOffset, myBorderlessWidth, myBorderlessHeight, myPitch);
|
||||
}
|
||||
|
||||
void RetroFrame::Initialize(bool resetVideoState)
|
||||
{
|
||||
CommonFrame::Initialize(resetVideoState);
|
||||
FrameRefreshStatus(DRAW_TITLE);
|
||||
|
||||
Video & video = GetVideo();
|
||||
|
||||
myBorderlessWidth = video.GetFrameBufferBorderlessWidth();
|
||||
myBorderlessHeight = video.GetFrameBufferBorderlessHeight();
|
||||
const size_t borderWidth = video.GetFrameBufferBorderWidth();
|
||||
const size_t borderHeight = video.GetFrameBufferBorderHeight();
|
||||
const size_t width = video.GetFrameBufferWidth();
|
||||
myHeight = video.GetFrameBufferHeight();
|
||||
|
||||
myFrameBuffer = video.GetFrameBuffer();
|
||||
|
||||
myPitch = width * sizeof(bgra_t);
|
||||
myOffset = (width * borderHeight + borderWidth) * sizeof(bgra_t);
|
||||
|
||||
const size_t size = myHeight * myPitch;
|
||||
myVideoBuffer.resize(size);
|
||||
}
|
||||
|
||||
void RetroFrame::Destroy()
|
||||
{
|
||||
CommonFrame::Destroy();
|
||||
myFrameBuffer = nullptr;
|
||||
myVideoBuffer.clear();
|
||||
}
|
||||
|
||||
void RetroFrame::GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits)
|
||||
{
|
||||
const std::string filename = getBitmapFilename(lpBitmapName);
|
||||
const std::string path = getResourcePath(filename);
|
||||
|
||||
std::vector<char> buffer;
|
||||
readFileToBuffer(path, buffer);
|
||||
|
||||
if (!buffer.empty())
|
||||
{
|
||||
int32_t width, height;
|
||||
uint16_t bpp;
|
||||
const char * data;
|
||||
uint32_t size;
|
||||
const bool res = getBitmapData(buffer, width, height, bpp, data, size);
|
||||
|
||||
log_cb(RETRO_LOG_INFO, "RA2: %s. %s = %dx%d, %dbpp\n", __FUNCTION__, path.c_str(),
|
||||
width, height, bpp);
|
||||
|
||||
if (res && height > 0 && size <= cb)
|
||||
{
|
||||
const size_t length = size / height;
|
||||
// rows are stored upside down
|
||||
char * out = static_cast<char *>(lpvBits);
|
||||
for (size_t row = 0; row < height; ++row)
|
||||
{
|
||||
const char * src = data + row * length;
|
||||
char * dst = out + (height - row - 1) * length;
|
||||
memcpy(dst, src, length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CommonFrame::GetBitmap(lpBitmapName, cb, lpvBits);
|
||||
}
|
||||
|
||||
int RetroFrame::FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType)
|
||||
{
|
||||
log_cb(RETRO_LOG_INFO, "RA2: %s: %s - %s\n", __FUNCTION__, lpCaption, lpText);
|
||||
return IDOK;
|
||||
}
|
||||
|
||||
}
|
35
source/frontends/libretro/retroframe.h
Normal file
35
source/frontends/libretro/retroframe.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/common2/commonframe.h"
|
||||
#include "frontends/common2/gnuframe.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class RetroFrame : public virtual common2::CommonFrame, public common2::GNUFrame
|
||||
{
|
||||
public:
|
||||
RetroFrame();
|
||||
|
||||
void VideoPresentScreen() override;
|
||||
void FrameRefreshStatus(int drawflags) override;
|
||||
void Initialize(bool resetVideoState) override;
|
||||
void Destroy() override;
|
||||
int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType) override;
|
||||
void GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits) override;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> myVideoBuffer;
|
||||
|
||||
size_t myPitch;
|
||||
size_t myOffset;
|
||||
size_t myHeight;
|
||||
size_t myBorderlessWidth;
|
||||
size_t myBorderlessHeight;
|
||||
uint8_t* myFrameBuffer;
|
||||
};
|
||||
|
||||
}
|
174
source/frontends/libretro/retroregistry.cpp
Normal file
174
source/frontends/libretro/retroregistry.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/common2/ptreeregistry.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "Card.h"
|
||||
#include "Video.h"
|
||||
|
||||
#include "libretro.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const std::string ourScope = "applewin_";
|
||||
|
||||
struct Variable
|
||||
{
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string section;
|
||||
std::string key;
|
||||
std::vector<std::pair<std::string, DWORD> > values;
|
||||
};
|
||||
|
||||
const std::vector<Variable> ourVariables =
|
||||
{
|
||||
{
|
||||
"machine",
|
||||
"Apple ][ type",
|
||||
REG_CONFIG,
|
||||
REGVALUE_APPLE2_TYPE,
|
||||
{
|
||||
{"Enhanced Apple //e", A2TYPE_APPLE2EENHANCED},
|
||||
{"Apple ][ (Original)", A2TYPE_APPLE2},
|
||||
{"Apple ][+", A2TYPE_APPLE2PLUS},
|
||||
{"Apple ][ J-Plus", A2TYPE_APPLE2JPLUS},
|
||||
{"Apple //e", A2TYPE_APPLE2E},
|
||||
{"Pravets 82", A2TYPE_PRAVETS82},
|
||||
{"Pravets 8M", A2TYPE_PRAVETS8M},
|
||||
{"Pravets 8A", A2TYPE_PRAVETS8A},
|
||||
{"Base64A", A2TYPE_BASE64A},
|
||||
{"TK3000 //e", A2TYPE_TK30002E},
|
||||
}
|
||||
},
|
||||
{
|
||||
"slot3",
|
||||
"Card in slot 3",
|
||||
"Configuration\\Slot 3",
|
||||
REGVALUE_CARD_TYPE,
|
||||
{
|
||||
{"Empty", CT_Empty},
|
||||
{"Video HD", CT_VidHD},
|
||||
}
|
||||
},
|
||||
{
|
||||
"slot4",
|
||||
"Card in slot 4",
|
||||
"Configuration\\Slot 4",
|
||||
REGVALUE_CARD_TYPE,
|
||||
{
|
||||
{"Empty", CT_Empty},
|
||||
{"Mouse", CT_MouseInterface},
|
||||
{"Mockingboard", CT_MockingboardC},
|
||||
{"Phasor", CT_Phasor},
|
||||
}
|
||||
},
|
||||
{
|
||||
"slot5",
|
||||
"Card in slot 5",
|
||||
"Configuration\\Slot 5",
|
||||
REGVALUE_CARD_TYPE,
|
||||
{
|
||||
{"Empty", CT_Empty},
|
||||
{"CP/M", CT_Z80},
|
||||
{"Mockingboard", CT_MockingboardC},
|
||||
{"SAM/DAC", CT_SAM},
|
||||
}
|
||||
},
|
||||
{
|
||||
"video",
|
||||
"Video mode",
|
||||
REG_CONFIG,
|
||||
REGVALUE_VIDEO_MODE,
|
||||
{
|
||||
{"Color (Composite Idealized)", VT_COLOR_IDEALIZED},
|
||||
{"Color (RGB Card/Monitor)", VT_COLOR_VIDEOCARD_RGB},
|
||||
{"Color (Composite Monitor)", VT_COLOR_MONITOR_NTSC},
|
||||
{"Color TV", VT_COLOR_TV},
|
||||
{"B&W TV", VT_MONO_TV},
|
||||
{"Monochrome (Amber)", VT_MONO_AMBER},
|
||||
{"Monochrome (Green)", VT_MONO_GREEN},
|
||||
{"Monochrome (White)", VT_MONO_WHITE},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
std::string getKey(const Variable & var)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << var.description << "; ";
|
||||
for (size_t i = 0; i < var.values.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
ss << "|";
|
||||
}
|
||||
ss << var.values[i].first;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
void SetupRetroVariables()
|
||||
{
|
||||
const size_t numberOfVariables = ourVariables.size();
|
||||
std::vector<retro_variable> retroVariables(numberOfVariables + 1);
|
||||
std::list<std::string> workspace; // so objects do not move when it resized
|
||||
|
||||
// we need to keep the char * alive till after the call to RETRO_ENVIRONMENT_SET_VARIABLES
|
||||
const auto c_str = [&workspace] (const auto & s)
|
||||
{
|
||||
workspace.push_back(s);
|
||||
return workspace.back().c_str();
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < numberOfVariables; ++i)
|
||||
{
|
||||
const Variable & variable = ourVariables[i];
|
||||
retro_variable & retroVariable = retroVariables[i];
|
||||
|
||||
retroVariable.key = c_str(ourScope + variable.name);
|
||||
retroVariable.value = c_str(getKey(variable));
|
||||
}
|
||||
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, retroVariables.data());
|
||||
}
|
||||
|
||||
std::shared_ptr<Registry> CreateRetroRegistry()
|
||||
{
|
||||
const auto registry = std::make_shared<common2::PTreeRegistry>();
|
||||
|
||||
for (const Variable & variable : ourVariables)
|
||||
{
|
||||
const std::string retroKey = ourScope + variable.name;
|
||||
retro_variable retroVariable;
|
||||
retroVariable.key = retroKey.c_str();
|
||||
retroVariable.value = nullptr;
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &retroVariable) && retroVariable.value)
|
||||
{
|
||||
const std::string value(retroVariable.value);
|
||||
const auto check = [&value] (const auto & x)
|
||||
{
|
||||
return x.first == value;
|
||||
};
|
||||
const auto it = std::find_if(variable.values.begin(), variable.values.end(), check);
|
||||
if (it != variable.values.end())
|
||||
{
|
||||
registry->putDWord(variable.section, variable.key, it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
}
|
13
source/frontends/libretro/retroregistry.h
Normal file
13
source/frontends/libretro/retroregistry.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Registry;
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
void SetupRetroVariables();
|
||||
std::shared_ptr<Registry> CreateRetroRegistry();
|
||||
|
||||
}
|
1
source/frontends/libretro/run
Normal file
1
source/frontends/libretro/run
Normal file
|
@ -0,0 +1 @@
|
|||
retroarch -L source/frontends/retro/libra2.so ../Disks/NoSlotClockTest.dsk
|
114
source/frontends/libretro/serialisation.cpp
Normal file
114
source/frontends/libretro/serialisation.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
#include "StdAfx.h"
|
||||
#include "SaveState.h"
|
||||
|
||||
#include "frontends/libretro/serialisation.h"
|
||||
#include "frontends/libretro/environment.h"
|
||||
#include "frontends/libretro/diskcontrol.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
class AutoFile
|
||||
{
|
||||
public:
|
||||
AutoFile();
|
||||
~AutoFile();
|
||||
|
||||
const std::string & getFilename() const; // only if true
|
||||
|
||||
protected:
|
||||
std::string myFilename;
|
||||
};
|
||||
|
||||
AutoFile::AutoFile()
|
||||
{
|
||||
// massive race condition, but without changes to AW, little can we do here
|
||||
const char * tmp = std::tmpnam(nullptr);
|
||||
if (!tmp)
|
||||
{
|
||||
throw std::runtime_error("Cannot create temporary file");
|
||||
}
|
||||
myFilename = tmp;
|
||||
}
|
||||
|
||||
AutoFile::~AutoFile()
|
||||
{
|
||||
std::remove(myFilename.c_str());
|
||||
}
|
||||
|
||||
const std::string & AutoFile::getFilename() const
|
||||
{
|
||||
return myFilename;
|
||||
}
|
||||
|
||||
void saveToFile(const std::string & filename) // cannot be null!
|
||||
{
|
||||
Snapshot_SetFilename(filename);
|
||||
Snapshot_SaveState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
size_t RetroSerialisation::getSize()
|
||||
{
|
||||
AutoFile autoFile;
|
||||
std::string const & filename = autoFile.getFilename();
|
||||
saveToFile(filename);
|
||||
std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
|
||||
|
||||
const size_t fileSize = ifs.tellg();
|
||||
// we add a buffer to include a few things
|
||||
// DiscControl images
|
||||
// various sizes
|
||||
// small variations in AW yaml format
|
||||
const size_t buffer = 4096;
|
||||
return fileSize + buffer;
|
||||
}
|
||||
|
||||
void RetroSerialisation::serialise(void * data, size_t size, const DiskControl & diskControl)
|
||||
{
|
||||
Buffer buffer(reinterpret_cast<char *>(data), size);
|
||||
diskControl.serialise(buffer);
|
||||
|
||||
AutoFile autoFile;
|
||||
std::string const & filename = autoFile.getFilename();
|
||||
saveToFile(filename);
|
||||
std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
|
||||
|
||||
size_t const fileSize = ifs.tellg();
|
||||
buffer.get<size_t>() = fileSize;
|
||||
|
||||
char * begin, * end;
|
||||
buffer.get(fileSize, begin, end);
|
||||
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
ifs.read(begin, end - begin);
|
||||
}
|
||||
|
||||
void RetroSerialisation::deserialise(const void * data, size_t size, DiskControl & diskControl)
|
||||
{
|
||||
Buffer buffer(reinterpret_cast<const char *>(data), size);
|
||||
diskControl.deserialise(buffer);
|
||||
|
||||
const size_t fileSize = buffer.get<size_t const>();
|
||||
|
||||
AutoFile autoFile;
|
||||
std::string const & filename = autoFile.getFilename();
|
||||
// do not remove the {} scope below! it ensures the file is flushed
|
||||
{
|
||||
char const * begin, * end;
|
||||
buffer.get(fileSize, begin, end);
|
||||
std::ofstream ofs(filename, std::ios::binary);
|
||||
ofs.write(begin, end - begin);
|
||||
}
|
||||
|
||||
Snapshot_SetFilename(filename);
|
||||
Snapshot_LoadState();
|
||||
}
|
||||
|
||||
}
|
16
source/frontends/libretro/serialisation.h
Normal file
16
source/frontends/libretro/serialisation.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include <cstddef>
|
||||
|
||||
namespace ra2
|
||||
{
|
||||
|
||||
class DiskControl;
|
||||
|
||||
class RetroSerialisation
|
||||
{
|
||||
public:
|
||||
static size_t getSize();
|
||||
static void serialise(void * data, size_t size, const DiskControl & diskControl);
|
||||
static void deserialise(const void * data, size_t size, DiskControl & diskControl);
|
||||
};
|
||||
|
||||
}
|
54
source/frontends/ncurses/CMakeLists.txt
Normal file
54
source/frontends/ncurses/CMakeLists.txt
Normal file
|
@ -0,0 +1,54 @@
|
|||
include(FindPkgConfig)
|
||||
include(FindCurses)
|
||||
|
||||
set(SOURCE_FILES
|
||||
main.cpp
|
||||
world.cpp
|
||||
colors.cpp
|
||||
evdevpaddle.cpp
|
||||
nframe.cpp
|
||||
asciiart.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
world.h
|
||||
colors.h
|
||||
evdevpaddle.h
|
||||
nframe.h
|
||||
asciiart.h
|
||||
)
|
||||
|
||||
add_executable(applen
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
)
|
||||
|
||||
find_package(Curses REQUIRED)
|
||||
# pkg_search_module(NCURSESW REQUIRED ncursesw)
|
||||
pkg_search_module(LIBEVDEV REQUIRED libevdev)
|
||||
|
||||
target_include_directories(applen PRIVATE
|
||||
${CURSES_INCLUDE_DIRS}
|
||||
${LIBEVDEV_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_compile_options(applen PRIVATE
|
||||
${CURSES_CFLAGS}
|
||||
${LIBEVDEV_CFLAGS_OTHER}
|
||||
)
|
||||
|
||||
target_link_libraries(applen PRIVATE
|
||||
${CURSES_LIBRARIES}
|
||||
${LIBEVDEV_LIBRARIES}
|
||||
appleii
|
||||
common2
|
||||
)
|
||||
|
||||
target_link_directories(applen PRIVATE
|
||||
${NCURSESW_LIBRARY_DIRS}
|
||||
${LIBEVDEV_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
install(TARGETS applen
|
||||
DESTINATION bin)
|
231
source/frontends/ncurses/asciiart.cpp
Normal file
231
source/frontends/ncurses/asciiart.cpp
Normal file
|
@ -0,0 +1,231 @@
|
|||
#include "frontends/ncurses/asciiart.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <memory>
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
ASCIIArt::Unicode::Unicode(const char * aC, Blocks aValues)
|
||||
: c(aC), values(aValues)
|
||||
{
|
||||
}
|
||||
|
||||
const int ASCIIArt::PPQ = 8 * (2 * 7) / 4;
|
||||
|
||||
ASCIIArt::ASCIIArt() : myRows(0), myColumns(0)
|
||||
{
|
||||
myGlyphs.push_back(Unicode("\u2580", {PPQ, PPQ, 0, 0})); // top half
|
||||
myGlyphs.push_back(Unicode("\u258C", {PPQ, 0, PPQ, 0})); // left half
|
||||
myGlyphs.push_back(Unicode("\u2596", { 0, 0, PPQ, 0})); // lower left
|
||||
myGlyphs.push_back(Unicode("\u2597", { 0, 0, 0, PPQ})); // lower right
|
||||
myGlyphs.push_back(Unicode("\u2598", {PPQ, 0, 0, 0})); // top left
|
||||
myGlyphs.push_back(Unicode("\u259A", {PPQ, 0, 0, PPQ})); // diagonal
|
||||
myGlyphs.push_back(Unicode("\u259D", { 0, PPQ, 0, 0})); // top right
|
||||
|
||||
myBlocks.resize(128);
|
||||
|
||||
init(1, 1); // normal size
|
||||
}
|
||||
|
||||
void ASCIIArt::init(const int rows, const int columns)
|
||||
{
|
||||
if (myRows != rows || myColumns != columns)
|
||||
{
|
||||
if (myColumns != columns)
|
||||
{
|
||||
myColumns = columns;
|
||||
for (size_t i = 0; i < myBlocks.size(); ++i)
|
||||
{
|
||||
myBlocks[i] = decodeByte(i);
|
||||
}
|
||||
}
|
||||
|
||||
myRows = rows;
|
||||
|
||||
myValues.resize(boost::extents[myRows][myColumns]);
|
||||
myChars.resize(boost::extents[rows][columns]);
|
||||
}
|
||||
}
|
||||
|
||||
void ASCIIArt::changeColumns(const int x)
|
||||
{
|
||||
int newColumns = myColumns + x;
|
||||
newColumns = std::max(1, std::min(7, newColumns));
|
||||
init(myRows, newColumns);
|
||||
}
|
||||
|
||||
void ASCIIArt::changeRows(const int x)
|
||||
{
|
||||
int newRows = x > 0 ? myRows * 2 : myRows / 2;
|
||||
newRows = std::max(1, std::min(4, newRows));
|
||||
init(newRows, myColumns);
|
||||
}
|
||||
|
||||
const ASCIIArt::array_char_t & ASCIIArt::getCharacters(const unsigned char * address)
|
||||
{
|
||||
const array_val_t & values = getQuadrantValues(address);
|
||||
return getCharacters(values);
|
||||
}
|
||||
|
||||
const ASCIIArt::array_val_t & ASCIIArt::getQuadrantValues(const unsigned char * address) const
|
||||
{
|
||||
std::fill(myValues.origin(), myValues.origin() + myValues.num_elements(), 0);
|
||||
|
||||
const int linesPerRow = 8 / myRows;
|
||||
const int linesPerQuadrant = linesPerRow / 2;
|
||||
|
||||
// 8 lines per text character
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
{
|
||||
const int offset = 0x0400 * i;
|
||||
// group color bit is ignored
|
||||
const unsigned char value = (*(address + offset)) & 0x7f;
|
||||
|
||||
const int row = i / linesPerRow;
|
||||
const int lineInRow = i % linesPerRow;
|
||||
const int quadrant = lineInRow / linesPerQuadrant;
|
||||
const int base = quadrant * 2;
|
||||
|
||||
const std::vector<Blocks> & decoded = myBlocks[value];
|
||||
|
||||
for (size_t col = 0; col < myColumns; ++col)
|
||||
{
|
||||
Blocks & blocks = myValues[row][col];
|
||||
blocks.add(base + 0, decoded[col][0] * myRows);
|
||||
blocks.add(base + 1, decoded[col][1] * myRows);
|
||||
}
|
||||
}
|
||||
|
||||
return myValues;
|
||||
}
|
||||
|
||||
std::vector<Blocks> ASCIIArt::decodeByte(const unsigned char value) const
|
||||
{
|
||||
const int each = myColumns * 4 * PPQ / (8 * 7);
|
||||
|
||||
int available = 7;
|
||||
int col = 0;
|
||||
int pos = 0; // left right
|
||||
|
||||
std::vector<Blocks> decoded(myColumns);
|
||||
|
||||
for (size_t j = 0; j < 7; ++j)
|
||||
{
|
||||
int to_allocate = each;
|
||||
do
|
||||
{
|
||||
const int here = std::min(available, to_allocate);
|
||||
if (value & (1 << j))
|
||||
{
|
||||
decoded[col].add(pos, here);
|
||||
}
|
||||
to_allocate -= here;
|
||||
available -= here;
|
||||
if (available == 0)
|
||||
{
|
||||
// new quadrant
|
||||
available = 7;
|
||||
++pos;
|
||||
if (pos == 2)
|
||||
{
|
||||
pos = 0;
|
||||
++col;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (to_allocate > 0);
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
const ASCIIArt::array_char_t & ASCIIArt::getCharacters(const array_val_t & values)
|
||||
{
|
||||
const int rows = values.shape()[0];
|
||||
const int columns = values.shape()[1];
|
||||
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < columns; ++j)
|
||||
{
|
||||
myChars[i][j] = getCharacter(values[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
return myChars;
|
||||
}
|
||||
|
||||
|
||||
const ASCIIArt::Character & ASCIIArt::getCharacter(const Blocks & values)
|
||||
{
|
||||
const int zip = values.value;
|
||||
|
||||
const std::unordered_map<int, Character>::const_iterator it = myAsciiPixels.find(zip);
|
||||
if (it == myAsciiPixels.end())
|
||||
{
|
||||
Character & best = myAsciiPixels[zip];
|
||||
best.error = DBL_MAX;
|
||||
|
||||
for (const Unicode & glyph: myGlyphs)
|
||||
{
|
||||
double foreground;
|
||||
double background;
|
||||
double error;
|
||||
fit(values, glyph, foreground, background, error);
|
||||
if (error < best.error)
|
||||
{
|
||||
best.error = error;
|
||||
best.foreground = foreground;
|
||||
best.background = background;
|
||||
best.c = glyph.c;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
else
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ASCIIArt::fit(const Blocks & art, const Unicode & glyph,
|
||||
double & foreground, double & background, double & error)
|
||||
{
|
||||
int num_fg = 0;
|
||||
int num_bg = 0;
|
||||
int den_fg = 0;
|
||||
int den_bg = 0;
|
||||
|
||||
for (size_t i = 0; i < art.size(); ++i)
|
||||
{
|
||||
const double f = glyph.values[i];
|
||||
const double b = PPQ - f;
|
||||
|
||||
num_fg += art[i] * f;
|
||||
den_fg += f * f;
|
||||
|
||||
num_bg += art[i] * b;
|
||||
den_bg += b * b;
|
||||
}
|
||||
|
||||
// close formula to minimise the L2 norm of the difference
|
||||
// of grey intensity
|
||||
foreground = double(num_fg) / double(den_fg);
|
||||
background = double(num_bg) / double(den_bg);
|
||||
|
||||
error = 0.0;
|
||||
for (size_t i = 0; i < art.size(); ++i)
|
||||
{
|
||||
const double f = glyph.values[i];
|
||||
const double b = PPQ - f;
|
||||
|
||||
const double g = foreground * f + background * b;
|
||||
const double e = art[i] - g;
|
||||
error += e * e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
106
source/frontends/ncurses/asciiart.h
Normal file
106
source/frontends/ncurses/asciiart.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
#include <boost/multi_array.hpp>
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
struct Blocks
|
||||
{
|
||||
Blocks() : value(0) { }
|
||||
|
||||
Blocks(std::initializer_list<int> q)
|
||||
{
|
||||
value = 0;
|
||||
for (auto it = rbegin(q); it != rend(q); ++it)
|
||||
{
|
||||
value <<= 5;
|
||||
value += *it;
|
||||
}
|
||||
}
|
||||
|
||||
void add(int i, int val)
|
||||
{
|
||||
value += val << (i * 5);
|
||||
}
|
||||
|
||||
int operator[](int i) const
|
||||
{
|
||||
const int val = (value >> (i * 5)) & 0b11111;
|
||||
return val;
|
||||
}
|
||||
|
||||
Blocks & operator=(const int val)
|
||||
{
|
||||
value = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
int value;
|
||||
};
|
||||
|
||||
|
||||
class ASCIIArt
|
||||
{
|
||||
public:
|
||||
ASCIIArt();
|
||||
|
||||
struct Character
|
||||
{
|
||||
const char * c;
|
||||
double foreground;
|
||||
double background;
|
||||
double error;
|
||||
};
|
||||
|
||||
typedef boost::multi_array<Character, 2> array_char_t;
|
||||
typedef boost::multi_array<Blocks, 2> array_val_t;
|
||||
|
||||
void init(const int rows, const int columns);
|
||||
|
||||
void changeColumns(const int x);
|
||||
void changeRows(const int x);
|
||||
|
||||
const array_char_t & getCharacters(const unsigned char * address);
|
||||
const array_char_t & getCharacters(const array_val_t & values);
|
||||
const array_val_t & getQuadrantValues(const unsigned char * address) const;
|
||||
std::vector<Blocks> decodeByte(const unsigned char value) const;
|
||||
|
||||
private:
|
||||
static const int PPQ; // Pixels per Quadrant
|
||||
|
||||
std::unordered_map<int, Character> myAsciiPixels;
|
||||
|
||||
struct Unicode
|
||||
{
|
||||
Unicode(const char * aC, Blocks aValues);
|
||||
|
||||
const char * c;
|
||||
Blocks values; // foreground: top left - top right - bottom left - bottom right
|
||||
};
|
||||
|
||||
int myRows;
|
||||
int myColumns;
|
||||
|
||||
std::vector<Unicode> myGlyphs;
|
||||
|
||||
std::vector<std::vector<Blocks>> myBlocks;
|
||||
|
||||
mutable array_val_t myValues; // workspace
|
||||
array_char_t myChars; // workspace
|
||||
|
||||
const Character & getCharacter(const Blocks & values);
|
||||
static void fit(const Blocks & art, const Unicode & glyph,
|
||||
double & foreground, double & background, double & error);
|
||||
|
||||
};
|
||||
|
||||
}
|
129
source/frontends/ncurses/colors.cpp
Normal file
129
source/frontends/ncurses/colors.cpp
Normal file
|
@ -0,0 +1,129 @@
|
|||
#include "frontends/ncurses/colors.h"
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
enum Color {
|
||||
BLACK,
|
||||
DEEP_RED,
|
||||
DARK_BLUE,
|
||||
MAGENTA,
|
||||
DARK_GREEN,
|
||||
DARK_GRAY,
|
||||
BLUE,
|
||||
LIGHT_BLUE,
|
||||
BROWN,
|
||||
ORANGE,
|
||||
LIGHT_GRAY,
|
||||
PINK,
|
||||
GREEN,
|
||||
YELLOW,
|
||||
AQUA,
|
||||
WHITE
|
||||
};
|
||||
|
||||
// input 0..255
|
||||
// output 0..1000
|
||||
int scaleRGB(int rgb)
|
||||
{
|
||||
return rgb * 1000 / 255;
|
||||
}
|
||||
}
|
||||
|
||||
#define SETFRAMECOLOR(c, r, g, b) init_color(firstColor + Color::c, scaleRGB(r), scaleRGB(g), scaleRGB(b));
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
GraphicsColors::GraphicsColors(const int firstColor, const int firstPair, const int numberOfGreys)
|
||||
: myNumberOfGRColors(16), myNumberOfGreys(numberOfGreys)
|
||||
{
|
||||
has_colors();
|
||||
start_color();
|
||||
can_change_color();
|
||||
|
||||
SETFRAMECOLOR(BLACK, 0x00,0x00,0x00); // 0
|
||||
SETFRAMECOLOR(DEEP_RED, 0x9D,0x09,0x66); // 0xD0,0x00,0x30 -> Linards Tweaked 0x9D,0x09,0x66
|
||||
SETFRAMECOLOR(DARK_BLUE, 0x00,0x00,0x80); // 4 // not used
|
||||
SETFRAMECOLOR(MAGENTA, 0xC7,0x34,0xFF); // FD Linards Tweaked 0xFF,0x00,0xFF -> 0xC7,0x34,0xFF
|
||||
|
||||
SETFRAMECOLOR(DARK_GREEN, 0x00,0x80,0x00); // 2 // not used
|
||||
SETFRAMECOLOR(DARK_GRAY, 0x80,0x80,0x80); // F8
|
||||
SETFRAMECOLOR(BLUE, 0x0D,0xA1,0xFF); // FC Linards Tweaked 0x00,0x00,0xFF -> 0x0D,0xA1,0xFF
|
||||
SETFRAMECOLOR(LIGHT_BLUE,0xAA,0xAA,0xFF); // 0x60,0xA0,0xFF -> Linards Tweaked 0xAA,0xAA,0xFF
|
||||
|
||||
SETFRAMECOLOR(BROWN, 0x55,0x55,0x00); // 0x80,0x50,0x00 -> Linards Tweaked 0x55,0x55,0x00
|
||||
SETFRAMECOLOR(ORANGE, 0xF2,0x5E,0x00); // 0xFF,0x80,0x00 -> Linards Tweaked 0xF2,0x5E,0x00
|
||||
SETFRAMECOLOR(LIGHT_GRAY, 0xC0,0xC0,0xC0); // 7 // GR: COLOR=10
|
||||
SETFRAMECOLOR(PINK, 0xFF,0x89,0xE5); // 0xFF,0x90,0x80 -> Linards Tweaked 0xFF,0x89,0xE5
|
||||
|
||||
SETFRAMECOLOR(GREEN, 0x38,0xCB,0x00); // FA Linards Tweaked 0x00,0xFF,0x00 -> 0x38,0xCB,0x00
|
||||
SETFRAMECOLOR(YELLOW, 0xD5,0xD5,0x1A); // FB Linards Tweaked 0xFF,0xFF,0x00 -> 0xD5,0xD5,0x1A
|
||||
SETFRAMECOLOR(AQUA, 0x62,0xF6,0x99); // 0x40,0xFF,0x90 -> Linards Tweaked 0x62,0xF6,0x99
|
||||
SETFRAMECOLOR(WHITE, 0xFF,0xFF,0xFF); // FF
|
||||
|
||||
int baseColor = firstColor;
|
||||
int basePair = firstPair;
|
||||
myFirstGRPair = basePair;
|
||||
|
||||
for (size_t i = 0; i < myNumberOfGRColors; ++i)
|
||||
{
|
||||
const int fg = firstColor + i;
|
||||
for (size_t j = 0; j < myNumberOfGRColors; ++j)
|
||||
{
|
||||
const int bg = firstColor + j;
|
||||
const int pair = myFirstGRPair + i * myNumberOfGRColors + j;
|
||||
|
||||
init_pair(pair, fg, bg);
|
||||
}
|
||||
}
|
||||
|
||||
baseColor += myNumberOfGRColors;
|
||||
basePair += myNumberOfGRColors * myNumberOfGRColors;
|
||||
myFirstHGRPair = basePair;
|
||||
|
||||
for (size_t i = 0; i < myNumberOfGreys; ++i)
|
||||
{
|
||||
const int color = baseColor + i;
|
||||
const int grey = 1000 * i / (myNumberOfGreys - 1);
|
||||
init_color(color, grey, grey, grey);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < myNumberOfGreys; ++i)
|
||||
{
|
||||
const int fg = baseColor + i;
|
||||
for (size_t j = 0; j < myNumberOfGreys; ++j)
|
||||
{
|
||||
const int bg = baseColor + j;
|
||||
const int pair = basePair + i * myNumberOfGreys + j;
|
||||
|
||||
init_pair(pair, fg, bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GraphicsColors::getPair(int color) const
|
||||
{
|
||||
const int fg = color & 0x0f;
|
||||
const int bg = color >> 4;
|
||||
|
||||
const int pair = myFirstGRPair + fg * myNumberOfGRColors + bg;
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
int GraphicsColors::getGrey(double foreground, double background) const
|
||||
{
|
||||
const int fg = std::nearbyint((myNumberOfGreys - 1) * foreground);
|
||||
const int bg = std::nearbyint((myNumberOfGreys - 1) * background);
|
||||
|
||||
const int basePair = myFirstHGRPair;
|
||||
|
||||
const int pair = basePair + fg * myNumberOfGreys + bg;
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
}
|
23
source/frontends/ncurses/colors.h
Normal file
23
source/frontends/ncurses/colors.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
class GraphicsColors
|
||||
{
|
||||
public:
|
||||
GraphicsColors(const int firstColor, const int firstPair, const int numberOfGreys);
|
||||
|
||||
int getPair(int color) const;
|
||||
int getGrey(double foreground, double background) const;
|
||||
|
||||
private:
|
||||
int myFirstGRPair;
|
||||
int myFirstHGRPair;
|
||||
|
||||
const int myNumberOfGRColors;
|
||||
const int myNumberOfGreys;
|
||||
|
||||
};
|
||||
|
||||
}
|
112
source/frontends/ncurses/evdevpaddle.cpp
Normal file
112
source/frontends/ncurses/evdevpaddle.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/ncurses/evdevpaddle.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
EvDevPaddle::EvDevPaddle(const std::string & device)
|
||||
: myButtonCodes(2), myAxisCodes(2), myAxisMins(2), myAxisMaxs(2)
|
||||
{
|
||||
myFD = open(device.c_str(), O_RDONLY | O_NONBLOCK);
|
||||
if (myFD > 0)
|
||||
{
|
||||
libevdev * dev;
|
||||
int rc = libevdev_new_from_fd(myFD, &dev);
|
||||
if (rc < 0)
|
||||
{
|
||||
LogFileOutput("Input: failed to init libevdev (%s): %s\n", strerror(-rc), device.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
myDev.reset(dev, libevdev_free);
|
||||
|
||||
myName = libevdev_get_name(dev);
|
||||
|
||||
myButtonCodes[0] = BTN_SOUTH;
|
||||
myButtonCodes[1] = BTN_EAST;
|
||||
myAxisCodes[0] = ABS_X;
|
||||
myAxisCodes[1] = ABS_Y;
|
||||
|
||||
for (size_t i = 0; i < myAxisCodes.size(); ++i)
|
||||
{
|
||||
myAxisMins[i] = libevdev_get_abs_minimum(dev, myAxisCodes[i]);
|
||||
myAxisMaxs[i] = libevdev_get_abs_maximum(dev, myAxisCodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFileOutput("Input: failed to open device (%s): %s\n", strerror(errno), device.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
EvDevPaddle::~EvDevPaddle()
|
||||
{
|
||||
if (myFD > 0)
|
||||
{
|
||||
close(myFD);
|
||||
}
|
||||
}
|
||||
|
||||
int EvDevPaddle::poll()
|
||||
{
|
||||
int counter = 0;
|
||||
if (!myDev)
|
||||
{
|
||||
return counter;
|
||||
}
|
||||
|
||||
input_event ev;
|
||||
int rc = LIBEVDEV_READ_STATUS_SUCCESS;
|
||||
do
|
||||
{
|
||||
if (rc == LIBEVDEV_READ_STATUS_SYNC)
|
||||
rc = libevdev_next_event(myDev.get(), LIBEVDEV_READ_FLAG_SYNC, &ev);
|
||||
else
|
||||
rc = libevdev_next_event(myDev.get(), LIBEVDEV_READ_FLAG_NORMAL, &ev);
|
||||
++counter;
|
||||
} while (rc >= 0);
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
const std::string & EvDevPaddle::getName() const
|
||||
{
|
||||
return myName;
|
||||
}
|
||||
|
||||
bool EvDevPaddle::getButton(int i) const
|
||||
{
|
||||
int value = 0;
|
||||
if (myDev)
|
||||
{
|
||||
int rc = libevdev_fetch_event_value(myDev.get(), EV_KEY, myButtonCodes[i], &value);
|
||||
}
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
double EvDevPaddle::getAxis(int i) const
|
||||
{
|
||||
if (myDev)
|
||||
{
|
||||
int value = 0;
|
||||
int rc = libevdev_fetch_event_value(myDev.get(), EV_ABS, myAxisCodes[i], &value);
|
||||
const double axis = 2.0 * (value - myAxisMins[i]) / (myAxisMaxs[i] - myAxisMins[i]) - 1.0;
|
||||
return axis;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
40
source/frontends/ncurses/evdevpaddle.h
Normal file
40
source/frontends/ncurses/evdevpaddle.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "linux/paddle.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct libevdev;
|
||||
struct input_event;
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
class EvDevPaddle : public Paddle
|
||||
{
|
||||
public:
|
||||
EvDevPaddle(const std::string & device);
|
||||
~EvDevPaddle();
|
||||
|
||||
int poll();
|
||||
|
||||
const std::string & getName() const;
|
||||
bool getButton(int i) const override;
|
||||
double getAxis(int i) const override;
|
||||
|
||||
private:
|
||||
int myFD;
|
||||
std::shared_ptr<libevdev> myDev;
|
||||
|
||||
void process(const input_event & ev);
|
||||
|
||||
std::string myName;
|
||||
|
||||
std::vector<unsigned int> myButtonCodes;
|
||||
std::vector<unsigned int> myAxisCodes;
|
||||
std::vector<int> myAxisMins;
|
||||
std::vector<int> myAxisMaxs;
|
||||
};
|
||||
|
||||
}
|
192
source/frontends/ncurses/main.cpp
Normal file
192
source/frontends/ncurses/main.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
#include "StdAfx.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
#include <ncurses.h>
|
||||
|
||||
#include "Common.h"
|
||||
#include "CardManager.h"
|
||||
#include "Core.h"
|
||||
#include "Log.h"
|
||||
#include "CPU.h"
|
||||
#include "NTSC.h"
|
||||
#include "SaveState.h"
|
||||
#include "Utilities.h"
|
||||
#include "Interface.h"
|
||||
|
||||
#include "linux/benchmark.h"
|
||||
#include "linux/paddle.h"
|
||||
#include "linux/context.h"
|
||||
#include "frontends/common2/fileregistry.h"
|
||||
#include "frontends/common2/programoptions.h"
|
||||
#include "frontends/common2/utils.h"
|
||||
#include "frontends/ncurses/world.h"
|
||||
#include "frontends/ncurses/nframe.h"
|
||||
#include "frontends/ncurses/evdevpaddle.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ContinueExecution(const common2::EmulatorOptions & options, const std::shared_ptr<na2::NFrame> & frame)
|
||||
{
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
|
||||
const double fUsecPerSec = 1.e6;
|
||||
#if 1
|
||||
const UINT nExecutionPeriodUsec = 1000000 / 60; // 60 FPS
|
||||
// const UINT nExecutionPeriodUsec = 100; // 0.1ms
|
||||
const double fExecutionPeriodClks = g_fCurrentCLK6502 * ((double)nExecutionPeriodUsec / fUsecPerSec);
|
||||
#else
|
||||
const double fExecutionPeriodClks = 1800.0;
|
||||
const UINT nExecutionPeriodUsec = (UINT) (fUsecPerSec * (fExecutionPeriodClks / g_fCurrentCLK6502));
|
||||
#endif
|
||||
|
||||
const DWORD uCyclesToExecute = fExecutionPeriodClks;
|
||||
|
||||
const bool bVideoUpdate = options.ntsc;
|
||||
g_bFullSpeed = !bVideoUpdate;
|
||||
|
||||
const DWORD uActualCyclesExecuted = CpuExecute(uCyclesToExecute, bVideoUpdate);
|
||||
g_dwCyclesThisFrame += uActualCyclesExecuted;
|
||||
|
||||
CardManager & cardManager = GetCardMgr();
|
||||
|
||||
cardManager.Update(uActualCyclesExecuted);
|
||||
|
||||
const int key = ProcessKeyboard(frame);
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case KEY_F(2):
|
||||
{
|
||||
ResetMachineState();
|
||||
break;
|
||||
}
|
||||
case 278: // Shift-F2
|
||||
{
|
||||
CtrlReset();
|
||||
break;
|
||||
}
|
||||
case KEY_F(3):
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case KEY_F(5):
|
||||
{
|
||||
CardManager & cardManager = GetCardMgr();
|
||||
if (cardManager.QuerySlot(SLOT6) == CT_Disk2)
|
||||
{
|
||||
dynamic_cast<Disk2InterfaceCard*>(cardManager.GetObj(SLOT6))->DriveSwap();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KEY_F(11):
|
||||
{
|
||||
Snapshot_SaveState();
|
||||
break;
|
||||
}
|
||||
case KEY_F(12):
|
||||
{
|
||||
Snapshot_LoadState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
frame->ProcessEvDev();
|
||||
|
||||
const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();
|
||||
if (g_dwCyclesThisFrame >= dwClksPerFrame)
|
||||
{
|
||||
g_dwCyclesThisFrame = g_dwCyclesThisFrame % dwClksPerFrame;
|
||||
if (!options.headless)
|
||||
{
|
||||
frame->VideoPresentScreen();
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.headless)
|
||||
{
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
const auto diff = end - start;
|
||||
const long us = std::chrono::duration_cast<std::chrono::microseconds>(diff).count();
|
||||
|
||||
const double coeff = exp(-0.000001 * nExecutionPeriodUsec); // 0.36 after 1 second
|
||||
|
||||
na2::g_relativeSpeed = na2::g_relativeSpeed * coeff + double(us) / double(nExecutionPeriodUsec) * (1.0 - coeff);
|
||||
|
||||
if (!cardManager.GetDisk2CardMgr().IsConditionForFullSpeed())
|
||||
{
|
||||
if (us < nExecutionPeriodUsec)
|
||||
{
|
||||
const auto duration = std::chrono::microseconds(nExecutionPeriodUsec - us);
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !na2::g_stop;
|
||||
}
|
||||
}
|
||||
|
||||
void EnterMessageLoop(const common2::EmulatorOptions & options, const std::shared_ptr<na2::NFrame> & frame)
|
||||
{
|
||||
while (ContinueExecution(options, frame))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
int run_ncurses(int argc, const char * argv [])
|
||||
{
|
||||
common2::EmulatorOptions options;
|
||||
const bool run = getEmulatorOptions(argc, argv, "ncurses", options);
|
||||
|
||||
if (!run)
|
||||
return 1;
|
||||
|
||||
const LoggerContext loggerContext(options.log);
|
||||
const RegistryContext registryContet(CreateFileRegistry(options));
|
||||
const std::shared_ptr<na2::EvDevPaddle> paddle(new na2::EvDevPaddle(options.paddleDeviceName));
|
||||
const std::shared_ptr<na2::NFrame> frame(new na2::NFrame(paddle));
|
||||
|
||||
const Initialisation init(frame, paddle);
|
||||
common2::applyOptions(options);
|
||||
frame->Begin();
|
||||
|
||||
common2::setSnapshotFilename(options.snapshotFilename);
|
||||
if (options.loadSnapshot)
|
||||
{
|
||||
frame->LoadSnapshot();
|
||||
}
|
||||
|
||||
na2::SetCtrlCHandler(options.headless);
|
||||
|
||||
if (options.benchmark)
|
||||
{
|
||||
const auto redraw = [&frame]() { frame->VideoRedrawScreen(); };
|
||||
VideoBenchmark(redraw, redraw);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnterMessageLoop(options, frame);
|
||||
}
|
||||
frame->End();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return run_ncurses(argc, argv);
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
std::cerr << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
399
source/frontends/ncurses/nframe.cpp
Normal file
399
source/frontends/ncurses/nframe.cpp
Normal file
|
@ -0,0 +1,399 @@
|
|||
#include "StdAfx.h"
|
||||
#include "frontends/ncurses/nframe.h"
|
||||
#include "frontends/ncurses/colors.h"
|
||||
#include "frontends/ncurses/asciiart.h"
|
||||
#include "frontends/ncurses/evdevpaddle.h"
|
||||
#include "Interface.h"
|
||||
#include "Memory.h"
|
||||
#include "Log.h"
|
||||
#include "Core.h"
|
||||
#include "CardManager.h"
|
||||
#include "Disk.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <locale.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
struct NCurses
|
||||
{
|
||||
NCurses()
|
||||
{
|
||||
setlocale(LC_ALL, "");
|
||||
initscr();
|
||||
|
||||
curs_set(0);
|
||||
|
||||
noecho();
|
||||
cbreak();
|
||||
set_escdelay(0);
|
||||
|
||||
// make sure this happens when ncurses is indeed initialised
|
||||
colors.reset(new GraphicsColors(20, 20, 32));
|
||||
}
|
||||
~NCurses()
|
||||
{
|
||||
endwin();
|
||||
colors.reset();
|
||||
}
|
||||
std::shared_ptr<GraphicsColors> colors;
|
||||
};
|
||||
|
||||
NFrame::NFrame(const std::shared_ptr<EvDevPaddle> & paddle)
|
||||
: myPaddle(paddle)
|
||||
, myRows(-1)
|
||||
, myColumns(-1)
|
||||
{
|
||||
// only initialise if actually used
|
||||
// so we can run headless
|
||||
}
|
||||
|
||||
void NFrame::Initialize(bool resetVideoState)
|
||||
{
|
||||
CommonFrame::Initialize(resetVideoState);
|
||||
myTextFlashCounter = 0;
|
||||
myTextFlashState = 0;
|
||||
myAsciiArt.reset(new ASCIIArt());
|
||||
}
|
||||
|
||||
void NFrame::Destroy()
|
||||
{
|
||||
CommonFrame::Destroy();
|
||||
myTextFlashCounter = 0;
|
||||
myTextFlashState = 0;
|
||||
myFrame.reset();
|
||||
myStatus.reset();
|
||||
myAsciiArt.reset();
|
||||
|
||||
myNCurses.reset();
|
||||
}
|
||||
|
||||
void NFrame::ProcessEvDev()
|
||||
{
|
||||
myPaddle->poll();
|
||||
}
|
||||
|
||||
void NFrame::ChangeColumns(const int x)
|
||||
{
|
||||
myAsciiArt->changeColumns(x);
|
||||
}
|
||||
|
||||
void NFrame::ChangeRows(const int x)
|
||||
{
|
||||
myAsciiArt->changeRows(x);
|
||||
}
|
||||
|
||||
void NFrame::Init(int rows, int columns)
|
||||
{
|
||||
if (myRows != rows || myColumns != columns)
|
||||
{
|
||||
InitialiseNCurses();
|
||||
if (columns < myColumns || rows < myRows)
|
||||
{
|
||||
werase(myStatus.get());
|
||||
wrefresh(myStatus.get());
|
||||
werase(myFrame.get());
|
||||
wrefresh(myFrame.get());
|
||||
}
|
||||
|
||||
myRows = rows;
|
||||
myColumns = columns;
|
||||
|
||||
const int width = 1 + myColumns + 1;
|
||||
const int left = (COLS - width) / 2;
|
||||
|
||||
myFrame.reset(newwin(1 + myRows + 1, width, 0, left), delwin);
|
||||
box(myFrame.get(), 0 , 0);
|
||||
wtimeout(myFrame.get(), 0);
|
||||
keypad(myFrame.get(), true);
|
||||
wrefresh(myFrame.get());
|
||||
|
||||
myStatus.reset(newwin(8, width, 1 + myRows + 1, left), delwin);
|
||||
FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES | DRAW_DISK_STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
WINDOW * NFrame::GetWindow()
|
||||
{
|
||||
return myFrame.get();
|
||||
}
|
||||
|
||||
WINDOW * NFrame::GetStatus()
|
||||
{
|
||||
return myStatus.get();
|
||||
}
|
||||
|
||||
void NFrame::InitialiseNCurses()
|
||||
{
|
||||
if (!myNCurses)
|
||||
{
|
||||
myNCurses.reset(new NCurses());
|
||||
}
|
||||
}
|
||||
|
||||
void NFrame::VideoPresentScreen()
|
||||
{
|
||||
VideoUpdateFlash();
|
||||
|
||||
Video & video = GetVideo();
|
||||
|
||||
// see NTSC_SetVideoMode in NTSC.cpp
|
||||
// we shoudl really use g_nTextPage and g_nHiresPage
|
||||
const int displaypage2 = (video.VideoGetSWPAGE2() && !video.VideoGetSW80STORE()) ? 1 : 0;
|
||||
|
||||
myHiresBank1 = MemGetAuxPtr (0x2000 << displaypage2);
|
||||
myHiresBank0 = MemGetMainPtr(0x2000 << displaypage2);
|
||||
myTextBank1 = MemGetAuxPtr (0x400 << displaypage2);
|
||||
myTextBank0 = MemGetMainPtr(0x400 << displaypage2);
|
||||
|
||||
typedef bool (NFrame::* VideoUpdateFuncPtr_t)(Video &, int, int, int, int, int);
|
||||
|
||||
VideoUpdateFuncPtr_t update = video.VideoGetSWTEXT()
|
||||
? video.VideoGetSW80COL()
|
||||
? &NFrame::Update80ColCell
|
||||
: &NFrame::Update40ColCell
|
||||
: video.VideoGetSWHIRES()
|
||||
? (video.VideoGetSWDHIRES() && video.VideoGetSW80COL())
|
||||
? &NFrame::UpdateDHiResCell
|
||||
: &NFrame::UpdateHiResCell
|
||||
: (video.VideoGetSWDHIRES() && video.VideoGetSW80COL())
|
||||
? &NFrame::UpdateDLoResCell
|
||||
: &NFrame::UpdateLoResCell;
|
||||
|
||||
int y = 0;
|
||||
int ypixel = 0;
|
||||
while (y < 20) {
|
||||
int offset = ((y & 7) << 7) + ((y >> 3) * 40);
|
||||
int x = 0;
|
||||
int xpixel = 0;
|
||||
while (x < 40) {
|
||||
(this->*update)(video, x, y, xpixel, ypixel, offset + x);
|
||||
++x;
|
||||
xpixel += 14;
|
||||
}
|
||||
++y;
|
||||
ypixel += 16;
|
||||
}
|
||||
|
||||
if (video.VideoGetSWMIXED())
|
||||
update = video.VideoGetSW80COL() ? &NFrame::Update80ColCell
|
||||
: &NFrame::Update40ColCell;
|
||||
|
||||
while (y < 24) {
|
||||
int offset = ((y & 7) << 7) + ((y >> 3) * 40);
|
||||
int x = 0;
|
||||
int xpixel = 0;
|
||||
while (x < 40) {
|
||||
(this->*update)(video, x, y, xpixel, ypixel, offset + x);
|
||||
++x;
|
||||
xpixel += 14;
|
||||
}
|
||||
++y;
|
||||
ypixel += 16;
|
||||
}
|
||||
|
||||
wrefresh(myFrame.get());
|
||||
}
|
||||
|
||||
void NFrame::FrameRefreshStatus(int /* drawflags */)
|
||||
{
|
||||
werase(myStatus.get());
|
||||
box(myStatus.get(), 0 , 0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
CardManager& cardManager = GetCardMgr();
|
||||
if (cardManager.QuerySlot(SLOT6) == CT_Disk2)
|
||||
{
|
||||
Disk2InterfaceCard& disk2 = dynamic_cast<Disk2InterfaceCard&>(cardManager.GetRef(SLOT6));
|
||||
const size_t maximumWidth = myColumns - 6; // 6 is the width of "S6D1: "
|
||||
for (UINT i = DRIVE_1; i <= DRIVE_2; ++i)
|
||||
{
|
||||
const std::string name = disk2.GetBaseName(i).substr(0, maximumWidth);
|
||||
mvwprintw(myStatus.get(), ++row, 1, "S6D%d: %s", 1 + i, name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
row += DRIVE_2 - DRIVE_1 + 1;
|
||||
}
|
||||
|
||||
++row;
|
||||
|
||||
mvwprintw(myStatus.get(), ++row, 1, "F2: ResetMachine / Shift-F2: CtrlReset");
|
||||
mvwprintw(myStatus.get(), ++row, 1, "F3: Exit / F5: Swap");
|
||||
mvwprintw(myStatus.get(), ++row, 1, "F11: Load State / F12: Save State");
|
||||
wrefresh(myStatus.get());
|
||||
}
|
||||
|
||||
void NFrame::VideoUpdateFlash()
|
||||
{
|
||||
++myTextFlashCounter;
|
||||
|
||||
if (myTextFlashCounter == 16) // Flash rate = 0.5 * 60 / 16 Hz (as we need 2 changes for a period)
|
||||
{
|
||||
myTextFlashCounter = 0;
|
||||
myTextFlashState = !myTextFlashState;
|
||||
}
|
||||
}
|
||||
|
||||
chtype NFrame::MapCharacter(Video & video, BYTE ch)
|
||||
{
|
||||
const char low = ch & 0x7f;
|
||||
const char high = ch & 0x80;
|
||||
|
||||
chtype result = low;
|
||||
|
||||
const int code = low >> 5;
|
||||
switch (code)
|
||||
{
|
||||
case 0: // 00 - 1F
|
||||
result += 0x40; // UPPERCASE
|
||||
break;
|
||||
case 1: // 20 - 3F
|
||||
// SPECIAL CHARACTER
|
||||
break;
|
||||
case 2: // 40 - 5F
|
||||
// UPPERCASE
|
||||
break;
|
||||
case 3: // 60 - 7F
|
||||
// LOWERCASE
|
||||
if (high == 0 && !video.VideoGetSWAltCharSet())
|
||||
{
|
||||
result -= 0x40;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == 0x7f)
|
||||
{
|
||||
result = ACS_CKBOARD;
|
||||
}
|
||||
|
||||
if (!high)
|
||||
{
|
||||
if (!video.VideoGetSWAltCharSet() && (low >= 0x40))
|
||||
{
|
||||
// result |= A_BLINK; // does not work on my terminal
|
||||
if (myTextFlashState)
|
||||
{
|
||||
result |= A_REVERSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= A_REVERSE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NFrame::Update40ColCell(Video & video, int x, int y, int xpixel, int ypixel, int offset)
|
||||
{
|
||||
Init(24, 40);
|
||||
myAsciiArt->init(1, 1);
|
||||
|
||||
BYTE ch = *(myTextBank0+offset);
|
||||
|
||||
const chtype ch2 = MapCharacter(video, ch);
|
||||
mvwaddch(myFrame.get(), 1 + y, 1 + x, ch2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFrame::Update80ColCell(Video & video, int x, int y, int xpixel, int ypixel, int offset)
|
||||
{
|
||||
Init(24, 80);
|
||||
myAsciiArt->init(1, 2);
|
||||
|
||||
BYTE ch1 = *(myTextBank1+offset);
|
||||
BYTE ch2 = *(myTextBank0+offset);
|
||||
|
||||
WINDOW * win = myFrame.get();
|
||||
|
||||
const chtype ch12 = MapCharacter(video, ch1);
|
||||
mvwaddch(win, 1 + y, 1 + 2 * x, ch12);
|
||||
|
||||
const chtype ch22 = MapCharacter(video, ch2);
|
||||
mvwaddch(win, 1 + y, 1 + 2 * x + 1, ch22);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFrame::UpdateLoResCell(Video &, int x, int y, int xpixel, int ypixel, int offset)
|
||||
{
|
||||
BYTE val = *(myTextBank0+offset);
|
||||
|
||||
const int pair = myNCurses->colors->getPair(val);
|
||||
|
||||
WINDOW * win = myFrame.get();
|
||||
|
||||
wcolor_set(win, pair, NULL);
|
||||
if (myColumns == 40)
|
||||
{
|
||||
mvwaddstr(win, 1 + y, 1 + x, "\u2580");
|
||||
}
|
||||
else
|
||||
{
|
||||
mvwaddstr(win, 1 + y, 1 + 2 * x, "\u2580\u2580");
|
||||
}
|
||||
wcolor_set(win, 0, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFrame::UpdateDLoResCell(Video &, int x, int y, int xpixel, int ypixel, int offset)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFrame::UpdateHiResCell(Video &, int x, int y, int xpixel, int ypixel, int offset)
|
||||
{
|
||||
const BYTE * base = myHiresBank0 + offset;
|
||||
|
||||
const ASCIIArt::array_char_t & chs = myAsciiArt->getCharacters(base);
|
||||
|
||||
const auto shape = chs.shape();
|
||||
const size_t rows = shape[0];
|
||||
const size_t cols = shape[1];
|
||||
|
||||
Init(24 * rows, 40 * cols);
|
||||
WINDOW * win = myFrame.get();
|
||||
|
||||
const GraphicsColors & colors = *myNCurses->colors;
|
||||
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < cols; ++j)
|
||||
{
|
||||
const int pair = colors.getGrey(chs[i][j].foreground, chs[i][j].background);
|
||||
|
||||
wcolor_set(win, pair, NULL);
|
||||
mvwaddstr(win, 1 + rows * y + i, 1 + cols * x + j, chs[i][j].c);
|
||||
}
|
||||
}
|
||||
wcolor_set(win, 0, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFrame::UpdateDHiResCell(Video &, int x, int y, int xpixel, int ypixel, int offset)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int NFrame::FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType)
|
||||
{
|
||||
LogFileOutput("MessageBox:\n%s\n%s\n\n", lpCaption, lpText);
|
||||
return IDOK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SingleStep(bool /* bReinit */)
|
||||
{
|
||||
|
||||
}
|
71
source/frontends/ncurses/nframe.h
Normal file
71
source/frontends/ncurses/nframe.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "frontends/common2/commonframe.h"
|
||||
#include "frontends/common2/gnuframe.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <ncurses.h>
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
class ASCIIArt;
|
||||
class EvDevPaddle;
|
||||
struct NCurses;
|
||||
|
||||
class NFrame : public virtual common2::CommonFrame, public common2::GNUFrame
|
||||
{
|
||||
public:
|
||||
NFrame(const std::shared_ptr<EvDevPaddle> & paddle);
|
||||
|
||||
WINDOW * GetWindow();
|
||||
WINDOW * GetStatus();
|
||||
|
||||
void Initialize(bool resetVideoState) override;
|
||||
void Destroy() override;
|
||||
void VideoPresentScreen() override;
|
||||
int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType) override;
|
||||
void FrameRefreshStatus(int drawflags) override;
|
||||
|
||||
void ProcessEvDev();
|
||||
|
||||
void ChangeColumns(const int x);
|
||||
void ChangeRows(const int x);
|
||||
|
||||
void Init(int rows, int columns);
|
||||
|
||||
private:
|
||||
|
||||
const std::shared_ptr<EvDevPaddle> myPaddle;
|
||||
|
||||
int myRows;
|
||||
int myColumns;
|
||||
int myTextFlashCounter;
|
||||
bool myTextFlashState;
|
||||
|
||||
std::shared_ptr<WINDOW> myFrame;
|
||||
std::shared_ptr<WINDOW> myStatus;
|
||||
std::shared_ptr<ASCIIArt> myAsciiArt;
|
||||
std::shared_ptr<NCurses> myNCurses;
|
||||
|
||||
LPBYTE myTextBank1; // Aux
|
||||
LPBYTE myTextBank0; // Main
|
||||
LPBYTE myHiresBank1;
|
||||
LPBYTE myHiresBank0;
|
||||
|
||||
void VideoUpdateFlash();
|
||||
|
||||
chtype MapCharacter(Video & video, BYTE ch);
|
||||
|
||||
bool Update40ColCell(Video & video, int x, int y, int xpixel, int ypixel, int offset);
|
||||
bool Update80ColCell(Video & video, int x, int y, int xpixel, int ypixel, int offset);
|
||||
bool UpdateLoResCell(Video &, int x, int y, int xpixel, int ypixel, int offset);
|
||||
bool UpdateDLoResCell(Video &, int x, int y, int xpixel, int ypixel, int offset);
|
||||
bool UpdateHiResCell(Video &, int x, int y, int xpixel, int ypixel, int offset);
|
||||
bool UpdateDHiResCell(Video &, int x, int y, int xpixel, int ypixel, int offset);
|
||||
|
||||
void InitialiseNCurses();
|
||||
};
|
||||
|
||||
}
|
137
source/frontends/ncurses/world.cpp
Normal file
137
source/frontends/ncurses/world.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#include "frontends/ncurses/world.h"
|
||||
#include "StdAfx.h"
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include "linux/linuxinterface.h"
|
||||
#include "linux/keyboard.h"
|
||||
|
||||
#include "frontends/ncurses/nframe.h"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void sig_handler_pass(int signo)
|
||||
{
|
||||
// Ctrl-C
|
||||
// is there a race condition here?
|
||||
// is it a problem?
|
||||
addKeyToBuffer(0x03);
|
||||
}
|
||||
|
||||
void sig_handler_exit(int signo)
|
||||
{
|
||||
na2::g_stop = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
double g_relativeSpeed = 1.0;
|
||||
bool g_stop = false;
|
||||
|
||||
void SetCtrlCHandler(const bool headless)
|
||||
{
|
||||
if (headless)
|
||||
{
|
||||
signal(SIGINT, sig_handler_exit);
|
||||
}
|
||||
else
|
||||
{
|
||||
signal(SIGINT, sig_handler_pass);
|
||||
// pass Ctrl-C to the emulator
|
||||
}
|
||||
}
|
||||
|
||||
int ProcessKeyboard(const std::shared_ptr<NFrame> & frame)
|
||||
{
|
||||
WINDOW * window = frame->GetWindow();
|
||||
if (!window)
|
||||
{
|
||||
return ERR;
|
||||
}
|
||||
|
||||
const int inch = wgetch(window);
|
||||
|
||||
int ch = ERR;
|
||||
|
||||
switch (inch)
|
||||
{
|
||||
case ERR:
|
||||
break;
|
||||
case '\n':
|
||||
ch = 0x0d; // ENTER
|
||||
break;
|
||||
case KEY_BACKSPACE:
|
||||
case KEY_LEFT:
|
||||
ch = 0x08;
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
ch = 0x15;
|
||||
break;
|
||||
case KEY_UP:
|
||||
ch = 0x0b;
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
ch = 0x0a;
|
||||
break;
|
||||
case 0x14a: // DEL
|
||||
ch = 0x7f;
|
||||
break;
|
||||
case 543 ... 546: // Various values for Ctrl/Alt - Left on Ubuntu and Pi OS
|
||||
frame->ChangeColumns(-1);
|
||||
break;
|
||||
case 558 ... 561: // Ctrl/Alt - Right
|
||||
frame->ChangeColumns(+1);
|
||||
break;
|
||||
case 564 ... 567: // Ctrl/Alt - Up
|
||||
frame->ChangeRows(-1);
|
||||
break;
|
||||
case 523 ... 526: // Ctrl/Alt - Down
|
||||
frame->ChangeRows(+1);
|
||||
break;
|
||||
default:
|
||||
if (inch < 0x80)
|
||||
{
|
||||
ch = inch;
|
||||
// Standard for Apple II is Upper case
|
||||
if (ch >= 'A' && ch <= 'Z')
|
||||
{
|
||||
ch += 'a' - 'A';
|
||||
}
|
||||
else if (ch >= 'a' && ch <= 'z')
|
||||
{
|
||||
ch -= 'a' - 'A';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ch != ERR)
|
||||
{
|
||||
addKeyToBuffer(ch);
|
||||
return ERR;
|
||||
}
|
||||
else
|
||||
{
|
||||
// pass it back
|
||||
return inch;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Mockingboard
|
||||
void registerSoundBuffer(IDirectSoundBuffer * buffer)
|
||||
{
|
||||
}
|
||||
|
||||
void unregisterSoundBuffer(IDirectSoundBuffer * buffer)
|
||||
{
|
||||
}
|
17
source/frontends/ncurses/world.h
Normal file
17
source/frontends/ncurses/world.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace na2
|
||||
{
|
||||
|
||||
class NFrame;
|
||||
|
||||
int ProcessKeyboard(const std::shared_ptr<NFrame> & frame);
|
||||
void SetCtrlCHandler(const bool headless);
|
||||
|
||||
extern double g_relativeSpeed;
|
||||
|
||||
extern bool g_stop;
|
||||
|
||||
}
|
61
source/frontends/qt/CMakeLists.txt
Normal file
61
source/frontends/qt/CMakeLists.txt
Normal file
|
@ -0,0 +1,61 @@
|
|||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
find_package(Qt5 REQUIRED
|
||||
COMPONENTS Widgets Gamepad Multimedia
|
||||
)
|
||||
|
||||
add_subdirectory(QHexView)
|
||||
|
||||
set(SOURCE_FILES
|
||||
main.cpp
|
||||
qapple.cpp
|
||||
preferences.cpp
|
||||
emulator.cpp
|
||||
memorycontainer.cpp
|
||||
gamepadpaddle.cpp
|
||||
qvideo.cpp
|
||||
configuration.cpp
|
||||
options.cpp
|
||||
loggingcategory.cpp
|
||||
viewbuffer.cpp
|
||||
qdirectsound.cpp
|
||||
qtframe.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
applicationname.h
|
||||
qapple.h
|
||||
preferences.h
|
||||
emulator.h
|
||||
memorycontainer.h
|
||||
gamepadpaddle.h
|
||||
qvideo.h
|
||||
configuration.h
|
||||
options.h
|
||||
loggingcategory.h
|
||||
viewbuffer.h
|
||||
qdirectsound.h
|
||||
qtframe.h
|
||||
)
|
||||
|
||||
add_executable(qapple
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
qapple.qrc
|
||||
)
|
||||
|
||||
target_link_libraries(qapple PRIVATE
|
||||
Qt5::Widgets
|
||||
Qt5::Gamepad
|
||||
Qt5::Multimedia
|
||||
appleii
|
||||
qhexview-lib
|
||||
windows
|
||||
)
|
||||
|
||||
install(TARGETS qapple
|
||||
DESTINATION bin)
|
1
source/frontends/qt/QHexView
Submodule
1
source/frontends/qt/QHexView
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b3c0e4a8b7318f3c6ed8a29a75694b376ba16cf1
|
4
source/frontends/qt/applicationname.h
Normal file
4
source/frontends/qt/applicationname.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
#define ORGANIZATION_NAME "AndSoft"
|
||||
#define APPLICATION_NAME "QAppleEmulator"
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue