mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-11 01:11:42 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86db200a23 | ||
|
|
2781365ac3 | ||
|
|
a238a7e754 | ||
|
|
299d211ab1 | ||
|
|
fc2278486e | ||
|
|
f7d5a87667 | ||
|
|
3e2934529e | ||
|
|
ff9afca894 | ||
|
|
3f002928ed | ||
|
|
b124e8a8f0 | ||
|
|
0f965050b1 | ||
|
|
b9cd02ddee | ||
|
|
b7198a2334 | ||
|
|
8967e05524 | ||
|
|
1b1e7f8f2c | ||
|
|
00eb27681e | ||
|
|
9ad4b27681 | ||
|
|
060eaba2ea | ||
|
|
d75a495fc1 | ||
|
|
f593b12416 | ||
|
|
2f3385776d | ||
|
|
fe8afe05de | ||
|
|
bd9f52cf8c | ||
|
|
3545360033 | ||
|
|
5c61c3acbd | ||
|
|
3d3513e365 | ||
|
|
6a0f2eaf1a | ||
|
|
9422ea5905 | ||
|
|
582fc8f846 | ||
|
|
73893352f7 | ||
|
|
47c9ec1f80 | ||
|
|
b482344691 | ||
|
|
cbdedc9f9c | ||
|
|
12a1585ee2 | ||
|
|
4644bb4494 | ||
|
|
a54b551e4d | ||
|
|
30724164be | ||
|
|
517e0cc454 | ||
|
|
2c489cdadd | ||
|
|
28a956111d | ||
|
|
7115ed9ab5 | ||
|
|
566184f669 | ||
|
|
8b5fbf511f | ||
|
|
c6a6972a75 | ||
|
|
59713ea9b1 | ||
|
|
4e1e3e02bb | ||
|
|
a26e1ae006 | ||
|
|
160ddb4457 | ||
|
|
8dda1836ac | ||
|
|
1e9c07c803 | ||
|
|
18884d6aa2 | ||
|
|
2c463d0322 | ||
|
|
2c3963c24e | ||
|
|
3d57bf7914 | ||
|
|
9fa501cc3a | ||
|
|
c66e3471e0 | ||
|
|
17924254a1 | ||
|
|
27fe06917e | ||
|
|
164ef7b678 | ||
|
|
2ad30215df | ||
|
|
d4ae964be4 | ||
|
|
228105e6de | ||
|
|
6c5e8ed49e | ||
|
|
019287a648 | ||
|
|
719dac2dc1 | ||
|
|
abf2ef5832 | ||
|
|
953a50d827 | ||
|
|
7c116300bc | ||
|
|
cbfd7ed270 | ||
|
|
78d926b31d | ||
|
|
a4d03c263f | ||
|
|
5283135a49 | ||
|
|
2c9cf460f3 | ||
|
|
2bcb236dd6 | ||
|
|
77ff400bb1 | ||
|
|
c87166f9c5 | ||
|
|
6827540f11 | ||
|
|
0615773904 | ||
|
|
c73e19bdf2 | ||
|
|
c0cac55065 | ||
|
|
e45e82bd19 | ||
|
|
26a7ac35ca | ||
|
|
9748b37532 | ||
|
|
0feacdc6a5 | ||
|
|
be2495b253 | ||
|
|
24762209aa | ||
|
|
74ec42e889 | ||
|
|
fa76d1fb16 | ||
|
|
c437bc2d25 | ||
|
|
86ec0d1226 | ||
|
|
12c771e5c9 | ||
|
|
451d562186 | ||
|
|
aa5efe67ef | ||
|
|
1749d4bd9b | ||
|
|
d21820dab7 | ||
|
|
51474d86e7 | ||
|
|
7fc641c787 | ||
|
|
1059725734 | ||
|
|
6396c8c9ac | ||
|
|
57647bf62b | ||
|
|
5176bfead1 | ||
|
|
91281a8247 | ||
|
|
c46beaff32 | ||
|
|
b3c49a4636 | ||
|
|
7ae217bc79 | ||
|
|
5d37682c6b | ||
|
|
d70cb368de | ||
|
|
4fcfa471a4 | ||
|
|
01da5ed20d | ||
|
|
433f230c9c | ||
|
|
91e49ab940 | ||
|
|
e464f45605 | ||
|
|
e062350552 | ||
|
|
74d6e885f6 | ||
|
|
ec34c02e4f | ||
|
|
0aa84312c5 | ||
|
|
17a654cfc4 | ||
|
|
027a3db94c | ||
|
|
38a7c74f28 | ||
|
|
3147c3c996 | ||
|
|
d6605625bc | ||
|
|
e534e56002 | ||
|
|
cf9c02e571 | ||
|
|
0483193439 | ||
|
|
6112bcaced | ||
|
|
64ae531ead | ||
|
|
e90c318c0e | ||
|
|
a4551216e9 | ||
|
|
2e38fa5fc6 | ||
|
|
e2b96c8bab | ||
|
|
4c3c07eb38 | ||
|
|
3e7ca5baec | ||
|
|
c74700f35d | ||
|
|
368578d736 | ||
|
|
65d9640b20 | ||
|
|
836f3fc976 | ||
|
|
5636e2b0f6 | ||
|
|
37bc375c64 | ||
|
|
50467b7e6d | ||
|
|
dc37e934e6 | ||
|
|
21e5daebfc | ||
|
|
8f579b1993 | ||
|
|
ed336af513 | ||
|
|
03186eb4d3 | ||
|
|
972495df89 | ||
|
|
3366b1dc6c | ||
|
|
2c86419dd8 | ||
|
|
0e30732950 | ||
|
|
0fb5569c6b | ||
|
|
f289cdd08a | ||
|
|
f0598aaeb3 | ||
|
|
1a498d64c9 | ||
|
|
c2f58d1c79 | ||
|
|
3d826f5b26 | ||
|
|
2bb95a13cb | ||
|
|
1de21e178c | ||
|
|
cde0e1603d | ||
|
|
6fe063815c | ||
|
|
b527a33b85 | ||
|
|
29a9c53a62 | ||
|
|
56fe0d8269 | ||
|
|
dc28723d52 | ||
|
|
36af497fdf | ||
|
|
d5c094e9e8 | ||
|
|
6adc70cbc8 | ||
|
|
2e385844d0 | ||
|
|
7e48079a46 | ||
|
|
f9eb9304f0 | ||
|
|
c3eb8bc49e | ||
|
|
8759a7d3e8 | ||
|
|
a785ce924b | ||
|
|
938b1647b0 | ||
|
|
6abe6bfb19 | ||
|
|
d22fae0cae | ||
|
|
4effa2b6d2 | ||
|
|
e6dfd5ee74 | ||
|
|
a5966490c9 | ||
|
|
f5f75cdeef | ||
|
|
a6209af97b | ||
|
|
93e24926ae | ||
|
|
6f3997dc38 | ||
|
|
b380834c0e | ||
|
|
9e551be17a | ||
|
|
d77c6f0788 | ||
|
|
a1df486779 | ||
|
|
cdedb9b0f5 | ||
|
|
9ff41ed45a | ||
|
|
56aa5dac0d | ||
|
|
ebde92c891 | ||
|
|
7dc55eeb54 | ||
|
|
ec4e0050e7 | ||
|
|
306d23f38b | ||
|
|
fa3dfa38cd | ||
|
|
a0601f1c79 | ||
|
|
19b56bc118 | ||
|
|
2f12a72cad | ||
|
|
ed0fe5599f | ||
|
|
081408e961 | ||
|
|
56d6488b3d | ||
|
|
85f1fb6b7f | ||
|
|
34d6d465c4 | ||
|
|
fcef806553 | ||
|
|
61258a7063 | ||
|
|
b1d216b8af | ||
|
|
c53023e6c8 | ||
|
|
637aa19a72 | ||
|
|
2cf4d8cac8 | ||
|
|
0071e8a7d4 | ||
|
|
d546f147c3 | ||
|
|
b03a942bf4 | ||
|
|
23416eae32 | ||
|
|
2ade0e39de | ||
|
|
62930f8da8 | ||
|
|
be1f424398 | ||
|
|
003afbbb58 | ||
|
|
3010c8a8c9 | ||
|
|
4a935893d2 | ||
|
|
32096b8e85 | ||
|
|
3142bd3aef | ||
|
|
9985c693bd | ||
|
|
81e15695c8 | ||
|
|
f486867292 | ||
|
|
ffa7040b6f | ||
|
|
247a269054 | ||
|
|
50b2a8f3ab | ||
|
|
f1e36f2775 | ||
|
|
997fda03fe | ||
|
|
c6ecbe81b2 | ||
|
|
9c9ef927d7 | ||
|
|
029ae18514 | ||
|
|
dcfce18d7f | ||
|
|
295c729ecf | ||
|
|
43e0dd770d | ||
|
|
6e9c08b491 | ||
|
|
1808e3f50c | ||
|
|
8f39e06900 | ||
|
|
7742b49289 | ||
|
|
4750a7f224 | ||
|
|
ff78815e90 | ||
|
|
00a5c6864e | ||
|
|
a38572199d | ||
|
|
bcd501edf6 | ||
|
|
8d1ad79fdb | ||
|
|
b2db7f16f6 | ||
|
|
080b53a652 | ||
|
|
2b9fcab30f | ||
|
|
5565d2def4 | ||
|
|
9aa05bcbe3 | ||
|
|
e0434ca82b | ||
|
|
9c5ecf52ce | ||
|
|
6d890244c1 | ||
|
|
f7657dca4f | ||
|
|
eda2918f58 | ||
|
|
d75c13107c | ||
|
|
1a5984be70 | ||
|
|
e1711eb28a | ||
|
|
1a564fa93e | ||
|
|
6118facaf2 | ||
|
|
d52a3b9dfc | ||
|
|
38495197d2 | ||
|
|
dd6a448bc4 | ||
|
|
1cea9ad1d9 | ||
|
|
14c9dc04fd | ||
|
|
9e983e720f | ||
|
|
b2dd52c0a4 | ||
|
|
9a3d949fc2 | ||
|
|
00819b70c0 | ||
|
|
cd83a939e8 | ||
|
|
443aabb89b | ||
|
|
93275dba2c | ||
|
|
3767568cdb | ||
|
|
7cfb150be8 | ||
|
|
049211abae | ||
|
|
a7ce0c22fa | ||
|
|
7205a39f23 | ||
|
|
99b759b630 | ||
|
|
a9ced1175b | ||
|
|
aeb5bc4597 | ||
|
|
f031407a14 | ||
|
|
3c42e12b75 | ||
|
|
2ce9d210af | ||
|
|
a038b2cf61 | ||
|
|
cee2ea332f | ||
|
|
b1e3d90790 | ||
|
|
f306d1223c | ||
|
|
e90e6ab846 | ||
|
|
6a103ed073 | ||
|
|
96663687f2 | ||
|
|
8d17ae1697 | ||
|
|
a2e4807810 | ||
|
|
803daf7642 | ||
|
|
c88fb1155b | ||
|
|
246a5eee93 | ||
|
|
4607b40e23 | ||
|
|
d0a963ab42 | ||
|
|
bb58366efd | ||
|
|
0891a102e4 | ||
|
|
0e74d5c8e3 | ||
|
|
672940cf06 | ||
|
|
9ecfbdbe76 | ||
|
|
679dbaccc9 | ||
|
|
57b4d68298 | ||
|
|
5d55f2b8e3 | ||
|
|
f094529d6d | ||
|
|
c2735c5c1c | ||
|
|
e1f5ab8333 | ||
|
|
cf0ef61c1f | ||
|
|
2b9907c8d0 | ||
|
|
2abe2286b2 | ||
|
|
b6d8be087f | ||
|
|
49892159d5 | ||
|
|
e88e2cf032 | ||
|
|
bcf9cab22d | ||
|
|
2973fa71e5 | ||
|
|
16f86afd9f | ||
|
|
329a8c958b | ||
|
|
91f3dfaf3d | ||
|
|
daa8d82f7b | ||
|
|
fb98870d56 | ||
|
|
07183d56f4 | ||
|
|
232cb6fcf6 | ||
|
|
320779a845 | ||
|
|
86e962bc13 | ||
|
|
0cafaad19b | ||
|
|
043b4ffd37 | ||
|
|
6174fa384e | ||
|
|
e5d1f2dfea | ||
|
|
54f6ea31f5 | ||
|
|
1d26512309 | ||
|
|
402c8acc5f | ||
|
|
43f1e524e0 | ||
|
|
2451fec704 | ||
|
|
8a70118446 | ||
|
|
fa017c26a5 | ||
|
|
670920a5a4 | ||
|
|
82364646d3 | ||
|
|
40220a6ba1 | ||
|
|
96a6f98862 | ||
|
|
3423bcf65e | ||
|
|
a74ef69d86 | ||
|
|
177475f053 | ||
|
|
32ba2fe676 | ||
|
|
3aa1b3b738 | ||
|
|
abf4c58ce6 | ||
|
|
c7e59d13fa | ||
|
|
6b0a52fcb6 | ||
|
|
ea2b6886b5 | ||
|
|
80e651741c | ||
|
|
6b70ae6d69 | ||
|
|
a3a777ef93 | ||
|
|
abaf7a82f3 | ||
|
|
0bf24b3161 | ||
|
|
a527ed6d1d | ||
|
|
cf0582f607 | ||
|
|
02942aca6b | ||
|
|
aae3caab38 | ||
|
|
2a71b978be | ||
|
|
336af4c219 | ||
|
|
1afbf893d7 | ||
|
|
6bcab5e7ed | ||
|
|
5e4e64dbd7 | ||
|
|
728db642f0 | ||
|
|
e1f1cbbb4b | ||
|
|
b7f96b7109 | ||
|
|
32c15726ae | ||
|
|
6160a81368 | ||
|
|
523b6ce5cf | ||
|
|
95e407aae1 | ||
|
|
5e1d6d1aab | ||
|
|
9f0f4b3432 | ||
|
|
f4a38d121d | ||
|
|
abf454cecb | ||
|
|
6c40a5b4c6 | ||
|
|
56458d5ba5 | ||
|
|
8d04515b5f | ||
|
|
f66c93c0c2 | ||
|
|
f4c9c11596 | ||
|
|
906191ac45 | ||
|
|
a0c063cabe | ||
|
|
ebf41cb696 | ||
|
|
c0dfad661b | ||
|
|
0b80107cdf | ||
|
|
f7eb4483b2 | ||
|
|
9178761932 | ||
|
|
235a04d54d | ||
|
|
75c0a5f36e | ||
|
|
6de4810ab7 | ||
|
|
1a5ab89cd4 | ||
|
|
4a80dbabcb | ||
|
|
6399a5cd15 | ||
|
|
acd2130a4d | ||
|
|
b3b32ef026 | ||
|
|
4ce8290e49 | ||
|
|
fd6698c5f6 | ||
|
|
f92605cfbd | ||
|
|
2c3c9fe25f | ||
|
|
18e013beda | ||
|
|
1e26a8be76 | ||
|
|
300fa4801f | ||
|
|
c5106a1982 | ||
|
|
1d22db7e1c | ||
|
|
c986a14401 | ||
|
|
a6d3a57df9 | ||
|
|
8d04df1641 | ||
|
|
6baebd0f06 | ||
|
|
e34bfc04e3 | ||
|
|
0d7c70bdba | ||
|
|
fccd73f8e3 | ||
|
|
c52c7fd1bb | ||
|
|
5210341ad3 | ||
|
|
d7e456a46f | ||
|
|
a3b126f079 | ||
|
|
bc8c308ee9 | ||
|
|
124a678c8e | ||
|
|
333dd85ba5 | ||
|
|
d08a9bdcc2 | ||
|
|
f4a018036d | ||
|
|
65f4e176c2 | ||
|
|
3b8e9bbff5 | ||
|
|
8f2c6eff9c | ||
|
|
85a5292648 | ||
|
|
c8f261201c | ||
|
|
e42c79677d | ||
|
|
de9ec5d83c | ||
|
|
93e89446e2 | ||
|
|
2a28c46a41 | ||
|
|
23fa258dfd | ||
|
|
e86a18f293 | ||
|
|
665229a892 | ||
|
|
4d46be8344 | ||
|
|
5a839f1010 | ||
|
|
d5dcc645e4 | ||
|
|
35f6a93713 | ||
|
|
d01b8db441 | ||
|
|
a8373002b5 | ||
|
|
5d192fb215 | ||
|
|
e13722870e | ||
|
|
82e49616a2 | ||
|
|
7d425dc5e7 | ||
|
|
aa975f8f6c | ||
|
|
7fcd6d1f5a | ||
|
|
9d7c83aa39 | ||
|
|
1d8aa3fc3f | ||
|
|
7692f102e5 | ||
|
|
3be1ced3ef | ||
|
|
c78d931e28 | ||
|
|
e7f24c06d5 | ||
|
|
dc5ff13b5c | ||
|
|
7140b64b13 | ||
|
|
4d4d3308b9 | ||
|
|
e02a7cc6fb | ||
|
|
ce981795f5 | ||
|
|
51d16a58cd | ||
|
|
89287dabf1 | ||
|
|
5b52f94aa1 | ||
|
|
f67373732c | ||
|
|
8d43e67b96 | ||
|
|
8a37b00fef | ||
|
|
80c2678930 | ||
|
|
510cc55d76 | ||
|
|
5c285e3ce3 | ||
|
|
21203b2401 | ||
|
|
2c8f3ef84d | ||
|
|
80348ae691 | ||
|
|
5e55f1a8a7 | ||
|
|
c63257029e | ||
|
|
93d44072da | ||
|
|
2cf921be41 | ||
|
|
cc8ccbf1bd | ||
|
|
10e589672f | ||
|
|
3c28ecd8b6 | ||
|
|
cc64eb50c0 | ||
|
|
5cb877b49c | ||
|
|
275ba735de | ||
|
|
2b894add40 | ||
|
|
58690cd5fc | ||
|
|
b03da384d8 | ||
|
|
f382a18d10 | ||
|
|
f63cad6de9 | ||
|
|
34d2c091c7 | ||
|
|
d845582d4a | ||
|
|
9788752bc1 | ||
|
|
5f60ab2dde | ||
|
|
628432e75c | ||
|
|
980e9ed687 | ||
|
|
e98a313ed6 | ||
|
|
90d42d1ea3 | ||
|
|
11d6b7c673 | ||
|
|
c04bcf03e0 | ||
|
|
c4d73cc427 | ||
|
|
8634c11cff | ||
|
|
8a5c48e3ed | ||
|
|
b22513a6e8 | ||
|
|
e5220e8fd0 | ||
|
|
8099bf54fd | ||
|
|
bed4ce3659 | ||
|
|
f630a9418d | ||
|
|
f9e4070b15 | ||
|
|
5050d6c45d | ||
|
|
b56dac15e9 | ||
|
|
a8e35d0299 | ||
|
|
1462bac4c5 | ||
|
|
5e2a8f08bf | ||
|
|
e88cb48f19 | ||
|
|
70a669287b | ||
|
|
9d3addc241 | ||
|
|
f22e1405dc | ||
|
|
4542ac5674 | ||
|
|
5c3ac88547 | ||
|
|
85fcda5b42 | ||
|
|
f512fdc969 | ||
|
|
64286fbd46 | ||
|
|
e0c4cc76f2 | ||
|
|
7b4a346336 | ||
|
|
763f7da939 | ||
|
|
ee188dcb31 | ||
|
|
1f8b3f86b9 | ||
|
|
18e334a6cf | ||
|
|
24675b0161 | ||
|
|
f4caedcd5e | ||
|
|
bdc6313e46 | ||
|
|
fd3a56b0a2 | ||
|
|
663b6951e3 | ||
|
|
554430276c | ||
|
|
3748baf8e8 | ||
|
|
e1bd1bfdf9 | ||
|
|
5f6b75a013 | ||
|
|
2d6fe4aa16 | ||
|
|
b49e5496da | ||
|
|
645d481493 | ||
|
|
bc14c35cb5 | ||
|
|
16210cf1bf | ||
|
|
d34e54c808 | ||
|
|
76508d02bd | ||
|
|
32690bb6f0 | ||
|
|
9eca885d8b | ||
|
|
f9a53d2167 | ||
|
|
fd5b22c5cd | ||
|
|
2f6edc7efe | ||
|
|
12ac1b5534 | ||
|
|
c8fa67ec39 | ||
|
|
5341d0d7d4 | ||
|
|
ad0105de25 | ||
|
|
e3663a9eec | ||
|
|
a01ccb72f2 | ||
|
|
27996d81d4 | ||
|
|
47af07c507 | ||
|
|
cc6ab702c0 | ||
|
|
d633b92a57 | ||
|
|
579be2bb04 | ||
|
|
89db771c73 | ||
|
|
33b95510cb | ||
|
|
31eee5f6f1 | ||
|
|
7ba1c2925d | ||
|
|
dbc1b4ccc6 | ||
|
|
a3a1969775 | ||
|
|
ab67ec12cd | ||
|
|
02cb1268e4 | ||
|
|
50994a2c60 | ||
|
|
9e099e9b15 | ||
|
|
16cbd8b149 | ||
|
|
5491a6a9c8 | ||
|
|
d0a14c682d | ||
|
|
4eb4b6cdb7 | ||
|
|
9bc59159d0 | ||
|
|
f518f04ffa | ||
|
|
2ce06bc4d1 | ||
|
|
a3285b6959 | ||
|
|
8a7ac00d03 | ||
|
|
38cf1b0f8e | ||
|
|
d26ffccc9f | ||
|
|
9ee4afd17f | ||
|
|
61dbb64999 | ||
|
|
2d5e1ba552 | ||
|
|
a31b0a7768 | ||
|
|
7c9ff4ba6d | ||
|
|
0bc30dd26d | ||
|
|
f73032e17f | ||
|
|
2eee9bcd4e | ||
|
|
e3a3927e95 | ||
|
|
f04e55f863 | ||
|
|
08aecccc5a | ||
|
|
3471756c06 | ||
|
|
d40e6e1cd3 | ||
|
|
6a262e01ae | ||
|
|
2f6a51b3ef | ||
|
|
ee3b764b8e | ||
|
|
4216d5ae0a | ||
|
|
878efe0a05 | ||
|
|
98aee19e93 | ||
|
|
7810a05b60 | ||
|
|
b83e868a6e | ||
|
|
ce3153b10e | ||
|
|
259642ae2a | ||
|
|
30921061c7 | ||
|
|
757da94463 | ||
|
|
1c026f5edf | ||
|
|
2d90b6343d | ||
|
|
4c312ab2af | ||
|
|
55d197fdc3 | ||
|
|
53290b9c71 | ||
|
|
764a881d36 | ||
|
|
a10e089100 | ||
|
|
590ec4cb32 | ||
|
|
30d143092d | ||
|
|
2dbd5a43a0 | ||
|
|
08c013dc46 | ||
|
|
0d169fe641 |
No files matched your search
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug *not* relating to game compatibility
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Game Issue Report
|
||||
about: Report an issue running a specific game in ares
|
||||
title: "[system] Game"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before submitting an issue, please use the search to make sure an issue doesn't already exist.
|
||||
|
||||
**Describe the issue**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Rom Information**
|
||||
Specify the impacted rom filename and any associated hashes (sha256 preferred)
|
||||
|
||||
```
|
||||
file: Some Game (REGION).ext
|
||||
sha256: <hash>
|
||||
```
|
||||
+82
-10
@@ -11,27 +11,99 @@ jobs:
|
||||
matrix:
|
||||
program:
|
||||
- ares
|
||||
os:
|
||||
platform:
|
||||
- name: windows
|
||||
version: latest
|
||||
os: windows-latest
|
||||
compiler: clang++
|
||||
shell: 'msys2 {0}'
|
||||
msystem: clang64
|
||||
msys-env: clang-x86_64
|
||||
- name: macos
|
||||
version: latest
|
||||
os: macos-latest
|
||||
compiler: clang++
|
||||
shell: sh
|
||||
- name: ubuntu
|
||||
version: latest
|
||||
runs-on: ${{ matrix.os.name }}-${{ matrix.os.version }}
|
||||
os: ubuntu-latest
|
||||
compiler: g++
|
||||
shell: sh
|
||||
name: ${{ matrix.program }}-${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.platform.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
if: matrix.os.name == 'ubuntu'
|
||||
- name: Install MSYS2 Dependencies
|
||||
if: matrix.platform.shell == 'msys2 {0}'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.platform.msystem }}
|
||||
install: make mingw-w64-${{ matrix.platform.msys-env }}-toolchain
|
||||
- name: Install Linux Dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update -y -qq
|
||||
sudo apt-get install libsdl2-dev libgtksourceview2.0-dev libgtk2.0-dev libao-dev libopenal-dev
|
||||
- name: "macOS: Import Certificate"
|
||||
if: runner.os == 'macOS'
|
||||
uses: devbotsxyz/import-signing-certificate@2ac4f44d28045073d23153256efbb4c4b2d8aa22 # Don't use rolling branch for security reasons
|
||||
with:
|
||||
certificate-data: ${{ secrets.MACOS_CERTIFICATE_DATA }}
|
||||
certificate-passphrase: ${{ secrets.MACOS_CERTIFICATE_PASSPHRASE }}
|
||||
keychain-name: ares-macos-keychain
|
||||
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
- name: "macOS: recover cache"
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: moltenvk
|
||||
with:
|
||||
path: thirdparty/MoltenVK/MoltenVK
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('thirdparty/MoltenVK/HEAD') }}
|
||||
- name: Install macOS Dependencies
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install make ninja
|
||||
echo "MAKE=gmake" >> $GITHUB_ENV
|
||||
pushd thirdparty/MoltenVK
|
||||
./build-moltenvk.sh
|
||||
popd
|
||||
- name: Make
|
||||
run: make -j4 -C desktop-ui build=optimized local=false lto=true
|
||||
if: runner.os != 'macOS'
|
||||
run: ${MAKE:-make} -j4 -C desktop-ui build=optimized local=false compiler=${{ matrix.platform.compiler }}
|
||||
- name: "macOS: Make universal app"
|
||||
if: runner.os == 'macOS'
|
||||
run: scripts/macos-make-universal.sh
|
||||
env:
|
||||
MAKEFLAGS: -j3
|
||||
MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_CERTIFICATE_NAME }}
|
||||
MACOS_KEYCHAIN_NAME: ares-macos-keychain
|
||||
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
|
||||
- name: "Windows: sign"
|
||||
if: runner.os == 'Windows'
|
||||
uses: DanaBear/code-sign-action@98c79121b376beab8d6a9484f445089db4461bca
|
||||
with:
|
||||
certificate: '${{ secrets.WIN_CERTIFICATE }}'
|
||||
password: '${{ secrets.WIN_PASSWORD }}'
|
||||
certificatesha1: '${{ secrets.WIN_CERTHASH }}'
|
||||
certificatename: '${{ secrets.WIN_CERTNAME }}'
|
||||
folder: 'desktop-ui/out'
|
||||
timestampUrl: 'http://timestamp.digicert.com'
|
||||
- name: "macOS: notarize"
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
ditto -c -k --keepParent desktop-ui/out/ares.app /tmp/ares.zip
|
||||
xcrun notarytool submit /tmp/ares.zip --apple-id "$MACOS_NOTARIZATION_USERNAME" --password "$MACOS_NOTARIZATION_PASSWORD" --team-id "$MACOS_NOTARIZATION_TEAMID" --wait
|
||||
xcrun stapler staple desktop-ui/out/ares.app
|
||||
env:
|
||||
MACOS_NOTARIZATION_USERNAME: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
|
||||
MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
|
||||
MACOS_NOTARIZATION_TEAMID: ${{ secrets.MACOS_NOTARIZATION_TEAMID }}
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.program }}-${{ matrix.os.name }}
|
||||
name: ${{ matrix.program }}-${{ matrix.platform.name }}
|
||||
path: desktop-ui/out/*
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
obj/
|
||||
out/
|
||||
obj-amd64/
|
||||
obj-arm64/
|
||||
out-amd64/
|
||||
out-arm64/
|
||||
@@ -0,0 +1,6 @@
|
||||
targets = all clean install uninstall verbose
|
||||
|
||||
$(targets):
|
||||
@$(MAKE) -C desktop-ui $@
|
||||
|
||||
.PHONY: $(targets)
|
||||
@@ -1,3 +1,4 @@
|
||||
----------------------------------------------------------------------
|
||||
ares
|
||||
|
||||
Copyright (c) 2004-2021 ares team, Near et al
|
||||
@@ -13,3 +14,378 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Stack-less Just-In-Time compiler
|
||||
|
||||
Copyright Zoltan Herczeg ([email protected]). All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
MAME
|
||||
Copyright (c) 1997-2021 MAMEdev and contributors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
libchdr
|
||||
|
||||
Copyright Romain Tisserand
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
LZMA SDK is placed in the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute
|
||||
the original LZMA SDK code, either in source code form or as a compiled binary,
|
||||
for any purpose, commercial or non-commercial, and by any means.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
zlib
|
||||
|
||||
(C) 1995-2017 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
[email protected] [email protected]
|
||||
|
||||
If you use the zlib library in a product, we would appreciate *not* receiving
|
||||
lengthy legal documents to sign. The sources are provided for free but without
|
||||
warranty of any kind. The library has been entirely written by Jean-loup
|
||||
Gailly and Mark Adler; it does not include third-party code.
|
||||
|
||||
If you redistribute modified sources, we would appreciate that you include in
|
||||
the file ChangeLog history information documenting your changes. Please read
|
||||
the FAQ for more information on the distribution of modified source versions.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
xxHash - Extremely Fast Hash algorithm
|
||||
Header File
|
||||
Copyright (C) 2012-2021 Yann Collet
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact the author at:
|
||||
- xxHash homepage: https://www.xxhash.com
|
||||
- xxHash source repository: https://github.com/Cyan4973/xxHash
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
MoltenVK
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
@@ -1,4 +1,4 @@
|
||||
# ares
|
||||

|
||||
|
||||
[](https://github.com/higan-emu/ares/blob/master/LICENSE)
|
||||
|
||||
@@ -9,10 +9,82 @@ Official Releases
|
||||
-----------------
|
||||
|
||||
Official releases are available from
|
||||
[the ares website](https://ares-emulator.github.io).
|
||||
[the ares website](https://ares-emu.net).
|
||||
|
||||
Nightly Builds
|
||||
--------------
|
||||
|
||||
Automated, untested builds of ares are available for Windows and macOS as a [pre-release](https://github.com/higan-emu/ares/releases/tag/nightly).
|
||||
Only the latest nightly build is kept.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
#### *nix building
|
||||
|
||||
g++ make pkg-config libgtk2.0-dev libcanberra-gtk-module libgl-dev libasound2-dev
|
||||
|
||||
By default, GTK2 is used, but support for GTK3 is available. You will need to install the additional package `libgtk-3-dev` as well
|
||||
as specifying the command line option `hiro=gtk3` at compile time.
|
||||
|
||||
#### Windows building
|
||||
|
||||
To build on Windows, using MSYS2 is recommended which can be download [here](https://www.msys2.org/). Follow the instructions
|
||||
on this page to install and setup an appropriate MINGW64 environment. Running the command `pacman -S --needed base-devel mingw-w64-x86_64-toolchain` from the MSYS2 MSYS terminal should setup everything you need to compile Ares. Note that in order
|
||||
to compile, you will want to be in a MINGW64 terminal window after install and setup is complete.
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
Check out the source code by running this command:
|
||||
|
||||
git clone https://github.com/ares-emulator/ares.git
|
||||
|
||||
From the root of the project directory run:
|
||||
|
||||
make -j4 build=release
|
||||
|
||||
that builds with build type of type 'release'.
|
||||
`-j4` indicates number of parallel build processes, and shouldn't be set higher than N-1 cores on your processor. Specifying this option can significantly decrease the time to build this project. There are multiple build types available (debug, etc.). Most additional options can be
|
||||
found in nall's make file (nall/GNUmakefile).
|
||||
|
||||
To start compilation from the beginning, run the following prior to compiling:
|
||||
|
||||
make clean
|
||||
|
||||
Build Output
|
||||
------------
|
||||
|
||||
There is a single binary produced at the end of compilation which can be found in `desktop-ui/out`. On OS's besides Linux, the `Database` & `Shader` directories are copied over here as well. On Linux, running `make install` after compilation will copy these directories and binary into suitable locations (see desktop-ui/GNUmakefile for details). Alternatively, these directories can be copied from `ares/Shaders/*` and `mia/Database/*`.
|
||||
|
||||
|
||||
Command-line options
|
||||
--------------------
|
||||
|
||||
When started from the command-line, ares accepts a few options.
|
||||
|
||||
```
|
||||
Usage: ./ares [options] game
|
||||
|
||||
--help Displays available options and exit
|
||||
--fullscreen Start in full screen mode
|
||||
--system system Specify the system name
|
||||
```
|
||||
|
||||
The --system option is useful when the system type cannot be auto-detected.
|
||||
--fullscreen will only have an effect if a game is also passed in argument.
|
||||
|
||||
Example:
|
||||
`ares --system MSX examples.rom --fullscreen`
|
||||
|
||||
|
||||
High-level Components
|
||||
---------------------
|
||||
|
||||
* __ares__: emulator cores and component implementations
|
||||
* __desktop-ui__: main GUI implementation for this project
|
||||
* __hiro__: cross-platform GUI toolkit that utilizes native APIs on supported platforms
|
||||
* __nall__: Near's alternative to the C++ standard library
|
||||
* __ruby__: interface between a hiro application and platform-specific APIs for emulator video, audio, and input
|
||||
* __mia__: internal ROM database and ROM/image loader
|
||||
* __libco__: cooperative multithreading library
|
||||
@@ -15,6 +15,10 @@ ares.objects := ares ares-fixed-allocator
|
||||
$(object.path)/ares.o: $(ares.path)/ares/ares.cpp
|
||||
$(object.path)/ares-fixed-allocator.o: $(ares.path)/ares/memory/fixed-allocator.cpp
|
||||
|
||||
ifeq ($(platform),macos)
|
||||
ares.dylibs :=
|
||||
endif
|
||||
|
||||
ifeq ($(vulkan),true)
|
||||
flags += -DVULKAN
|
||||
endif
|
||||
@@ -27,6 +31,10 @@ ifeq ($(profile),performance)
|
||||
flags += -DPROFILE_PERFORMANCE
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),a26),)
|
||||
include $(ares.path)/a26/GNUmakefile
|
||||
endif
|
||||
|
||||
ifneq ($(filter $(cores),fc),)
|
||||
include $(ares.path)/fc/GNUmakefile
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
ares.components += mos6502
|
||||
|
||||
ares.objects += ares-a26-system
|
||||
ares.objects += ares-a26-controller
|
||||
ares.objects += ares-a26-cartridge
|
||||
ares.objects += ares-a26-cpu
|
||||
ares.objects += ares-a26-tia
|
||||
ares.objects += ares-a26-riot
|
||||
|
||||
$(object.path)/ares-a26-system.o: $(ares.path)/a26/system/system.cpp
|
||||
$(object.path)/ares-a26-controller.o: $(ares.path)/a26/controller/controller.cpp
|
||||
$(object.path)/ares-a26-cartridge.o: $(ares.path)/a26/cartridge/cartridge.cpp
|
||||
$(object.path)/ares-a26-cpu.o: $(ares.path)/a26/cpu/cpu.cpp
|
||||
$(object.path)/ares-a26-tia.o: $(ares.path)/a26/tia/tia.cpp
|
||||
$(object.path)/ares-a26-riot.o: $(ares.path)/a26/riot/riot.cpp
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <ares/ares.hpp>
|
||||
|
||||
#include <component/processor/mos6502/mos6502.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
#include <ares/inline.hpp>
|
||||
auto enumerate() -> vector<string>;
|
||||
auto load(Node::System& node, string name) -> bool;
|
||||
|
||||
struct Region {
|
||||
static inline auto NTSC() -> bool;
|
||||
static inline auto PAL() -> bool;
|
||||
static inline auto SECAM() -> bool;
|
||||
};
|
||||
|
||||
#include <a26/controller/controller.hpp>
|
||||
#include <a26/system/system.hpp>
|
||||
#include <a26/cartridge/cartridge.hpp>
|
||||
#include <a26/cpu/cpu.hpp>
|
||||
#include <a26/tia/tia.hpp>
|
||||
#include <a26/riot/riot.hpp>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
struct Atari16k : Interface {
|
||||
using Interface::Interface;
|
||||
Memory::Readable<n8> rom;
|
||||
Memory::Writable<n8> ram;
|
||||
n2 bank;
|
||||
bool hasRam;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(rom, "program.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
}
|
||||
|
||||
auto read(n16 address) -> n8 override {
|
||||
if(address == 0x1ff6) bank = 0;
|
||||
if(address == 0x1ff7) bank = 1;
|
||||
if(address == 0x1ff8) bank = 2;
|
||||
if(address == 0x1ff9) bank = 3;
|
||||
|
||||
if(address.bit(12)) {
|
||||
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
|
||||
return rom.read((bank * 0x1000) + (address & 0xfff));
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 data) -> bool override {
|
||||
if(address == 0x1ff6) bank = 0;
|
||||
if(address == 0x1ff7) bank = 1;
|
||||
if(address == 0x1ff8) bank = 2;
|
||||
if(address == 0x1ff9) bank = 3;
|
||||
|
||||
if(address >= 0x1000 && address <= 0x107f) {
|
||||
hasRam = true;
|
||||
ram.write(address & 0x7f, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
bank = 0;
|
||||
hasRam = 0;
|
||||
ram.allocate(128);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(bank);
|
||||
s(hasRam);
|
||||
s(ram);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
struct Atari32k : Interface {
|
||||
using Interface::Interface;
|
||||
Memory::Readable<n8> rom;
|
||||
Memory::Writable<n8> ram;
|
||||
n3 bank;
|
||||
bool hasRam;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(rom, "program.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
}
|
||||
|
||||
auto read(n16 address) -> n8 override {
|
||||
if(address == 0x1ff4) bank = 0;
|
||||
if(address == 0x1ff5) bank = 1;
|
||||
if(address == 0x1ff6) bank = 2;
|
||||
if(address == 0x1ff7) bank = 3;
|
||||
if(address == 0x1ff8) bank = 4;
|
||||
if(address == 0x1ff9) bank = 5;
|
||||
if(address == 0x1ffa) bank = 6;
|
||||
if(address == 0x1ffb) bank = 7;
|
||||
|
||||
if(address.bit(12)) {
|
||||
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
|
||||
return rom.read((bank * 0x1000) + (address & 0xfff));
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 data) -> bool override {
|
||||
if(address == 0x1ff4) bank = 0;
|
||||
if(address == 0x1ff5) bank = 1;
|
||||
if(address == 0x1ff6) bank = 2;
|
||||
if(address == 0x1ff7) bank = 3;
|
||||
if(address == 0x1ff8) bank = 4;
|
||||
if(address == 0x1ff9) bank = 5;
|
||||
if(address == 0x1ffa) bank = 6;
|
||||
if(address == 0x1ffb) bank = 7;
|
||||
|
||||
if(address >= 0x1000 && address <= 0x107f) {
|
||||
hasRam = true;
|
||||
ram.write(address & 0x7f, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
bank = 0;
|
||||
hasRam = 0;
|
||||
ram.allocate(128);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(bank);
|
||||
s(hasRam);
|
||||
s(ram);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
struct Atari8k : Interface {
|
||||
using Interface::Interface;
|
||||
Memory::Readable<n8> rom;
|
||||
Memory::Writable<n8> ram;
|
||||
n1 bank;
|
||||
bool hasRam;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(rom, "program.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
}
|
||||
|
||||
auto read(n16 address) -> n8 override {
|
||||
if(address == 0x1ff8) bank = 0;
|
||||
if(address == 0x1ff9) bank = 1;
|
||||
|
||||
if(address.bit(12)) {
|
||||
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
|
||||
return rom.read((bank * 0x1000) + (address & 0xfff));
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 data) -> bool override {
|
||||
if(address == 0x1ff8) bank = 0;
|
||||
if(address == 0x1ff9) bank = 1;
|
||||
|
||||
if(address >= 0x1000 && address <= 0x107f) {
|
||||
hasRam = true;
|
||||
ram.write(address & 0x7f, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
bank = 0;
|
||||
hasRam = 0;
|
||||
ram.allocate(128);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(bank);
|
||||
s(hasRam);
|
||||
s(ram);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Board {
|
||||
|
||||
#include "linear.cpp"
|
||||
#include "atari8k.cpp"
|
||||
#include "atari16k.cpp"
|
||||
#include "atari32k.cpp"
|
||||
#include "commavid.cpp"
|
||||
#include "parker-bros.cpp"
|
||||
#include "tigervision.cpp"
|
||||
|
||||
auto Interface::load(Memory::Readable<n8>& memory, string name) -> bool {
|
||||
if(auto fp = pak->read(name)) {
|
||||
memory.allocate(fp->size());
|
||||
memory.load(fp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::load(Memory::Writable<n8>& memory, string name) -> bool {
|
||||
if(auto fp = pak->read(name)) {
|
||||
memory.allocate(fp->size());
|
||||
memory.load(fp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::save(Memory::Writable<n8>& memory, string name) -> bool {
|
||||
if(auto fp = pak->write(name)) {
|
||||
memory.save(fp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Board {
|
||||
|
||||
struct Interface {
|
||||
VFS::Pak pak;
|
||||
|
||||
Interface(Cartridge& cartridge) : cartridge(cartridge) {}
|
||||
virtual ~Interface() = default;
|
||||
virtual auto load() -> void {}
|
||||
virtual auto save() -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
virtual auto read(n16 address) -> n8 { return 0xff; }
|
||||
virtual auto write(n16 address, n8 data) -> bool { return false; }
|
||||
virtual auto power() -> void {}
|
||||
virtual auto serialize(serializer&) -> void {}
|
||||
|
||||
auto load(Memory::Readable<n8>&, string name) -> bool;
|
||||
auto load(Memory::Writable<n8>&, string name) -> bool;
|
||||
auto save(Memory::Writable<n8>&, string name) -> bool;
|
||||
|
||||
Cartridge& cartridge;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
struct Commavid : Interface {
|
||||
using Interface::Interface;
|
||||
Memory::Readable<n8> rom;
|
||||
Memory::Writable<n8> ram;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(rom, "program.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
}
|
||||
|
||||
auto read(n16 address) -> n8 override {
|
||||
if(address.bit(12)) {
|
||||
if(address >= 0x1000 && address <= 0x13ff) return ram.read(address & 0x3ff);
|
||||
return rom.read(address & 0xfff);
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 data) -> bool override {
|
||||
if(address >= 0x1400 && address <= 0x17ff) {
|
||||
ram.write(address & 0x3ff, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
ram.allocate(1_KiB);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(ram);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
struct Linear : Interface {
|
||||
using Interface::Interface;
|
||||
Memory::Readable<n8> rom;
|
||||
Memory::Writable<n8> ram;
|
||||
bool hasRam;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(rom, "program.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
}
|
||||
|
||||
auto read(n16 address) -> n8 override {
|
||||
if(address.bit(12)) {
|
||||
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
|
||||
return rom.read(address & 0xfff);
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 data) -> bool override {
|
||||
if(address >= 0x1000 && address <= 0x107f) {
|
||||
hasRam = true;
|
||||
ram.write(address & 0x7f, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
hasRam = 0;
|
||||
ram.allocate(128);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(hasRam);
|
||||
s(ram);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
struct ParkerBros : Interface {
|
||||
using Interface::Interface;
|
||||
Memory::Readable<n8> rom;
|
||||
n3 banks[3];
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(rom, "program.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
}
|
||||
|
||||
auto read(n16 address) -> n8 override {
|
||||
bankswitch(address);
|
||||
|
||||
if(address.bit(12)) {
|
||||
if(address >= 0x1000 && address <= 0x13ff) return rom.read((banks[0] * 0x400) + (address & 0x3ff));
|
||||
if(address >= 0x1400 && address <= 0x17ff) return rom.read((banks[1] * 0x400) + (address & 0x3ff));
|
||||
if(address >= 0x1800 && address <= 0x1bff) return rom.read((banks[2] * 0x400) + (address & 0x3ff));
|
||||
if(address >= 0x1c00 && address <= 0x1fff) return rom.read(( 7 * 0x400) + (address & 0x3ff));
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 data) -> bool override {
|
||||
bankswitch(address);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto bankswitch(n16 address) -> void {
|
||||
if(address >= 0x1fe0 && address <= 0x1fe7) banks[0] = address - 0x1fe0;
|
||||
if(address >= 0x1fe8 && address <= 0x1fef) banks[1] = address - 0x1fe8;
|
||||
if(address >= 0x1ff0 && address <= 0x1ff7) banks[2] = address - 0x1ff0;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
banks[0] = 0;
|
||||
banks[1] = 1;
|
||||
banks[2] = 2;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(banks[0]);
|
||||
s(banks[1]);
|
||||
s(banks[2]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
struct Tigervision : Interface {
|
||||
using Interface::Interface;
|
||||
Memory::Readable<n8> rom;
|
||||
n8 bank;
|
||||
n8 nextBank;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(rom, "program.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
}
|
||||
|
||||
auto read(n16 address) -> n8 override {
|
||||
if(address.bit(12)) {
|
||||
if(bank != nextBank) bank = nextBank;
|
||||
if(address <= 0x17ff) return rom.read((bank * 0x800) + (address & 0x7ff));
|
||||
return rom.read((rom.size() - 0x800) + (address & 0x7ff));
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 data) -> bool override {
|
||||
if(address.bit(12) && bank != nextBank) bank = nextBank;
|
||||
if(address <= 0x3f) nextBank = data;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
bank = 0;
|
||||
nextBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(bank);
|
||||
s(nextBank);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
#include <a26/a26.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
|
||||
Cartridge& cartridge = cartridgeSlot.cartridge;
|
||||
#include "board/board.cpp"
|
||||
#include "slot.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto Cartridge::allocate(Node::Port parent) -> Node::Peripheral {
|
||||
return node = parent->append<Node::Peripheral>(string{system.name(), " Cartridge"});
|
||||
}
|
||||
|
||||
auto Cartridge::connect() -> void {
|
||||
if(!node->setPak(pak = platform->pak(node))) return;
|
||||
|
||||
information = {};
|
||||
information.title = pak->attribute("title");
|
||||
information.region = pak->attribute("region");
|
||||
information.board = pak->attribute("board");
|
||||
|
||||
if(information.board == "Linear") board = new Board::Linear{*this};
|
||||
if(information.board == "Atari8k") board = new Board::Atari8k{*this};
|
||||
if(information.board == "Atari16k") board = new Board::Atari16k{*this};
|
||||
if(information.board == "Atari32k") board = new Board::Atari32k{*this};
|
||||
if(information.board == "Commavid") board = new Board::Commavid{*this};
|
||||
if(information.board == "ParkerBros8k") board = new Board::ParkerBros{*this};
|
||||
if(information.board == "Tigervision") board = new Board::Tigervision{*this};
|
||||
|
||||
if(!board) board = new Board::Interface{*this};
|
||||
board->pak = pak;
|
||||
board->load();
|
||||
power();
|
||||
}
|
||||
|
||||
auto Cartridge::disconnect() -> void {
|
||||
if(!node || !board) return;
|
||||
board->unload();
|
||||
board->pak.reset();
|
||||
board.reset();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
if(!node) return;
|
||||
if(board) board->save();
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
if(board) board->power();
|
||||
}
|
||||
|
||||
auto Cartridge::read(n16 address) -> n8 {
|
||||
if(!node) return 0xff;
|
||||
if(board) return board->read(address);
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto Cartridge::write(n16 address, n8 data) -> bool {
|
||||
if(!node) return false;
|
||||
if(board) return board->write(address, data);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
struct Cartridge;
|
||||
#include "board/board.hpp"
|
||||
|
||||
struct Cartridge {
|
||||
Node::Peripheral node;
|
||||
VFS::Pak pak;
|
||||
|
||||
auto title() const -> string { return information.title; }
|
||||
auto region() const -> string { return information.region; }
|
||||
|
||||
//cartridge.cpp
|
||||
auto allocate(Node::Port) -> Node::Peripheral;
|
||||
auto connect() -> void;
|
||||
auto disconnect() -> void;
|
||||
|
||||
auto save() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto read(n16 address) -> n8;
|
||||
auto write(n16 address, n8 data) -> bool;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
unique_pointer<Board::Interface> board;
|
||||
|
||||
//private:
|
||||
struct Information {
|
||||
string title;
|
||||
string region;
|
||||
string board;
|
||||
} information;
|
||||
};
|
||||
|
||||
#include "slot.hpp"
|
||||
extern Cartridge& cartridge;
|
||||
@@ -0,0 +1,3 @@
|
||||
auto Cartridge::serialize(serializer& s) -> void {
|
||||
if(board) s(*board);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
CartridgeSlot cartridgeSlot{"Cartridge Slot"};
|
||||
|
||||
CartridgeSlot::CartridgeSlot(string name) : name(name) {
|
||||
}
|
||||
|
||||
auto CartridgeSlot::load(Node::Object parent) -> void {
|
||||
port = parent->append<Node::Port>(name);
|
||||
port->setFamily(system.name());
|
||||
port->setType("Cartridge");
|
||||
port->setAllocate([&](auto name) { return cartridge.allocate(port); });
|
||||
port->setConnect([&] { return cartridge.connect(); });
|
||||
port->setDisconnect([&] { return cartridge.disconnect(); });
|
||||
}
|
||||
|
||||
auto CartridgeSlot::unload() -> void {
|
||||
cartridge.disconnect();
|
||||
port = {};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
struct CartridgeSlot {
|
||||
Node::Port port;
|
||||
Cartridge cartridge;
|
||||
|
||||
//slot.cpp
|
||||
CartridgeSlot(string name);
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
const string name;
|
||||
};
|
||||
|
||||
extern CartridgeSlot cartridgeSlot;
|
||||
@@ -0,0 +1,8 @@
|
||||
#include <a26/a26.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
|
||||
#include "port.cpp"
|
||||
#include "gamepad/gamepad.cpp"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
struct Controller {
|
||||
Node::Peripheral node;
|
||||
|
||||
virtual ~Controller() = default;
|
||||
|
||||
virtual auto read() -> n8 { return 0xff; }
|
||||
virtual auto write(n8 data) -> void {}
|
||||
};
|
||||
|
||||
#include "port.hpp"
|
||||
#include "gamepad/gamepad.hpp"
|
||||
@@ -0,0 +1,37 @@
|
||||
Gamepad::Gamepad(Node::Port parent) {
|
||||
node = parent->append<Node::Peripheral>("Gamepad");
|
||||
|
||||
up = node->append<Node::Input::Button>("Up");
|
||||
down = node->append<Node::Input::Button>("Down");
|
||||
left = node->append<Node::Input::Button>("Left");
|
||||
right = node->append<Node::Input::Button>("Right");
|
||||
fire = node->append<Node::Input::Button>("Fire");
|
||||
}
|
||||
|
||||
auto Gamepad::read() -> n8 {
|
||||
platform->input(up);
|
||||
platform->input(down);
|
||||
platform->input(left);
|
||||
platform->input(right);
|
||||
platform->input(fire);
|
||||
|
||||
if(!(up->value() & down->value())) {
|
||||
yHold = 0, upLatch = up->value(), downLatch = down->value();
|
||||
} else if(!yHold) {
|
||||
yHold = 1, swap(upLatch, downLatch);
|
||||
}
|
||||
|
||||
if(!(left->value() & right->value())) {
|
||||
xHold = 0, leftLatch = left->value(), rightLatch = right->value();
|
||||
} else if(!xHold) {
|
||||
xHold = 1, swap(leftLatch, rightLatch);
|
||||
}
|
||||
|
||||
n8 data = 0xff;
|
||||
data.bit(0) = !upLatch;
|
||||
data.bit(1) = !downLatch;
|
||||
data.bit(2) = !leftLatch;
|
||||
data.bit(3) = !rightLatch;
|
||||
data.bit(4) = !fire->value();
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
struct Gamepad : Controller {
|
||||
Node::Input::Button up;
|
||||
Node::Input::Button down;
|
||||
Node::Input::Button left;
|
||||
Node::Input::Button right;
|
||||
Node::Input::Button fire;
|
||||
|
||||
Gamepad(Node::Port);
|
||||
|
||||
auto read() -> n8 override;
|
||||
|
||||
private:
|
||||
b1 yHold;
|
||||
b1 upLatch;
|
||||
b1 downLatch;
|
||||
b1 xHold;
|
||||
b1 leftLatch;
|
||||
b1 rightLatch;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
ControllerPort controllerPort1{"Controller Port 1"};
|
||||
ControllerPort controllerPort2{"Controller Port 2"};
|
||||
|
||||
ControllerPort::ControllerPort(string name) : name(name) {
|
||||
}
|
||||
|
||||
auto ControllerPort::load(Node::Object parent) -> void {
|
||||
port = parent->append<Node::Port>(name);
|
||||
port->setFamily(system.name());
|
||||
port->setType("Controller");
|
||||
port->setHotSwappable(true);
|
||||
port->setAllocate([&](auto name) { return allocate(name); });
|
||||
port->setSupported({"Gamepad"});
|
||||
}
|
||||
|
||||
auto ControllerPort::unload() -> void {
|
||||
device = {};
|
||||
port = {};
|
||||
}
|
||||
|
||||
auto ControllerPort::allocate(string name) -> Node::Peripheral {
|
||||
if(name == "Gamepad") device = new Gamepad(port);
|
||||
if(device) return device->node;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ControllerPort::serialize(serializer& s) -> void {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
struct ControllerPort {
|
||||
Node::Port port;
|
||||
unique_pointer<Controller> device;
|
||||
|
||||
//port.cpp
|
||||
ControllerPort(string name);
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
auto allocate(string name) -> Node::Peripheral;
|
||||
|
||||
auto read() -> n8 { if(device) return device->read(); return 0xff; }
|
||||
auto write(n8 data) -> void { if(device) return device->write(data); }
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
const string name;
|
||||
};
|
||||
|
||||
extern ControllerPort controllerPort1;
|
||||
extern ControllerPort controllerPort2;
|
||||
@@ -0,0 +1,42 @@
|
||||
#include <a26/a26.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
|
||||
CPU cpu;
|
||||
#include "memory.cpp"
|
||||
#include "timing.cpp"
|
||||
#include "debugger.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto CPU::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("CPU");
|
||||
debugger.load(node);
|
||||
}
|
||||
|
||||
auto CPU::unload() -> void {
|
||||
debugger = {};
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
debugger.instruction();
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::step(u32 clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize();
|
||||
}
|
||||
|
||||
auto CPU::power(bool reset) -> void {
|
||||
MOS6502::BCD = 1;
|
||||
MOS6502::power();
|
||||
Thread::create(system.frequency() / 3, {&CPU::main, this});
|
||||
|
||||
PC.byte(0) = readBus(0xfffc);
|
||||
PC.byte(1) = readBus(0xfffd);
|
||||
|
||||
io = {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
struct CPU : MOS6502, Thread {
|
||||
Node::Object node;
|
||||
|
||||
struct Debugger {
|
||||
//debugger.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto instruction() -> void;
|
||||
|
||||
struct Tracer {
|
||||
Node::Debugger::Tracer::Instruction instruction;
|
||||
} tracer;
|
||||
} debugger;
|
||||
|
||||
//cpu.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto step(u32 clocks) -> void;
|
||||
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto readBus(n16 address) -> n8;
|
||||
auto writeBus(n16 address, n8 data) -> void;
|
||||
|
||||
auto readIO(n16 address) -> n8;
|
||||
auto writeIO(n16 address, n8 data) -> void;
|
||||
|
||||
auto readDebugger(n16 address) -> n8 override;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//timing.cpp
|
||||
auto read(n16 address) -> n8 override;
|
||||
auto write(n16 address, n8 data) -> void override;
|
||||
auto lastCycle() -> void override;
|
||||
auto nmi(n16& vector) -> void override;
|
||||
|
||||
auto rdyLine(bool) -> void;
|
||||
|
||||
//protected:
|
||||
struct IO {
|
||||
n1 rdyLine = 1;
|
||||
} io;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
@@ -0,0 +1,12 @@
|
||||
auto CPU::Debugger::load(Node::Object parent) -> void {
|
||||
tracer.instruction = parent->append<Node::Debugger::Tracer::Instruction>("Instruction", "CPU");
|
||||
tracer.instruction->setAddressBits(16);
|
||||
}
|
||||
|
||||
auto CPU::Debugger::instruction() -> void {
|
||||
if(tracer.instruction->enabled() && tracer.instruction->address(cpu.PC)) {
|
||||
tracer.instruction->notify(cpu.disassembleInstruction(), cpu.disassembleContext());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
inline auto CPU::readBus(n16 address) -> n8 {
|
||||
address &= 0x1fff;
|
||||
n8 data = cartridge.read(address);
|
||||
|
||||
if(address.bit(12) == 0 && address.bit(7) == 0) {
|
||||
return tia.read(address & 0xf);
|
||||
}
|
||||
|
||||
if(address.bit(12) == 0 && address.bit(9) == 0 && address.bit(7) == 1) {
|
||||
return riot.readRam(address & 0x7f);
|
||||
}
|
||||
|
||||
if(address.bit(12) == 0 && address.bit(9) == 1 && address.bit(7) == 1) {
|
||||
return riot.readIo((address - 0x80) & 0x1f);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
inline auto CPU::writeBus(n16 address, n8 data) -> void {
|
||||
address &= 0x1fff;
|
||||
cartridge.write(address, data);
|
||||
|
||||
if(address.bit(12) == 0 && address.bit(7) == 0) {
|
||||
return tia.write(address & 0x3f, data);
|
||||
}
|
||||
|
||||
if(address.bit(12) == 0 && address.bit(9) == 0 && address.bit(7) == 1) {
|
||||
return riot.writeRam(address & 0x7f, data);
|
||||
}
|
||||
|
||||
if(address.bit(12) == 0 && address.bit(9) == 1 && address.bit(7) == 1) {
|
||||
return riot.writeIo((address - 0x80) & 0x1f, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto CPU::readDebugger(n16 address) -> n8 {
|
||||
return readBus(address);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
auto CPU::serialize(serializer& s) -> void {
|
||||
MOS6502::serialize(s);
|
||||
Thread::serialize(s);
|
||||
s(io.rdyLine);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
auto CPU::read(n16 address) -> n8 {
|
||||
if(io.rdyLine == 0) {
|
||||
while (io.rdyLine == 0) {
|
||||
step(1);
|
||||
}
|
||||
|
||||
MDR = readBus(address);
|
||||
return MDR;
|
||||
}
|
||||
|
||||
step(1);
|
||||
MDR = readBus(address);
|
||||
return MDR;
|
||||
}
|
||||
|
||||
auto CPU::write(n16 address, n8 data) -> void {
|
||||
step(1);
|
||||
writeBus(address, MDR = data);
|
||||
}
|
||||
|
||||
auto CPU::lastCycle() -> void {
|
||||
}
|
||||
|
||||
auto CPU::nmi(n16& vector) -> void {
|
||||
|
||||
}
|
||||
|
||||
auto CPU::rdyLine(bool line) -> void {
|
||||
io.rdyLine = line;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
auto RIOT::readRam(n8 address) -> n8 {
|
||||
return ram[address];
|
||||
}
|
||||
|
||||
auto RIOT::writeRam(n8 address, n8 data) -> void {
|
||||
ram[address] = data;
|
||||
}
|
||||
|
||||
auto RIOT::readIo(n8 address) -> n8 {
|
||||
switch(address) {
|
||||
case 0x00: return readPortA();
|
||||
case 0x01: return port[0].direction;
|
||||
case 0x02: return readPortB();
|
||||
case 0x03: return port[1].direction;
|
||||
case 0x04: return timer.counter;
|
||||
}
|
||||
|
||||
debug(unimplemented, "[RIOT] IO read ", hex(address));
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto RIOT::writeIo(n8 address, n8 data) -> void {
|
||||
switch(address) {
|
||||
case 0x00: writePortA(data); return;
|
||||
case 0x01: port[0].direction = data; return;
|
||||
case 0x02: writePortB(data); return;
|
||||
case 0x03: port[1].direction = data; return;
|
||||
case 0x14: case 0x15: case 0x16: case 0x17: {
|
||||
const n16 intervals[] = {1, 8, 64, 1024};
|
||||
timer.counter = data;
|
||||
timer.interval = intervals[address - 0x14];
|
||||
timer.reload = timer.interval;
|
||||
return; }
|
||||
}
|
||||
|
||||
debug(unimplemented, "[RIOT] IO write ",hex(address), " = ", hex(data));
|
||||
}
|
||||
|
||||
auto RIOT::readPortA() -> n8 {
|
||||
n8 data;
|
||||
// TODO: Handle when configured as outputs
|
||||
data.bit(0, 3) = controllerPort2.read().bit(0, 4);
|
||||
data.bit(4, 7) = controllerPort1.read().bit(0, 4);
|
||||
return data;
|
||||
}
|
||||
|
||||
auto RIOT::writePortA(n8 data) -> void {
|
||||
port[0].data = data;
|
||||
}
|
||||
|
||||
auto RIOT::readPortB() -> n8 {
|
||||
system.controls.poll();
|
||||
|
||||
n8 data;
|
||||
|
||||
for(auto n : range(8)) if(port[1].direction.bit(n)) { data.bit(n) = port[1].data.bit(n); }
|
||||
|
||||
// Atari hard-wires these bits as input
|
||||
data.bit(0) = !system.controls.reset->value();
|
||||
data.bit(1) = !system.controls.select->value();
|
||||
|
||||
// These are toggle switches; flip the values on the rising edge
|
||||
if(system.controls.tvType->value() && !tvTypeLatch) {
|
||||
tvType = !tvType;
|
||||
}
|
||||
tvTypeLatch = system.controls.tvType->value();
|
||||
|
||||
if(system.controls.leftDifficulty->value() && !leftDifficultyLatch) {
|
||||
leftDifficulty = !leftDifficulty;
|
||||
}
|
||||
leftDifficultyLatch = system.controls.leftDifficulty->value();
|
||||
|
||||
if(system.controls.rightDifficulty->value() && !rightDifficultyLatch) {
|
||||
rightDifficulty = !rightDifficulty;
|
||||
}
|
||||
rightDifficultyLatch = system.controls.rightDifficulty->value();
|
||||
|
||||
data.bit(3) = tvType;
|
||||
data.bit(6) = leftDifficulty;
|
||||
data.bit(7) = rightDifficulty;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
auto RIOT::writePortB(n8 data) -> void {
|
||||
port[1].data = data;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <a26/a26.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
|
||||
RIOT riot;
|
||||
|
||||
#include "io.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto RIOT::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("RIOT");
|
||||
|
||||
ram.allocate(128);
|
||||
}
|
||||
|
||||
auto RIOT::unload() -> void {
|
||||
ram.reset();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto RIOT::main() -> void {
|
||||
decrementTimer();
|
||||
step(1);
|
||||
}
|
||||
|
||||
auto RIOT::decrementTimer() -> void {
|
||||
if(--timer.interval == 0) {
|
||||
if(--timer.counter == 0xff) {
|
||||
timer.reload = 1;
|
||||
}
|
||||
|
||||
timer.interval = timer.reload;
|
||||
}
|
||||
}
|
||||
|
||||
auto RIOT::step(u32 clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize();
|
||||
}
|
||||
|
||||
auto RIOT::power(bool reset) -> void {
|
||||
Thread::create(system.frequency() / 3, {&RIOT::main, this});
|
||||
timer = {};
|
||||
port[0] = {.data = 0xff};
|
||||
port[1] = {.data = 0xff};
|
||||
leftDifficulty = 1;
|
||||
rightDifficulty = 1;
|
||||
tvType = 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
struct RIOT : Thread {
|
||||
Node::Object node;
|
||||
Memory::Writable<n8> ram;
|
||||
|
||||
//riot.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto step(u32 clocks) -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
auto decrementTimer() -> void;
|
||||
|
||||
//io.cpp
|
||||
auto readRam(n8 address) -> n8;
|
||||
auto writeRam(n8 address, n8 data) -> void;
|
||||
auto readIo(n8 address) -> n8;
|
||||
auto writeIo(n8 address, n8 data) -> void;
|
||||
|
||||
auto readPortA() -> n8;
|
||||
auto readPortB() -> n8;
|
||||
auto writePortA(n8 data) -> void;
|
||||
auto writePortB(n8 data) -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct {
|
||||
u8 counter;
|
||||
n16 interval;
|
||||
n16 reload;
|
||||
} timer;
|
||||
|
||||
struct Port {
|
||||
n8 data;
|
||||
n8 direction;
|
||||
} port[2];
|
||||
|
||||
n1 leftDifficulty;
|
||||
n1 leftDifficultyLatch;
|
||||
n1 rightDifficulty;
|
||||
n1 rightDifficultyLatch;
|
||||
n1 tvType;
|
||||
n1 tvTypeLatch;
|
||||
};
|
||||
|
||||
extern RIOT riot;
|
||||
@@ -0,0 +1,21 @@
|
||||
//serialization.cpp
|
||||
auto RIOT::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
s(ram);
|
||||
s(timer.counter);
|
||||
s(timer.interval);
|
||||
s(timer.reload);
|
||||
|
||||
for(auto n : range(2)) {
|
||||
s(port[n].data);
|
||||
s(port[n].direction);
|
||||
}
|
||||
|
||||
s(leftDifficulty);
|
||||
s(leftDifficultyLatch);
|
||||
s(rightDifficulty);
|
||||
s(rightDifficultyLatch);
|
||||
s(tvType);
|
||||
s(tvTypeLatch);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
auto System::Controls::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("Controls");
|
||||
|
||||
reset = node->append<Node::Input::Button>("Reset");
|
||||
select = node->append<Node::Input::Button>("Select");
|
||||
leftDifficulty = node->append<Node::Input::Button>("Left Difficulty");
|
||||
rightDifficulty = node->append<Node::Input::Button>("Right Difficulty");
|
||||
tvType = node->append<Node::Input::Button>("TV Type");
|
||||
}
|
||||
|
||||
auto System::Controls::poll() -> void {
|
||||
platform->input(reset);
|
||||
platform->input(select);
|
||||
platform->input(leftDifficulty);
|
||||
platform->input(rightDifficulty);
|
||||
platform->input(tvType);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
serializer s;
|
||||
|
||||
u32 signature = SerializerSignature;
|
||||
char version[16] = {};
|
||||
char description[512] = {};
|
||||
memory::copy(&version, (const char*)SerializerVersion, SerializerVersion.size());
|
||||
|
||||
s(signature);
|
||||
s(synchronize);
|
||||
s(version);
|
||||
s(description);
|
||||
|
||||
serialize(s, synchronize);
|
||||
return s;
|
||||
}
|
||||
|
||||
auto System::unserialize(serializer& s) -> bool {
|
||||
u32 signature = 0;
|
||||
bool synchronize = true;
|
||||
char version[16] = {};
|
||||
char description[512] = {};
|
||||
|
||||
s(signature);
|
||||
s(synchronize);
|
||||
s(version);
|
||||
s(description);
|
||||
|
||||
if(signature != SerializerSignature) return false;
|
||||
if(string{version} != SerializerVersion) return false;
|
||||
|
||||
if(synchronize) power(/* reset = */ false);
|
||||
serialize(s, synchronize);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s, bool synchronize) -> void {
|
||||
scheduler.setSynchronize(synchronize);
|
||||
s(cartridge);
|
||||
s(cpu);
|
||||
s(tia);
|
||||
s(riot);
|
||||
s(controllerPort1);
|
||||
s(controllerPort2);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#include <a26/a26.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
|
||||
auto enumerate() -> vector<string> {
|
||||
return {
|
||||
"[Atari] Atari 2600 (NTSC)",
|
||||
"[Atari] Atari 2600 (PAL)",
|
||||
"[Atari] Atari 2600 (SECAM)",
|
||||
};
|
||||
}
|
||||
|
||||
auto load(Node::System& node, string name) -> bool {
|
||||
if(!enumerate().find(name)) return false;
|
||||
return system.load(node, name);
|
||||
}
|
||||
|
||||
Random random;
|
||||
Scheduler scheduler;
|
||||
System system;
|
||||
#include "controls.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto System::game() -> string {
|
||||
if(cartridge.node) {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
return "(no cartridge connected)";
|
||||
}
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.enter();
|
||||
}
|
||||
|
||||
auto System::load(Node::System& root, string name) -> bool {
|
||||
if(node) unload();
|
||||
|
||||
information = {};
|
||||
if(name.find("NTSC")) {
|
||||
information.name = "Atari 2600";
|
||||
information.region = Region::NTSC;
|
||||
information.frequency = 3579546;
|
||||
}
|
||||
if(name.find("PAL")) {
|
||||
information.name = "Atari 2600";
|
||||
information.region = Region::PAL;
|
||||
information.frequency = 3546894;
|
||||
}
|
||||
if(name.find("SECAM")) {
|
||||
information.name = "Atari 2600";
|
||||
information.region = Region::SECAM;
|
||||
information.frequency = 3546894;
|
||||
}
|
||||
|
||||
node = Node::System::create(information.name);
|
||||
node->setGame({&System::game, this});
|
||||
node->setRun({&System::run, this});
|
||||
node->setPower({&System::power, this});
|
||||
node->setSave({&System::save, this});
|
||||
node->setUnload({&System::unload, this});
|
||||
node->setSerialize({&System::serialize, this});
|
||||
node->setUnserialize({&System::unserialize, this});
|
||||
root = node;
|
||||
|
||||
scheduler.reset();
|
||||
controls.load(node);
|
||||
riot.load(node);
|
||||
cpu.load(node);
|
||||
tia.load(node);
|
||||
cartridgeSlot.load(node);
|
||||
controllerPort1.load(node);
|
||||
controllerPort2.load(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
if(!node) return;
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!node) return;
|
||||
save();
|
||||
riot.unload();
|
||||
cpu.unload();
|
||||
tia.unload();
|
||||
cartridgeSlot.unload();
|
||||
controllerPort1.unload();
|
||||
controllerPort2.unload();
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto System::power(bool reset) -> void {
|
||||
for(auto& setting : node->find<Node::Setting::Setting>()) setting->setLatch();
|
||||
|
||||
random.entropy(Random::Entropy::Low);
|
||||
cartridge.power();
|
||||
riot.power(reset);
|
||||
cpu.power(reset);
|
||||
tia.power(reset);
|
||||
scheduler.power(cpu);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
extern Random random;
|
||||
|
||||
struct System {
|
||||
Node::System node;
|
||||
|
||||
struct Controls {
|
||||
Node::Object node;
|
||||
Node::Input::Button reset;
|
||||
Node::Input::Button select;
|
||||
Node::Input::Button leftDifficulty;
|
||||
Node::Input::Button rightDifficulty;
|
||||
Node::Input::Button tvType;
|
||||
|
||||
//controls.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto poll() -> void;
|
||||
} controls;
|
||||
|
||||
enum class Region : u32 { NTSC, PAL, SECAM };
|
||||
|
||||
auto name() const -> string { return information.name; }
|
||||
auto region() const -> Region { return information.region; }
|
||||
auto frequency() const -> u32 { return information.frequency; }
|
||||
|
||||
//system.cpp
|
||||
auto game() -> string;
|
||||
auto run() -> void;
|
||||
|
||||
auto load(Node::System& node, string name) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(bool synchronize) -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
string name = "Atari 2600";
|
||||
Region region = Region::NTSC;
|
||||
u32 frequency = Constants::Colorburst::NTSC;
|
||||
} information;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&, bool synchronize) -> void;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
|
||||
auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; }
|
||||
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
|
||||
auto Region::SECAM() -> bool { return system.region() == System::Region::SECAM; }
|
||||
@@ -0,0 +1,64 @@
|
||||
auto TIA::runAudio() -> void {
|
||||
if(io.hcounter == 9 || io.hcounter == 81) {
|
||||
audio[0].phase0();
|
||||
audio[1].phase0();
|
||||
}
|
||||
|
||||
if(io.hcounter == 37 || io.hcounter == 149) {
|
||||
auto a = audio[0].phase1();
|
||||
auto b = audio[1].phase1();
|
||||
|
||||
double output = 0;
|
||||
output += volume[a];
|
||||
output += volume[b];
|
||||
|
||||
stream->frame(output / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
auto TIA::AudioChannel::phase0() -> void {
|
||||
if (enable) {
|
||||
switch (control.bit(0, 1)) {
|
||||
case 0x00: case 0x01: pulseCounterPaused = 0; break;
|
||||
case 0x02: pulseCounterPaused = noiseCounter.bit(1, 4) != 1; break;
|
||||
case 0x03: pulseCounterPaused = !noiseCounter.bit(0); break;
|
||||
}
|
||||
|
||||
noiseFeedback = (noiseCounter.bit(2) ^ noiseCounter.bit(0)) || noiseCounter == 0;
|
||||
if(control.bit(0, 2) == 0) {
|
||||
noiseFeedback = !(control.bit(2, 3)) ||
|
||||
!(noiseCounter || (pulseCounter != 0x0a)) ||
|
||||
(pulseCounter.bit(0) ^ noiseCounter.bit(0));
|
||||
}
|
||||
}
|
||||
|
||||
enable = divCounter == frequency;
|
||||
if (divCounter == frequency || divCounter == 0x1f) {
|
||||
divCounter = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
divCounter++;
|
||||
}
|
||||
|
||||
auto TIA::AudioChannel::phase1() -> u8 {
|
||||
if(!enable) return pulseCounter.bit(0) * volume;
|
||||
|
||||
pulseFeedback = 0;
|
||||
switch(control.bit(2, 3)) {
|
||||
case 0x00: pulseFeedback = pulseCounter != 0x0a && control.bit(0, 2) && (pulseCounter.bit(1) ^ pulseCounter.bit(0)); break;
|
||||
case 0x01: pulseFeedback = !pulseCounter.bit(3); ; break;
|
||||
case 0x02: pulseFeedback = !noiseCounter.bit(0); ; break;
|
||||
case 0x03: pulseFeedback = !(pulseCounter.bit(1) || !pulseCounter.bit(1, 3)); ; break;
|
||||
}
|
||||
|
||||
noiseCounter >>= 1;
|
||||
if (noiseFeedback) noiseCounter.bit(4) = 1;
|
||||
|
||||
if(!pulseCounterPaused) {
|
||||
pulseCounter = ~(pulseCounter >> 1) & 7;
|
||||
if (pulseFeedback) pulseCounter.bit(3) = 1;
|
||||
}
|
||||
|
||||
return pulseCounter.bit(0) * volume;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
auto TIA::color(n32 color) -> n64 {
|
||||
u64 ntscColors[128] = {
|
||||
0x0000'0000'0000ull, 0x4040'4040'4040ull, 0x6c6c'6c6c'6c6cull, 0x9090'9090'9090ull, 0xb0b0'b0b0'b0b0ull, 0xc8c8'c8c8'c8c8ull, 0xdcdc'dcdc'dcdcull, 0xecec'ecec'ececull,
|
||||
0x4444'4444'0000ull, 0x6464'6464'1010ull, 0x8484'8484'2424ull, 0xa0a0'a0a0'3434ull, 0xb8b8'b8b8'4040ull, 0xd0d0'd0d0'5050ull, 0xe8e8'e8e8'5c5cull, 0xfcfc'fcfc'6868ull,
|
||||
0x7070'2828'0000ull, 0x8484'4444'1414ull, 0x9898'5c5c'2828ull, 0xacac'7878'3c4cull, 0xbcbc'8c8c'4c4cull, 0xcccc'a0a0'5c5cull, 0xdcdc'b4b4'6868ull, 0xecec'c8c8'7878ull,
|
||||
0x8484'1818'0000ull, 0x9898'3434'1818ull, 0xacac'5050'3030ull, 0xc0c0'6868'4848ull, 0xd0d0'8080'5c5cull, 0xe0e0'9494'7070ull, 0xecec'a8a8'8080ull, 0xfcfc'bcbc'9494ull,
|
||||
0x8888'0000'0000ull, 0x9c9c'2020'2020ull, 0xb0b0'3c3c'3c3cull, 0xc0c0'5858'5858ull, 0xd0d0'7070'7070ull, 0xe0e0'8888'8888ull, 0xecec'a0a0'a0a0ull, 0xfcfc'b4b4'b4b4ull,
|
||||
0x7878'0000'5c5cull, 0x8c8c'2020'7474ull, 0xa0a0'3c3c'8888ull, 0xb0b0'5858'9c9cull, 0xc0c0'7070'b0b0ull, 0xd0d0'8484'c0c0ull, 0xdcdc'9c9c'd0d0ull, 0xecec'b0b0'e0e0ull,
|
||||
0x4848'0000'7878ull, 0x6060'2020'9090ull, 0x7878'3c3c'a4a4ull, 0x8c8c'5858'b8b8ull, 0xa0a0'7070'ccccull, 0xb4b4'8484'dcdcull, 0xc4c4'9c9c'ececull, 0xd4d4'b0b0'fcfcull,
|
||||
0x1414'0000'8484ull, 0x3030'2020'9898ull, 0x4c4c'3c3c'acacull, 0x6868'5858'c0c0ull, 0x7c7c'7070'd0d0ull, 0x9494'8888'e0e0ull, 0xa8a8'a0a0'ececull, 0xbcbc'b4b4'fcfcull,
|
||||
0x0000'0000'8888ull, 0x1c1c'2020'9c9cull, 0x3838'4040'b0b0ull, 0x5050'5c5c'c0c0ull, 0x6868'7474'd0d0ull, 0x7c7c'8c8c'e0e0ull, 0x9090'a4a4'ececull, 0xa4a4'b8b8'fcfcull,
|
||||
0x0000'1818'7c7cull, 0x1c1c'3838'9090ull, 0x3838'5454'a8a8ull, 0x5050'7070'bcbcull, 0x6868'8888'ccccull, 0x7c7c'9c9c'dcdcull, 0x9090'b4b4'ececull, 0xa4a4'c8c8'fcfcull,
|
||||
0x0000'2c2c'5c5cull, 0x1c1c'4c4c'7878ull, 0x3838'6868'9090ull, 0x5050'8484'acacull, 0x6868'9c9c'c0c0ull, 0x7c7c'b4b4'd4d4ull, 0x9090'cccc'e8e8ull, 0xa4a4'e0e0'fcfcull,
|
||||
0x0000'3c3c'2c2cull, 0x1c1c'5c5c'4848ull, 0x3838'7c7c'6464ull, 0x5050'9c9c'8080ull, 0x6868'b4b4'9494ull, 0x7c7c'd0d0'acacull, 0x9090'e4e4'c0c0ull, 0xa4a4'fcfc'd4d4ull,
|
||||
0x0000'3c3c'0000ull, 0x2020'5c5c'2020ull, 0x4040'7c7c'4040ull, 0x5c5c'9c9c'5c5cull, 0x7474'b4b4'7474ull, 0x8c8c'd0d0'8c8cull, 0xa4a4'e4e4'a4a4ull, 0xb8b8'fcfc'b8b8ull,
|
||||
0x1414'3838'0000ull, 0x3434'5c5c'1c1cull, 0x5050'7c7c'3838ull, 0x6c6c'9898'5050ull, 0x8484'b4b4'6868ull, 0x9c9c'cccc'7c7cull, 0xb4b4'e4e4'9090ull, 0xc8c8'fcfc'a4a4ull,
|
||||
0x2c2c'3030'0000ull, 0x4c4c'5050'1c1cull, 0x6868'7070'3434ull, 0x8484'8c8c'4c4cull, 0x9c9c'a8a8'6464ull, 0xb4b4'c0c0'7878ull, 0xcccc'd4d4'8888ull, 0xe0e0'ecec'9c9cull,
|
||||
0x4444'2828'0000ull, 0x6464'4848'1818ull, 0x8484'6868'3030ull, 0xa0a0'8484'4444ull, 0xb8b8'9c9c'5858ull, 0xd0d0'b4b4'6c6cull, 0x8e8c'cccc'7c7cull, 0xfcfc'c0c0'8c8cull,
|
||||
};
|
||||
|
||||
u64 palColors[128] = {
|
||||
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
|
||||
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
|
||||
0x8080'5858'0000ull, 0x9494'7070'2020ull, 0xa8a8'8484'3c3cull, 0xbcbc'9c9c'5858ull, 0xcccc'acac'7070ull, 0xdcdc'c0c0'8484ull, 0xecec'd0d0'9c9cull, 0xfcfc'e0e0'b0b0ull,
|
||||
0x4444'5c5c'0000ull, 0x5c5c'7878'2020ull, 0x7474'9090'3c3cull, 0x8c8c'acac'5858ull, 0xa0a0'c0c0'7070ull, 0xb0b0'd4d4'8484ull, 0xc4c4'e8e8'9c9cull, 0xd4d4'fcfc'b0b0ull,
|
||||
0x7070'3434'0000ull, 0x8888'5050'2020ull, 0xa0a0'6868'3c3cull, 0xb4b4'8484'5858ull, 0xc8c8'9898'7070ull, 0xdcdc'acac'8484ull, 0xecec'c0c0'9c9cull, 0xfcfc'd4d4'b0b0ull,
|
||||
0x0000'6464'1414ull, 0x2020'8080'3434ull, 0x3c3c'9898'5050ull, 0x5858'b0b0'6c6cull, 0x7070'c4c4'8484ull, 0x8484'd8d8'9c9cull, 0x9c9c'e8e8'b4b4ull, 0xb0b0'fcfc'c8c8ull,
|
||||
0x7070'0000'1414ull, 0x8888'2020'3434ull, 0xa0a0'3c3c'5050ull, 0xb4b4'5858'6c6cull, 0xc8c8'7070'8484ull, 0xdcdc'8484'9c9cull, 0xecec'9c9c'b4b4ull, 0xfcfc'b0b0'c8c8ull,
|
||||
0x0000'5c5c'5c5cull, 0x2020'7474'7474ull, 0x3c3c'8c8c'8c8cull, 0x5858'a4a4'a4a4ull, 0x7070'b8b8'b8b8ull, 0x8484'c8c8'c8c8ull, 0x9c9c'dcdc'dcdcull, 0xb0b0'ecec'ececull,
|
||||
0x7070'0000'5c5cull, 0x8484'2020'7474ull, 0x9494'3c3c'8888ull, 0xa8a8'5858'9c9cull, 0xb4b4'7070'b0b0ull, 0xc4c4'8484'c0c0ull, 0xd0d0'9c9c'd0d0ull, 0xe0e0'b0b0'e0e0ull,
|
||||
0x0000'3c3c'7070ull, 0x1c1c'5858'8888ull, 0x3838'7474'a0a0ull, 0x5050'8c8c'b4b4ull, 0x6868'a4a4'c8c8ull, 0x7c7c'b8b8'dcdcull, 0x9090'cccc'ececull, 0xa4a4'e0e0'fcfcull,
|
||||
0x5858'0000'7070ull, 0x6c6c'2020'8888ull, 0x8080'3c3c'a0a0ull, 0x9494'5858'b4b4ull, 0xa4a4'7070'c8c8ull, 0xb4b4'8484'dcdcull, 0xc4c4'9c9c'ececull, 0xd4d4'b0b0'fcfcull,
|
||||
0x0000'2020'7070ull, 0x1c1c'3c3c'8888ull, 0x3838'5858'a0a0ull, 0x5050'7474'b4b4ull, 0x6868'8888'c8c8ull, 0x7c7c'a0a0'dcdcull, 0x9090'b4b4'ececull, 0xa4a4'c8c8'fcfcull,
|
||||
0x3c3c'0000'8080ull, 0x5454'2020'9494ull, 0x6c6c'3c3c'a8a8ull, 0x8080'5858'bcbcull, 0x9494'7070'ccccull, 0xa8a8'8484'dcdcull, 0xb8b8'9c9c'ececull, 0xc8c8'b0b0'fcfcull,
|
||||
0x0000'0000'8888ull, 0x2020'2020'9c9cull, 0x3c3c'3c3c'b0b0ull, 0x5858'5858'c0c0ull, 0x7070'7070'd0d0ull, 0x8484'8484'e0e0ull, 0x9c9c'9c9c'ececull, 0xb0b0'b0b0'fcfcull,
|
||||
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
|
||||
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
|
||||
};
|
||||
|
||||
return Region::PAL() ? palColors[color & 0x7f] : ntscColors[color & 0x7f];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
auto TIA::read(n8 address) -> n8 {
|
||||
n8 value;
|
||||
|
||||
switch(address) {
|
||||
case 0x00: value.bit(7) = collision.M0P1; value.bit(6) = collision.M0P0; return value; // CXM0P
|
||||
case 0x01: value.bit(7) = collision.M1P0; value.bit(6) = collision.M1P1; return value; // CXM1P
|
||||
case 0x02: value.bit(7) = collision.P0PF; value.bit(6) = collision.P0BL; return value; // CX0FB
|
||||
case 0x03: value.bit(7) = collision.P1PF; value.bit(6) = collision.P1BL; return value; // CX1FB
|
||||
case 0x04: value.bit(7) = collision.M0PF; value.bit(6) = collision.M0BL; return value; // CXM0FB
|
||||
case 0x05: value.bit(7) = collision.M1PF; value.bit(6) = collision.M1BL; return value; // CXM1FB
|
||||
case 0x06: value.bit(7) = collision.BLPF; return value; // CXBLPF
|
||||
case 0x07: value.bit(7) = collision.P0P1; value.bit(6) = collision.M0M1; return value; // CXPPMM
|
||||
case 0x0c: value.bit(7) = controllerPort1.read().bit(4); return value; // INPT4
|
||||
case 0x0d: value.bit(7) = controllerPort2.read().bit(4); return value; // INPT5
|
||||
}
|
||||
|
||||
debug(unimplemented, "[TIA] read ", hex(address));
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto TIA::write(n8 address, n8 data) -> void {
|
||||
switch(address) {
|
||||
case 0x00: vsync(data.bit(1)); return; // VSYNC
|
||||
case 0x01: writeQueue.add(address, data, 1); return; // VBLANK
|
||||
case 0x02: cpu.rdyLine(0); return; // WSYNC
|
||||
case 0x03: io.hcounter = 0; return; // RSYNC
|
||||
case 0x04: player[0].size = data.bit(0,2); missile[0].size = data.bit(4, 5); return; // NUSIZ1
|
||||
case 0x05: player[1].size = data.bit(0,2); missile[1].size = data.bit(4, 5); return; // NUSIZ1
|
||||
case 0x06: io.p0Color = data.bit(1, 7); return; // COLUP0
|
||||
case 0x07: io.p1Color = data.bit(1, 7); return; // COLUP1
|
||||
case 0x08: io.fgColor = data.bit(1, 7); return; // COLUPF
|
||||
case 0x09: io.bgColor = data.bit(1, 7); return; // COLUBK
|
||||
case 0x0a: ctrlpf(data); return; // CTRLPF
|
||||
case 0x0b: writeQueue.add(address, data, 1); return; // REFP0
|
||||
case 0x0c: writeQueue.add(address, data, 1); return; // REFP1
|
||||
case 0x0d: writeQueue.add(address, data, 6); return; // PF0
|
||||
case 0x0e: writeQueue.add(address, data, 6); return; // PF1
|
||||
case 0x0f: writeQueue.add(address, data, 6); return; // PF2
|
||||
case 0x10: resp(0); return; // RESP0
|
||||
case 0x11: resp(1); return; // RESP1
|
||||
case 0x12: resm(0); return; // RESM0
|
||||
case 0x13: resm(1); return; // RESM1
|
||||
case 0x14: resbl(); return; // RESBL
|
||||
case 0x15: audio[0].control = data; return; // AUDC0
|
||||
case 0x16: audio[1].control = data; return; // AUDC1
|
||||
case 0x17: audio[0].frequency = data; return; // AUDF0
|
||||
case 0x18: audio[1].frequency = data; return; // AUDF1
|
||||
case 0x19: audio[0].volume = data; return; // AUDV0
|
||||
case 0x1a: audio[1].volume = data; return; // AUDV1
|
||||
case 0x1b: writeQueue.add(address, data, 3); return; // GRP0
|
||||
case 0x1c: writeQueue.add(address, data, 3); return; // GRP1
|
||||
case 0x1d: writeQueue.add(address, data, 1); return; // ENAM0
|
||||
case 0x1e: writeQueue.add(address, data, 1); return; // ENAM1
|
||||
case 0x1f: writeQueue.add(address, data, 1); return; // ENAB1
|
||||
case 0x20: writeQueue.add(address, data, 2); return; // HMP0
|
||||
case 0x21: writeQueue.add(address, data, 2); return; // HMP1
|
||||
case 0x22: writeQueue.add(address, data, 2); return; // HMM0
|
||||
case 0x23: writeQueue.add(address, data, 2); return; // HMM1
|
||||
case 0x24: writeQueue.add(address, data, 2); return; // HMBL
|
||||
case 0x25: player[0].delay = data.bit(0); return; // VDELP0
|
||||
case 0x26: player[1].delay = data.bit(0); return; // VDELP1
|
||||
case 0x27: ball.delay = data.bit(0); return; // VDELBL
|
||||
case 0x28: resmp(0, data); return; // RESMP0
|
||||
case 0x29: resmp(1, data); return; // RESMP1
|
||||
case 0x2a: writeQueue.add(address, data, 6); return; // HMOVE
|
||||
case 0x2b: writeQueue.add(address, data, 2); return; // HMCLR
|
||||
case 0x2c: cxclr(); return; // CXCLR
|
||||
}
|
||||
|
||||
debug(unimplemented, "[TIA] write ",hex(address), " = ", hex(data));
|
||||
}
|
||||
|
||||
|
||||
auto TIA::vsync(n1 state) -> void {
|
||||
if(io.vsync && !state) {
|
||||
io.vcounter = 0;
|
||||
frame();
|
||||
}
|
||||
|
||||
io.vsync = state;
|
||||
}
|
||||
|
||||
auto TIA::ctrlpf(n8 data) -> void {
|
||||
playfield.mirror = data.bit(0);
|
||||
playfield.scoreMode = data.bit(1);
|
||||
playfield.priority = data.bit(2);
|
||||
ball.size = data.bit(4, 5);
|
||||
}
|
||||
|
||||
auto TIA::cxclr() -> void {
|
||||
collision.M0P0 = 0;
|
||||
collision.M0P1 = 0;
|
||||
collision.M1P0 = 0;
|
||||
collision.M1P1 = 0;
|
||||
collision.P0PF = 0;
|
||||
collision.P0BL = 0;
|
||||
collision.P1PF = 0;
|
||||
collision.P1BL = 0;
|
||||
collision.M0PF = 0;
|
||||
collision.M0BL = 0;
|
||||
collision.M1PF = 0;
|
||||
collision.M1BL = 0;
|
||||
collision.BLPF = 0;
|
||||
collision.P0P1 = 0;
|
||||
collision.M0M1 = 0;
|
||||
}
|
||||
|
||||
auto TIA::hmove() -> void {
|
||||
for(auto& p : player) p.position = (p.position - p.offset + 160) % 160;
|
||||
for(auto& m : missile) m.position = (m.position - m.offset + 160) % 160;
|
||||
ball.position = (ball.position - ball.offset + 160) % 160;
|
||||
io.hmoveTriggered = io.vcounter;
|
||||
}
|
||||
|
||||
auto TIA::hmclr() -> void {
|
||||
for(auto& p : player) p.offset = 0;
|
||||
for(auto& m : missile) m.offset = 0;
|
||||
ball.offset = 0;
|
||||
}
|
||||
|
||||
auto TIA::grp(n1 index, n8 data) -> void {
|
||||
player[index].graphics[0] = data;
|
||||
player[!index].graphics[1] = player[!index].graphics[0];
|
||||
if(index) ball.enable[1] = ball.enable[0];
|
||||
}
|
||||
|
||||
auto TIA::resp(n1 index) -> void {
|
||||
player[index].position = (io.hcounter < 68 ? 3 : io.hcounter - 68 + 8) % 160;
|
||||
}
|
||||
|
||||
auto TIA::resm(n1 index) -> void {
|
||||
missile[index].position = (io.hcounter < 68 ? 2 : io.hcounter - 68 + 7) % 160;
|
||||
}
|
||||
|
||||
auto TIA::resmp(n1 index, n8 data) -> void {
|
||||
missile[index].reset = data.bit(1);
|
||||
if(missile[index].reset) {
|
||||
auto offset = 3;
|
||||
if(player[index].size == 5) offset = 6;
|
||||
if(player[index].size == 7) offset = 10;
|
||||
missile[index].position = player[index].position + offset;
|
||||
}
|
||||
}
|
||||
|
||||
auto TIA::resbl() -> void {
|
||||
ball.position = (io.hcounter < 68 ? 2 : io.hcounter - 68 + 7);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//serialization.cpp
|
||||
auto TIA::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
for(auto n : range(writeQueue.maxItems)) {
|
||||
s(writeQueue.items[n].active);
|
||||
s(writeQueue.items[n].address);
|
||||
s(writeQueue.items[n].data);
|
||||
s(writeQueue.items[n].delay);
|
||||
}
|
||||
|
||||
s(io.vcounter);
|
||||
s(io.hcounter);
|
||||
s(io.hmoveTriggered);
|
||||
s(io.vsync);
|
||||
s(io.vblank);
|
||||
s(io.bgColor);
|
||||
s(io.p0Color);
|
||||
s(io.p1Color);
|
||||
s(io.fgColor);
|
||||
|
||||
s(playfield.graphics);
|
||||
s(playfield.mirror);
|
||||
s(playfield.scoreMode);
|
||||
s(playfield.priority);
|
||||
|
||||
for(auto n : range(2)) {
|
||||
s(player[n].graphics[0]);
|
||||
s(player[n].graphics[1]);
|
||||
s(player[n].reflect);
|
||||
s(player[n].size);
|
||||
s(player[n].offset);
|
||||
s(player[n].position);
|
||||
s(player[n].delay);
|
||||
}
|
||||
|
||||
for(auto n : range(2)) {
|
||||
s(missile[n].enable);
|
||||
s(missile[n].reset);
|
||||
s(missile[n].size);
|
||||
s(missile[n].offset);
|
||||
s(missile[n].position);
|
||||
}
|
||||
|
||||
for(auto n : range(2)) {
|
||||
s(ball.enable[0]);
|
||||
s(ball.enable[1]);
|
||||
s(ball.size);
|
||||
s(ball.offset);
|
||||
s(ball.position);
|
||||
}
|
||||
|
||||
s(collision.M0P0);
|
||||
s(collision.M0P1);
|
||||
s(collision.M1P0);
|
||||
s(collision.M1P1);
|
||||
s(collision.P0PF);
|
||||
s(collision.P0BL);
|
||||
s(collision.P1PF);
|
||||
s(collision.P1BL);
|
||||
s(collision.M0PF);
|
||||
s(collision.M0BL);
|
||||
s(collision.M1PF);
|
||||
s(collision.M1BL);
|
||||
s(collision.BLPF);
|
||||
s(collision.P0P1);
|
||||
s(collision.M0M1);
|
||||
|
||||
for(auto n : range(26)) {
|
||||
s(volume[n]);
|
||||
}
|
||||
|
||||
for(auto n : range(2)) {
|
||||
s(audio[n].enable);
|
||||
s(audio[n].divCounter);
|
||||
s(audio[n].noiseCounter);
|
||||
s(audio[n].noiseFeedback);
|
||||
s(audio[n].pulseCounter);
|
||||
s(audio[n].pulseCounterPaused);
|
||||
s(audio[n].pulseFeedback);
|
||||
s(audio[n].volume);
|
||||
s(audio[n].control);
|
||||
s(audio[n].frequency);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
#include <a26/a26.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
|
||||
TIA tia;
|
||||
|
||||
#include "audio.cpp"
|
||||
#include "color.cpp"
|
||||
#include "io.cpp"
|
||||
#include "serialization.cpp"
|
||||
#include "write-queue.cpp"
|
||||
|
||||
auto TIA::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("TIA");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 160, vlines());
|
||||
screen->colors(1 << 7, {&TIA::color, this});
|
||||
screen->setSize(160, vlines());
|
||||
screen->setScale(4.0, 2.0);
|
||||
screen->setAspect(1.0, 1.0);
|
||||
screen->setViewport(0, 0, 160, vlines());
|
||||
|
||||
stream = node->append<Node::Audio::Stream>("Audio");
|
||||
stream->setChannels(1);
|
||||
stream->setFrequency(31'403);
|
||||
stream->addHighPassFilter( 20.0, 1);
|
||||
stream->addLowPassFilter (2840.0, 1);
|
||||
}
|
||||
|
||||
auto TIA::unload() -> void {
|
||||
screen->quit();
|
||||
node->remove(screen);
|
||||
screen.reset();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto TIA::main() -> void {
|
||||
scanline();
|
||||
io.vcounter++;
|
||||
io.hmoveTriggered = 255;
|
||||
}
|
||||
|
||||
auto TIA::scanline() -> void {
|
||||
cpu.rdyLine(1);
|
||||
for(io.hcounter = 0; io.hcounter < 228; io.hcounter++) {
|
||||
auto x = io.hcounter - 68;
|
||||
if(x >= 0 && io.vcounter < vlines()) pixel(x);
|
||||
writeQueue.step();
|
||||
runAudio();
|
||||
step();
|
||||
}
|
||||
}
|
||||
|
||||
auto TIA::pixel(n8 x) -> void {
|
||||
auto output = screen->pixels().data() + (io.vcounter * 160);
|
||||
|
||||
// Output only black during vblank, or for the first 8px of a scanline where hmove was triggered
|
||||
if (io.vblank || (io.hmoveTriggered == io.vcounter && x < 8)) {
|
||||
output[x] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
output[x] = io.bgColor;
|
||||
auto pf = runPlayfield(x);
|
||||
auto bl = runBall(x);
|
||||
auto p0 = runPlayer(x, 0);
|
||||
auto p1 = runPlayer(x, 1);
|
||||
auto m0 = runMissile(x, 0);
|
||||
auto m1 = runMissile(x, 1);
|
||||
|
||||
if(!playfield.priority && (pf || bl)) output[x] = io.fgColor;
|
||||
if(p1 || m1) output[x] = io.p1Color;
|
||||
if(p0 || m0) output[x] = io.p0Color;
|
||||
if(playfield.priority && (pf || bl)) output[x] = io.fgColor;
|
||||
|
||||
// Update Collision
|
||||
if(m0 && p1) collision.M0P1 = 1;
|
||||
if(m0 && p0) collision.M0P0 = 1;
|
||||
if(m1 && p0) collision.M1P0 = 1;
|
||||
if(m1 && p1) collision.M1P1 = 1;
|
||||
if(p0 && pf) collision.P0PF = 1;
|
||||
if(p0 && bl) collision.P0BL = 1;
|
||||
if(p1 && pf) collision.P1PF = 1;
|
||||
if(p1 && bl) collision.P1BL = 1;
|
||||
if(m0 && pf) collision.M0PF = 1;
|
||||
if(m0 && bl) collision.M0BL = 1;
|
||||
if(m1 && pf) collision.M1PF = 1;
|
||||
if(m1 && bl) collision.M1BL = 1;
|
||||
if(bl && pf) collision.BLPF = 1;
|
||||
if(p0 && p1) collision.P0P1 = 1;
|
||||
if(m0 && m1) collision.M0M1 = 1;
|
||||
}
|
||||
|
||||
auto TIA::runPlayfield(n8 x) -> n1 {
|
||||
auto pos = x >> 2;
|
||||
|
||||
if(!playfield.mirror || pos < 20) return playfield.graphics.bit(pos % 20);
|
||||
return playfield.graphics.bit(19 - (pos % 20));
|
||||
}
|
||||
|
||||
auto TIA::runPlayer(n8 x, n1 index) -> n1 {
|
||||
auto& player = this->player[index];
|
||||
auto position = player.position;
|
||||
|
||||
// Handle player stretch capability
|
||||
auto width = 8;
|
||||
if(player.size == 5) width = 16;
|
||||
if(player.size == 7) width = 32;
|
||||
|
||||
// Handle repeat capability
|
||||
auto repeat = 1;
|
||||
if(player.size == 1 || player.size == 2 || player.size == 4) repeat = 2;
|
||||
if(player.size == 3 || player.size == 6) repeat = 3;
|
||||
|
||||
auto spacing = 8;
|
||||
if(player.size == 2 || player.size == 6) spacing = 24;
|
||||
if(player.size == 4 ) spacing = 56;
|
||||
|
||||
for(int i = 0; i < repeat; i++) {
|
||||
if (x >= position && x < position + width) {
|
||||
auto bit = player.reflect ? (x - position) / (width / 8) : 7 - ((x - position) / (width / 8));
|
||||
return player.graphics[player.delay].bit(bit);
|
||||
}
|
||||
|
||||
position = (position + (spacing + width)) % 160;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto TIA::runMissile(n8 x, n1 index) -> n1 {
|
||||
auto& missile = this->missile[index];
|
||||
if (!missile.enable || missile.reset) return 0;
|
||||
|
||||
const int missileSizes[4] = {1, 2, 4, 8};
|
||||
return x >= missile.position && x < missile.position + missileSizes[missile.size];
|
||||
}
|
||||
|
||||
auto TIA::runBall(n8 x) -> n1 {
|
||||
if(!ball.enable[ball.delay]) return 0;
|
||||
|
||||
const int ballSizes[4] = {1, 2, 4, 8};
|
||||
return x >= ball.position && x < ball.position + ballSizes[ball.size];
|
||||
}
|
||||
|
||||
auto TIA::frame() -> void {
|
||||
screen->frame();
|
||||
scheduler.exit(Event::Frame);
|
||||
}
|
||||
|
||||
auto TIA::step(u32 clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize();
|
||||
}
|
||||
|
||||
auto TIA::power(bool reset) -> void {
|
||||
Thread::create(system.frequency(), {&TIA::main, this});
|
||||
screen->power();
|
||||
io = {};
|
||||
playfield = {};
|
||||
for(auto& p : player) p = {};
|
||||
for(auto& m : missile) m = {};
|
||||
ball = {};
|
||||
collision = {};
|
||||
for(auto n : range(writeQueue.maxItems)) writeQueue.items[n] = {};
|
||||
|
||||
for(u32 level : range(15)) {
|
||||
volume[level] = pow(2, level * -2.0 / 6.0);
|
||||
}
|
||||
volume[15] = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
struct TIA : Thread {
|
||||
Node::Object node;
|
||||
Node::Video::Screen screen;
|
||||
Node::Audio::Stream stream;
|
||||
|
||||
auto vlines() const -> u32 { return Region::NTSC() ? 262 : 312; }
|
||||
|
||||
//tia.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto step(u32 clocks = 1) -> void;
|
||||
auto power(bool reset) -> void;
|
||||
auto frame() -> void;
|
||||
auto scanline() -> void;
|
||||
auto pixel(n8 x) -> void;
|
||||
|
||||
auto runPlayfield(n8 x) -> n1;
|
||||
auto runBall(n8 x) -> n1;
|
||||
auto runPlayer(n8 x, n1 index) -> n1;
|
||||
auto runMissile(n8 x, n1 index) -> n1;
|
||||
auto runCollision() -> void;
|
||||
|
||||
//io.cpp
|
||||
auto read(n8 address) -> n8;
|
||||
auto write(n8 address, n8 data) -> void;
|
||||
auto vsync(n1 state) -> void;
|
||||
auto ctrlpf(n8 data) -> void;
|
||||
auto cxclr() -> void;
|
||||
auto hmove() -> void;
|
||||
auto hmclr() -> void;
|
||||
auto grp(n1 index, n8 data) -> void;
|
||||
auto resp(n1 index) -> void;
|
||||
auto resm(n1 index) -> void;
|
||||
auto resmp(n1 index, n8 data) -> void;
|
||||
auto resbl() -> void;
|
||||
|
||||
// audio.cpp
|
||||
auto runAudio() -> void;
|
||||
|
||||
//color.cpp
|
||||
auto color(n32) -> n64;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
// write-queue.cpp
|
||||
struct WriteQueue {
|
||||
inline auto add(u8 address, n8 data, i8 delay) -> void;
|
||||
inline auto step() -> void;
|
||||
|
||||
struct QueueItem {
|
||||
inline auto commit() -> void;
|
||||
n1 active;
|
||||
i8 address;
|
||||
n8 data;
|
||||
i8 delay;
|
||||
};
|
||||
|
||||
QueueItem items[16];
|
||||
const int maxItems = 16;
|
||||
} writeQueue;
|
||||
|
||||
struct {
|
||||
u9 vcounter;
|
||||
u8 hcounter;
|
||||
u8 hmoveTriggered;
|
||||
|
||||
n1 vsync;
|
||||
n1 vblank;
|
||||
|
||||
n7 bgColor;
|
||||
n7 p0Color;
|
||||
n7 p1Color;
|
||||
n7 fgColor;
|
||||
} io;
|
||||
|
||||
struct {
|
||||
n20 graphics;
|
||||
n1 mirror;
|
||||
n1 scoreMode;
|
||||
n1 priority;
|
||||
} playfield;
|
||||
|
||||
struct {
|
||||
n8 graphics[2];
|
||||
n1 reflect;
|
||||
n3 size;
|
||||
i4 offset;
|
||||
n8 position;
|
||||
n1 delay;
|
||||
} player[2];
|
||||
|
||||
struct {
|
||||
n1 enable;
|
||||
n1 reset;
|
||||
n2 size;
|
||||
i4 offset;
|
||||
n8 position;
|
||||
} missile[2];
|
||||
|
||||
struct {
|
||||
n1 enable[2];
|
||||
n2 size;
|
||||
i4 offset;
|
||||
n1 delay;
|
||||
n8 position;
|
||||
} ball;
|
||||
|
||||
struct {
|
||||
n1 M0P0;
|
||||
n1 M0P1;
|
||||
n1 M1P0;
|
||||
n1 M1P1;
|
||||
n1 P0PF;
|
||||
n1 P0BL;
|
||||
n1 P1PF;
|
||||
n1 P1BL;
|
||||
n1 M0PF;
|
||||
n1 M0BL;
|
||||
n1 M1PF;
|
||||
n1 M1BL;
|
||||
n1 BLPF;
|
||||
n1 P0P1;
|
||||
n1 M0M1;
|
||||
} collision;
|
||||
|
||||
struct AudioChannel {
|
||||
auto phase0() -> void;
|
||||
auto phase1() -> u8;
|
||||
n1 enable;
|
||||
n8 divCounter;
|
||||
n8 noiseCounter;
|
||||
n1 noiseFeedback;
|
||||
n8 pulseCounter;
|
||||
n1 pulseCounterPaused;
|
||||
n1 pulseFeedback;
|
||||
n4 volume;
|
||||
n4 control;
|
||||
n5 frequency;
|
||||
} audio[2];
|
||||
|
||||
f64 volume[16];
|
||||
};
|
||||
|
||||
extern TIA tia;
|
||||
@@ -0,0 +1,46 @@
|
||||
auto TIA::WriteQueue::add(u8 address, n8 data, i8 delay) -> void {
|
||||
for(auto n : range(maxItems)) {
|
||||
if(!items[n].active) {
|
||||
items[n].address = address;
|
||||
items[n].data = data;
|
||||
items[n].delay = delay;
|
||||
items[n].active = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto TIA::WriteQueue::step() -> void {
|
||||
auto& items = tia.writeQueue.items;
|
||||
|
||||
for(auto n : range(maxItems)) {
|
||||
if(items[n].active && --items[n].delay == 0) {
|
||||
items[n].commit();
|
||||
items[n].active = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto TIA::WriteQueue::QueueItem::commit() -> void {
|
||||
switch(address) {
|
||||
case 0x01: tia.io.vblank = data.bit(1); return; // VBLANK
|
||||
case 0x0b: tia.player[0].reflect = data.bit(3); return; // REFP0
|
||||
case 0x0c: tia.player[1].reflect = data.bit(3); return; // REFP1
|
||||
case 0x0d: tia.playfield.graphics.bit(0, 3) = data.bit(4, 7); return; // PF0
|
||||
case 0x0e: for(auto n : range(8)) tia.playfield.graphics.bit(4 + n) = data.bit(7 - n); return; // PF1
|
||||
case 0x0f: tia.playfield.graphics.bit(12, 19) = data; return; // PF2
|
||||
case 0x1b: tia.grp(0, data); return; // GRP0
|
||||
case 0x1c: tia.grp(1, data); return; // GRP1
|
||||
case 0x1d: tia.missile[0].enable = data.bit(1); return; // ENAM0
|
||||
case 0x1e: tia.missile[1].enable = data.bit(1); return; // ENAM1
|
||||
case 0x1f: tia.ball.enable[0] = data.bit(1); return; // ENABL
|
||||
case 0x20: tia.player[0].offset = data.bit(4, 7); return; // HMP0
|
||||
case 0x21: tia.player[1].offset = data.bit(4, 7); return; // HMP1
|
||||
case 0x22: tia.missile[0].offset = data.bit(4, 7); return; // HMM0
|
||||
case 0x23: tia.missile[1].offset = data.bit(4, 7); return; // HMM1
|
||||
case 0x24: tia.ball.offset = data.bit(4, 7); return; // HMBL
|
||||
case 0x2a: tia.hmove(); return; // HMOVE
|
||||
case 0x2b: tia.hmclr(); return; // HMCLR
|
||||
}
|
||||
debug(unimplemented, "[TIA] writeQueue: ",hex(address), " = ", hex(data));
|
||||
}
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libco/libco.h>
|
||||
#include <sljit.h>
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/adaptive-array.hpp>
|
||||
@@ -37,16 +38,16 @@ using namespace nall;
|
||||
|
||||
namespace ares {
|
||||
static const string Name = "ares";
|
||||
static const string Version = "124";
|
||||
static const string Version = "130";
|
||||
static const string Copyright = "ares team, Near";
|
||||
static const string License = "ISC";
|
||||
static const string LicenseURI = "https://opensource.org/licenses/ISC";
|
||||
static const string Website = "ares-emulator.github.io";
|
||||
static const string WebsiteURI = "https://ares-emulator.github.io";
|
||||
static const string Website = "ares-emu.net";
|
||||
static const string WebsiteURI = "https://ares-emu.net/";
|
||||
|
||||
//incremented only when serialization format changes
|
||||
static const u32 SerializerSignature = 0x31545342; //"BST1" (little-endian)
|
||||
static const string SerializerVersion = "123.1";
|
||||
static const string SerializerVersion = "130";
|
||||
|
||||
namespace VFS {
|
||||
using Pak = shared_pointer<vfs::directory>;
|
||||
|
||||
@@ -4,19 +4,19 @@ struct Debug {
|
||||
auto reset() -> void;
|
||||
|
||||
template<typename... P> auto unhandled(P&&... p) -> void {
|
||||
return _unhandled({forward<P>(p)...});
|
||||
return _unhandled({std::forward<P>(p)...});
|
||||
}
|
||||
|
||||
template<typename... P> auto unimplemented(P&&... p) -> void {
|
||||
return _unimplemented({forward<P>(p)...});
|
||||
return _unimplemented({std::forward<P>(p)...});
|
||||
}
|
||||
|
||||
template<typename... P> auto unusual(P&&... p) -> void {
|
||||
return _unusual({forward<P>(p)...});
|
||||
return _unusual({std::forward<P>(p)...});
|
||||
}
|
||||
|
||||
template<typename... P> auto unverified(P&&... p) -> void {
|
||||
return _unverified({forward<P>(p)...});
|
||||
return _unverified({std::forward<P>(p)...});
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
#include <ares/ares.hpp>
|
||||
|
||||
namespace ares::Memory {
|
||||
|
||||
alignas(4096) u8 fixedBuffer[1_GiB];
|
||||
|
||||
FixedAllocator::FixedAllocator() {
|
||||
_allocator.resize(sizeof(fixedBuffer), 0, fixedBuffer);
|
||||
}
|
||||
|
||||
auto FixedAllocator::get() -> bump_allocator& {
|
||||
static FixedAllocator allocator;
|
||||
return allocator._allocator;
|
||||
}
|
||||
|
||||
}
|
||||
#include <ares/ares.hpp>
|
||||
|
||||
namespace ares::Memory {
|
||||
|
||||
#if defined(PLATFORM_MACOS) && defined(ARCHITECTURE_ARM64)
|
||||
//stub for unsupported platforms
|
||||
FixedAllocator::FixedAllocator() {
|
||||
}
|
||||
#else
|
||||
alignas(4096) u8 fixedBuffer[128_MiB];
|
||||
|
||||
FixedAllocator::FixedAllocator() {
|
||||
_allocator.resize(sizeof(fixedBuffer), 0, fixedBuffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto FixedAllocator::get() -> bump_allocator& {
|
||||
static FixedAllocator allocator;
|
||||
return allocator._allocator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ struct Stream : Audio {
|
||||
template<typename... P>
|
||||
auto frame(P&&... p) -> void {
|
||||
if(runAhead()) return;
|
||||
f64 samples[sizeof...(p)] = {forward<P>(p)...};
|
||||
f64 samples[sizeof...(p)] = {std::forward<P>(p)...};
|
||||
write(samples);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ struct Instruction : Tracer {
|
||||
for(auto& history : _history) history = ~0;
|
||||
}
|
||||
|
||||
auto address(u32 address) -> bool {
|
||||
address &= (1ull << _addressBits) - 1; //mask upper bits of address
|
||||
auto address(u64 address) -> bool {
|
||||
address &= ~0ull >> (64 - _addressBits); //mask upper bits of address
|
||||
_address = address;
|
||||
address >>= _addressMask; //clip unneeded alignment bits (to reduce _masks size)
|
||||
|
||||
@@ -55,9 +55,9 @@ struct Instruction : Tracer {
|
||||
|
||||
//mark an already-executed address as not executed yet for trace masking.
|
||||
//call when writing to executable RAM to support self-modifying code.
|
||||
auto invalidate(u32 address) -> void {
|
||||
auto invalidate(u64 address) -> void {
|
||||
if(unlikely(_mask && updateMasks())) {
|
||||
address &= (1ull << _addressBits) - 1;
|
||||
address &= ~0ull >> (64 - _addressBits);
|
||||
address >>= _addressMask;
|
||||
_masks[address >> 3] &= ~(1 << (address & 7));
|
||||
}
|
||||
@@ -104,7 +104,7 @@ struct Instruction : Tracer {
|
||||
|
||||
protected:
|
||||
auto updateMasks() -> bool {
|
||||
auto size = 1ull << _addressBits >> _addressMask >> 3;
|
||||
auto size = 1ull << (_addressBits - _addressMask - 3);
|
||||
if(!_mask || !size) return _masks.reset(), false;
|
||||
if(_masks.size() == size) return true;
|
||||
_masks.reset();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
struct Input : Object {
|
||||
DeclareClass(Input, "input")
|
||||
using Object::Object;
|
||||
u64 lastPoll;
|
||||
};
|
||||
@@ -33,7 +33,7 @@ struct Object : shared_pointer_this<Object> {
|
||||
template<typename T, typename... P>
|
||||
auto prepend(P&&... p) -> Node::Object {
|
||||
using Type = typename T::type;
|
||||
return prepend(shared_pointer<Type>::create(forward<P>(p)...));
|
||||
return prepend(shared_pointer<Type>::create(std::forward<P>(p)...));
|
||||
}
|
||||
|
||||
auto append(Node::Object node) -> Node::Object {
|
||||
@@ -47,7 +47,7 @@ struct Object : shared_pointer_this<Object> {
|
||||
template<typename T, typename... P>
|
||||
auto append(P&&... p) -> Node::Object {
|
||||
using Type = typename T::type;
|
||||
return append(shared_pointer<Type>::create(forward<P>(p)...));
|
||||
return append(shared_pointer<Type>::create(std::forward<P>(p)...));
|
||||
}
|
||||
|
||||
auto remove(Node::Object node) -> void {
|
||||
|
||||
@@ -36,6 +36,7 @@ auto Screen::main(uintptr_t) -> void {
|
||||
auto Screen::quit() -> void {
|
||||
_kill = true;
|
||||
_thread.join();
|
||||
_sprites.reset();
|
||||
}
|
||||
|
||||
auto Screen::power() -> void {
|
||||
|
||||
@@ -82,10 +82,10 @@ protected:
|
||||
u32 _rotation = 0; //counter-clockwise (90 = left, 270 = right)
|
||||
|
||||
function<n64 (n32)> _color;
|
||||
unique_pointer<u32> _inputA;
|
||||
unique_pointer<u32> _inputB;
|
||||
unique_pointer<u32> _output;
|
||||
unique_pointer<u32> _rotate;
|
||||
unique_pointer<u32[]> _inputA;
|
||||
unique_pointer<u32[]> _inputB;
|
||||
unique_pointer<u32[]> _output;
|
||||
unique_pointer<u32[]> _rotate;
|
||||
unique_pointer<u32[]> _palette;
|
||||
vector<Node::Video::Sprite> _sprites;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ struct Platform {
|
||||
virtual auto pak(Node::Object) -> shared_pointer<vfs::directory> { return {}; }
|
||||
virtual auto event(Event) -> void {}
|
||||
virtual auto log(string_view message) -> void {}
|
||||
virtual auto status(string_view message) -> void {}
|
||||
virtual auto video(Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void {}
|
||||
virtual auto audio(Node::Audio::Stream) -> void {}
|
||||
virtual auto input(Node::Input::Input) -> void {}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
build := stable
|
||||
nall.path := ../../../nall
|
||||
include $(nall.path)/GNUmakefile
|
||||
|
||||
all:
|
||||
sourcery resource.bml resource.cpp resource.hpp
|
||||
|
||||
clean:
|
||||
rm resource.cpp
|
||||
rm resource.hpp
|
||||
$(call delete,resource.cpp)
|
||||
$(call delete,resource.hpp)
|
||||
@@ -84,14 +84,15 @@ inline auto Thread::synchronize() -> void {
|
||||
template<typename... P>
|
||||
inline auto Thread::synchronize(Thread& thread, P&&... p) -> void {
|
||||
//switching to another thread does not guarantee it will catch up before switching back.
|
||||
while(thread.clock() < clock()) {
|
||||
//make sure not to switch to threads that were destroyed during synchronization
|
||||
while(thread.clock() < clock() && thread.handle()) {
|
||||
//disable synchronization for auxiliary threads during scheduler synchronization.
|
||||
//synchronization can begin inside of this while loop.
|
||||
if(scheduler.synchronizing()) break;
|
||||
co_switch(thread.handle());
|
||||
}
|
||||
//convenience: allow synchronizing multiple threads with one function call.
|
||||
if constexpr(sizeof...(p) > 0) synchronize(forward<P>(p)...);
|
||||
if constexpr(sizeof...(p) > 0) synchronize(std::forward<P>(p)...);
|
||||
}
|
||||
|
||||
inline auto Thread::serialize(serializer& s) -> void {
|
||||
|
||||
@@ -196,9 +196,11 @@ auto AY38910::write(n8 data) -> void {
|
||||
envelope.output = !envelope.attacking ? 15 : 0;
|
||||
break;
|
||||
case 14:
|
||||
portA.data = data;
|
||||
writeIO(0, data);
|
||||
break;
|
||||
case 15:
|
||||
portB.data = data;
|
||||
writeIO(1, data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ auto SN76489::write(n8 data) -> void {
|
||||
case 0: tone0.pitch.bit(4,9) = data.bit(0,5); break;
|
||||
case 1: tone1.pitch.bit(4,9) = data.bit(0,5); break;
|
||||
case 2: tone2.pitch.bit(4,9) = data.bit(0,5); noise.pitch = tone2.pitch; break;
|
||||
case 3: noise.rate = data.bit(0,1);
|
||||
noise.enable = data.bit(2);
|
||||
noise.lfsr = 0x8000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,84 @@
|
||||
auto YM2610::read(n2 address) -> n8 {
|
||||
switch(address) {
|
||||
case 0: return fm.readStatus();
|
||||
case 1: return ssg.read();
|
||||
|
||||
}
|
||||
|
||||
debug(unimplemented, "[YM2610] read ", hex(address));
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto YM2610::write(n2 address, n8 data) -> void {
|
||||
switch(address) {
|
||||
case 0: register = 0x000 | data; return;
|
||||
case 0: {
|
||||
registerAddress = 0x000 | data;
|
||||
if (registerAddress <= 0xf) ssg.select(registerAddress);
|
||||
else fm.writeAddress(registerAddress);
|
||||
return;
|
||||
}
|
||||
case 1: return writeLower(data);
|
||||
case 2: register = 0x100 | data; return;
|
||||
case 2: registerAddress = 0x100 | data; fm.writeAddress(registerAddress); return;
|
||||
case 3: return writeUpper(data);
|
||||
}
|
||||
}
|
||||
|
||||
auto YM2610::writeLower(n8 data) -> void {
|
||||
switch(register) {
|
||||
switch(registerAddress) {
|
||||
case 0x000 ... 0x00f:
|
||||
ssg.select(register);
|
||||
ssg.write(data);
|
||||
return;
|
||||
case 0x010 ... 0x01b:
|
||||
//ADPCM-B
|
||||
return;
|
||||
case 0x010:
|
||||
pcmB.repeat = data.bit(4);
|
||||
if(data.bit(0)) pcmB.power();
|
||||
if(data.bit(7)) pcmB.beginPlay();
|
||||
else pcmB.playing = 0;
|
||||
return;
|
||||
case 0x011: pcmB.left = data.bit(7);
|
||||
pcmB.right = data.bit(6); return;
|
||||
case 0x012: pcmB.startAddress.bit( 8, 15) = data; return;
|
||||
case 0x013: pcmB.startAddress.bit(16, 23) = data; return;
|
||||
case 0x014: pcmB.endAddress.bit ( 8, 15) = data; return;
|
||||
case 0x015: pcmB.endAddress.bit (16, 23) = data; return;
|
||||
case 0x016 ... 0x018: debug(unimplemented, "[YM2610::ADPCMB] Write ", hex(registerAddress), " = ", data);
|
||||
case 0x019: pcmB.delta.bit ( 0, 7) = data; return;
|
||||
case 0x01a: pcmB.delta.bit ( 8, 15) = data; return;
|
||||
case 0x01b: pcmB.volume = data; return;
|
||||
case 0x01c:
|
||||
//EOS
|
||||
debug(unimplemented, "[YM2610::EOS] Write ", hex(registerAddress), " = ", data);
|
||||
return;
|
||||
case 0x01d ... 0x1ff:
|
||||
fm.writeAddress(register);
|
||||
fm.writeData(data);
|
||||
return;
|
||||
}
|
||||
unreachable;
|
||||
|
||||
debug(unimplemented, "[YM2610] Write ", hex(registerAddress), " = ", data);
|
||||
}
|
||||
|
||||
auto YM2610::writeUpper(n8 data) -> void {
|
||||
switch(register) {
|
||||
case 0x000 ... 0x12f:
|
||||
//ADPCM-A
|
||||
return;
|
||||
case 0x130 ... 0x1ff:
|
||||
fm.writeAddress(register);
|
||||
fm.writeData(data);
|
||||
switch(registerAddress) {
|
||||
case 0x100:
|
||||
for(auto n : range(6)) {
|
||||
if(!data.bit(n)) continue;
|
||||
if(data.bit(7)) {
|
||||
pcmA.channels[n].keyOff();
|
||||
continue;
|
||||
}
|
||||
|
||||
pcmA.channels[n].keyOn();
|
||||
}
|
||||
return;
|
||||
case 0x101: pcmA.volume = data.bit(0, 5); return;
|
||||
case 0x108 ... 0x10d: pcmA.channels[registerAddress - 0x108].volume = data.bit(0,4);
|
||||
pcmA.channels[registerAddress - 0x108].left = data.bit(7);
|
||||
pcmA.channels[registerAddress - 0x108].right = data.bit(6); return;
|
||||
case 0x110 ... 0x115: pcmA.channels[registerAddress - 0x110].startAddress.bit( 8, 15) = data; return;
|
||||
case 0x118 ... 0x11d: pcmA.channels[registerAddress - 0x118].startAddress.bit(16, 23) = data; return;
|
||||
case 0x120 ... 0x125: pcmA.channels[registerAddress - 0x120].endAddress.bit ( 8, 15) = data; return;
|
||||
case 0x128 ... 0x12d: pcmA.channels[registerAddress - 0x128].endAddress.bit (16, 23) = data; return;
|
||||
case 0x130 ... 0x1ff: fm.writeAddress(registerAddress); fm.writeData(data); return;
|
||||
}
|
||||
|
||||
debug(unimplemented, "[YM2610] Write ", hex(registerAddress), " = ", data);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
auto YM2610::PCMA::power() -> void {
|
||||
for(auto c : range(6)) {
|
||||
channels[c].startAddress.bit(0, 7) = 0x00;
|
||||
channels[c].endAddress.bit(0, 7) = 0x00;
|
||||
channels[c].decodeAccumulator = 0;
|
||||
channels[c].decodeStep = 0;
|
||||
}
|
||||
|
||||
i16 stepSize[] = {
|
||||
16, 17, 19, 21, 23, 25, 28, 31, 34, 37,
|
||||
41, 45, 50, 55, 60, 66, 73, 80, 88, 97,
|
||||
107, 118, 130, 143, 157, 173, 190, 209, 230, 253,
|
||||
279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
|
||||
724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
|
||||
};
|
||||
|
||||
// Generate a decode table
|
||||
for (int step = 0; step < 49; step++) {
|
||||
for (int nibble = 0; nibble < 16; nibble++) {
|
||||
int value = (2 * (nibble & 0x07) + 1) * stepSize[step] / 8;
|
||||
decodeTable[step * 16 + nibble] = ((nibble & 0x08) != 0) ? -value : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto YM2610::PCMA::clock() -> array<i16[2]> {
|
||||
f32 left = 0;
|
||||
f32 right = 0;
|
||||
|
||||
for(auto c : range(6)) {
|
||||
if(!channels[c].playing) continue;
|
||||
if(((channels[c].currentAddress ^ (channels[c].endAddress + (1 << 8))) & 0xfffff) == 0) {
|
||||
channels[c].playing = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sample = channels[c].decode(self.readPCMA(channels[c].currentAddress));
|
||||
|
||||
int totalVolume = (volume ^ 0x3f) + (channels[c].volume ^ 0x1f);
|
||||
int volumeMultiplier = 15 - (totalVolume & 7);
|
||||
int volumeShift = 5 + (totalVolume >> 3);
|
||||
|
||||
sample = ((sample * volumeMultiplier) >> volumeShift) & ~3;
|
||||
|
||||
if(channels[c].left) left += sample / 2047.0f;
|
||||
if(channels[c].right) right += sample / 2047.0f;
|
||||
}
|
||||
|
||||
return {sclamp<16>(left * 32768.0), sclamp<16>(right * 32768.0)};
|
||||
}
|
||||
|
||||
auto YM2610::PCMA::Channel::keyOn() -> void {
|
||||
playing = 1;
|
||||
currentAddress = startAddress;
|
||||
decodeAccumulator = 0;
|
||||
decodeStep = 0;
|
||||
currentNibble = 0;
|
||||
}
|
||||
|
||||
auto YM2610::PCMA::Channel::keyOff() -> void {
|
||||
playing = 0;
|
||||
}
|
||||
|
||||
auto YM2610::PCMA::Channel::decode(n8 value) -> i12 {
|
||||
int stepAdjust[] = { -1, -1, -1, -1, 2, 5, 7, 9, -1, -1, -1, -1, 2, 5, 7, 9 };
|
||||
|
||||
value = currentNibble ? value.bit(0, 3) : value.bit(4, 7);
|
||||
if(currentNibble) currentAddress++;
|
||||
currentNibble = !currentNibble;
|
||||
|
||||
decodeAccumulator += self.decodeTable[decodeStep + value];
|
||||
|
||||
decodeStep += stepAdjust[value & 7] * 16;
|
||||
decodeStep = std::clamp((int)decodeStep, 0, 48 * 16);
|
||||
|
||||
return decodeAccumulator;
|
||||
}
|
||||
|
||||
auto YM2610::PCMB::power() -> void {
|
||||
currentNibble = 0;
|
||||
currentAddress = 0;
|
||||
decodeAccumulator = 0;
|
||||
previousAccumulator = 0;
|
||||
decodePosition = 0;
|
||||
decodeStep = 127;
|
||||
volume = 255;
|
||||
}
|
||||
|
||||
auto YM2610::PCMB::clock() -> array<i16[2]> {
|
||||
f32 leftSample = 0;
|
||||
f32 rightSample = 0;
|
||||
|
||||
decode();
|
||||
|
||||
i32 sample = (previousAccumulator * (i32)((decodePosition ^ 0xffff) + 1) + decodeAccumulator * (i32)(decodePosition)) >> 16;
|
||||
sample = (sample * (i32)volume) >> 9;
|
||||
|
||||
if(left) leftSample += (sample / 65535.0);
|
||||
if(right) rightSample += (sample / 65535.0);
|
||||
|
||||
return {sclamp<16>(leftSample * 32768.0), sclamp<16>(rightSample * 32768.0)};
|
||||
}
|
||||
|
||||
auto YM2610::PCMB::decode() -> void {
|
||||
if(!playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
u8 stepSize[8] = { 57, 57, 57, 57, 77, 102, 128, 153 };
|
||||
|
||||
u32 position = decodePosition + delta;
|
||||
decodePosition = (u16)position;
|
||||
if(position < 0x10000) return;
|
||||
|
||||
n8 value = self.readPCMB(currentAddress);
|
||||
value = currentNibble ? value.bit(0, 3) : value.bit(4, 7);
|
||||
currentNibble = !currentNibble;
|
||||
|
||||
if(currentNibble == 0) {
|
||||
currentAddress++;
|
||||
if(currentAddress == endAddress) {
|
||||
if(repeat) beginPlay();
|
||||
else {
|
||||
decodeAccumulator = 0;
|
||||
previousAccumulator = 0;
|
||||
playing = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousAccumulator = decodeAccumulator;
|
||||
|
||||
i32 _delta = (value.bit(0, 2) * 2 + 1) * decodeStep / 8;
|
||||
if(value.bit(3)) _delta = -_delta;
|
||||
|
||||
decodeAccumulator = sclamp<16>(decodeAccumulator + _delta);
|
||||
|
||||
decodeStep = std::clamp(((int)decodeStep * stepSize[value.bit(0, 2)]) / 64, 127, 24576);
|
||||
}
|
||||
|
||||
auto YM2610::PCMB::beginPlay() -> void {
|
||||
currentAddress = startAddress;
|
||||
currentNibble = 0;
|
||||
decodeAccumulator = 0;
|
||||
previousAccumulator = 0;
|
||||
decodePosition = 0;
|
||||
decodeStep = 127;
|
||||
playing = 1;
|
||||
}
|
||||
@@ -1,5 +1,41 @@
|
||||
auto YM2610::serialize(serializer& s) -> void {
|
||||
s(register);
|
||||
s(registerAddress);
|
||||
s(fm);
|
||||
s(ssg);
|
||||
s(pcmA);
|
||||
s(pcmB);
|
||||
}
|
||||
|
||||
auto YM2610::PCMA::serialize(serializer& s) -> void {
|
||||
for(auto n : range(6)) {
|
||||
s(channels[n].playing);
|
||||
s(channels[n].ended);
|
||||
s(channels[n].left);
|
||||
s(channels[n].right);
|
||||
s(channels[n].volume);
|
||||
s(channels[n].startAddress);
|
||||
s(channels[n].endAddress);
|
||||
s(channels[n].currentAddress);
|
||||
s(channels[n].decodeAccumulator);
|
||||
s(channels[n].decodeStep);
|
||||
s(channels[n].currentNibble);
|
||||
}
|
||||
}
|
||||
|
||||
auto YM2610::PCMB::serialize(serializer& s) -> void {
|
||||
s(playing);
|
||||
s(repeat);
|
||||
s(left);
|
||||
s(right);
|
||||
s(startAddress);
|
||||
s(endAddress);
|
||||
s(delta);
|
||||
s(volume);
|
||||
|
||||
s(currentAddress);
|
||||
s(currentNibble);
|
||||
s(decodeAccumulator);
|
||||
s(decodePosition);
|
||||
s(previousAccumulator);
|
||||
s(decodeStep);
|
||||
}
|
||||
@@ -4,11 +4,14 @@
|
||||
namespace ares {
|
||||
|
||||
#include "io.cpp"
|
||||
#include "pcm.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto YM2610::power() -> void {
|
||||
fm.power();
|
||||
ssg.power();
|
||||
pcmA.power();
|
||||
pcmB.power();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,10 +20,74 @@ struct YM2610 {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
virtual auto readPCMA(u32 address) -> u8 { return 0; }
|
||||
virtual auto readPCMB(u32 address) -> u8 { return 0; }
|
||||
|
||||
protected:
|
||||
n9 register;
|
||||
n9 registerAddress;
|
||||
YM2612 fm;
|
||||
YM2149 ssg;
|
||||
|
||||
struct PCMA {
|
||||
YM2610& self;
|
||||
|
||||
// pcm.cpp
|
||||
auto power() -> void;
|
||||
auto clock() -> array<i16[2]>;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Channel {
|
||||
PCMA& self;
|
||||
auto keyOn() -> void;
|
||||
auto keyOff() -> void;
|
||||
auto decode(n8) -> i12;
|
||||
n1 playing;
|
||||
n1 ended;
|
||||
n1 left;
|
||||
n1 right;
|
||||
n5 volume;
|
||||
n32 startAddress;
|
||||
n32 endAddress;
|
||||
n32 currentAddress;
|
||||
i12 decodeAccumulator;
|
||||
i32 decodeStep;
|
||||
bool currentNibble;
|
||||
} channels[6]{{*this}, {*this}, {*this}, {*this}, {*this}, {*this}};
|
||||
|
||||
n6 volume;
|
||||
int decodeTable[16 * 49];
|
||||
} pcmA{*this};
|
||||
|
||||
struct PCMB {
|
||||
YM2610& self;
|
||||
|
||||
// pcm.cpp
|
||||
auto power() -> void;
|
||||
auto clock() -> array<i16[2]>;
|
||||
auto decode() -> void;
|
||||
auto beginPlay() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
n1 playing;
|
||||
n1 repeat;
|
||||
n1 left;
|
||||
n2 right;
|
||||
n32 startAddress;
|
||||
n32 endAddress;
|
||||
n16 delta;
|
||||
n8 volume;
|
||||
|
||||
n32 currentAddress;
|
||||
bool currentNibble;
|
||||
i32 decodeAccumulator;
|
||||
u32 decodePosition;
|
||||
i32 previousAccumulator;
|
||||
i32 decodeStep;
|
||||
} pcmB{*this};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
auto YM2612::Channel::Operator::trigger(bool state) -> void {
|
||||
auto YM2612::Channel::Operator::updateKeyState(bool state) -> void {
|
||||
if(keyOn == state) return; //no change
|
||||
|
||||
keyOn = state;
|
||||
envelope.state = Release;
|
||||
updateEnvelope();
|
||||
|
||||
if(keyOn) {
|
||||
//restart phase and envelope generators
|
||||
@@ -13,42 +10,46 @@ auto YM2612::Channel::Operator::trigger(bool state) -> void {
|
||||
updateEnvelope();
|
||||
|
||||
if(envelope.rate >= 62) {
|
||||
//skip attack and possibly decay stages
|
||||
//skip attack phase
|
||||
envelope.value = 0;
|
||||
envelope.state = envelope.sustainLevel ? Decay : Sustain;
|
||||
updateEnvelope();
|
||||
}
|
||||
} else if(ssg.enable && ssg.attack != ssg.invert) {
|
||||
//SSG-EG key-off
|
||||
envelope.value = 0x200 - envelope.value;
|
||||
} else {
|
||||
envelope.state = Release;
|
||||
updateEnvelope();
|
||||
|
||||
if(ssg.enable && ssg.attack != ssg.invert) {
|
||||
//SSG-EG key-off
|
||||
envelope.value = 0x200 - envelope.value;
|
||||
}
|
||||
}
|
||||
|
||||
updateLevel();
|
||||
}
|
||||
|
||||
auto YM2612::Channel::Operator::runEnvelope() -> void {
|
||||
u32 sustain = envelope.sustainLevel < 15 ? envelope.sustainLevel << 5 : 0x3f0;
|
||||
updateKeyState(keyLine);
|
||||
if(ym2612.envelope.clock & (1 << envelope.divider) - 1) return;
|
||||
|
||||
u32 value = ym2612.envelope.clock >> envelope.divider;
|
||||
u32 step = envelope.steps >> ((~value & 7) << 2) & 0xf;
|
||||
if(ssg.enable) step <<= 2; //SSG results in a 4x faster envelope
|
||||
u32 sustain = envelope.sustainLevel < 15 ? envelope.sustainLevel << 5 : 0x1f << 5;
|
||||
|
||||
if(envelope.state == Attack) {
|
||||
u32 next = envelope.value + (~u16(envelope.value) * step >> 4) & 0x3ff;
|
||||
if(next <= envelope.value) {
|
||||
envelope.value = next;
|
||||
} else {
|
||||
envelope.value = 0;
|
||||
envelope.state = envelope.value < sustain ? Decay : Sustain;
|
||||
if(envelope.value == 0) {
|
||||
envelope.state = Decay;
|
||||
updateEnvelope();
|
||||
} else if(envelope.rate < 62) {
|
||||
// will stop updating if attack rate is increased to upper threshold during attack phase (confirmed behavior)
|
||||
envelope.value += ~u16(envelope.value) * step >> 4;
|
||||
}
|
||||
} else if(!ssg.enable || envelope.value < 0x200) {
|
||||
envelope.value = min(envelope.value + step, 0x3ff);
|
||||
}
|
||||
if(envelope.state != Attack) {
|
||||
if(envelope.state == Decay && envelope.value >= sustain) {
|
||||
envelope.state = Sustain;
|
||||
updateEnvelope();
|
||||
}
|
||||
if(ssg.enable) step = envelope.value < 0x200 ? step << 2 : 0; //SSG results in a 4x faster envelope
|
||||
envelope.value = min(envelope.value + step, 0x3ff);
|
||||
}
|
||||
|
||||
updateLevel();
|
||||
@@ -59,18 +60,19 @@ auto YM2612::Channel::Operator::runPhase() -> void {
|
||||
if(!(ssg.enable && envelope.value >= 0x200)) return; //SSG loop check
|
||||
|
||||
if(!ssg.hold && !ssg.alternate) phase.value = 0;
|
||||
if(!ssg.hold || ssg.attack == ssg.invert) ssg.invert ^= ssg.alternate;
|
||||
if(!(ssg.hold && ssg.invert)) ssg.invert ^= ssg.alternate;
|
||||
|
||||
if(envelope.state == Attack) {
|
||||
//do nothing; SSG is meant to skip the attack phase
|
||||
} else if(envelope.state != Release && !ssg.hold) {
|
||||
//if still looping, reset the envelope
|
||||
envelope.state = Attack;
|
||||
if(envelope.attackRate >= 62) {
|
||||
envelope.value = 0;
|
||||
envelope.state = envelope.sustainLevel ? Decay : Sustain;
|
||||
}
|
||||
updateEnvelope();
|
||||
|
||||
if(envelope.rate >= 62) {
|
||||
//skip attack phase
|
||||
envelope.value = 0;
|
||||
}
|
||||
} else if(envelope.state == Release || (ssg.hold && ssg.attack == ssg.invert)) {
|
||||
//clear envelope once finished
|
||||
envelope.value = 0x3ff;
|
||||
@@ -115,11 +117,9 @@ auto YM2612::Channel::Operator::updatePhase() -> void {
|
||||
u32 tuning = detune & 3 ? detunes[(detune & 3) - 1][ksr & 7] >> (3 - (ksr >> 3)) : 0;
|
||||
|
||||
u32 lfo = ym2612.lfo.clock >> 2 & 0x1f;
|
||||
u32 pm = 4 * vibratos[channel.vibrato][lfo & 15] * (-lfo >> 4);
|
||||
u32 msb = 10;
|
||||
while(msb > 4 && ~pitch.value & 1 << msb) msb--;
|
||||
s32 pm = (pitch.value * vibratos[channel.vibrato][lfo & 15] >> 9) * (lfo > 15 ? -1 : 1);
|
||||
|
||||
phase.delta = pitch.value + (pm >> 10 - msb) << 6 >> 7 - octave.value;
|
||||
phase.delta = pitch.value + pm << 6 >> 7 - octave.value;
|
||||
phase.delta = (!detune.bit(2) ? phase.delta + tuning : phase.delta - tuning) & 0x1ffff;
|
||||
phase.delta = (multiple ? phase.delta * multiple : phase.delta >> 1) & 0xfffff;
|
||||
}
|
||||
@@ -147,6 +147,7 @@ auto YM2612::Channel::power() -> void {
|
||||
|
||||
for(auto& op : operators) {
|
||||
op.keyOn = 0;
|
||||
op.keyLine = 0;
|
||||
op.lfoEnable = 0;
|
||||
op.detune = 0;
|
||||
op.multiple = 0;
|
||||
|
||||
@@ -14,6 +14,8 @@ auto YM2612::writeData(n8 data) -> void {
|
||||
case 0x022: {
|
||||
lfo.rate = data.bit(0,2);
|
||||
lfo.enable = data.bit(3);
|
||||
lfo.clock = 0;
|
||||
lfo.divider = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -37,11 +39,12 @@ auto YM2612::writeData(n8 data) -> void {
|
||||
|
||||
//timer control
|
||||
case 0x027: {
|
||||
//d6,d7 = mode (unimplemented; treated as mode 0 always)
|
||||
|
||||
//reload period on 0->1 transition
|
||||
if(!timerA.enable && data.bit(0)) timerA.counter = timerA.period;
|
||||
if(!timerB.enable && data.bit(1)) timerB.counter = timerB.period;
|
||||
if(!timerB.enable && data.bit(1)) {
|
||||
timerB.counter = timerB.period;
|
||||
timerB.divider = 0;
|
||||
}
|
||||
|
||||
timerA.enable = data.bit(0);
|
||||
timerB.enable = data.bit(1);
|
||||
@@ -51,6 +54,7 @@ auto YM2612::writeData(n8 data) -> void {
|
||||
if(data.bit(4)) timerA.line = 0;
|
||||
if(data.bit(5)) timerB.line = 0;
|
||||
|
||||
//d7 is enable bit for CSM mode (not yet implemented)
|
||||
channels[2].mode = data.bit(6,7);
|
||||
for(auto& op : channels[2].operators) op.updatePitch();
|
||||
|
||||
@@ -64,10 +68,10 @@ auto YM2612::writeData(n8 data) -> void {
|
||||
if(index == 3 || index == 7) break;
|
||||
if(index >= 4) index--;
|
||||
|
||||
channels[index][0].trigger(data.bit(4));
|
||||
channels[index][1].trigger(data.bit(5));
|
||||
channels[index][2].trigger(data.bit(6));
|
||||
channels[index][3].trigger(data.bit(7));
|
||||
channels[index][0].keyLine = data.bit(4);
|
||||
channels[index][1].keyLine = data.bit(5);
|
||||
channels[index][2].keyLine = data.bit(6);
|
||||
channels[index][3].keyLine = data.bit(7);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -145,10 +149,11 @@ auto YM2612::writeData(n8 data) -> void {
|
||||
|
||||
//SSG-EG
|
||||
case 0x090: {
|
||||
op.ssg.hold = data.bit(0);
|
||||
op.ssg.alternate = data.bit(1);
|
||||
op.ssg.attack = data.bit(2);
|
||||
op.ssg.enable = data.bit(3);
|
||||
op.ssg.hold = op.ssg.enable ? data.bit(0) : 0;
|
||||
op.ssg.alternate = op.ssg.enable ? data.bit(1) : 0;
|
||||
op.ssg.attack = op.ssg.enable ? data.bit(2) : 0;
|
||||
channel[index].updateLevel();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ auto YM2612::Channel::serialize(serializer& s) -> void {
|
||||
|
||||
auto YM2612::Channel::Operator::serialize(serializer& s) -> void {
|
||||
s(keyOn);
|
||||
s(keyLine);
|
||||
s(lfoEnable);
|
||||
s(detune);
|
||||
s(multiple);
|
||||
|
||||
@@ -27,7 +27,7 @@ auto YM2612::clock() -> array<i16[2]> {
|
||||
auto wave = [&](u32 n, u32 modulation) -> s32 {
|
||||
s32 x = (modulation >> 1) + (op[n].phase.value >> 10);
|
||||
s32 y = sine[x & 0x3ff] + op[n].outputLevel;
|
||||
return y < 0x2000 ? pow2[y & 0x1ff] << 2 >> (y >> 9) : 0;
|
||||
return y < 0x1a00 ? pow2[y & 0x1ff] << 2 >> (y >> 9) : 0; // -78 dB floor
|
||||
};
|
||||
|
||||
s32 feedback = modMask & op[0].output + op[0].prior >> 9 - channel.feedback;
|
||||
@@ -124,7 +124,7 @@ auto YM2612::clock() -> array<i16[2]> {
|
||||
|
||||
if(++envelope.divider == 3) {
|
||||
envelope.divider = 0;
|
||||
envelope.clock++;
|
||||
if(!++envelope.clock) ++envelope.clock; // 12-bit counter: 1..4095 - zero-value is skipped (confirmed behavior)
|
||||
}
|
||||
|
||||
for(auto& channel : channels) {
|
||||
|
||||
@@ -36,7 +36,7 @@ protected:
|
||||
} dac;
|
||||
|
||||
struct Envelope {
|
||||
n32 clock = 0;
|
||||
n12 clock = 0;
|
||||
n32 divider = 0;
|
||||
} envelope;
|
||||
|
||||
@@ -91,7 +91,7 @@ protected:
|
||||
Operator(Channel& channel) : channel(channel), ym2612(channel.ym2612) {}
|
||||
|
||||
//channel.cpp
|
||||
auto trigger(bool) -> void;
|
||||
auto updateKeyState(bool) -> void;
|
||||
|
||||
auto runEnvelope() -> void;
|
||||
auto runPhase() -> void;
|
||||
@@ -105,6 +105,7 @@ protected:
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
n1 keyOn = 0;
|
||||
n1 keyLine = 0;
|
||||
n1 lfoEnable = 0;
|
||||
n3 detune = 0;
|
||||
n4 multiple = 0;
|
||||
|
||||
@@ -74,7 +74,7 @@ auto M24C::write() -> void {
|
||||
case Mode::Device:
|
||||
if(counter <= 8) {
|
||||
device = device << 1 | data();
|
||||
} else if(!select()) {
|
||||
} else if(select() != Acknowledge) {
|
||||
mode = Mode::Standby;
|
||||
} else if(device & 1) {
|
||||
mode = Mode::Read;
|
||||
|
||||
@@ -50,7 +50,7 @@ template<u32 Size> auto M68000::_address(EffectiveAddress& ea) -> string {
|
||||
if(ea.mode == 2) return {_addressRegister(AddressRegister{ea.reg})};
|
||||
if(ea.mode == 5) return {"$", hex(_readDisplacement(read(AddressRegister{ea.reg})), 6L)};
|
||||
if(ea.mode == 6) return {"$", hex(_readIndex(read(AddressRegister{ea.reg})), 6L)};
|
||||
if(ea.mode == 7) return {"$", hex((i16)_readPC<Word>(), 6L)};
|
||||
if(ea.mode == 7) { auto imm = _readPC<Word>(); return {"$", imm >= 0x8000 ? hex((i16)imm, 6L, 'f') : hex((i16)imm, 6L, '0') }; }
|
||||
if(ea.mode == 8) return {"$", hex(_readPC<Long>(), 6L)};
|
||||
if(ea.mode == 9) return {"$", hex(_pc + (i16)_readPC(), 6L)};
|
||||
if(ea.mode == 10) return {"$", hex(_readIndex(_pc), 6L)};
|
||||
@@ -65,7 +65,7 @@ template<u32 Size> auto M68000::_effectiveAddress(EffectiveAddress& ea) -> strin
|
||||
if(ea.mode == 4) return {"-(", _addressRegister(AddressRegister{ea.reg}), ")"};
|
||||
if(ea.mode == 5) return {"($", hex(_readDisplacement(read(AddressRegister{ea.reg})), 6L), ")"};
|
||||
if(ea.mode == 6) return {"($", hex(_readIndex(read(AddressRegister{ea.reg})), 6L), ")"};
|
||||
if(ea.mode == 7) return {"($", hex((i16)_readPC<Word>(), 6L), ")"};
|
||||
if(ea.mode == 7) { auto imm = _readPC<Word>(); return {"($", imm >= 0x8000 ? hex((i16)imm, 6L, 'f') : hex((i16)imm, 6L, '0'), ")"}; }
|
||||
if(ea.mode == 8) return {"($", hex(_readPC<Long>(), 6L), ")"};
|
||||
if(ea.mode == 9) return {"($", hex(_readDisplacement(_pc), 6L), ")"};
|
||||
if(ea.mode == 10) return {"($", hex(_readIndex(_pc), 6L), ")"};
|
||||
|
||||
@@ -414,6 +414,7 @@ M68000::M68000() {
|
||||
for(n3 mode : range(8))
|
||||
for(n3 reg : range(8)) {
|
||||
auto opcode = pattern("1011 ---+ 11-- ----") | areg << 9 | mode << 3 | reg << 0;
|
||||
if(mode == 7 && reg >= 5) continue;
|
||||
|
||||
AddressRegister with{areg};
|
||||
EffectiveAddress from{mode, reg};
|
||||
@@ -1256,7 +1257,7 @@ M68000::M68000() {
|
||||
for(n3 mode : range(8))
|
||||
for(n3 reg : range(8)) {
|
||||
auto opcode = pattern("0100 1010 ++-- ----") | mode << 3 | reg << 0;
|
||||
if(mode == 7 && reg >= 2) continue;
|
||||
if(mode == 1 || mode == 7 && reg >= 2) continue;
|
||||
|
||||
EffectiveAddress from{mode, reg};
|
||||
bind(opcode | 0 << 6, TST<Byte>, from);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
auto M68000::instructionABCD(EffectiveAddress from, EffectiveAddress with) -> void {
|
||||
if(from.mode == DataRegisterDirect) idle(2);
|
||||
auto target = read<Byte, Hold, Fast>(with);
|
||||
auto source = read<Byte>(from);
|
||||
auto target = read<Byte, Hold, Fast>(with);
|
||||
auto result = source + target + r.x;
|
||||
bool c = false;
|
||||
bool v = false;
|
||||
@@ -98,8 +98,8 @@ template<u32 Size> auto M68000::instructionADDX(EffectiveAddress from, Effective
|
||||
if constexpr(Size == Long) {
|
||||
if(from.mode == DataRegisterDirect) idle(4);
|
||||
}
|
||||
auto target = read<Size, Hold, Fast>(with);
|
||||
auto source = read<Size>(from);
|
||||
auto target = read<Size, Hold, Fast>(with);
|
||||
auto result = ADD<Size, Extend>(source, target);
|
||||
prefetch();
|
||||
write<Size>(with, result);
|
||||
@@ -332,14 +332,20 @@ auto M68000::instructionCHK(DataRegister compare, EffectiveAddress maximum) -> v
|
||||
|
||||
r.z = clip<Word>(target) == 0;
|
||||
r.n = sign<Word>(target) < 0;
|
||||
if(r.n) return exception(Exception::BoundsCheck, Vector::BoundsCheck);
|
||||
if(r.n) {
|
||||
prefetched();
|
||||
return exception(Exception::BoundsCheck, Vector::BoundsCheck);
|
||||
}
|
||||
|
||||
auto result = (n64)target - source;
|
||||
r.c = sign<Word>(result >> 1) < 0;
|
||||
r.v = sign<Word>((target ^ source) & (target ^ result)) < 0;
|
||||
r.z = clip<Word>(result) == 0;
|
||||
r.n = sign<Word>(result) < 0;
|
||||
if(r.n == r.v && !r.z) return exception(Exception::BoundsCheck, Vector::BoundsCheck);
|
||||
if(r.n == r.v && !r.z) {
|
||||
prefetched();
|
||||
return exception(Exception::BoundsCheck, Vector::BoundsCheck);
|
||||
}
|
||||
prefetch();
|
||||
}
|
||||
|
||||
@@ -390,22 +396,30 @@ template<u32 Size> auto M68000::instructionCMPM(EffectiveAddress from, Effective
|
||||
}
|
||||
|
||||
auto M68000::instructionDBCC(n4 test, DataRegister with) -> void {
|
||||
auto displacement = extension<Word>();
|
||||
if(condition(test)) {
|
||||
idle(4);
|
||||
} else {
|
||||
idle(2);
|
||||
r.pc -= 2;
|
||||
if(!condition(test)) { // cc false
|
||||
auto disp = sign<Word>(r.irc);
|
||||
// predict branch
|
||||
r.pc += disp;
|
||||
prefetch();
|
||||
// decrement register
|
||||
n16 result = read<Word>(with);
|
||||
write<Word>(with, result - 1);
|
||||
if(result) {
|
||||
idle(2);
|
||||
r.pc -= 4;
|
||||
r.pc += sign<Word>(displacement);
|
||||
prefetch();
|
||||
// branch taken
|
||||
prefetch();
|
||||
return;
|
||||
} else {
|
||||
idle(2);
|
||||
// branch not taken
|
||||
r.pc -= disp;
|
||||
}
|
||||
} else { // cc true
|
||||
idle(2);
|
||||
r.pc += 2;
|
||||
}
|
||||
prefetch();
|
||||
prefetch();
|
||||
}
|
||||
|
||||
auto M68000::instructionDIVS(EffectiveAddress from, DataRegister with) -> void {
|
||||
@@ -413,6 +427,7 @@ auto M68000::instructionDIVS(EffectiveAddress from, DataRegister with) -> void {
|
||||
n32 divisor = read<Word>(from) << 16, odivisor = divisor;
|
||||
|
||||
if(divisor == 0) {
|
||||
prefetched();
|
||||
return exception(Exception::DivisionByZero, Vector::DivisionByZero);
|
||||
}
|
||||
|
||||
@@ -476,8 +491,8 @@ auto M68000::instructionDIVS(EffectiveAddress from, DataRegister with) -> void {
|
||||
return;
|
||||
}
|
||||
|
||||
r.z = quotient == 0;
|
||||
r.n = quotient < 0;
|
||||
r.z = clip<Word>(quotient) == 0;
|
||||
r.n = sign<Word>(quotient) < 0;
|
||||
|
||||
idle(ticks);
|
||||
write<Long>(with, dividend | quotient);
|
||||
@@ -489,6 +504,7 @@ auto M68000::instructionDIVU(EffectiveAddress from, DataRegister with) -> void {
|
||||
n32 divisor = read<Word>(from) << 16;
|
||||
|
||||
if(divisor == 0) {
|
||||
prefetched();
|
||||
return exception(Exception::DivisionByZero, Vector::DivisionByZero);
|
||||
}
|
||||
|
||||
@@ -515,8 +531,8 @@ auto M68000::instructionDIVU(EffectiveAddress from, DataRegister with) -> void {
|
||||
ticks += force ? 6 : carry ? 4 : 2;
|
||||
quotient = quotient << 1 | carry;
|
||||
|
||||
r.z = quotient == 0;
|
||||
r.n = quotient < 0;
|
||||
r.z = clip<Word>(quotient) == 0;
|
||||
r.n = sign<Word>(quotient) < 0;
|
||||
|
||||
idle(ticks);
|
||||
write<Long>(with, dividend | quotient);
|
||||
@@ -1118,8 +1134,8 @@ auto M68000::instructionRTS() -> void {
|
||||
|
||||
auto M68000::instructionSBCD(EffectiveAddress from, EffectiveAddress with) -> void {
|
||||
if(from.mode == DataRegisterDirect) idle(2);
|
||||
auto target = read<Byte, Hold, Fast>(with);
|
||||
auto source = read<Byte>(from);
|
||||
auto target = read<Byte, Hold, Fast>(with);
|
||||
auto result = target - source - r.x;
|
||||
bool c = false;
|
||||
bool v = false;
|
||||
@@ -1240,8 +1256,8 @@ template<u32 Size> auto M68000::instructionSUBX(EffectiveAddress from, Effective
|
||||
if constexpr(Size == Long) {
|
||||
if(from.mode == DataRegisterDirect) idle(4);
|
||||
}
|
||||
auto target = read<Size, Hold, Fast>(with);
|
||||
auto source = read<Size>(from);
|
||||
auto target = read<Size, Hold, Fast>(with);
|
||||
auto result = SUB<Size, Extend>(source, target);
|
||||
prefetch();
|
||||
write<Size>(with, result);
|
||||
@@ -1262,7 +1278,7 @@ auto M68000::instructionSWAP(DataRegister with) -> void {
|
||||
auto M68000::instructionTAS(EffectiveAddress with) -> void {
|
||||
n32 data;
|
||||
|
||||
if(with.mode == DataRegisterDirect) {
|
||||
if(lockable() || with.mode == DataRegisterDirect) {
|
||||
data = read<Byte, Hold>(with);
|
||||
prefetch();
|
||||
write<Byte>(with, data | 0x80);
|
||||
|
||||
@@ -50,21 +50,36 @@ auto M68000::supervisor() -> bool {
|
||||
|
||||
auto M68000::exception(u32 exception, u32 vector, u32 priority) -> void {
|
||||
r.stop = false;
|
||||
idle(10); //todo: not accurate
|
||||
|
||||
auto pc = r.pc;
|
||||
auto sr = readSR();
|
||||
|
||||
// register setup (+6 cyc)
|
||||
idle(6);
|
||||
n32 pc = r.pc - 4;
|
||||
n32 sr = readSR();
|
||||
if(!r.s) swap(r.a[7], r.sp);
|
||||
r.i = priority;
|
||||
r.s = 1;
|
||||
r.t = 0;
|
||||
|
||||
push<Long>(pc - 4);
|
||||
push<Word>(sr);
|
||||
// push pc low (+4 cyc)
|
||||
push<Word>(pc & 0x0000ffff);
|
||||
|
||||
// external interrupt handling
|
||||
if(exception == Exception::Interrupt) {
|
||||
// IACK vector number acquisition (+4 cyc normal or +10 to +18 cyc autovectored)
|
||||
// & justify vector number (+4 cyc)
|
||||
idle(16+4); // assuming autovector cycles (approximated for Megadrive)
|
||||
r.i = priority;
|
||||
}
|
||||
|
||||
// push pc high & sr (+8 cyc)
|
||||
push<Long>(sr << 16 | pc >> 16);
|
||||
|
||||
// read vector address (+8 cyc)
|
||||
r.pc = read<Long>(vector << 2);
|
||||
// todo: if the vector address read causes an address error, this exception should be escalated
|
||||
|
||||
// prefetch (+8 cyc)
|
||||
prefetch();
|
||||
if(exception == Exception::Interrupt) idle(2); // dead cycles (+2 cyc)
|
||||
prefetch();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ struct M68000 {
|
||||
virtual auto wait(u32 clocks) -> void = 0;
|
||||
virtual auto read(n1 upper, n1 lower, n24 address, n16 data = 0) -> n16 = 0;
|
||||
virtual auto write(n1 upper, n1 lower, n24 address, n16 data) -> void = 0;
|
||||
virtual auto lockable() -> bool { return true; }
|
||||
|
||||
auto ird() const -> n16 { return r.ird; }
|
||||
auto irc() const -> n16 { return r.irc; }
|
||||
|
||||
enum : bool { User, Supervisor };
|
||||
enum : u32 { Byte, Word, Long };
|
||||
@@ -70,8 +72,8 @@ struct M68000 {
|
||||
M68000();
|
||||
auto power() -> void;
|
||||
auto supervisor() -> bool;
|
||||
auto exception(u32 exception, u32 vector, u32 priority = 7) -> void;
|
||||
auto interrupt(u32 vector, u32 priority = 7) -> void;
|
||||
auto exception(u32 exception, u32 vector, u32 priority = 0) -> void;
|
||||
auto interrupt(u32 vector, u32 priority = 0) -> void;
|
||||
|
||||
//registers.cpp
|
||||
struct DataRegister {
|
||||
|
||||
@@ -17,6 +17,16 @@ auto MOS6502::algorithmADC(n8 i) -> n8 {
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmALR(n8 i) -> n8 {
|
||||
n8 o = A & i;
|
||||
C = o.bit(0);
|
||||
o >>= 1;
|
||||
Z = o == 0;
|
||||
N = o.bit(7);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmAND(n8 i) -> n8 {
|
||||
n8 o = A & i;
|
||||
Z = o == 0;
|
||||
@@ -24,6 +34,29 @@ auto MOS6502::algorithmAND(n8 i) -> n8 {
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmANC(n8 i) -> n8 {
|
||||
n8 o = A & i;
|
||||
Z = o == 0;
|
||||
N = o.bit(7);
|
||||
C = N;
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmARR(n8 i) -> n8 {
|
||||
n8 o = A & i;
|
||||
Z = o == 0;
|
||||
N = o.bit(7);
|
||||
|
||||
bool c = C;
|
||||
o = c << 7 | o >> 1;
|
||||
Z = o == 0;
|
||||
N = o.bit(7);
|
||||
C = o.bit(6);
|
||||
V = o.bit(6) ^ o.bit(5);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmASL(n8 i) -> n8 {
|
||||
C = i.bit(7);
|
||||
i <<= 1;
|
||||
@@ -32,6 +65,23 @@ auto MOS6502::algorithmASL(n8 i) -> n8 {
|
||||
return i;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmATX(n8 i) -> n8 {
|
||||
A |= 0xff; //TODO: OR value may differ on non-NES platforms
|
||||
n8 o = A & i;
|
||||
X = o;
|
||||
Z = o == 0;
|
||||
N = o.bit(7);
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmAXS(n8 i) -> n8 {
|
||||
n9 o = (A & X) - i;
|
||||
C = !o.bit(8);
|
||||
Z = n8(o) == 0;
|
||||
N = o.bit(7);
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmBIT(n8 i) -> n8 {
|
||||
Z = (A & i) == 0;
|
||||
V = i.bit(6);
|
||||
@@ -142,3 +192,15 @@ auto MOS6502::algorithmSBC(n8 i) -> n8 {
|
||||
N = o.bit(7);
|
||||
return o;
|
||||
}
|
||||
|
||||
auto MOS6502::algorithmSLO(n8 i) -> n8 {
|
||||
C = i.bit(7);
|
||||
i <<= 1;
|
||||
Z = i == 0;
|
||||
N = i.bit(7);
|
||||
|
||||
A |= i;
|
||||
Z = A == 0;
|
||||
N = A.bit(7);
|
||||
return i;
|
||||
}
|
||||
@@ -57,19 +57,24 @@ auto MOS6502::disassembleInstruction(maybe<n16> _pc) -> string {
|
||||
switch(auto opcode = readDebugger(pc)) {
|
||||
op(0x00, "brk", immediate)
|
||||
op(0x01, "ora", indirectX)
|
||||
op(0x04, "nop", zeroPage)
|
||||
op(0x05, "ora", zeroPage)
|
||||
op(0x06, "asl", zeroPage)
|
||||
op(0x08, "php", implied)
|
||||
op(0x09, "ora", immediate)
|
||||
op(0x0a, "asl", implied)
|
||||
op(0x0c, "nop", absolute)
|
||||
op(0x0d, "ora", absolute)
|
||||
op(0x0e, "asl", absolute)
|
||||
op(0x10, "bpl", branch)
|
||||
op(0x11, "ora", indirectY)
|
||||
op(0x14, "nop", zeroPageX)
|
||||
op(0x15, "ora", zeroPageX)
|
||||
op(0x16, "asl", zeroPageX)
|
||||
op(0x18, "clc", implied)
|
||||
op(0x19, "ora", absoluteY)
|
||||
op(0x1a, "nop", implied)
|
||||
op(0x1c, "nop", absoluteX)
|
||||
op(0x1d, "ora", absoluteX)
|
||||
op(0x1e, "asl", absoluteX)
|
||||
op(0x20, "jsr", absolute)
|
||||
@@ -85,14 +90,18 @@ auto MOS6502::disassembleInstruction(maybe<n16> _pc) -> string {
|
||||
op(0x2e, "rol", absolute)
|
||||
op(0x30, "bmi", branch)
|
||||
op(0x31, "and", indirectY)
|
||||
op(0x34, "nop", zeroPageX)
|
||||
op(0x35, "and", zeroPageX)
|
||||
op(0x36, "rol", zeroPageX)
|
||||
op(0x38, "sec", implied)
|
||||
op(0x39, "and", absoluteY)
|
||||
op(0x3a, "nop", implied)
|
||||
op(0x3c, "nop", absoluteX)
|
||||
op(0x3d, "and", absoluteX)
|
||||
op(0x3e, "rol", absoluteX)
|
||||
op(0x40, "rti", implied)
|
||||
op(0x41, "eor", indirectX)
|
||||
op(0x44, "nop", zeroPage)
|
||||
op(0x45, "eor", zeroPage)
|
||||
op(0x46, "lsr", zeroPage)
|
||||
op(0x48, "pha", implied)
|
||||
@@ -103,14 +112,18 @@ auto MOS6502::disassembleInstruction(maybe<n16> _pc) -> string {
|
||||
op(0x4e, "lsr", absolute)
|
||||
op(0x50, "bvc", branch)
|
||||
op(0x51, "eor", indirectY)
|
||||
op(0x54, "nop", zeroPageX)
|
||||
op(0x55, "eor", zeroPageX)
|
||||
op(0x56, "lsr", zeroPageX)
|
||||
op(0x58, "cli", implied)
|
||||
op(0x59, "eor", absoluteY)
|
||||
op(0x5a, "nop", implied)
|
||||
op(0x5c, "nop", absoluteX)
|
||||
op(0x5d, "eor", absoluteX)
|
||||
op(0x5e, "lsr", absoluteX)
|
||||
op(0x60, "rts", implied)
|
||||
op(0x61, "adc", indirectX)
|
||||
op(0x64, "nop", zeroPage)
|
||||
op(0x65, "adc", zeroPage)
|
||||
op(0x66, "ror", zeroPage)
|
||||
op(0x68, "pla", implied)
|
||||
@@ -121,17 +134,23 @@ auto MOS6502::disassembleInstruction(maybe<n16> _pc) -> string {
|
||||
op(0x6e, "ror", absolute)
|
||||
op(0x70, "bvs", branch)
|
||||
op(0x71, "adc", indirectY)
|
||||
op(0x74, "nop", zeroPageX)
|
||||
op(0x75, "adc", zeroPageX)
|
||||
op(0x76, "ror", zeroPageX)
|
||||
op(0x78, "sei", implied)
|
||||
op(0x79, "adc", absoluteY)
|
||||
op(0x7a, "nop", implied)
|
||||
op(0x7c, "nop", absoluteX)
|
||||
op(0x7d, "adc", absoluteX)
|
||||
op(0x7e, "ror", absoluteX)
|
||||
op(0x80, "nop", immediate)
|
||||
op(0x81, "sta", indirectX)
|
||||
op(0x82, "nop", immediate)
|
||||
op(0x84, "sty", zeroPage)
|
||||
op(0x85, "sta", zeroPage)
|
||||
op(0x86, "stx", zeroPage)
|
||||
op(0x88, "dey", implied)
|
||||
op(0x89, "nop", immediate)
|
||||
op(0x8a, "txa", implied)
|
||||
op(0x8c, "sty", absolute)
|
||||
op(0x8d, "sta", absolute)
|
||||
@@ -170,6 +189,7 @@ auto MOS6502::disassembleInstruction(maybe<n16> _pc) -> string {
|
||||
op(0xbe, "ldx", absoluteY)
|
||||
op(0xc0, "cpy", immediate)
|
||||
op(0xc1, "cmp", indirectX)
|
||||
op(0xc2, "nop", immediate)
|
||||
op(0xc4, "cpy", zeroPage)
|
||||
op(0xc5, "cmp", zeroPage)
|
||||
op(0xc6, "dec", zeroPage)
|
||||
@@ -181,14 +201,18 @@ auto MOS6502::disassembleInstruction(maybe<n16> _pc) -> string {
|
||||
op(0xce, "dec", absolute)
|
||||
op(0xd0, "bne", branch)
|
||||
op(0xd1, "cmp", indirectY)
|
||||
op(0xd4, "nop", zeroPageX)
|
||||
op(0xd5, "cmp", zeroPageX)
|
||||
op(0xd6, "dec", zeroPageX)
|
||||
op(0xd8, "cld", implied)
|
||||
op(0xd9, "cmp", absoluteY)
|
||||
op(0xda, "nop", implied)
|
||||
op(0xdc, "nop", absoluteX)
|
||||
op(0xdd, "cmp", absoluteX)
|
||||
op(0xde, "dec", absoluteX)
|
||||
op(0xe0, "cpx", immediate)
|
||||
op(0xe1, "sbc", indirectX)
|
||||
op(0xe2, "nop", immediate)
|
||||
op(0xe4, "cpx", zeroPage)
|
||||
op(0xe5, "sbc", zeroPage)
|
||||
op(0xe6, "inc", zeroPage)
|
||||
@@ -200,10 +224,13 @@ auto MOS6502::disassembleInstruction(maybe<n16> _pc) -> string {
|
||||
op(0xee, "inc", absolute)
|
||||
op(0xf0, "beq", branch)
|
||||
op(0xf1, "sbc", indirectY)
|
||||
op(0xf4, "nop", zeroPageX)
|
||||
op(0xf5, "sbc", zeroPageX)
|
||||
op(0xf6, "inc", zeroPageX)
|
||||
op(0xf8, "sed", implied)
|
||||
op(0xf9, "sbc", absoluteY)
|
||||
op(0xfa, "nop", implied)
|
||||
op(0xfc, "nop", absoluteX)
|
||||
op(0xfd, "sbc", absoluteX)
|
||||
op(0xfe, "inc", absoluteX)
|
||||
|
||||
|
||||
@@ -20,21 +20,32 @@ auto MOS6502::instruction() -> void {
|
||||
switch(code) {
|
||||
op(0x00, Break)
|
||||
op(0x01, IndirectXRead, fp(ORA), A)
|
||||
op(0x04, NoOperationZeroPage)
|
||||
op(0x05, ZeroPageRead, fp(ORA), A)
|
||||
op(0x06, ZeroPageModify, fp(ASL))
|
||||
op(0x07, ZeroPageModify, fp(SLO))
|
||||
op(0x08, PushP)
|
||||
op(0x09, Immediate, fp(ORA), A)
|
||||
op(0x0a, Implied, fp(ASL), A)
|
||||
op(0x0b, Immediate, fp(ANC), A)
|
||||
op(0x0c, NoOperationAbsolute)
|
||||
op(0x0d, AbsoluteRead, fp(ORA), A)
|
||||
op(0x0e, AbsoluteModify, fp(ASL))
|
||||
op(0x0f, AbsoluteModify, fp(SLO))
|
||||
op(0x10, Branch, N == 0)
|
||||
op(0x11, IndirectYRead, fp(ORA), A)
|
||||
op(0x14, NoOperationZeroPage, X)
|
||||
op(0x15, ZeroPageRead, fp(ORA), A, X)
|
||||
op(0x16, ZeroPageModify, fp(ASL), X)
|
||||
op(0x17, ZeroPageModify, fp(SLO), X)
|
||||
op(0x18, Clear, C)
|
||||
op(0x19, AbsoluteRead, fp(ORA), A, Y)
|
||||
op(0x1a, NoOperation)
|
||||
op(0x1b, AbsoluteModify, fp(SLO), Y)
|
||||
op(0x1c, NoOperationAbsolute, X)
|
||||
op(0x1d, AbsoluteRead, fp(ORA), A, X)
|
||||
op(0x1e, AbsoluteModify, fp(ASL), X)
|
||||
op(0x1f, AbsoluteModify, fp(SLO), X)
|
||||
op(0x20, CallAbsolute)
|
||||
op(0x21, IndirectXRead, fp(AND), A)
|
||||
op(0x24, ZeroPageRead, fp(BIT), A)
|
||||
@@ -43,58 +54,75 @@ auto MOS6502::instruction() -> void {
|
||||
op(0x28, PullP)
|
||||
op(0x29, Immediate, fp(AND), A)
|
||||
op(0x2a, Implied, fp(ROL), A)
|
||||
op(0x2b, Immediate, fp(ANC), A)
|
||||
op(0x2c, AbsoluteRead, fp(BIT), A)
|
||||
op(0x2d, AbsoluteRead, fp(AND), A)
|
||||
op(0x2e, AbsoluteModify, fp(ROL))
|
||||
op(0x30, Branch, N == 1)
|
||||
op(0x31, IndirectYRead, fp(AND), A)
|
||||
op(0x34, NoOperationZeroPage, X)
|
||||
op(0x35, ZeroPageRead, fp(AND), A, X)
|
||||
op(0x36, ZeroPageModify, fp(ROL), X)
|
||||
op(0x38, Set, C)
|
||||
op(0x39, AbsoluteRead, fp(AND), A, Y)
|
||||
op(0x3a, NoOperation)
|
||||
op(0x3c, NoOperationAbsolute, X)
|
||||
op(0x3d, AbsoluteRead, fp(AND), A, X)
|
||||
op(0x3e, AbsoluteModify, fp(ROL), X)
|
||||
op(0x40, ReturnInterrupt)
|
||||
op(0x41, IndirectXRead, fp(EOR), A)
|
||||
op(0x44, NoOperationZeroPage)
|
||||
op(0x45, ZeroPageRead, fp(EOR), A)
|
||||
op(0x46, ZeroPageModify, fp(LSR))
|
||||
op(0x48, Push, A)
|
||||
op(0x49, Immediate, fp(EOR), A)
|
||||
op(0x4a, Implied, fp(LSR), A)
|
||||
op(0x4b, Immediate, fp(ALR), A)
|
||||
op(0x4c, JumpAbsolute)
|
||||
op(0x4d, AbsoluteRead, fp(EOR), A)
|
||||
op(0x4e, AbsoluteModify, fp(LSR))
|
||||
op(0x50, Branch, V == 0)
|
||||
op(0x51, IndirectYRead, fp(EOR), A)
|
||||
op(0x54, NoOperationZeroPage, X)
|
||||
op(0x55, ZeroPageRead, fp(EOR), A, X)
|
||||
op(0x56, ZeroPageModify, fp(LSR), X)
|
||||
op(0x58, Clear, I)
|
||||
op(0x59, AbsoluteRead, fp(EOR), A, Y)
|
||||
op(0x5a, NoOperation)
|
||||
op(0x5c, NoOperationAbsolute, X)
|
||||
op(0x5d, AbsoluteRead, fp(EOR), A, X)
|
||||
op(0x5e, AbsoluteModify, fp(LSR), X)
|
||||
op(0x60, ReturnSubroutine)
|
||||
op(0x61, IndirectXRead, fp(ADC), A)
|
||||
op(0x64, NoOperationZeroPage)
|
||||
op(0x65, ZeroPageRead, fp(ADC), A)
|
||||
op(0x66, ZeroPageModify, fp(ROR))
|
||||
op(0x68, Pull, A)
|
||||
op(0x69, Immediate, fp(ADC), A)
|
||||
op(0x6a, Implied, fp(ROR), A)
|
||||
op(0x6b, Immediate, fp(ARR), A)
|
||||
op(0x6c, JumpIndirect)
|
||||
op(0x6d, AbsoluteRead, fp(ADC), A)
|
||||
op(0x6e, AbsoluteModify, fp(ROR))
|
||||
op(0x70, Branch, V == 1)
|
||||
op(0x71, IndirectYRead, fp(ADC), A)
|
||||
op(0x74, NoOperationZeroPage, X)
|
||||
op(0x75, ZeroPageRead, fp(ADC), A, X)
|
||||
op(0x76, ZeroPageModify, fp(ROR), X)
|
||||
op(0x78, Set, I)
|
||||
op(0x79, AbsoluteRead, fp(ADC), A, Y)
|
||||
op(0x7a, NoOperation)
|
||||
op(0x7c, NoOperationAbsolute, X)
|
||||
op(0x7d, AbsoluteRead, fp(ADC), A, X)
|
||||
op(0x7e, AbsoluteModify, fp(ROR), X)
|
||||
op(0x80, NoOperationImmediate)
|
||||
op(0x81, IndirectXWrite, A)
|
||||
op(0x82, NoOperationImmediate)
|
||||
op(0x84, ZeroPageWrite, Y)
|
||||
op(0x85, ZeroPageWrite, A)
|
||||
op(0x86, ZeroPageWrite, X)
|
||||
op(0x88, Implied, fp(DEC), Y)
|
||||
op(0x89, NoOperationImmediate)
|
||||
op(0x8a, Transfer, X, A, 1)
|
||||
op(0x8c, AbsoluteWrite, Y)
|
||||
op(0x8d, AbsoluteWrite, A)
|
||||
@@ -117,11 +145,13 @@ auto MOS6502::instruction() -> void {
|
||||
op(0xa8, Transfer, A, Y, 1)
|
||||
op(0xa9, Immediate, fp(LD), A)
|
||||
op(0xaa, Transfer, A, X, 1)
|
||||
op(0xab, Immediate, fp(ATX), A)
|
||||
op(0xac, AbsoluteRead, fp(LD), Y)
|
||||
op(0xad, AbsoluteRead, fp(LD), A)
|
||||
op(0xae, AbsoluteRead, fp(LD), X)
|
||||
op(0xb0, Branch, C == 1)
|
||||
op(0xb1, IndirectYRead, fp(LD), A)
|
||||
op(0xb3, IndirectYRead, fp(LD), A, X)
|
||||
op(0xb4, ZeroPageRead, fp(LD), Y, X)
|
||||
op(0xb5, ZeroPageRead, fp(LD), A, X)
|
||||
op(0xb6, ZeroPageRead, fp(LD), X, Y)
|
||||
@@ -133,45 +163,56 @@ auto MOS6502::instruction() -> void {
|
||||
op(0xbe, AbsoluteRead, fp(LD), X, Y)
|
||||
op(0xc0, Immediate, fp(CPY), Y)
|
||||
op(0xc1, IndirectXRead, fp(CMP), A)
|
||||
op(0xc2, NoOperationImmediate)
|
||||
op(0xc4, ZeroPageRead, fp(CPY), Y)
|
||||
op(0xc5, ZeroPageRead, fp(CMP), A)
|
||||
op(0xc6, ZeroPageModify, fp(DEC))
|
||||
op(0xc8, Implied, fp(INC), Y)
|
||||
op(0xc9, Immediate, fp(CMP), A)
|
||||
op(0xca, Implied, fp(DEC), X)
|
||||
op(0xcb, Immediate, fp(AXS), X)
|
||||
op(0xcc, AbsoluteRead, fp(CPY), Y)
|
||||
op(0xcd, AbsoluteRead, fp(CMP), A)
|
||||
op(0xce, AbsoluteModify, fp(DEC))
|
||||
op(0xd0, Branch, Z == 0)
|
||||
op(0xd1, IndirectYRead, fp(CMP), A)
|
||||
op(0xd4, NoOperationZeroPage, X)
|
||||
op(0xd5, ZeroPageRead, fp(CMP), A, X)
|
||||
op(0xd6, ZeroPageModify, fp(DEC), X)
|
||||
op(0xd8, Clear, D)
|
||||
op(0xd9, AbsoluteRead, fp(CMP), A, Y)
|
||||
op(0xda, NoOperation)
|
||||
op(0xdc, NoOperationAbsolute, X)
|
||||
op(0xdd, AbsoluteRead, fp(CMP), A, X)
|
||||
op(0xde, AbsoluteModify, fp(DEC), X)
|
||||
op(0xe0, Immediate, fp(CPX), X)
|
||||
op(0xe1, IndirectXRead, fp(SBC), A)
|
||||
op(0xe2, NoOperationImmediate)
|
||||
op(0xe4, ZeroPageRead, fp(CPX), X)
|
||||
op(0xe5, ZeroPageRead, fp(SBC), A)
|
||||
op(0xe6, ZeroPageModify, fp(INC))
|
||||
op(0xe8, Implied, fp(INC), X)
|
||||
op(0xe9, Immediate, fp(SBC), A)
|
||||
op(0xea, NoOperation)
|
||||
op(0xeb, Immediate, fp(SBC), A)
|
||||
op(0xec, AbsoluteRead, fp(CPX), X)
|
||||
op(0xed, AbsoluteRead, fp(SBC), A)
|
||||
op(0xee, AbsoluteModify, fp(INC))
|
||||
op(0xf0, Branch, Z == 1)
|
||||
op(0xf1, IndirectYRead, fp(SBC), A)
|
||||
op(0xf4, NoOperationZeroPage, X)
|
||||
op(0xf5, ZeroPageRead, fp(SBC), A, X)
|
||||
op(0xf6, ZeroPageModify, fp(INC), X)
|
||||
op(0xf8, Set, D)
|
||||
op(0xf9, AbsoluteRead, fp(SBC), A, Y)
|
||||
op(0xfa, NoOperation)
|
||||
op(0xfc, NoOperationAbsolute, X)
|
||||
op(0xfd, AbsoluteRead, fp(SBC), A, X)
|
||||
op(0xfe, AbsoluteModify, fp(INC), X)
|
||||
}
|
||||
|
||||
//unimplemented instruction
|
||||
debug(unusual, "[6502] unimplemented opcode ", hex(code));
|
||||
return instructionNoOperation();
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,11 @@ auto MOS6502::instructionIndirectYRead(fp alu, n8& data) -> void {
|
||||
L data = ALU(read(absolute + Y));
|
||||
}
|
||||
|
||||
auto MOS6502::instructionIndirectYRead(fp alu, n8& data, n8& data2) -> void {
|
||||
instructionIndirectYRead(alu, data);
|
||||
data2 = data;
|
||||
}
|
||||
|
||||
auto MOS6502::instructionIndirectYWrite(n8& data) -> void {
|
||||
auto zeroPage = operand();
|
||||
n16 absolute = load(zeroPage + 0);
|
||||
@@ -139,6 +144,34 @@ auto MOS6502::instructionNoOperation() -> void {
|
||||
L idle();
|
||||
}
|
||||
|
||||
auto MOS6502::instructionNoOperationAbsolute() -> void {
|
||||
n16 absolute = operand();
|
||||
absolute |= operand() << 8;
|
||||
L read(absolute);
|
||||
}
|
||||
|
||||
auto MOS6502::instructionNoOperationAbsolute(n8 index) -> void {
|
||||
n16 absolute = operand();
|
||||
absolute |= operand() << 8;
|
||||
idlePageCrossed(absolute, absolute + index);
|
||||
L read(absolute + index);
|
||||
}
|
||||
|
||||
auto MOS6502::instructionNoOperationImmediate() -> void {
|
||||
L operand();
|
||||
}
|
||||
|
||||
auto MOS6502::instructionNoOperationZeroPage() -> void {
|
||||
auto zeroPage = operand();
|
||||
L load(zeroPage);
|
||||
}
|
||||
|
||||
auto MOS6502::instructionNoOperationZeroPage(n8 index) -> void {
|
||||
auto zeroPage = operand();
|
||||
load(zeroPage);
|
||||
L load(zeroPage + index);
|
||||
}
|
||||
|
||||
auto MOS6502::instructionPull(n8& data) -> void {
|
||||
idle();
|
||||
idle();
|
||||
|
||||
@@ -31,7 +31,12 @@ struct MOS6502 {
|
||||
using fp = auto (MOS6502::*)(n8) -> n8;
|
||||
auto algorithmADC(n8) -> n8;
|
||||
auto algorithmAND(n8) -> n8;
|
||||
auto algorithmANC(n8) -> n8;
|
||||
auto algorithmARR(n8) -> n8;
|
||||
auto algorithmASL(n8) -> n8;
|
||||
auto algorithmATX(n8) -> n8;
|
||||
auto algorithmALR(n8) -> n8;
|
||||
auto algorithmAXS(n8) -> n8;
|
||||
auto algorithmBIT(n8) -> n8;
|
||||
auto algorithmCMP(n8) -> n8;
|
||||
auto algorithmCPX(n8) -> n8;
|
||||
@@ -45,6 +50,7 @@ struct MOS6502 {
|
||||
auto algorithmROL(n8) -> n8;
|
||||
auto algorithmROR(n8) -> n8;
|
||||
auto algorithmSBC(n8) -> n8;
|
||||
auto algorithmSLO(n8) -> n8;
|
||||
|
||||
//instruction.cpp
|
||||
auto interrupt() -> void;
|
||||
@@ -66,10 +72,16 @@ struct MOS6502 {
|
||||
auto instructionIndirectXRead(fp alu, n8& data) -> void;
|
||||
auto instructionIndirectXWrite(n8& data) -> void;
|
||||
auto instructionIndirectYRead(fp alu, n8& data) -> void;
|
||||
auto instructionIndirectYRead(fp, n8& data, n8& data2) -> void;
|
||||
auto instructionIndirectYWrite(n8& data) -> void;
|
||||
auto instructionJumpAbsolute() -> void;
|
||||
auto instructionJumpIndirect() -> void;
|
||||
auto instructionNoOperation() -> void;
|
||||
auto instructionNoOperationAbsolute() -> void;
|
||||
auto instructionNoOperationAbsolute(n8 index) -> void;
|
||||
auto instructionNoOperationImmediate() -> void;
|
||||
auto instructionNoOperationZeroPage() -> void;
|
||||
auto instructionNoOperationZeroPage(n8 index) -> void;
|
||||
auto instructionPull(n8& data) -> void;
|
||||
auto instructionPullP() -> void;
|
||||
auto instructionPush(n8& data) -> void;
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
auto SH2::Recompiler::invalidate(u32 address) -> void {
|
||||
auto pool = pools[address >> 8 & 0xffffff];
|
||||
if(!pool) return;
|
||||
memory::jitprotect(false);
|
||||
pool->blocks[address >> 1 & 0x7f] = nullptr;
|
||||
memory::jitprotect(true);
|
||||
}
|
||||
|
||||
auto SH2::Recompiler::pool(u32 address) -> Pool* {
|
||||
auto& pool = pools[address >> 8 & 0xffffff];
|
||||
if(!pool) pool = (Pool*)allocator.acquire(sizeof(Pool));
|
||||
@@ -7,7 +15,9 @@ auto SH2::Recompiler::pool(u32 address) -> Pool* {
|
||||
auto SH2::Recompiler::block(u32 address) -> Block* {
|
||||
if(auto block = pool(address)->blocks[address >> 1 & 0x7f]) return block;
|
||||
auto block = emit(address);
|
||||
return pool(address)->blocks[address >> 1 & 0x7f] = block;
|
||||
pool(address)->blocks[address >> 1 & 0x7f] = block;
|
||||
memory::jitprotect(true);
|
||||
return block;
|
||||
}
|
||||
|
||||
alwaysinline auto SH2::Recompiler::emitInstruction(u16 opcode) -> bool {
|
||||
@@ -25,34 +35,37 @@ alwaysinline auto SH2::Recompiler::emitInstruction(u16 opcode) -> bool {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CCR mem(sreg(1), offsetof(Registers, CCR))
|
||||
|
||||
auto SH2::Recompiler::emit(u32 address) -> Block* {
|
||||
if(unlikely(allocator.available() < 1_MiB)) {
|
||||
print("SH2 allocator flush\n");
|
||||
memory::jitprotect(false);
|
||||
allocator.release(bump_allocator::zero_fill);
|
||||
memory::jitprotect(true);
|
||||
reset();
|
||||
}
|
||||
|
||||
auto block = (Block*)allocator.acquire(sizeof(Block));
|
||||
block->code = allocator.acquire();
|
||||
bind({block->code, allocator.available()});
|
||||
beginFunction(2);
|
||||
|
||||
bool hasBranched = 0;
|
||||
while(true) {
|
||||
u16 instruction = self.readWord(address);
|
||||
bool branched = emitInstruction(instruction);
|
||||
mov(rax, mem64(&self.CCR));
|
||||
inc(rax);
|
||||
mov(mem64(&self.CCR), rax);
|
||||
call(&SH2::instructionEpilogue, &self);
|
||||
add64(CCR, CCR, imm(1));
|
||||
call(&SH2::instructionEpilogue);
|
||||
address += 2;
|
||||
if(hasBranched || (address & 0xfe) == 0) break; //block boundary
|
||||
hasBranched = branched;
|
||||
test(al, al);
|
||||
jz(imm8(1));
|
||||
ret();
|
||||
testJumpEpilog();
|
||||
}
|
||||
ret();
|
||||
jumpEpilog();
|
||||
|
||||
memory::jitprotect(false);
|
||||
block->code = endFunction();
|
||||
|
||||
allocator.reserve(size());
|
||||
return block;
|
||||
}
|
||||
|
||||
#undef CCR
|
||||
@@ -1,8 +1,8 @@
|
||||
template<typename... P>
|
||||
auto SH2::hint(P&&... p) const -> string {
|
||||
if(1) return {};
|
||||
if(1) return {"\e[0m\e[37m", forward<P>(p)..., "\e[0m"};
|
||||
return {forward<P>(p)...};
|
||||
if(1) return {"\e[0m\e[37m", std::forward<P>(p)..., "\e[0m"};
|
||||
return {std::forward<P>(p)...};
|
||||
}
|
||||
|
||||
auto SH2::disassembleInstruction() -> string {
|
||||
|
||||
@@ -34,22 +34,16 @@ auto SH2::push(u32 data) -> void {
|
||||
|
||||
auto SH2::interrupt(u8 level, u8 vector) -> void {
|
||||
push(SR);
|
||||
push(PC);
|
||||
push(PC - 4);
|
||||
jump(readLong(VBR + vector * 4) + 4);
|
||||
SR.I = level;
|
||||
}
|
||||
|
||||
auto SH2::exception(u8 vector) -> void {
|
||||
push(SR);
|
||||
push(PC - 2);
|
||||
jump(readLong(VBR + vector * 4) + 4);
|
||||
}
|
||||
|
||||
auto SH2::addressErrorCPU() -> void {
|
||||
static constexpr u8 vector = 0x09;
|
||||
SP &= ~3; //not accurate; but prevents infinite recursion
|
||||
push(SR);
|
||||
push(PC);
|
||||
push(PC - 4);
|
||||
jump(readLong(VBR + vector * 4) + 4);
|
||||
}
|
||||
|
||||
@@ -57,23 +51,23 @@ auto SH2::addressErrorDMA() -> void {
|
||||
static constexpr u8 vector = 0x0a;
|
||||
SP &= ~3; //not accurate; but prevents infinite recursion
|
||||
push(SR);
|
||||
push(PC);
|
||||
push(PC - 4);
|
||||
jump(readLong(VBR + vector * 4) + 4);
|
||||
}
|
||||
|
||||
auto SH2::illegalInstruction() -> void {
|
||||
if(inDelaySlot()) return illegalSlotInstruction();
|
||||
debug(unusual, "[SH2] illegal instruction: 0x", hex(busReadWord(PC - 4), 4L), " @ 0x", hex(PC - 4));
|
||||
static constexpr u8 vector = 0x04;
|
||||
push(SR);
|
||||
push(PC);
|
||||
push(PC - 4);
|
||||
jump(readLong(VBR + vector * 4) + 4);
|
||||
debug(unusual, "[SH2] illegal instruction: 0x", hex(busReadWord(PC - 4), 4L));
|
||||
}
|
||||
|
||||
auto SH2::illegalSlotInstruction() -> void {
|
||||
debug(unusual, "[SH2] illegal slot instruction: 0x", hex(busReadWord(PC - 4), 4L));
|
||||
static constexpr u8 vector = 0x06;
|
||||
push(SR);
|
||||
push(PC - 2);
|
||||
push(PPC - 4);
|
||||
jump(readLong(VBR + vector * 4) + 4);
|
||||
debug(unusual, "[SH2] illegal slot instruction: 0x", hex(busReadWord(PC - 4), 4L));
|
||||
}
|
||||
@@ -37,7 +37,7 @@ auto SH2::instruction() -> void {
|
||||
do {
|
||||
auto block = recompiler.block(PC - 4);
|
||||
block->execute(*this);
|
||||
} while (CCR <= recompiler.min_cycles);
|
||||
} while (CCR < cyclesUntilSync);
|
||||
|
||||
step(CCR);
|
||||
CCR = 0;
|
||||
@@ -45,7 +45,7 @@ auto SH2::instruction() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto SH2::instructionEpilogue() -> bool {
|
||||
auto SH2::instructionEpilogue() -> s32 {
|
||||
switch(PPM) {
|
||||
case Branch::Step: PC = PC + 2; return 0;
|
||||
case Branch::Slot: PC = PC + 2; PPM = Branch::Take; return 0;
|
||||
|
||||
@@ -5,9 +5,10 @@ auto SH2::ADD(u32 m, u32 n) -> void {
|
||||
|
||||
//ADDC Rm,Rn
|
||||
auto SH2::ADDC(u32 m, u32 n) -> void {
|
||||
bool c = (u64)R[n] + R[m] >> 32;
|
||||
R[n] = R[n] + R[m] + SR.T;
|
||||
SR.T = c;
|
||||
u32 a = R[n];
|
||||
u32 b = a + R[m];
|
||||
R[n] = b + SR.T;
|
||||
SR.T = a > b || b > R[n];
|
||||
}
|
||||
|
||||
//ADD #imm,Rn
|
||||
@@ -17,9 +18,11 @@ auto SH2::ADDI(u32 i, u32 n) -> void {
|
||||
|
||||
//ADDV Rm,Rn
|
||||
auto SH2::ADDV(u32 m, u32 n) -> void {
|
||||
bool v = ~(R[n] ^ R[m]) & (R[n] ^ R[n] + R[m]) >> 31;
|
||||
u32 d = ((s32)R[n] < 0);
|
||||
u32 s = ((s32)R[m] < 0) + d;
|
||||
R[n] += R[m];
|
||||
SR.T = v;
|
||||
u32 r = ((s32)R[n] < 0) + d;
|
||||
SR.T = s != 1 ? r == 1 : 0;
|
||||
}
|
||||
|
||||
//AND Rm,Rn
|
||||
@@ -140,7 +143,11 @@ auto SH2::CMPPZ(u32 n) -> void {
|
||||
|
||||
//CMP/STR Rm,Rn
|
||||
auto SH2::CMPSTR(u32 m, u32 n) -> void {
|
||||
SR.T = bool(R[n] ^ R[m]);
|
||||
u32 t = R[n] ^ R[m];
|
||||
SR.T = !(t & 0xff << 24)
|
||||
|| !(t & 0xff << 16)
|
||||
|| !(t & 0xff << 8)
|
||||
|| !(t & 0xff);
|
||||
}
|
||||
|
||||
//DIV0S Rm,Rn
|
||||
@@ -353,8 +360,9 @@ auto SH2::MOVBLG(u32 d) -> void {
|
||||
|
||||
//MOV.B Rm,@-Rn
|
||||
auto SH2::MOVBM(u32 m, u32 n) -> void {
|
||||
u32 t = R[m];
|
||||
R[n] -= 1;
|
||||
writeByte(R[n], R[m]);
|
||||
writeByte(R[n], t);
|
||||
}
|
||||
|
||||
//MOV.B @Rm+,Rn
|
||||
@@ -405,8 +413,9 @@ auto SH2::MOVWLG(u32 d) -> void {
|
||||
|
||||
//MOV.W Rm,@-Rn
|
||||
auto SH2::MOVWM(u32 m, u32 n) -> void {
|
||||
u32 t = R[m];
|
||||
R[n] -= 2;
|
||||
writeWord(R[n], R[m]);
|
||||
writeWord(R[n], t);
|
||||
}
|
||||
|
||||
//MOV.W @Rm+,Rn
|
||||
@@ -457,8 +466,9 @@ auto SH2::MOVLLG(u32 d) -> void {
|
||||
|
||||
//MOV.L Rm,@-Rn
|
||||
auto SH2::MOVLM(u32 m, u32 n) -> void {
|
||||
u32 t = R[m];
|
||||
R[n] -= 4;
|
||||
writeLong(R[n], R[m]);
|
||||
writeLong(R[n], t);
|
||||
}
|
||||
|
||||
//MOV.L @Rm+,Rn
|
||||
@@ -493,18 +503,21 @@ auto SH2::MOVI(u32 i, u32 n) -> void {
|
||||
}
|
||||
|
||||
//MOV.W @(disp,PC),Rn
|
||||
auto SH2::MOVWI(u32 d, u32 n) -> void {
|
||||
R[n] = (s16)readWord(PC + d * 2);
|
||||
noinline auto SH2::MOVWI(u32 d, u32 n) -> void {
|
||||
u32 pc = inDelaySlot() ? PPC - 2 : PC;
|
||||
R[n] = (s16)readWord(pc + d * 2);
|
||||
}
|
||||
|
||||
//MOV.L @(disp,PC),Rn
|
||||
auto SH2::MOVLI(u32 d, u32 n) -> void {
|
||||
R[n] = readLong((PC & ~3) + d * 4);
|
||||
noinline auto SH2::MOVLI(u32 d, u32 n) -> void {
|
||||
u32 pc = inDelaySlot() ? PPC - 2 : PC;
|
||||
R[n] = readLong((pc & ~3) + d * 4);
|
||||
}
|
||||
|
||||
//MOVA @(disp,PC),R0
|
||||
auto SH2::MOVA(u32 d) -> void {
|
||||
R[0] = (PC & ~3) + d * 4 - inDelaySlot() * 2;
|
||||
noinline auto SH2::MOVA(u32 d) -> void {
|
||||
u32 pc = inDelaySlot() ? PPC - 2 : PC;
|
||||
R[0] = (pc & ~3) + d * 4;
|
||||
}
|
||||
|
||||
//MOVT Rn
|
||||
@@ -536,7 +549,7 @@ auto SH2::NEG(u32 m, u32 n) -> void {
|
||||
auto SH2::NEGC(u32 m, u32 n) -> void {
|
||||
u32 t = 0 - R[m];
|
||||
R[n] = t - SR.T;
|
||||
SR.T = (s32)t >= 0 || t < R[n];
|
||||
SR.T = 0 < t || t < R[n];
|
||||
}
|
||||
|
||||
//NOP
|
||||
@@ -592,7 +605,7 @@ auto SH2::ROTR(u32 n) -> void {
|
||||
|
||||
//RTE
|
||||
auto SH2::RTE() -> void {
|
||||
delaySlot(readLong(SP + 0));
|
||||
delaySlot(readLong(SP + 0) + 4);
|
||||
SR = readLong(SP + 4);
|
||||
SP += 8;
|
||||
}
|
||||
@@ -790,10 +803,7 @@ auto SH2::TAS(u32 n) -> void {
|
||||
) return (void)(exceptions |= AddressErrorCPU);
|
||||
}
|
||||
|
||||
auto cacheEnable = cache.enable;
|
||||
cache.enable = 0;
|
||||
u8 b = readByte(R[n]);
|
||||
cache.enable = cacheEnable;
|
||||
u8 b = readByte<Bus::Internal>(R[n]);
|
||||
SR.T = b == 0;
|
||||
writeByte(R[n], b | 0x80);
|
||||
}
|
||||
@@ -801,7 +811,7 @@ auto SH2::TAS(u32 n) -> void {
|
||||
//TRAPA #imm
|
||||
auto SH2::TRAPA(u32 i) -> void {
|
||||
push(SR);
|
||||
push(PC + 2);
|
||||
push(PC - 2);
|
||||
branch(readLong(VBR + i * 4) + 4);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -133,6 +133,7 @@ auto SH2::WDT::serialize(serializer& s) -> void {
|
||||
s(rstcsr.rste);
|
||||
s(rstcsr.wovf);
|
||||
s(wtcnt);
|
||||
s(pendingIRQ);
|
||||
}
|
||||
|
||||
auto SH2::UBC::serialize(serializer& s) -> void {
|
||||
|
||||
@@ -3,14 +3,26 @@
|
||||
|
||||
namespace ares {
|
||||
|
||||
#define SP R[15]
|
||||
#define SP R[15]
|
||||
#define R regs.R
|
||||
#define PC regs.PC
|
||||
#define PR regs.PR
|
||||
#define GBR regs.GBR
|
||||
#define VBR regs.VBR
|
||||
#define MAC regs.MAC
|
||||
#define MACL regs.MACL
|
||||
#define MACH regs.MACH
|
||||
#define CCR regs.CCR
|
||||
#define SR regs.SR
|
||||
#define PPC regs.PPC
|
||||
#define PPM regs.PPM
|
||||
#define ET regs.ET
|
||||
#define ID regs.ID
|
||||
|
||||
#include "sh7604/sh7604.cpp"
|
||||
#include "exceptions.cpp"
|
||||
#include "instruction.cpp"
|
||||
#include "instructions.cpp"
|
||||
#include "cached.cpp"
|
||||
//#include "recompiler.cpp"
|
||||
#include "serialization.cpp"
|
||||
#include "disassembler.cpp"
|
||||
|
||||
@@ -30,13 +42,15 @@ auto SH2::power(bool reset) -> void {
|
||||
SR.M = undefined;
|
||||
PPC = 0;
|
||||
PPM = Branch::Step;
|
||||
ET = 0;
|
||||
ID = 0;
|
||||
exceptions = !reset ? ResetCold : ResetWarm;
|
||||
cyclesUntilSync = 0;
|
||||
|
||||
cache = {*this};
|
||||
intc = {*this};
|
||||
dmac = {*this};
|
||||
sci = {*this};
|
||||
sci = {*this, reset ? sci.link : maybe<SH2&>()};
|
||||
wdt = {};
|
||||
ubc = {};
|
||||
frt = {*this};
|
||||
@@ -47,10 +61,29 @@ auto SH2::power(bool reset) -> void {
|
||||
cache.power();
|
||||
|
||||
if constexpr(Accuracy::Recompiler) {
|
||||
auto buffer = ares::Memory::FixedAllocator::get().acquire(512_MiB);
|
||||
recompiler.allocator.resize(512_MiB, bump_allocator::executable | bump_allocator::zero_fill, buffer);
|
||||
auto buffer = ares::Memory::FixedAllocator::get().tryAcquire(64_MiB);
|
||||
recompiler.allocator.resize(64_MiB, bump_allocator::executable | bump_allocator::zero_fill, buffer);
|
||||
recompiler.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#undef SP
|
||||
#undef R
|
||||
#undef PC
|
||||
#undef PR
|
||||
#undef GBR
|
||||
#undef VBR
|
||||
#undef MAC
|
||||
#undef MACL
|
||||
#undef MACH
|
||||
#undef CCR
|
||||
#undef SR
|
||||
#undef PPC
|
||||
#undef PPM
|
||||
#undef ET
|
||||
#undef ID
|
||||
|
||||
//#include "cached.cpp"
|
||||
#include "recompiler.cpp"
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/recompiler/amd64/amd64.hpp>
|
||||
#include <nall/recompiler/generic/generic.hpp>
|
||||
|
||||
//Hitachi SH-2
|
||||
|
||||
@@ -16,7 +16,7 @@ struct SH2 {
|
||||
virtual auto busWriteLong(u32 address, u32 data) -> void = 0;
|
||||
|
||||
auto inDelaySlot() const -> bool {
|
||||
return PPM != Branch::Step;
|
||||
return regs.PPM != Branch::Step;
|
||||
}
|
||||
|
||||
//sh2.cpp
|
||||
@@ -29,7 +29,6 @@ struct SH2 {
|
||||
auto exceptionHandler() -> void;
|
||||
auto push(u32 data) -> void;
|
||||
auto interrupt(u8 level, u8 vector) -> void;
|
||||
auto exception(u8 vector) -> void;
|
||||
auto addressErrorCPU() -> void;
|
||||
auto addressErrorDMA() -> void;
|
||||
auto illegalInstruction() -> void;
|
||||
@@ -39,7 +38,7 @@ struct SH2 {
|
||||
auto jump(u32 pc) -> void;
|
||||
auto branch(u32 pc) -> void;
|
||||
auto delaySlot(u32 pc) -> void;
|
||||
auto instructionEpilogue() -> bool;
|
||||
auto instructionEpilogue() -> s32;
|
||||
auto instruction() -> void;
|
||||
auto execute(u16 opcode) -> void;
|
||||
|
||||
@@ -220,21 +219,23 @@ struct SH2 {
|
||||
u32 M;
|
||||
};
|
||||
|
||||
u32 R[16]; //general purpose registers
|
||||
u32 PC; //program counter
|
||||
u32 PR; //procedure register
|
||||
u32 GBR; //global base register
|
||||
u32 VBR; //vector base register
|
||||
union {
|
||||
u64 MAC; //multiply-and-accumulate register
|
||||
struct { u32 order_msb2(MACH, MACL); };
|
||||
};
|
||||
u32 CCR; //clock counter register
|
||||
S32 SR; //status register
|
||||
u32 PPC; //program counter for delay slots
|
||||
u32 PPM; //delay slot mode
|
||||
u32 ET; //exception triggered flag
|
||||
u32 ID; //interrupts disabled flag
|
||||
struct Registers {
|
||||
u32 R[16]; //general purpose registers
|
||||
u32 PC; //program counter
|
||||
u32 PR; //procedure register
|
||||
u32 GBR; //global base register
|
||||
u32 VBR; //vector base register
|
||||
union {
|
||||
u64 MAC; //multiply-and-accumulate register
|
||||
struct { u32 order_msb2(MACH, MACL); };
|
||||
};
|
||||
u32 CCR; //clock counter register
|
||||
S32 SR; //status register
|
||||
u32 PPC; //program counter for delay slots
|
||||
u32 PPM; //delay slot mode
|
||||
u32 ET; //exception triggered flag
|
||||
u32 ID; //interrupts disabled flag
|
||||
} regs;
|
||||
|
||||
enum : u32 {
|
||||
ResetCold = 1 << 0,
|
||||
@@ -244,14 +245,16 @@ struct SH2 {
|
||||
};
|
||||
u32 exceptions = 0; //delayed exception flags
|
||||
|
||||
struct Recompiler : recompiler::amd64 {
|
||||
using recompiler::amd64::call;
|
||||
s32 cyclesUntilSync = 0;
|
||||
s32 minCyclesBetweenSyncs = 0;
|
||||
|
||||
struct Recompiler : recompiler::generic {
|
||||
SH2& self;
|
||||
Recompiler(SH2& self) : self(self) {}
|
||||
Recompiler(SH2& self) : self(self), generic(allocator) {}
|
||||
|
||||
struct Block {
|
||||
auto execute(SH2& self) -> void {
|
||||
((void (*)(u32*, SH2*))code)(&self.R[0], &self);
|
||||
((void (*)(SH2*, Registers*))code)(&self, &self.regs);
|
||||
}
|
||||
|
||||
u8* code;
|
||||
@@ -265,29 +268,16 @@ struct SH2 {
|
||||
for(u32 index : range(1 << 24)) pools[index] = nullptr;
|
||||
}
|
||||
|
||||
auto invalidate(u32 address) -> void {
|
||||
pool(address)->blocks[address >> 1 & 0x7f] = nullptr;
|
||||
}
|
||||
|
||||
auto invalidate(u32 address) -> void;
|
||||
auto pool(u32 address) -> Pool*;
|
||||
auto block(u32 address) -> Block*;
|
||||
auto emit(u32 address) -> Block*;
|
||||
auto emitInstruction(u16 opcode) -> bool;
|
||||
|
||||
template<typename V, typename... P>
|
||||
alwaysinline auto call(V (SH2::*function)(P...)) -> void {
|
||||
static_assert(sizeof...(P) <= 5);
|
||||
if constexpr(ABI::Windows) {
|
||||
if constexpr(sizeof...(P) >= 5) mov(dis8(rsp, 0x28), ra5);
|
||||
if constexpr(sizeof...(P) >= 4) mov(dis8(rsp, 0x20), ra4);
|
||||
}
|
||||
mov(ra0, rbp);
|
||||
call(imm64{function}, rax);
|
||||
}
|
||||
auto getSR(reg dst) -> void;
|
||||
auto setSR(reg src) -> void;
|
||||
|
||||
bump_allocator allocator;
|
||||
Pool* pools[1 << 24];
|
||||
int min_cycles = 0;
|
||||
} recompiler{*this};
|
||||
|
||||
#include "sh7604/sh7604.hpp"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
auto SH2::readByte(u32 address) -> u32 {
|
||||
template<u32 Origin> auto SH2::readByte(u32 address) -> u32 {
|
||||
switch(address >> 29) {
|
||||
|
||||
case Area::Cached: {
|
||||
if(likely(cache.enable)) return cache.read<Byte>(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
if(likely(cache.enable)) return cache.read<Byte>(address);
|
||||
}
|
||||
return busReadByte(address & 0x1fff'ffff);
|
||||
}
|
||||
|
||||
@@ -11,7 +13,10 @@ auto SH2::readByte(u32 address) -> u32 {
|
||||
}
|
||||
|
||||
case Area::Data: {
|
||||
return cache.readData<Byte>(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.readData<Byte>(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::IO: {
|
||||
@@ -28,7 +33,7 @@ auto SH2::readByte(u32 address) -> u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto SH2::readWord(u32 address) -> u32 {
|
||||
template<u32 Origin> auto SH2::readWord(u32 address) -> u32 {
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
if(unlikely(address & 1)) return exceptions |= AddressErrorCPU, 0;
|
||||
}
|
||||
@@ -36,7 +41,9 @@ auto SH2::readWord(u32 address) -> u32 {
|
||||
switch(address >> 29) {
|
||||
|
||||
case Area::Cached: {
|
||||
if(likely(cache.enable)) return cache.read<Word>(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
if(likely(cache.enable)) return cache.read<Word>(address);
|
||||
}
|
||||
return busReadWord(address & 0x1fff'fffe);
|
||||
}
|
||||
|
||||
@@ -45,7 +52,10 @@ auto SH2::readWord(u32 address) -> u32 {
|
||||
}
|
||||
|
||||
case Area::Data: {
|
||||
return cache.readData<Word>(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.readData<Word>(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::IO: {
|
||||
@@ -60,7 +70,7 @@ auto SH2::readWord(u32 address) -> u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto SH2::readLong(u32 address) -> u32 {
|
||||
template<u32 Origin> auto SH2::readLong(u32 address) -> u32 {
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
if(unlikely(address & 3)) return exceptions |= AddressErrorCPU, 0;
|
||||
}
|
||||
@@ -68,7 +78,9 @@ auto SH2::readLong(u32 address) -> u32 {
|
||||
switch(address >> 29) {
|
||||
|
||||
case Area::Cached: {
|
||||
if(likely(cache.enable)) return cache.read<Long>(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
if(likely(cache.enable)) return cache.read<Long>(address);
|
||||
}
|
||||
return busReadLong(address & 0x1fff'fffc);
|
||||
}
|
||||
|
||||
@@ -77,11 +89,17 @@ auto SH2::readLong(u32 address) -> u32 {
|
||||
}
|
||||
|
||||
case Area::Address: {
|
||||
return cache.readAddress(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.readAddress(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::Data: {
|
||||
return cache.readData<Long>(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.readData<Long>(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::IO: {
|
||||
@@ -104,7 +122,7 @@ auto SH2::readLong(u32 address) -> u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto SH2::writeByte(u32 address, u32 data) -> void {
|
||||
template<u32 Origin> auto SH2::writeByte(u32 address, u32 data) -> void {
|
||||
if constexpr(Accuracy::Recompiler) {
|
||||
recompiler.invalidate(address);
|
||||
}
|
||||
@@ -112,20 +130,29 @@ auto SH2::writeByte(u32 address, u32 data) -> void {
|
||||
switch(address >> 29) {
|
||||
|
||||
case Area::Cached: {
|
||||
if(likely(cache.enable)) cache.write<Byte>(address, data);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
if(likely(cache.enable)) cache.write<Byte>(address, data);
|
||||
}
|
||||
return busWriteByte(address & 0x1fff'ffff, data);
|
||||
}
|
||||
|
||||
case Area::Uncached: {
|
||||
cyclesUntilSync = 0;
|
||||
return busWriteByte(address & 0x1fff'ffff, data);
|
||||
}
|
||||
|
||||
case Area::Purge: {
|
||||
return cache.purge(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.purge(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::Data: {
|
||||
return cache.writeData<Byte>(address, data);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.writeData<Byte>(address, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::IO: {
|
||||
@@ -140,7 +167,7 @@ auto SH2::writeByte(u32 address, u32 data) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto SH2::writeWord(u32 address, u32 data) -> void {
|
||||
template<u32 Origin> auto SH2::writeWord(u32 address, u32 data) -> void {
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
if(unlikely(address & 1)) return (void)(exceptions |= AddressErrorCPU);
|
||||
}
|
||||
@@ -152,20 +179,29 @@ auto SH2::writeWord(u32 address, u32 data) -> void {
|
||||
switch(address >> 29) {
|
||||
|
||||
case Area::Cached: {
|
||||
if(likely(cache.enable)) cache.write<Word>(address, data);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
if(likely(cache.enable)) cache.write<Word>(address, data);
|
||||
}
|
||||
return busWriteWord(address & 0x1fff'fffe, data);
|
||||
}
|
||||
|
||||
case Area::Uncached: {
|
||||
cyclesUntilSync = 0;
|
||||
return busWriteWord(address & 0x1fff'fffe, data);
|
||||
}
|
||||
|
||||
case Area::Purge: {
|
||||
return cache.purge(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.purge(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::Data: {
|
||||
return cache.writeData<Word>(address, data);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.writeData<Word>(address, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::IO: {
|
||||
@@ -177,7 +213,7 @@ auto SH2::writeWord(u32 address, u32 data) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto SH2::writeLong(u32 address, u32 data) -> void {
|
||||
template<u32 Origin> auto SH2::writeLong(u32 address, u32 data) -> void {
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
if(unlikely(address & 3)) return (void)(exceptions |= AddressErrorCPU);
|
||||
}
|
||||
@@ -190,24 +226,36 @@ auto SH2::writeLong(u32 address, u32 data) -> void {
|
||||
switch(address >> 29) {
|
||||
|
||||
case Area::Cached: {
|
||||
if(likely(cache.enable)) cache.write<Long>(address, data);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
if(likely(cache.enable)) cache.write<Long>(address, data);
|
||||
}
|
||||
return busWriteLong(address & 0x1fff'fffc, data);
|
||||
}
|
||||
|
||||
case Area::Uncached: {
|
||||
cyclesUntilSync = 0;
|
||||
return busWriteLong(address & 0x1fff'fffc, data);
|
||||
}
|
||||
|
||||
case Area::Purge: {
|
||||
return cache.purge(address);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.purge(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::Address: {
|
||||
return cache.writeAddress(address, data);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.writeAddress(address, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::Data: {
|
||||
return cache.writeData<Long>(address, data);
|
||||
if constexpr(Origin == Bus::Cache) {
|
||||
return cache.writeData<Long>(address, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Area::IO: {
|
||||
|
||||
@@ -18,7 +18,7 @@ auto SH2::DMAC::transfer(bool c) -> void {
|
||||
switch(chcr[c].ts) {
|
||||
|
||||
case 0: //8-bit
|
||||
self->writeByte(dar[c], self->readByte(sar[c]));
|
||||
self->writeByte<Bus::Internal>(dar[c], self->readByte<Bus::Internal>(sar[c]));
|
||||
break;
|
||||
case 1: //16-bit
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
@@ -27,7 +27,7 @@ auto SH2::DMAC::transfer(bool c) -> void {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self->writeWord(dar[c], self->readWord(sar[c]));
|
||||
self->writeWord<Bus::Internal>(dar[c], self->readWord<Bus::Internal>(sar[c]));
|
||||
break;
|
||||
case 2: //32-bit
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
@@ -36,7 +36,7 @@ auto SH2::DMAC::transfer(bool c) -> void {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self->writeLong(dar[c], self->readLong(sar[c]));
|
||||
self->writeLong<Bus::Internal>(dar[c], self->readLong<Bus::Internal>(sar[c]));
|
||||
break;
|
||||
case 3: //32-bit x4
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
@@ -45,10 +45,10 @@ auto SH2::DMAC::transfer(bool c) -> void {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self->writeLong(dar[c] + 0, self->readLong(sar[c] + 0));
|
||||
self->writeLong(dar[c] + 4, self->readLong(sar[c] + 4));
|
||||
self->writeLong(dar[c] + 8, self->readLong(sar[c] + 8));
|
||||
self->writeLong(dar[c] + 12, self->readLong(sar[c] + 12));
|
||||
self->writeLong<Bus::Internal>(dar[c] + 0, self->readLong<Bus::Internal>(sar[c] + 0));
|
||||
self->writeLong<Bus::Internal>(dar[c] + 4, self->readLong<Bus::Internal>(sar[c] + 4));
|
||||
self->writeLong<Bus::Internal>(dar[c] + 8, self->readLong<Bus::Internal>(sar[c] + 8));
|
||||
self->writeLong<Bus::Internal>(dar[c] + 12, self->readLong<Bus::Internal>(sar[c] + 12));
|
||||
sar[c] += 16; //always increments regardless of chcr[c].sm
|
||||
tcr[c] -= 3; //always decrements by four instead of by one
|
||||
break;
|
||||
|
||||
@@ -22,6 +22,13 @@ print("dreq1\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(self->wdt.pendingIRQ) {
|
||||
if(self->SR.I < ipra.wdtip) {
|
||||
self->interrupt(ipra.wdtip, vcrwdt.witv);
|
||||
self->wdt.pendingIRQ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(self->sci.pendingTransmitEmptyIRQ) {
|
||||
if(self->SR.I < iprb.sciip) {
|
||||
self->interrupt(iprb.sciip, vcrb.stxv);
|
||||
|
||||
@@ -553,7 +553,7 @@ auto SH2::internalWriteByte(u32 address, n8 data) -> void {
|
||||
sci.scr.te = data.bit(5);
|
||||
sci.scr.rie = data.bit(6);
|
||||
sci.scr.tie = data.bit(7);
|
||||
if(!te && sci.scr.te) sci.run();
|
||||
if(te && !sci.scr.te) sci.ssr.tdre = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -563,7 +563,8 @@ auto SH2::internalWriteByte(u32 address, n8 data) -> void {
|
||||
return;
|
||||
|
||||
//SSR: serial status register
|
||||
case 0xffff'fe04:
|
||||
case 0xffff'fe04: {
|
||||
bool tdre = sci.ssr.tdre;
|
||||
sci.ssr.mpbt = data.bit(0);
|
||||
//sci.ssr.mpb = data.bit(1) = readonly;
|
||||
//sci.ssr.tend = data.bit(2) = readonly;
|
||||
@@ -572,8 +573,9 @@ auto SH2::internalWriteByte(u32 address, n8 data) -> void {
|
||||
sci.ssr.orer &= data.bit(5);
|
||||
sci.ssr.rdrf &= data.bit(6);
|
||||
sci.ssr.tdre &= data.bit(7);
|
||||
if(sci.scr.te) sci.run();
|
||||
if(tdre && !sci.ssr.tdre) sci.run();
|
||||
return;
|
||||
}
|
||||
|
||||
//RDR: receive data register
|
||||
case 0xffff'fe05:
|
||||
@@ -675,17 +677,26 @@ auto SH2::internalWriteByte(u32 address, n8 data) -> void {
|
||||
dmac.drcr[1] = data.bit(0,1);
|
||||
return;
|
||||
|
||||
//WTCSR: watchdog timer control/status register
|
||||
//WTCSR/WTCNT select
|
||||
case 0xffff'fe80:
|
||||
wdt.wtcsr.cks = data.bit(0,2);
|
||||
wdt.wtcsr.tme = data.bit(5);
|
||||
wdt.wtcsr.wtit = data.bit(6);
|
||||
wdt.wtcsr.ovf &= data.bit(7);
|
||||
wdt.select = data.bit(0,7);
|
||||
return;
|
||||
|
||||
//WTCNT: watchdog timer counter
|
||||
//WTCSR/WTCNT data
|
||||
case 0xffff'fe81:
|
||||
wdt.wtcnt = data.bit(0,7);
|
||||
//WTCSR: watchdog timer control/status register
|
||||
if(wdt.select == 0xa5) {
|
||||
wdt.wtcsr.cks = data.bit(0,2);
|
||||
wdt.wtcsr.tme = data.bit(5);
|
||||
wdt.wtcsr.wtit = data.bit(6);
|
||||
wdt.wtcsr.ovf &= data.bit(7);
|
||||
if(!wdt.wtcsr.tme) wdt.wtcnt = 0;
|
||||
}
|
||||
|
||||
//WTCNT: watchdog timer counter
|
||||
if(wdt.select == 0x5a) {
|
||||
wdt.wtcnt = data.bit(0,7);
|
||||
}
|
||||
return;
|
||||
|
||||
//RSTCSR: reset control/status register
|
||||
|
||||
Loaded 100 of 1064 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user