mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-12 02:24:31 +00:00
Compare commits
977 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
015a1fbd53 | ||
|
318b61b4d7 | ||
|
60640517ca | ||
|
051e38f034 | ||
|
300054b9f7 | ||
|
4f9e1e3a6b | ||
|
856bc27bf1 | ||
|
408b774b42 | ||
|
f82ef5aad4 | ||
|
a92df41eb5 | ||
|
605990929d | ||
|
925ca28659 | ||
|
41e3fa7aa7 | ||
|
5fa27448f8 | ||
|
c3428bdaed | ||
|
0539de9c4e | ||
|
eed87164b1 | ||
|
0fe726c503 | ||
|
f7f2113f1c | ||
|
49d931f5cc | ||
|
4dbd63de08 | ||
|
3a53c349a0 | ||
|
a9fe5d5c87 | ||
|
9cecccf5da | ||
|
6cb3bbaa2d | ||
|
0ff6a0bb53 | ||
|
8ba57dec03 | ||
|
d749c305ed | ||
|
f46ac2d0ed | ||
|
d7b7152315 | ||
|
da1d52033b | ||
|
01ddc24c02 | ||
|
53a3e88d16 | ||
|
bc8d1cc384 | ||
|
ed2ba63a5f | ||
|
8a2c009653 | ||
|
55690a4c7f | ||
|
161ad4b143 | ||
|
d701990df4 | ||
|
95a2d1013c | ||
|
5d4f3c0b3e | ||
|
f8e4023307 | ||
|
609aba7c73 | ||
|
bc7ab0eba1 | ||
|
56f271c8ad | ||
|
11190cff1d | ||
|
348a593dc1 | ||
|
b67bb50f4a | ||
|
1174f651ab | ||
|
dad9777c3e | ||
|
4f6285a8e7 | ||
|
20cecf4702 | ||
|
5763eabff4 | ||
|
0fc753949d | ||
|
083c1b7ca7 | ||
|
8f6b1b11e5 | ||
|
53b7d19c10 | ||
|
b0b4f5e51a | ||
|
b7414aa59c | ||
|
f449045118 | ||
|
1e9ddada37 | ||
|
55d59a1854 | ||
|
beb9f38514 | ||
|
00b1865fc8 | ||
|
0f545608c4 | ||
|
bde2047184 | ||
|
0a22d8fb9e | ||
|
1bef37d504 | ||
|
7f5d290b66 | ||
|
9461e6f285 | ||
|
3f59a03f29 | ||
|
062b581b55 | ||
|
58d3fdc1c2 | ||
|
2f546842a7 | ||
|
f089a85908 | ||
|
a6e453a452 | ||
|
3adf3dc547 | ||
|
1d0ea96ae9 | ||
|
a4cb17a1cb | ||
|
733da3161b | ||
|
1b1a0f553d | ||
|
972619c1fe | ||
|
61086d5360 | ||
|
37513d726c | ||
|
6510bee327 | ||
|
cd36f3f096 | ||
|
407a6f5e31 | ||
|
2b28df280e | ||
|
eb763ed82c | ||
|
755f53cce0 | ||
|
c190ab40b0 | ||
|
a3ad82de42 | ||
|
0f6cd6904d | ||
|
79c89af6ea | ||
|
56f10a9a52 | ||
|
c679e2c067 | ||
|
0677987320 | ||
|
5fb6e6780c | ||
|
58ef91a7b1 | ||
|
e1ae65b6d1 | ||
|
65307186dc | ||
|
6fa29c204b | ||
|
8219beeb1a | ||
|
ace7e24dfb | ||
|
828c2a6883 | ||
|
f195dc313d | ||
|
5b8a005f41 | ||
|
9feb75e645 | ||
|
7f8e90bd29 | ||
|
2fd34b649d | ||
|
104054ed1a | ||
|
457b28c22c | ||
|
095c8dcd0c | ||
|
8463e9ed94 | ||
|
b6278c6144 | ||
|
1c1e1eee47 | ||
|
b37ed9ec60 | ||
|
45f3ef6920 | ||
|
2cd6c4238b | ||
|
f6ed0b33eb | ||
|
c4f4ca3f90 | ||
|
9a6780616b | ||
|
db4eca0a42 | ||
|
6d674edb48 | ||
|
c0469a044b | ||
|
b9b64eba9a | ||
|
2d74387a00 | ||
|
f66b6fc20c | ||
|
f0711a9fbc | ||
|
83a8c7215a | ||
|
a86f966cb4 | ||
|
74db978b81 | ||
|
1300546a52 | ||
|
3aeb0bba71 | ||
|
03d3efa323 | ||
|
f9c220bee0 | ||
|
114c2e2636 | ||
|
75a0e622ad | ||
|
8e2de4ee30 | ||
|
b1602261cf | ||
|
e5ed11f8ec | ||
|
c1ecfd289e | ||
|
c5cca15b4e | ||
|
fa978315e6 | ||
|
c5bffc38f4 | ||
|
88b5f6b148 | ||
|
fc04742151 | ||
|
c618d18d46 | ||
|
33bc7c00df | ||
|
1ed550d7f9 | ||
|
18b87f2c80 | ||
|
fad503ca80 | ||
|
37ec3e4605 | ||
|
70e3d23f26 | ||
|
6ebf415a52 | ||
|
5c31104d0e | ||
|
aed8b65e2b | ||
|
906e8aa2b2 | ||
|
d0703f95af | ||
|
538b00797d | ||
|
985c555518 | ||
|
5ef26a25ee | ||
|
3db0e30d12 | ||
|
a666cabae9 | ||
|
2e8d9018ef | ||
|
ae49505e67 | ||
|
09bd5503b4 | ||
|
dbe733524c | ||
|
1625f5c0f9 | ||
|
7cd49d07f2 | ||
|
a542345456 | ||
|
0b98f21443 | ||
|
c42e231e99 | ||
|
ab653af4b3 | ||
|
8653f572c8 | ||
|
39b431fb19 | ||
|
e158c5bc30 | ||
|
d0fdfda4cb | ||
|
668a5ca041 | ||
|
5e3947b8bc | ||
|
233a627c9f | ||
|
6197486d49 | ||
|
60856b974b | ||
|
d1eca5dc21 | ||
|
24b281d625 | ||
|
b0d1dee38b | ||
|
0751c51803 | ||
|
0005229c1e | ||
|
33c2353107 | ||
|
cb98297bb5 | ||
|
0e663e1da8 | ||
|
918a8c5f8b | ||
|
34938e8c62 | ||
|
da6efe52ff | ||
|
570f1caa8f | ||
|
6f638805f7 | ||
|
c92b0dc886 | ||
|
b4b216de84 | ||
|
80f5d7c735 | ||
|
01aeb46664 | ||
|
2cfd2ff624 | ||
|
ff12bbbdb6 | ||
|
e19fe5d0e2 | ||
|
9670d5f4de | ||
|
863b09c39b | ||
|
6b90de539e | ||
|
4bb53ed6ba | ||
|
e6523f3ec1 | ||
|
c8ad8c79bd | ||
|
b08cc9cb49 | ||
|
4f93dc0adf | ||
|
096f48be33 | ||
|
81398d58a2 | ||
|
7466da5651 | ||
|
c8fdde4c5e | ||
|
15583e7975 | ||
|
5acdf39566 | ||
|
2240ada5db | ||
|
9d5c10d440 | ||
|
709f350d60 | ||
|
3d7e016b42 | ||
|
b76104d145 | ||
|
702b6d6567 | ||
|
3e93004db6 | ||
|
589903c43c | ||
|
f41b54de21 | ||
|
700b848f26 | ||
|
a1f6e93e22 | ||
|
1628af2ffc | ||
|
1b5d446635 | ||
|
83a9ef772a | ||
|
1d9c3fb827 | ||
|
ff92bdb324 | ||
|
363ad7342a | ||
|
663acd3810 | ||
|
c2fc26089e | ||
|
58b464bdfc | ||
|
ed766c74e6 | ||
|
1d07b8238c | ||
|
41c6ed7c5a | ||
|
f7750af3d0 | ||
|
8854ffddee | ||
|
a487619578 | ||
|
0eab6146fc | ||
|
389ba95e5a | ||
|
84d178c0ca | ||
|
aed8f8efa8 | ||
|
38325741de | ||
|
891d5c2066 | ||
|
6b7edac6e4 | ||
|
ab2a576e1b | ||
|
5a3e4dd47b | ||
|
064c4b4312 | ||
|
cbde504057 | ||
|
949cfcfa69 | ||
|
06a005321e | ||
|
d3587f595f | ||
|
b0158ed7ce | ||
|
e5f4300e54 | ||
|
7cc6f8604e | ||
|
657960e7d0 | ||
|
aecd7f9283 | ||
|
6f1b30cd24 | ||
|
a6ba549b67 | ||
|
84ea04f61d | ||
|
0d52cf5f97 | ||
|
b15a083a15 | ||
|
a128247ef5 | ||
|
590bd934c0 | ||
|
e9826d2e7e | ||
|
58ed63cd18 | ||
|
fd1bd3032f | ||
|
d7206096ea | ||
|
f43e594eca | ||
|
ea4fe5e809 | ||
|
9fcb634510 | ||
|
c14a4515ce | ||
|
8e71180cd2 | ||
|
08f98aa32f | ||
|
e8aa9b9eb2 | ||
|
19f815eeff | ||
|
a508f7a463 | ||
|
e58d3ee060 | ||
|
268842681a | ||
|
9b357a9fbf | ||
|
6f80018b6e | ||
|
7a1153be65 | ||
|
85d4c24aba | ||
|
48c0ae8fe4 | ||
|
e835b2c68c | ||
|
10d20f5d09 | ||
|
88b31ea940 | ||
|
9cb28d23a3 | ||
|
ce5aae3f7d | ||
|
e7f0eb6746 | ||
|
65a118d1f3 | ||
|
3d2eefc7e7 | ||
|
f804c32eee | ||
|
b89ecadc3a | ||
|
6d4ff0b89a | ||
|
598003ea39 | ||
|
6ef63790a9 | ||
|
3ffd986a1c | ||
|
0b5cd4c665 | ||
|
0371b0507a | ||
|
9fa71231c4 | ||
|
32beafc12d | ||
|
09e2ef334b | ||
|
c0ce62ed2b | ||
|
d3ed485e7a | ||
|
31c878b654 | ||
|
3a0f4a0bfc | ||
|
8b88d1294d | ||
|
43fcf46d69 | ||
|
394fe0f1f1 | ||
|
872921f635 | ||
|
7248470950 | ||
|
9d87296316 | ||
|
23c67f7e38 | ||
|
bd98d95dbf | ||
|
3addb8d72b | ||
|
5545906063 | ||
|
36edfe9715 | ||
|
030b54a2b6 | ||
|
f332613922 | ||
|
088bc14b11 | ||
|
86fa8da8c5 | ||
|
abfc73299e | ||
|
bd97fd5973 | ||
|
f00e7c4a80 | ||
|
49c811b5f5 | ||
|
b6c21e071d | ||
|
e68129e47e | ||
|
72d7917415 | ||
|
53837fe132 | ||
|
08d094c786 | ||
|
a1634ab496 | ||
|
d35165bd8e | ||
|
b701ce9721 | ||
|
f3e18da416 | ||
|
131ab00304 | ||
|
26d7d58a5f | ||
|
02f92a7818 | ||
|
b6fff521e4 | ||
|
23f1308231 | ||
|
947e890c59 | ||
|
0f1714de7c | ||
|
a7d2b0f63b | ||
|
9c550a8154 | ||
|
49012a21c8 | ||
|
f136151064 | ||
|
4838728521 | ||
|
95fac5dc13 | ||
|
ac1f7884b5 | ||
|
ab411512d4 | ||
|
704495ff42 | ||
|
9acc80260f | ||
|
7759fb7e68 | ||
|
0d71724598 | ||
|
ae436f7a51 | ||
|
43ac20cbd2 | ||
|
2d90868f5c | ||
|
60987ae4a7 | ||
|
65c1d99120 | ||
|
35acf88847 | ||
|
45549b5fcd | ||
|
2eb9fb6a08 | ||
|
0d0e1083e6 | ||
|
e650f3772a | ||
|
e5ff4c65b7 | ||
|
276809f76a | ||
|
5e3840c5f1 | ||
|
6eace2a3ef | ||
|
7817b23857 | ||
|
432854aeb5 | ||
|
433c8f9c3c | ||
|
ea25dbfd1e | ||
|
10f8318e79 | ||
|
17ff0c4f65 | ||
|
9abd653fb9 | ||
|
ff6753fcdf | ||
|
a65551f652 | ||
|
f0d807a0fe | ||
|
dfcdbe5b6a | ||
|
53e73238fd | ||
|
581454db69 | ||
|
63d501b629 | ||
|
60bd877ed9 | ||
|
44574465c5 | ||
|
2b7382a014 | ||
|
584b6df40d | ||
|
e55f61deb2 | ||
|
a6c6a1c6da | ||
|
bdb5abe47b | ||
|
dbe0ebc93e | ||
|
1c2f66e855 | ||
|
7eee3f9e5e | ||
|
b7f069e1bd | ||
|
51c8396e32 | ||
|
0efe649ca5 | ||
|
75db0018bc | ||
|
2a9e1ea045 | ||
|
8feb8aaadc | ||
|
b8f4385501 | ||
|
d8b6d87a1c | ||
|
f10702b3ca | ||
|
88248d7062 | ||
|
5ca1659bcc | ||
|
59530a12fd | ||
|
aab2dd68b6 | ||
|
83f5065642 | ||
|
7e3a331eba | ||
|
b5932edff3 | ||
|
12846317cb | ||
|
051f0546c7 | ||
|
eece8c54a4 | ||
|
69ba94e379 | ||
|
0de7057d6f | ||
|
3dcbb40c55 | ||
|
91b263f0cf | ||
|
bcd558867d | ||
|
a9c8ef642c | ||
|
43887b42b1 | ||
|
30b1b36e63 | ||
|
ef11262721 | ||
|
2d049f5fdc | ||
|
05f0a122f4 | ||
|
1977675a73 | ||
|
4ceaab7c26 | ||
|
6c33177548 | ||
|
76ca607021 | ||
|
59e1a5e5f6 | ||
|
4f55b2a554 | ||
|
78b2a89554 | ||
|
1e84a735e6 | ||
|
9e8801d867 | ||
|
0e58f7fa69 | ||
|
94058d498c | ||
|
2621bcc005 | ||
|
e750866ab6 | ||
|
00b3007b9f | ||
|
dbc0ecde31 | ||
|
0e30e2d865 | ||
|
ba1879ef78 | ||
|
7a145d72f9 | ||
|
f742266177 | ||
|
63737c09aa | ||
|
09e8d4ba0a | ||
|
7d728c37ee | ||
|
e46b12e359 | ||
|
b3012bd89e | ||
|
f521c12d85 | ||
|
58f04848a9 | ||
|
6488f46850 | ||
|
5f4b798cff | ||
|
ffa8f1db04 | ||
|
cf2711f6dd | ||
|
25eded1895 | ||
|
a8ac8f4a23 | ||
|
c67a53e95b | ||
|
edf4ba2533 | ||
|
71d337c10e | ||
|
eb9e5fb727 | ||
|
f133000656 | ||
|
71361638bb | ||
|
edc7fe9c72 | ||
|
3110041a06 | ||
|
ff78e4172d | ||
|
a1c23be73d | ||
|
eb2b1cb093 | ||
|
f2245b8066 | ||
|
793b6d1deb | ||
|
537b91fa3f | ||
|
b7777c9ca3 | ||
|
5235262855 | ||
|
7b90c36463 | ||
|
6407ab0673 | ||
|
78ec9e5a60 | ||
|
778ac6e6d1 | ||
|
5280f5aba2 | ||
|
67add0da93 | ||
|
a32da9a6e1 | ||
|
b6b70bb7ff | ||
|
6d769c9e89 | ||
|
0c683c2c81 | ||
|
8e51bd7578 | ||
|
6d6dfa4f44 | ||
|
7d044ad0ab | ||
|
826f4c1d48 | ||
|
3be5d60b1e | ||
|
26375dc023 | ||
|
8d0d7abd5a | ||
|
ef03ddf2ae | ||
|
1d8b33d7ae | ||
|
308b3ca448 | ||
|
ca67afea4c | ||
|
0b11fc259b | ||
|
18ffb9294f | ||
|
c82517c9fd | ||
|
6d42c9aaf9 | ||
|
02ee3a7804 | ||
|
bdf1dff976 | ||
|
e6724a701a | ||
|
d90eedfc8c | ||
|
63009d00b4 | ||
|
6a2261d217 | ||
|
c3ad2154b5 | ||
|
3d61861737 | ||
|
7545786436 | ||
|
a997b6c677 | ||
|
72d4f638aa | ||
|
b15ff6d442 | ||
|
cb70967971 | ||
|
42aea2663c | ||
|
a882faa7f6 | ||
|
5da01e4fd8 | ||
|
71c5a1d419 | ||
|
03c3da7338 | ||
|
47b276ca0b | ||
|
c7747ec5a0 | ||
|
5a84e98256 | ||
|
b66d69b60c | ||
|
dfaea5a922 | ||
|
f4da417c3a | ||
|
fa7fff86eb | ||
|
d480f9eae2 | ||
|
4f1aef90b8 | ||
|
24f4538eb7 | ||
|
38d096cad6 | ||
|
b82af9c471 | ||
|
0bff2089c4 | ||
|
36d9c40d7b | ||
|
becb6ce2e0 | ||
|
56b65780d2 | ||
|
265d151879 | ||
|
c485097eed | ||
|
f86e9fe086 | ||
|
c91ce4cfea | ||
|
8e64a854fc | ||
|
7c9383cd6b | ||
|
82d03e3980 | ||
|
0775e3ad58 | ||
|
ea3eef3817 | ||
|
83eac172c9 | ||
|
5b13d3e893 | ||
|
807835b9fe | ||
|
4bf02122ee | ||
|
e6c4454059 | ||
|
d464ce831a | ||
|
018f0e097f | ||
|
2acb853021 | ||
|
acd477df39 | ||
|
da520de9ef | ||
|
e680a973b0 | ||
|
07984a2f8b | ||
|
16532136e9 | ||
|
30c2c65b77 | ||
|
b63178132d | ||
|
6d66c90aad | ||
|
c807c75412 | ||
|
f6feaddfe6 | ||
|
87d1a476a4 | ||
|
f7337f2400 | ||
|
fac94a5d36 | ||
|
140228a936 | ||
|
06fd91f002 | ||
|
c3d4d0ee38 | ||
|
30cca54e6c | ||
|
6ac6e48b95 | ||
|
779794632e | ||
|
88bb16f261 | ||
|
c134c7bdc2 | ||
|
6c6cda3db5 | ||
|
a29f246536 | ||
|
d9d675a74f | ||
|
d62ea95889 | ||
|
e2e951ad0b | ||
|
a5a653d684 | ||
|
6123350895 | ||
|
ec73c00c3b | ||
|
dd24f5f4f3 | ||
|
82a2e802ea | ||
|
3b75eeb70a | ||
|
ee4575b70f | ||
|
46a4ec1cb1 | ||
|
8ab77a3260 | ||
|
b875e349c1 | ||
|
169298af42 | ||
|
5e502df48b | ||
|
4f58664f97 | ||
|
ffd298218c | ||
|
d2b077c573 | ||
|
547dc29a60 | ||
|
69aeca5c0e | ||
|
ed7cd4b277 | ||
|
7bf831e1a6 | ||
|
0092cb8c36 | ||
|
543b1c644a | ||
|
cfaea7a90c | ||
|
b821645644 | ||
|
2865190499 | ||
|
3f40e409c5 | ||
|
002e235d90 | ||
|
7d8a364658 | ||
|
41c471ca52 | ||
|
dd127f64fe | ||
|
b19dcfd6dc | ||
|
55369464ad | ||
|
609c117267 | ||
|
3b62a2fe7a | ||
|
7c9715f00c | ||
|
7de92a9457 | ||
|
0866caf934 | ||
|
914b88d115 | ||
|
cc122a7a68 | ||
|
31979649c6 | ||
|
335d13d06d | ||
|
ec785f3a8a | ||
|
1f83a5425e | ||
|
4882d6d0f2 | ||
|
722743659b | ||
|
6e64a79b52 | ||
|
8a6bf84cff | ||
|
a0fdd8f4eb | ||
|
bda1783624 | ||
|
2a14557478 | ||
|
0ddbc67b1f | ||
|
ffb5149890 | ||
|
bb339d619f | ||
|
2ed11877e8 | ||
|
ea6b83815b | ||
|
740b0e35d5 | ||
|
2e7c1acb88 | ||
|
4fcb85d132 | ||
|
f175dcea58 | ||
|
c04c708a9d | ||
|
f4cf1e5313 | ||
|
0e17f382a1 | ||
|
f38bca37a2 | ||
|
166793ebe6 | ||
|
8b04d0e3ef | ||
|
a3931674dc | ||
|
bd4ef5ec57 | ||
|
3ba12630ab | ||
|
342d90c929 | ||
|
9078fc994b | ||
|
f46af4b702 | ||
|
b112987556 | ||
|
fc880ac130 | ||
|
a2d95cb982 | ||
|
d2776071e4 | ||
|
72a645ec1e | ||
|
1154ffd072 | ||
|
8ba9708942 | ||
|
521fca6089 | ||
|
ae684edbe1 | ||
|
fa0a9aa611 | ||
|
5da9e0486a | ||
|
6980fd760c | ||
|
3549488b7a | ||
|
c1602cc8fe | ||
|
189dd176de | ||
|
3cf262d1f7 | ||
|
ccfc389274 | ||
|
0e07f802ac | ||
|
55f92e2411 | ||
|
c720f3910a | ||
|
4215edd11b | ||
|
09a61cf1a7 | ||
|
5967ad0865 | ||
|
eb34c38332 | ||
|
5ccb18225a | ||
|
58bbce1a15 | ||
|
9ea3e547ee | ||
|
fb5fdc9f10 | ||
|
de7b7818f4 | ||
|
c4e6b18294 | ||
|
ae6cf69449 | ||
|
4a2dcff028 | ||
|
aa6acec8fa | ||
|
4ac4da908c | ||
|
66e62857c4 | ||
|
bbc0d8b050 | ||
|
0f8bc416d1 | ||
|
2ec235170e | ||
|
2de1a2dd0d | ||
|
1f49c3b113 | ||
|
5c645fb3c2 | ||
|
40b5227f0b | ||
|
847dba8f07 | ||
|
417c6c4629 | ||
|
2d6a4d490e | ||
|
a6ec870872 | ||
|
389541be6d | ||
|
208f3e24de | ||
|
f7e36a1e03 | ||
|
1341816791 | ||
|
b986add74a | ||
|
08673ff021 | ||
|
3a2d9c6082 | ||
|
43a3959b8f | ||
|
85a738acff | ||
|
17dbdce230 | ||
|
9d084782ae | ||
|
106937b679 | ||
|
623eda7162 | ||
|
2ad6bb099b | ||
|
9d858bc61b | ||
|
612c9ce49a | ||
|
64e025484a | ||
|
7b1f800387 | ||
|
2712d50e05 | ||
|
47e9279bd4 | ||
|
635efd0212 | ||
|
1c1d2891c7 | ||
|
1979d2e5ba | ||
|
c25d0e8843 | ||
|
3a899ea4be | ||
|
9d08282e28 | ||
|
18154278d1 | ||
|
9063852857 | ||
|
bc27e3998d | ||
|
19fa0b8945 | ||
|
4987bdfec9 | ||
|
0e4615564d | ||
|
7aeea535a1 | ||
|
6b18d775ab | ||
|
2ed031e440 | ||
|
5d6bb11eb7 | ||
|
c6b91559e1 | ||
|
6efc41ded7 | ||
|
e9c5582fe1 | ||
|
8b3c0abe93 | ||
|
a5ebac1b29 | ||
|
1ccfae885c | ||
|
971bfb2ecb | ||
|
e7457461ba | ||
|
e8c1e8fd3f | ||
|
ca779bc841 | ||
|
a28c97c0de | ||
|
db49146efe | ||
|
830d70d3aa | ||
|
336292bc49 | ||
|
bd62228cc6 | ||
|
ccdd340c9a | ||
|
0b42f5fb30 | ||
|
e9e1db7a05 | ||
|
21278d028c | ||
|
fbc273f114 | ||
|
06a5df029d | ||
|
e17700b495 | ||
|
655b1e516c | ||
|
4e7a63f792 | ||
|
a2896b9bd0 | ||
|
a4cf86268e | ||
|
d059e7c5d8 | ||
|
d6f882a8bb | ||
|
08f50f3eff | ||
|
47f7340dfc | ||
|
fdef8901ab | ||
|
ca1c3dc005 | ||
|
9406a97141 | ||
|
a46ec4cffb | ||
|
9bb5dc3c2b | ||
|
f6ea442606 | ||
|
fa8fcd2218 | ||
|
2a36d0fcbc | ||
|
0e92885ed5 | ||
|
f5225b69e5 | ||
|
15ee84b2eb | ||
|
d380cecdb7 | ||
|
ae3cd924e8 | ||
|
a0f0f73bde | ||
|
7cdceb7b4f | ||
|
38b5624639 | ||
|
3405b3b287 | ||
|
173fc9329a | ||
|
691a42d81e | ||
|
4059905f85 | ||
|
bbb520fd12 | ||
|
108a056f1c | ||
|
ed92e98ca2 | ||
|
0d666f9935 | ||
|
fe467be124 | ||
|
ba5f142515 | ||
|
ed586e80bc | ||
|
871c5467d7 | ||
|
387791635e | ||
|
b7a1363add | ||
|
341b705bef | ||
|
0b65aa39cd | ||
|
1b7c3644f4 | ||
|
0cdca12e06 | ||
|
61d4c69e45 | ||
|
79865e295b | ||
|
1f43047de8 | ||
|
6f0ad0ab71 | ||
|
447734b1e9 | ||
|
3e80651a0e | ||
|
692a9da2e4 | ||
|
e27312c980 | ||
|
eae92a0cdb | ||
|
95cc34ba23 | ||
|
7532b461cd | ||
|
230e9c6327 | ||
|
11c4d2f09e | ||
|
f2db1b4aae | ||
|
b42a6e447d | ||
|
8a83d71560 | ||
|
9fd7d5c10f | ||
|
7a5ed6c427 | ||
|
4e7963ee81 | ||
|
945b7e90da | ||
|
99f0233b76 | ||
|
62da0dee7f | ||
|
1663d3d9d1 | ||
|
37499d493a | ||
|
c0dd96eb7c | ||
|
2abae4c8bf | ||
|
c865da67e0 | ||
|
e6f77a9b80 | ||
|
7b28b3d634 | ||
|
42ba6d1281 | ||
|
85b7afd530 | ||
|
f2f59a4de5 | ||
|
5759798ad7 | ||
|
ab1dd7f57e | ||
|
53a2ea3a57 | ||
|
1f1e7236be | ||
|
fd2c5b6679 | ||
|
0b287c55d5 | ||
|
0de8240238 | ||
|
1449b2a2a6 | ||
|
0f691766ee | ||
|
3ce05e9de1 | ||
|
98f5d0cdb7 | ||
|
93b4008f81 | ||
|
904462b881 | ||
|
3b320bcdef | ||
|
3368bdb99f | ||
|
4d400c3cb7 | ||
|
474f9da3c2 | ||
|
c49b26701f | ||
|
9b42d35d56 | ||
|
645152a1fd | ||
|
487ade56ed | ||
|
60d1b36e9a | ||
|
5a48c15e46 | ||
|
d6bf1808f9 | ||
|
b676153d21 | ||
|
a3339cf882 | ||
|
b4e0b46bac | ||
|
09c1b2d7db | ||
|
4255283e33 | ||
|
16e827bb2c | ||
|
def69ce6d5 | ||
|
054a799699 | ||
|
580f402bb6 | ||
|
030dda34f0 | ||
|
cd21b39f44 | ||
|
481b6d0e69 | ||
|
a88d41bf00 | ||
|
0ee3b628e8 | ||
|
45628ba9df | ||
|
c843c395ea | ||
|
9bdaf31d04 | ||
|
4ac2baeb9d | ||
|
c56e82207a | ||
|
82abebec6e | ||
|
60286c3a15 | ||
|
8460fe2118 | ||
|
4b5456c9ba | ||
|
ddf136556d | ||
|
4c45f4468e | ||
|
73d2acca12 | ||
|
56a5df3783 | ||
|
d205e538e1 | ||
|
f9cbec668b | ||
|
6577f68efc | ||
|
e986ae2878 | ||
|
b2696450d5 | ||
|
2bbaf73aa2 | ||
|
0fe2c1406b | ||
|
954d920b9e | ||
|
57b45076c5 | ||
|
d639dc8bcb | ||
|
9a74ab6a8e | ||
|
4c53414cc3 | ||
|
c36288dd6b | ||
|
bc5727af14 | ||
|
bd0a15c054 | ||
|
a758112084 | ||
|
3981f3a874 | ||
|
e8036127fe | ||
|
17abd87791 | ||
|
35545451fe | ||
|
64bec0cc3d | ||
|
fadd3bc6fc | ||
|
d9ec11c62e | ||
|
093a029b8c | ||
|
b4a3b23571 | ||
|
be99183f1d | ||
|
dda5f41487 | ||
|
a09457dab5 | ||
|
ac171d166e | ||
|
51de1892c0 | ||
|
e1fdda928a | ||
|
1c8261dc09 | ||
|
cb22278c7f | ||
|
809bc9d6a8 | ||
|
be11f31d5d | ||
|
0103761b7b | ||
|
3ac5fdafab | ||
|
1e877c7563 | ||
|
07c11e8268 | ||
|
0dcceff410 | ||
|
2a684ab302 | ||
|
27059233b3 | ||
|
7f84d5ac6f | ||
|
d43f050922 | ||
|
3ba2618547 | ||
|
a3e104f8e2 | ||
|
1bb82189e9 | ||
|
e06a66644c | ||
|
6dcc13921f | ||
|
fd45745600 | ||
|
507c3da927 | ||
|
f14e45f93e | ||
|
3d2d9ac45e | ||
|
1895b4ee5d | ||
|
d49c07687c | ||
|
3a208460e2 | ||
|
472297e411 | ||
|
25085cb5af | ||
|
9909146c59 | ||
|
609d81d75d | ||
|
c105acf1c7 | ||
|
f3d0827d14 | ||
|
a4a983eb81 | ||
|
48be7c677e | ||
|
147d817977 | ||
|
d481f335b8 | ||
|
228012cd0c | ||
|
f4d8c04f3c | ||
|
c6c9be0b08 | ||
|
3827929a15 | ||
|
ca7e4b3a0e | ||
|
fd73c24fc3 | ||
|
ce0d53b277 | ||
|
7dc3b5ba06 | ||
|
17cad73177 | ||
|
b28e3eb419 | ||
|
d811501421 | ||
|
c68dd50fde | ||
|
ad8abf2e05 | ||
|
01d9455897 | ||
|
cbf8849004 | ||
|
f3a7d82dc1 | ||
|
017674de35 | ||
|
4f211334b5 | ||
|
31e261f7e5 | ||
|
15b5a62e01 | ||
|
f5800aa004 | ||
|
584aa78695 | ||
|
030f49db83 | ||
|
cc165b65be | ||
|
cb125e6336 | ||
|
a3337ea90f | ||
|
ae31f85f0c | ||
|
5e9f484662 | ||
|
f2e29357bf | ||
|
8a1a14ba4c | ||
|
31cbcb206f | ||
|
bf9e913a7b | ||
|
d3cea4a10f | ||
|
a6df20ff84 | ||
|
7122a9ee16 |
70
.github/workflows/build.yml
vendored
70
.github/workflows/build.yml
vendored
@@ -1,20 +1,56 @@
|
||||
name: Build
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
build-mac:
|
||||
name: Mac UI on ${{ matrix.os }}
|
||||
# build-mac-xcodebuild:
|
||||
# name: Mac UI / xcodebuild / ${{ matrix.os }}
|
||||
# strategy:
|
||||
# matrix:
|
||||
# os: [macos-13, macos-14, macos-15]
|
||||
# runs-on: ${{ matrix.os }}
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# - name: Make
|
||||
# working-directory: OSBindings/Mac
|
||||
# run: xcodebuild CODE_SIGN_IDENTITY=-
|
||||
build-sdl-cmake:
|
||||
name: SDL UI / cmake / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest]
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
case $RUNNER_OS in
|
||||
Linux)
|
||||
sudo apt-get --allow-releaseinfo-change update
|
||||
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
|
||||
;;
|
||||
macOS)
|
||||
brew install cmake sdl2
|
||||
;;
|
||||
esac
|
||||
- name: Make
|
||||
working-directory: OSBindings/Mac
|
||||
run: xcodebuild CODE_SIGN_IDENTITY=-
|
||||
build-sdl:
|
||||
name: SDL UI on ${{ matrix.os }}
|
||||
shell: bash
|
||||
run: |
|
||||
case $RUNNER_OS in
|
||||
Linux)
|
||||
jobs=$(nproc --all)
|
||||
;;
|
||||
macOS)
|
||||
jobs=$(sysctl -n hw.activecpu)
|
||||
;;
|
||||
*)
|
||||
jobs=1
|
||||
esac
|
||||
cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -v -j"$jobs"
|
||||
build-sdl-scons:
|
||||
name: SDL UI / scons / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
@@ -49,3 +85,23 @@ jobs:
|
||||
jobs=1
|
||||
esac
|
||||
scons -j"$jobs"
|
||||
build-qt:
|
||||
name: Qt / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '5.15.2'
|
||||
archives: 'qtbase qtmultimedia qtx11extras icu'
|
||||
- name: Make
|
||||
working-directory: OSBindings/Qt
|
||||
shell: bash
|
||||
run: |
|
||||
qmake -o Makefile clksignal.pro
|
||||
make
|
||||
|
@@ -22,38 +22,38 @@ namespace Activity {
|
||||
and/or to show or unshow status indicators.
|
||||
*/
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() {}
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
|
||||
Persistent = (1 << 0),
|
||||
};
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
|
||||
Persistent = (1 << 0),
|
||||
};
|
||||
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
|
||||
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
///
|
||||
/// If a drive has the same name as an LED, that LED goes with this drive.
|
||||
virtual void register_drive([[maybe_unused]] const std::string &name) {}
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
///
|
||||
/// If a drive has the same name as an LED, that LED goes with this drive.
|
||||
virtual void register_drive([[maybe_unused]] const std::string &name) {}
|
||||
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
|
||||
|
||||
enum class DriveEvent {
|
||||
StepNormal,
|
||||
StepBelowZero,
|
||||
StepBeyondMaximum
|
||||
};
|
||||
enum class DriveEvent {
|
||||
StepNormal,
|
||||
StepBelowZero,
|
||||
StepBeyondMaximum
|
||||
};
|
||||
|
||||
/// Informs the receiver that the named event just occurred for the drive with name @c name.
|
||||
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
|
||||
/// Informs the receiver that the named event just occurred for the drive with name @c name.
|
||||
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
|
||||
|
||||
/// Informs the receiver of the motor-on status of the drive with name @c name.
|
||||
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
|
||||
/// Informs the receiver of the motor-on status of the drive with name @c name.
|
||||
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -13,8 +13,8 @@
|
||||
namespace Activity {
|
||||
|
||||
class Source {
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *observer) = 0;
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *observer) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -18,25 +18,25 @@ namespace Analyser::Dynamic {
|
||||
The initial value of the confidence counter is 0.5.
|
||||
*/
|
||||
class ConfidenceCounter: public ConfidenceSource {
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float get_confidence() final;
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float get_confidence() final;
|
||||
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
|
||||
/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
|
||||
void add_miss();
|
||||
/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
|
||||
void add_miss();
|
||||
|
||||
/*!
|
||||
Records an event that could be correct but isn't necessarily so; which can push probability
|
||||
down towards 0.5, but will never push it upwards.
|
||||
*/
|
||||
void add_equivocal();
|
||||
/*!
|
||||
Records an event that could be correct but isn't necessarily so; which can push probability
|
||||
down towards 0.5, but will never push it upwards.
|
||||
*/
|
||||
void add_equivocal();
|
||||
|
||||
private:
|
||||
int hits_ = 1;
|
||||
int misses_ = 1;
|
||||
private:
|
||||
int hits_ = 1;
|
||||
int misses_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -13,7 +13,10 @@
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
|
||||
ConfidenceSummary::ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights
|
||||
) :
|
||||
sources_(sources), weights_(weights) {
|
||||
assert(weights.size() == sources.size());
|
||||
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
|
||||
|
@@ -18,24 +18,24 @@ namespace Analyser::Dynamic {
|
||||
Summaries a collection of confidence sources by calculating their weighted sum.
|
||||
*/
|
||||
class ConfidenceSummary: public ConfidenceSource {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a summary that will produce the weighted sum of
|
||||
@c sources, each using the corresponding entry of @c weights.
|
||||
public:
|
||||
/*!
|
||||
Instantiates a summary that will produce the weighted sum of
|
||||
@c sources, each using the corresponding entry of @c weights.
|
||||
|
||||
Requires that @c sources and @c weights are of the same length.
|
||||
*/
|
||||
ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights);
|
||||
Requires that @c sources and @c weights are of the same length.
|
||||
*/
|
||||
ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights);
|
||||
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float get_confidence() final;
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float get_confidence() final;
|
||||
|
||||
private:
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
const std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
private:
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
const std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -15,90 +15,90 @@ using namespace Analyser::Dynamic;
|
||||
namespace {
|
||||
|
||||
class MultiStruct: public Reflection::Struct {
|
||||
public:
|
||||
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
|
||||
for(auto device: devices) {
|
||||
options_.emplace_back(device->get_options());
|
||||
public:
|
||||
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
|
||||
for(auto device: devices) {
|
||||
options_.emplace_back(device->get_options());
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
auto options = options_.begin();
|
||||
for(auto device: devices_) {
|
||||
device->set_options(*options);
|
||||
++options;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
keys.insert(new_keys.begin(), new_keys.end());
|
||||
}
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
values.insert(new_values.begin(), new_values.end());
|
||||
}
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return options->count_of(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *get(const std::string &name) final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set(const std::string &name, const void *value, const size_t offset) final {
|
||||
const auto safe_type = type_of(name);
|
||||
if(!safe_type) return;
|
||||
|
||||
// Set this property only where the child's type is the same as that
|
||||
// which was returned from here for type_of.
|
||||
for(auto &options: options_) {
|
||||
const auto type = options->type_of(name);
|
||||
if(!type) continue;
|
||||
|
||||
if(*type == *safe_type) {
|
||||
options->set(name, value, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
auto options = options_.begin();
|
||||
for(auto device: devices_) {
|
||||
device->set_options(*options);
|
||||
++options;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
keys.insert(new_keys.begin(), new_keys.end());
|
||||
}
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
values.insert(new_values.begin(), new_values.end());
|
||||
}
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return options->count_of(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *get(const std::string &name) final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set(const std::string &name, const void *value, size_t offset) final {
|
||||
const auto safe_type = type_of(name);
|
||||
if(!safe_type) return;
|
||||
|
||||
// Set this property only where the child's type is the same as that
|
||||
// which was returned from here for type_of.
|
||||
for(auto &options: options_) {
|
||||
const auto type = options->type_of(name);
|
||||
if(!type) continue;
|
||||
|
||||
if(*type == *safe_type) {
|
||||
options->set(name, value, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Configurable::Device *> &devices_;
|
||||
std::vector<std::unique_ptr<Reflection::Struct>> options_;
|
||||
private:
|
||||
const std::vector<Configurable::Device *> &devices_;
|
||||
std::vector<std::unique_ptr<Reflection::Struct>> options_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -115,6 +115,6 @@ void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &s
|
||||
options->apply();
|
||||
}
|
||||
|
||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
|
||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() const {
|
||||
return std::make_unique<MultiStruct>(devices_);
|
||||
}
|
||||
|
@@ -23,15 +23,15 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiConfigurable: public Configurable::Device {
|
||||
public:
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
|
||||
std::unique_ptr<Reflection::Struct> get_options() final;
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &) final;
|
||||
std::unique_ptr<Reflection::Struct> get_options() const final;
|
||||
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -15,52 +15,52 @@ using namespace Analyser::Dynamic;
|
||||
namespace {
|
||||
|
||||
class MultiJoystick: public Inputs::Joystick {
|
||||
public:
|
||||
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
|
||||
for(const auto &machine: machines) {
|
||||
const auto &joysticks = machine->get_joysticks();
|
||||
if(joysticks.size() >= index) {
|
||||
joysticks_.push_back(joysticks[index].get());
|
||||
}
|
||||
public:
|
||||
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, const std::size_t index) {
|
||||
for(const auto &machine: machines) {
|
||||
const auto &joysticks = machine->get_joysticks();
|
||||
if(joysticks.size() > index) {
|
||||
joysticks_.push_back(joysticks[index].get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
}
|
||||
const std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, bool is_active) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, float value) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
void set_input(const Input &digital_input, const bool is_active) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_all_inputs() final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
void set_input(const Input &digital_input, const float value) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs;
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
void reset_all_inputs() final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs;
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -22,14 +22,14 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ void MultiKeyboardMachine::clear_all_keys() {
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
|
||||
void MultiKeyboardMachine::set_key_state(const uint16_t key, const bool is_pressed) {
|
||||
for(const auto &machine: machines_) {
|
||||
machine->set_key_state(key, is_pressed);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::can_type(char c) const {
|
||||
bool MultiKeyboardMachine::can_type(const char c) const {
|
||||
bool can_type = true;
|
||||
for(const auto &machine: machines_) {
|
||||
can_type &= machine->can_type(c);
|
||||
@@ -51,12 +51,20 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
|
||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
|
||||
: machines_(machines) {
|
||||
for(const auto &machine: machines_) {
|
||||
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
|
||||
observed_keys_.insert(
|
||||
machine->get_keyboard().observed_keys().begin(),
|
||||
machine->get_keyboard().observed_keys().end()
|
||||
);
|
||||
is_exclusive_ |= machine->get_keyboard().is_exclusive();
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(
|
||||
const Key key,
|
||||
const char value,
|
||||
const bool is_pressed,
|
||||
const bool is_repeat
|
||||
) {
|
||||
bool was_consumed = false;
|
||||
for(const auto &machine: machines_) {
|
||||
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
|
||||
|
@@ -23,34 +23,34 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
private:
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
std::unique_ptr<MultiKeyboard> keyboard_;
|
||||
private:
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
|
||||
|
||||
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
bool can_type(char c) const final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
std::unique_ptr<MultiKeyboard> keyboard_;
|
||||
|
||||
public:
|
||||
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
bool can_type(char c) const final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -18,6 +18,9 @@ MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::
|
||||
}
|
||||
|
||||
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
|
||||
// TODO: copy media afresh for each target machine; media
|
||||
// generally has mutable state.
|
||||
|
||||
bool inserted = false;
|
||||
for(const auto &target : targets_) {
|
||||
inserted |= target->insert_media(media);
|
||||
|
@@ -23,14 +23,14 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
struct MultiMediaTarget: public MachineTypes::MediaTarget {
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &media) final;
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &) final;
|
||||
|
||||
private:
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
private:
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ void MultiInterface<MachineType>::perform_serial(const std::function<void(Machin
|
||||
}
|
||||
|
||||
// MARK: - MultiScanProducer
|
||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
|
||||
scan_target_ = scan_target;
|
||||
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
@@ -80,7 +80,12 @@ void MultiScanProducer::did_change_machine_order() {
|
||||
}
|
||||
|
||||
// MARK: - MultiAudioProducer
|
||||
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
|
||||
MultiAudioProducer::MultiAudioProducer(
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
|
||||
std::recursive_mutex &machines_mutex
|
||||
) :
|
||||
MultiInterface(machines, machines_mutex)
|
||||
{
|
||||
speaker_ = MultiSpeaker::create(machines);
|
||||
}
|
||||
|
||||
@@ -96,7 +101,7 @@ void MultiAudioProducer::did_change_machine_order() {
|
||||
|
||||
// MARK: - MultiTimedMachine
|
||||
|
||||
void MultiTimedMachine::run_for(Time::Seconds duration) {
|
||||
void MultiTimedMachine::run_for(const Time::Seconds duration) {
|
||||
perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
@@ -21,88 +21,91 @@
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
template <typename MachineType> class MultiInterface {
|
||||
public:
|
||||
MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
|
||||
public:
|
||||
MultiInterface(
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
|
||||
std::recursive_mutex &machines_mutex
|
||||
) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
protected:
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(MachineType *)> &);
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(MachineType *)> &);
|
||||
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(MachineType *)> &);
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(MachineType *)> &);
|
||||
|
||||
protected:
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
protected:
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
};
|
||||
|
||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine *) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine *) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *const delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void run_for(Time::Seconds duration) final;
|
||||
void run_for(Time::Seconds duration) final;
|
||||
|
||||
private:
|
||||
void run_for(const Cycles) final {}
|
||||
Delegate *delegate_ = nullptr;
|
||||
private:
|
||||
void run_for(Cycles) final {}
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Informs the MultiScanProducer that the order of machines has changed; it
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
/*!
|
||||
Informs the MultiScanProducer that the order of machines has changed; it
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
void set_scan_target(Outputs::Display::ScanTarget *) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
|
||||
private:
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
private:
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
|
||||
public:
|
||||
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
|
||||
public:
|
||||
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &, std::recursive_mutex &);
|
||||
|
||||
/*!
|
||||
Informs the MultiAudio that the order of machines has changed; it
|
||||
uses this as an opportunity to switch speaker delegates as appropriate.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
/*!
|
||||
Informs the MultiAudio that the order of machines has changed; it
|
||||
uses this as an opportunity to switch speaker delegates as appropriate.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
|
||||
private:
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
private:
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@@ -28,7 +28,7 @@ MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speak
|
||||
}
|
||||
}
|
||||
|
||||
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const float maximum) {
|
||||
float ideal = 0.0f;
|
||||
for(const auto &speaker: speakers_) {
|
||||
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
|
||||
@@ -37,7 +37,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
|
||||
return ideal / float(speakers_.size());
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
|
||||
void MultiSpeaker::set_computed_output_rate(const float cycles_per_second, const int buffer_size, const bool stereo) {
|
||||
stereo_output_ = stereo;
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
|
||||
@@ -54,14 +54,14 @@ bool MultiSpeaker::get_is_stereo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_output_volume(float volume) {
|
||||
void MultiSpeaker::set_output_volume(const float volume) {
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_output_volume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *const speaker, const std::vector<int16_t> &buffer) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
@@ -70,8 +70,8 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
|
||||
did_complete_samples(this, buffer, stereo_output_);
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *const speaker) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
@@ -80,12 +80,12 @@ void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
delegate->speaker_did_change_input_clock(this);
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
front_speaker_ = machine->audio_producer()->get_speaker();
|
||||
}
|
||||
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(delegate) {
|
||||
delegate->speaker_did_change_input_clock(this);
|
||||
}
|
||||
|
@@ -25,32 +25,32 @@ namespace Analyser::Dynamic {
|
||||
abreast of the current frontmost machine.
|
||||
*/
|
||||
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
|
||||
public:
|
||||
/*!
|
||||
Provides a construction mechanism that may return nullptr, in the case that all included
|
||||
machines return nullptr as their speaker.
|
||||
*/
|
||||
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
/*!
|
||||
Provides a construction mechanism that may return nullptr, in the case that all included
|
||||
machines return nullptr as their speaker.
|
||||
*/
|
||||
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||
void set_new_front_machine(::Machine::DynamicMachine *machine);
|
||||
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||
void set_new_front_machine(::Machine::DynamicMachine *);
|
||||
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
|
||||
bool stereo_output_ = false;
|
||||
bool stereo_output_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -27,7 +27,8 @@ MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machin
|
||||
audio_producer_(machines_, machines_mutex_),
|
||||
joystick_machine_(machines_),
|
||||
keyboard_machine_(machines_),
|
||||
media_target_(machines_) {
|
||||
media_target_(machines_)
|
||||
{
|
||||
timed_machine_.set_delegate(this);
|
||||
}
|
||||
|
||||
@@ -35,13 +36,13 @@ Activity::Source *MultiMachine::activity_source() {
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
#define Provider(type, name, member) \
|
||||
type *MultiMachine::name() { \
|
||||
if(has_picked_) { \
|
||||
#define Provider(type, name, member) \
|
||||
type *MultiMachine::name() { \
|
||||
if(has_picked_) { \
|
||||
return machines_.front()->name(); \
|
||||
} else { \
|
||||
return &member; \
|
||||
} \
|
||||
} else { \
|
||||
return &member; \
|
||||
} \
|
||||
}
|
||||
|
||||
Provider(Configurable::Device, configurable_device, configurable_)
|
||||
|
@@ -38,44 +38,44 @@ namespace Analyser::Dynamic {
|
||||
the others in the set, that machine stops running.
|
||||
*/
|
||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
|
||||
public:
|
||||
/*!
|
||||
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||
requesting this class as a proxy.
|
||||
public:
|
||||
/*!
|
||||
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||
requesting this class as a proxy.
|
||||
|
||||
@returns @c true if the multimachine would discard all but the first machine in this list;
|
||||
@c false otherwise.
|
||||
*/
|
||||
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
|
||||
@returns @c true if the multimachine would discard all but the first machine in this list;
|
||||
@c false otherwise.
|
||||
*/
|
||||
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &);
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&);
|
||||
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
MachineTypes::TimedMachine *timed_machine() final;
|
||||
MachineTypes::ScanProducer *scan_producer() final;
|
||||
MachineTypes::AudioProducer *audio_producer() final;
|
||||
MachineTypes::JoystickMachine *joystick_machine() final;
|
||||
MachineTypes::KeyboardMachine *keyboard_machine() final;
|
||||
MachineTypes::MouseMachine *mouse_machine() final;
|
||||
MachineTypes::MediaTarget *media_target() final;
|
||||
void *raw_pointer() final;
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
MachineTypes::TimedMachine *timed_machine() final;
|
||||
MachineTypes::ScanProducer *scan_producer() final;
|
||||
MachineTypes::AudioProducer *audio_producer() final;
|
||||
MachineTypes::JoystickMachine *joystick_machine() final;
|
||||
MachineTypes::KeyboardMachine *keyboard_machine() final;
|
||||
MachineTypes::MouseMachine *mouse_machine() final;
|
||||
MachineTypes::MediaTarget *media_target() final;
|
||||
void *raw_pointer() final;
|
||||
|
||||
private:
|
||||
void did_run_machines(MultiTimedMachine *) final;
|
||||
private:
|
||||
void did_run_machines(MultiTimedMachine *) final;
|
||||
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
|
||||
MultiConfigurable configurable_;
|
||||
MultiTimedMachine timed_machine_;
|
||||
MultiScanProducer scan_producer_;
|
||||
MultiAudioProducer audio_producer_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
MultiConfigurable configurable_;
|
||||
MultiTimedMachine timed_machine_;
|
||||
MultiScanProducer scan_producer_;
|
||||
MultiAudioProducer audio_producer_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ enum class Machine {
|
||||
Atari2600,
|
||||
AtariST,
|
||||
Amiga,
|
||||
Archimedes,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
@@ -24,6 +25,7 @@ enum class Machine {
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
Plus4,
|
||||
PCCompatible,
|
||||
Vic20,
|
||||
ZX8081,
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
@@ -48,27 +49,39 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
char name[10];
|
||||
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
|
||||
new_file.name = name;
|
||||
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
|
||||
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
|
||||
new_file.load_address = uint32_t(
|
||||
details->samples[0][file_offset] |
|
||||
(details->samples[0][file_offset+1] << 8) |
|
||||
((details->samples[0][file_offset+6]&0x0c) << 14)
|
||||
);
|
||||
new_file.execution_address = uint32_t(
|
||||
details->samples[0][file_offset+2] |
|
||||
(details->samples[0][file_offset+3] << 8) |
|
||||
((details->samples[0][file_offset+6]&0xc0) << 10)
|
||||
);
|
||||
if(names->samples[0][file_offset + 7] & 0x80) {
|
||||
// File is locked; it may not be altered or deleted.
|
||||
new_file.flags |= File::Flags::Locked;
|
||||
}
|
||||
|
||||
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
|
||||
auto data_length = long(
|
||||
details->samples[0][file_offset+4] |
|
||||
(details->samples[0][file_offset+5] << 8) |
|
||||
((details->samples[0][file_offset+6]&0x30) << 12)
|
||||
);
|
||||
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
|
||||
new_file.data.reserve(size_t(data_length));
|
||||
|
||||
if(start_sector < 2) continue;
|
||||
while(data_length > 0) {
|
||||
uint8_t sector = uint8_t(start_sector % 10);
|
||||
uint8_t track = uint8_t(start_sector / 10);
|
||||
start_sector++;
|
||||
const uint8_t sector = uint8_t(start_sector % 10);
|
||||
const uint8_t track = uint8_t(start_sector / 10);
|
||||
++start_sector;
|
||||
|
||||
const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
|
||||
if(!next_sector) break;
|
||||
|
||||
long length_from_sector = std::min(data_length, 256l);
|
||||
const long length_from_sector = std::min(data_length, 256l);
|
||||
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
|
||||
data_length -= length_from_sector;
|
||||
}
|
||||
@@ -86,34 +99,57 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
auto catalogue = std::make_unique<Catalogue>();
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
|
||||
// Grab the second half of the free-space map because it has the boot option in it.
|
||||
const Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.sector(0, 0, 1);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
catalogue->has_large_sectors = free_space_map_second_half->samples[0].size() == 1024;
|
||||
|
||||
// Possibility: this is a large-sector disk with an old-style free space map. In which
|
||||
// case the above just read the start of the root directory.
|
||||
uint8_t first_directory_sector = 2;
|
||||
if(catalogue->has_large_sectors && !memcmp(&free_space_map_second_half->samples[0][1], "Hugo", 4)) {
|
||||
free_space_map_second_half = parser.sector(0, 0, 0);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
first_directory_sector = 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++) {
|
||||
root_directory.reserve(catalogue->has_large_sectors ? 2*1024 : 5*256);
|
||||
|
||||
for(uint8_t c = first_directory_sector; c < first_directory_sector + (catalogue->has_large_sectors ? 2 : 5); c++) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
|
||||
if(!sector) return nullptr;
|
||||
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
|
||||
}
|
||||
|
||||
// Quick sanity checks.
|
||||
if(root_directory[0x4cb]) return nullptr;
|
||||
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
|
||||
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
|
||||
// Check for end of directory marker.
|
||||
if(root_directory[catalogue->has_large_sectors ? 0x7d7 : 0x4cb]) return nullptr;
|
||||
|
||||
switch(free_space_map_second_half->samples[0][0xfd]) {
|
||||
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
// Check for both directory identifiers.
|
||||
const uint8_t *const start_id = &root_directory[1];
|
||||
const uint8_t *const end_id = &root_directory[root_directory.size() - 5];
|
||||
catalogue->is_hugo = !memcmp(start_id, "Hugo", 4) && !memcmp(end_id, "Hugo", 4);
|
||||
const bool is_nick = !memcmp(start_id, "Nick", 4) && !memcmp(end_id, "Nick", 4);
|
||||
if(!catalogue->is_hugo && !is_nick) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!catalogue->has_large_sectors) {
|
||||
// TODO: I don't know where the boot option rests with large sectors.
|
||||
switch(free_space_map_second_half->samples[0][0xfd]) {
|
||||
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the root directory, at least.
|
||||
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
|
||||
const std::size_t directory_extent = catalogue->has_large_sectors ? 0x7d7 : 0x4cb;
|
||||
for(std::size_t file_offset = 0x005; file_offset < directory_extent; file_offset += 0x1a) {
|
||||
// Obtain the name, which will be at most ten characters long, and will
|
||||
// be terminated by either a NULL character or a \r.
|
||||
char name[11];
|
||||
char name[11]{};
|
||||
std::size_t c = 0;
|
||||
for(; c < 10; c++) {
|
||||
const char next = root_directory[file_offset + c] & 0x7f;
|
||||
@@ -122,8 +158,9 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
}
|
||||
name[c] = '\0';
|
||||
|
||||
// Skip if the name is empty.
|
||||
if(name[0] == '\0') continue;
|
||||
// An empty name implies the directory has ended; files are always listed in case-insensitive
|
||||
// sorted order, with that list being terminated by a '\0'.
|
||||
if(name[0] == '\0') break;
|
||||
|
||||
// Populate a file then.
|
||||
File new_file;
|
||||
@@ -166,16 +203,36 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
|
||||
new_file.data.reserve(size);
|
||||
while(new_file.data.size() < size) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
const Storage::Encodings::MFM::Sector *const sector =
|
||||
parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
if(!sector) break;
|
||||
|
||||
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
|
||||
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->samples[0].begin(),
|
||||
sector->samples[0].begin() + ssize_t(length_from_sector)
|
||||
);
|
||||
++start_sector;
|
||||
}
|
||||
|
||||
catalogue->files.push_back(std::move(new_file));
|
||||
}
|
||||
|
||||
// Include the directory title.
|
||||
const char *title, *name;
|
||||
if(catalogue->has_large_sectors) {
|
||||
title = reinterpret_cast<const char *>(&root_directory[0x7dd]);
|
||||
name = reinterpret_cast<const char *>(&root_directory[0x7f0]);
|
||||
} else {
|
||||
title = reinterpret_cast<const char *>(&root_directory[0x4d9]);
|
||||
name = reinterpret_cast<const char *>(&root_directory[0x4cc]);
|
||||
}
|
||||
|
||||
catalogue->name = std::string(title, strnlen(title, 19));
|
||||
if(catalogue->name.empty() || catalogue->name == "$") {
|
||||
catalogue->name = std::string(name, strnlen(name, 10));
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
@@ -15,6 +15,8 @@ namespace Analyser::Static::Acorn {
|
||||
|
||||
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
|
||||
struct Catalogue {
|
||||
bool is_hugo = false;
|
||||
bool has_large_sectors = false;
|
||||
std::string name;
|
||||
std::vector<File> files;
|
||||
enum class BootOption {
|
||||
@@ -25,7 +27,7 @@ struct Catalogue {
|
||||
} bootOption;
|
||||
};
|
||||
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
|
||||
}
|
||||
|
@@ -12,25 +12,28 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Numeric/StringSimilarity.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
// only one mapped item is allowed
|
||||
// Only one mapped item is allowed.
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 8 or 16 kb in size
|
||||
// Cartridges must be 8 or 16 kb in size.
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
|
||||
|
||||
// is a copyright string present?
|
||||
// Check copyright string.
|
||||
const uint8_t copyright_offset = segment.data[7];
|
||||
if(
|
||||
segment.data[copyright_offset] != 0x00 ||
|
||||
@@ -39,16 +42,16 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
segment.data[copyright_offset+3] != 0x29
|
||||
) continue;
|
||||
|
||||
// is the language entry point valid?
|
||||
// Check language entry point.
|
||||
if(!(
|
||||
(segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) ||
|
||||
(segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0)
|
||||
)) continue;
|
||||
|
||||
// is the service entry point valid?
|
||||
// Check service entry point.
|
||||
if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue;
|
||||
|
||||
// probability of a random binary blob that isn't an Acorn ROM proceeding to here:
|
||||
// Probability of a random binary blob that isn't an Acorn ROM proceeding to here:
|
||||
// 1/(2^32) *
|
||||
// ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) *
|
||||
// 1/4
|
||||
@@ -59,65 +62,78 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target8bit = std::make_unique<ElectronTarget>();
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
// Copy appropriate cartridges to the 8-bit target.
|
||||
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
|
||||
// if there are any tapes, attempt to get data from the first
|
||||
// If there are tapes, attempt to get data from the first.
|
||||
if(!media.tapes.empty()) {
|
||||
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
|
||||
std::vector<File> files = GetFiles(tape);
|
||||
tape->reset();
|
||||
auto serialiser = tape->serialiser();
|
||||
std::vector<File> files = GetFiles(*serialiser);
|
||||
|
||||
// continue if there are any files
|
||||
if(!files.empty()) {
|
||||
bool is_basic = true;
|
||||
|
||||
// If a file is execute-only, that means *RUN.
|
||||
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
|
||||
if(files.front().flags & File::Flags::ExecuteOnly) {
|
||||
is_basic = false;
|
||||
}
|
||||
|
||||
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
|
||||
// so that's also justification to *RUN
|
||||
std::size_t pointer = 0;
|
||||
uint8_t *const data = &files.front().data[0];
|
||||
const std::size_t data_size = files.front().data.size();
|
||||
while(1) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 13) {
|
||||
is_basic = false;
|
||||
break;
|
||||
// Check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
|
||||
// so that's also justification to *RUN.
|
||||
if(is_basic) {
|
||||
std::size_t pointer = 0;
|
||||
uint8_t *const data = &files.front().data[0];
|
||||
const std::size_t data_size = files.front().data.size();
|
||||
while(true) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 0x0d) {
|
||||
is_basic = false;
|
||||
break;
|
||||
}
|
||||
if((data[pointer+1]&0x7f) == 0x7f) break;
|
||||
pointer += data[pointer+3];
|
||||
}
|
||||
if((data[pointer+1]&0x7f) == 0x7f) break;
|
||||
pointer += data[pointer+3];
|
||||
}
|
||||
|
||||
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
||||
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
|
||||
target->media.tapes = media.tapes;
|
||||
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
target8bit->media.tapes = media.tapes;
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
|
||||
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
|
||||
|
||||
// Get any sort of catalogue that can be found.
|
||||
dfs_catalogue = GetDFSCatalogue(disk);
|
||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||
if(dfs_catalogue || adfs_catalogue) {
|
||||
|
||||
// 8-bit options: DFS and Hugo-style ADFS.
|
||||
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
|
||||
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
||||
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
|
||||
target->media.disks = media.disks;
|
||||
target->has_dfs = bool(dfs_catalogue);
|
||||
target->has_pres_adfs = bool(adfs_catalogue);
|
||||
target8bit->media.disks = media.disks;
|
||||
target8bit->has_dfs = bool(dfs_catalogue);
|
||||
target8bit->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
target->should_shift_restart = true;
|
||||
target8bit->should_shift_restart = true;
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
}
|
||||
|
||||
// Check whether adding the AP6 ROM is justified.
|
||||
@@ -133,39 +149,79 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
"VERIFY", "ZERO"
|
||||
}) {
|
||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(adfs_catalogue) {
|
||||
// Archimedes options, implicitly: ADFS, non-Hugo.
|
||||
targetArchimedes->media.disks = media.disks;
|
||||
|
||||
// Also look for the best possible startup program name, if it can be discerned.
|
||||
std::multimap<double, std::string, std::greater<double>> options;
|
||||
for(const auto &file: adfs_catalogue->files) {
|
||||
// Skip non-Pling files.
|
||||
if(file.name[0] != '!') continue;
|
||||
|
||||
// Take whatever else comes with a preference for things that don't
|
||||
// have 'boot' or 'read' in them (the latter of which will tend to be
|
||||
// read_me or read_this or similar).
|
||||
constexpr char read[] = "read";
|
||||
constexpr char boot[] = "boot";
|
||||
const auto has = [&](const char *begin, const char *end) {
|
||||
return std::search(
|
||||
file.name.begin(), file.name.end(),
|
||||
begin, end - 1, // i.e. don't compare the trailing NULL.
|
||||
[](char lhs, char rhs) {
|
||||
return std::tolower(lhs) == rhs;
|
||||
}
|
||||
) != file.name.end();
|
||||
};
|
||||
const auto has_read = has(std::begin(read), std::end(read));
|
||||
const auto has_boot = has(std::begin(boot), std::end(boot));
|
||||
|
||||
const auto probability =
|
||||
Numeric::similarity(file.name, adfs_catalogue->name) +
|
||||
Numeric::similarity(file.name, file_name) -
|
||||
((has_read || has_boot) ? 0.2 : 0.0);
|
||||
options.emplace(probability, file.name);
|
||||
}
|
||||
|
||||
if(!options.empty()) {
|
||||
targetArchimedes->main_program = options.begin()->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the Acorn ADFS if a mass-storage device is attached;
|
||||
// unlike the Pres ADFS it retains SCSI logic.
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target->has_acorn_adfs = true;
|
||||
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target8bit->has_acorn_adfs = true;
|
||||
|
||||
// Assume some sort of later-era Acorn work is likely to happen;
|
||||
// so ensure *TYPE, etc are present.
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
|
||||
target->media.mass_storage_devices = media.mass_storage_devices;
|
||||
target8bit->media.mass_storage_devices = media.mass_storage_devices;
|
||||
|
||||
// Check for a boot option.
|
||||
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
|
||||
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
|
||||
if(sector[0xfd]) {
|
||||
target->should_shift_restart = true;
|
||||
target8bit->should_shift_restart = true;
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
if(!target->media.empty()) {
|
||||
targets.push_back(std::move(target));
|
||||
if(!target8bit->media.empty()) {
|
||||
targets.push_back(std::move(target8bit));
|
||||
}
|
||||
if(!targetArchimedes->media.empty()) {
|
||||
targets.push_back(std::move(targetArchimedes));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -15,65 +15,63 @@
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(
|
||||
Storage::Tape::TapeSerialiser &serialiser,
|
||||
Storage::Tape::Acorn::Parser &parser
|
||||
) {
|
||||
auto new_chunk = std::make_unique<File::Chunk>();
|
||||
int shift_register = 0;
|
||||
|
||||
// TODO: move this into the parser
|
||||
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
|
||||
|
||||
// find next area of high tone
|
||||
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
|
||||
shift();
|
||||
}
|
||||
|
||||
// find next 0x2a (swallowing stop bit)
|
||||
while(!tape->is_at_end() && (shift_register != 0x254)) {
|
||||
shift();
|
||||
}
|
||||
|
||||
#undef shift
|
||||
// TODO: move this into the parser
|
||||
const auto find = [&](int target) {
|
||||
while(!serialiser.is_at_end() && (shift_register != target)) {
|
||||
shift_register = (shift_register >> 1) | (parser.get_next_bit(serialiser) << 9);
|
||||
}
|
||||
};
|
||||
|
||||
// Find first sync byte that follows high tone.
|
||||
find(0x3ff);
|
||||
find(0x254); // i.e. 0x2a wrapped in a 1 start bit and a 0 stop bit.
|
||||
parser.reset_crc();
|
||||
parser.reset_error_flag();
|
||||
|
||||
// read out name
|
||||
char name[11];
|
||||
// Read name.
|
||||
char name[11]{};
|
||||
std::size_t name_ptr = 0;
|
||||
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
|
||||
name[name_ptr] = char(parser.get_next_byte(tape));
|
||||
while(!serialiser.is_at_end() && name_ptr < sizeof(name)) {
|
||||
name[name_ptr] = char(parser.get_next_byte(serialiser));
|
||||
if(!name[name_ptr]) break;
|
||||
++name_ptr;
|
||||
}
|
||||
name[sizeof(name)-1] = '\0';
|
||||
new_chunk->name = name;
|
||||
|
||||
// addresses
|
||||
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
|
||||
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
|
||||
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
|
||||
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
|
||||
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
|
||||
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
|
||||
// Read rest of header fields.
|
||||
new_chunk->load_address = uint32_t(parser.get_next_word(serialiser));
|
||||
new_chunk->execution_address = uint32_t(parser.get_next_word(serialiser));
|
||||
new_chunk->block_number = uint16_t(parser.get_next_short(serialiser));
|
||||
new_chunk->block_length = uint16_t(parser.get_next_short(serialiser));
|
||||
new_chunk->block_flag = uint8_t(parser.get_next_byte(serialiser));
|
||||
new_chunk->next_address = uint32_t(parser.get_next_word(serialiser));
|
||||
|
||||
uint16_t calculated_header_crc = parser.get_crc();
|
||||
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
|
||||
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
|
||||
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
|
||||
const auto matched_crc = [&]() {
|
||||
const uint16_t calculated_crc = parser.get_crc();
|
||||
uint16_t stored_crc = uint16_t(parser.get_next_short(serialiser));
|
||||
stored_crc = uint16_t((stored_crc >> 8) | (stored_crc << 8));
|
||||
return stored_crc == calculated_crc;
|
||||
};
|
||||
|
||||
new_chunk->header_crc_matched = matched_crc();
|
||||
|
||||
if(!new_chunk->header_crc_matched) return nullptr;
|
||||
|
||||
parser.reset_crc();
|
||||
new_chunk->data.reserve(new_chunk->block_length);
|
||||
for(int c = 0; c < new_chunk->block_length; c++) {
|
||||
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
|
||||
}
|
||||
|
||||
// Bit 6 of the block flag means 'empty block'; allow it to override declared block length.
|
||||
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
|
||||
uint16_t calculated_data_crc = parser.get_crc();
|
||||
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
|
||||
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
|
||||
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
|
||||
parser.reset_crc();
|
||||
new_chunk->data.reserve(new_chunk->block_length);
|
||||
for(int c = 0; c < new_chunk->block_length; c++) {
|
||||
new_chunk->data.push_back(uint8_t(parser.get_next_byte(serialiser)));
|
||||
}
|
||||
new_chunk->data_crc_matched = matched_crc();
|
||||
} else {
|
||||
new_chunk->data_crc_matched = true;
|
||||
}
|
||||
@@ -82,67 +80,62 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
}
|
||||
|
||||
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
||||
// find next chunk with a block number of 0
|
||||
while(chunks.size() && chunks.front().block_number) {
|
||||
// Find next chunk with a block number of 0.
|
||||
while(!chunks.empty() && chunks.front().block_number) {
|
||||
chunks.pop_front();
|
||||
}
|
||||
if(chunks.empty()) return nullptr;
|
||||
|
||||
if(!chunks.size()) return nullptr;
|
||||
|
||||
// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
|
||||
// Accumulate sequential blocks until end-of-file bit is set.
|
||||
auto file = std::make_unique<File>();
|
||||
|
||||
uint16_t block_number = 0;
|
||||
|
||||
while(chunks.size()) {
|
||||
while(!chunks.empty()) {
|
||||
if(chunks.front().block_number != block_number) return nullptr;
|
||||
|
||||
bool was_last = chunks.front().block_flag & 0x80;
|
||||
const bool was_last = chunks.front().block_flag & 0x80;
|
||||
file->chunks.push_back(chunks.front());
|
||||
chunks.pop_front();
|
||||
block_number++;
|
||||
++block_number;
|
||||
|
||||
if(was_last) break;
|
||||
}
|
||||
|
||||
// accumulate total data, copy flags appropriately
|
||||
// Grab metadata flags.
|
||||
file->name = file->chunks.front().name;
|
||||
file->load_address = file->chunks.front().load_address;
|
||||
file->execution_address = file->chunks.front().execution_address;
|
||||
// I think the final chunk's flags are the ones that count; TODO: check.
|
||||
if(file->chunks.back().block_flag & 0x01) {
|
||||
// File is locked, which in more generalised terms means it is
|
||||
// for execution only.
|
||||
// File is locked i.e. for execution only.
|
||||
file->flags |= File::Flags::ExecuteOnly;
|
||||
}
|
||||
|
||||
// copy all data into a single big block
|
||||
for(File::Chunk chunk : file->chunks) {
|
||||
// Copy data into a single big block.
|
||||
file->data.reserve(file->chunks.size() * 256);
|
||||
for(auto &chunk : file->chunks) {
|
||||
file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> Analyser::Static::Acorn::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
Storage::Tape::Acorn::Parser parser;
|
||||
|
||||
// populate chunk list
|
||||
// Read all chunks.
|
||||
std::deque<File::Chunk> chunk_list;
|
||||
while(!tape->is_at_end()) {
|
||||
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
|
||||
while(!serialiser.is_at_end()) {
|
||||
const std::unique_ptr<File::Chunk> chunk = GetNextChunk(serialiser, parser);
|
||||
if(chunk) {
|
||||
chunk_list.push_back(*chunk);
|
||||
chunk_list.push_back(std::move(*chunk));
|
||||
}
|
||||
}
|
||||
|
||||
// decompose into file list
|
||||
// Convert to files.
|
||||
std::vector<File> file_list;
|
||||
|
||||
while(chunk_list.size()) {
|
||||
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
||||
while(!chunk_list.empty()) {
|
||||
const std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
||||
if(next_file) {
|
||||
file_list.push_back(*next_file);
|
||||
file_list.push_back(std::move(*next_file));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,6 @@
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
|
||||
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> {
|
||||
bool has_acorn_adfs = false;
|
||||
bool has_pres_adfs = false;
|
||||
bool has_dfs = false;
|
||||
@@ -23,15 +23,27 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Electron) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
DeclareField(has_dfs);
|
||||
DeclareField(has_ap6_rom);
|
||||
DeclareField(has_sideways_ram);
|
||||
}
|
||||
ElectronTarget() : Analyser::Static::Target(Machine::Electron) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<ElectronTarget>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
DeclareField(has_dfs);
|
||||
DeclareField(has_ap6_rom);
|
||||
DeclareField(has_sideways_ram);
|
||||
}
|
||||
};
|
||||
|
||||
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
|
||||
std::string main_program;
|
||||
|
||||
ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<ArchimedesTarget>;
|
||||
void declare_fields() {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -9,9 +9,14 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool is_confident
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
if(media.disks.empty() && !is_confident) return {};
|
||||
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -28,13 +28,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
|
||||
FastRAM fast_ram = FastRAM::EightMegabytes;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Amiga) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(fast_ram);
|
||||
DeclareField(chip_ram);
|
||||
AnnounceEnum(FastRAM);
|
||||
AnnounceEnum(ChipRAM);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::Amiga) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(fast_ram);
|
||||
DeclareField(chip_ram);
|
||||
AnnounceEnum(FastRAM);
|
||||
AnnounceEnum(ChipRAM);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -63,8 +63,8 @@ std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
|
||||
void InspectCatalogue(
|
||||
const Storage::Disk::CPM::Catalogue &catalogue,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
|
||||
) {
|
||||
std::vector<const Storage::Disk::CPM::File *> candidate_files;
|
||||
candidate_files.reserve(catalogue.files.size());
|
||||
for(const auto &file : catalogue.files) {
|
||||
@@ -158,7 +158,10 @@ void InspectCatalogue(
|
||||
target->loading_command = "cat\n";
|
||||
}
|
||||
|
||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
bool CheckBootSector(
|
||||
const std::shared_ptr<Storage::Disk::Disk> &disk,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
|
||||
) {
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||
@@ -182,7 +185,7 @@ bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
// Limited sophistication here; look for a CPC-style file header, that is
|
||||
// any Spectrum-esque block with a synchronisation character of 0x2c.
|
||||
//
|
||||
@@ -191,7 +194,7 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
Parser parser(Parser::MachineType::AmstradCPC);
|
||||
|
||||
while(true) {
|
||||
const auto block = parser.find_block(tape);
|
||||
const auto block = parser.find_block(serialiser);
|
||||
if(!block) break;
|
||||
|
||||
if(block->type == 0x2c) {
|
||||
@@ -204,7 +207,12 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
@@ -214,7 +222,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
if(!media.tapes.empty()) {
|
||||
bool has_cpc_tape = false;
|
||||
for(auto &tape: media.tapes) {
|
||||
has_cpc_tape |= IsAmstradTape(tape);
|
||||
const auto serialiser = tape->serialiser();
|
||||
has_cpc_tape |= IsAmstradTape(*serialiser);
|
||||
}
|
||||
|
||||
if(has_cpc_tape) {
|
||||
@@ -228,26 +237,14 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
Storage::Disk::CPM::ParameterBlock data_format;
|
||||
data_format.sectors_per_track = 9;
|
||||
data_format.tracks = 40;
|
||||
data_format.block_size = 1024;
|
||||
data_format.first_sector = 0xc1;
|
||||
data_format.catalogue_allocation_bitmap = 0xc000;
|
||||
data_format.reserved_tracks = 0;
|
||||
|
||||
Storage::Disk::CPM::ParameterBlock system_format;
|
||||
system_format.sectors_per_track = 9;
|
||||
system_format.tracks = 40;
|
||||
system_format.block_size = 1024;
|
||||
system_format.first_sector = 0x41;
|
||||
system_format.catalogue_allocation_bitmap = 0xc000;
|
||||
system_format.reserved_tracks = 2;
|
||||
const auto data_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format();
|
||||
const auto system_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format();
|
||||
|
||||
for(auto &disk: media.disks) {
|
||||
// Check for an ordinary catalogue.
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
|
||||
if(data_catalogue) {
|
||||
// Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk.
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue =
|
||||
Storage::Disk::CPM::GetCatalogue(disk, data_format, false);
|
||||
if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) {
|
||||
InspectCatalogue(*data_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
continue;
|
||||
@@ -260,8 +257,9 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
}
|
||||
|
||||
// Failing that check for a system catalogue.
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
|
||||
if(system_catalogue) {
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue =
|
||||
Storage::Disk::CPM::GetCatalogue(disk, system_format, false);
|
||||
if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) {
|
||||
InspectCatalogue(*system_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
continue;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -20,11 +20,21 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Model model = Model::CPC464;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AmstradCPC) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
ReflectableEnum(CRTCType, Type0, Type1, Type2, Type3);
|
||||
CRTCType crtc_type = CRTCType::Type2;
|
||||
|
||||
// This is used internally for testing; it therefore isn't exposed reflectively.
|
||||
bool catch_ssm_codes = false;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AmstradCPC) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
DeclareField(crtc_type);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(CRTCType);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -34,18 +34,26 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Model model = Model::IIe;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
SCSIController scsi_controller = SCSIController::None;
|
||||
bool has_mockingboard = true;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
DeclareField(scsi_controller);
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {}
|
||||
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
AnnounceEnum(SCSIController);
|
||||
}
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
DeclareField(scsi_controller);
|
||||
DeclareField(has_mockingboard);
|
||||
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
AnnounceEnum(SCSIController);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool is_iie(const Target::Model model) {
|
||||
return model == Target::Model::IIe || model == Target::Model::EnhancedIIe;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -29,13 +29,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Model model = Model::ROM01;
|
||||
MemoryModel memory_model = MemoryModel::EightMB;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleIIgs) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(memory_model);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::AppleIIgs) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
DeclareField(memory_model);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -33,11 +33,13 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
|
||||
// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
||||
// large amounts of memory.
|
||||
bool has_wide_area_store = false;
|
||||
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
|
||||
for(const auto &entry : high_location_disassembly.instructions_by_address) {
|
||||
using Instruction = Analyser::Static::MOS6502::Instruction;
|
||||
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
|
||||
has_wide_area_store |=
|
||||
entry.second.addressing_mode == Instruction::Indirect ||
|
||||
entry.second.addressing_mode == Instruction::IndexedIndirectX ||
|
||||
entry.second.addressing_mode == Instruction::IndirectIndexedY;
|
||||
|
||||
if(has_wide_area_store) break;
|
||||
}
|
||||
@@ -50,13 +52,21 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
|
||||
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor8kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &segment,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
|
||||
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
|
||||
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
|
||||
if(
|
||||
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
||||
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
|
||||
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 &&
|
||||
segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
||||
(
|
||||
segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 ||
|
||||
segment.data[8190] != 0x00 || segment.data[8188] != 0x00
|
||||
) &&
|
||||
segment.data[0] == 0x78
|
||||
) {
|
||||
target.paging_model = Target::PagingModel::ActivisionStack;
|
||||
@@ -88,7 +98,11 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
|
||||
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor16kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Make an assumption that this is the Atari paging model.
|
||||
target.paging_model = Target::PagingModel::Atari16k;
|
||||
|
||||
@@ -108,7 +122,11 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri
|
||||
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor64kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Make an assumption that this is a Tigervision if there is a write to 3F.
|
||||
target.paging_model =
|
||||
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
|
||||
@@ -121,8 +139,12 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
||||
const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
||||
const auto word = [](const uint8_t low, const uint8_t high) {
|
||||
return uint16_t(low | (high << 8));
|
||||
};
|
||||
|
||||
const auto entry_address = word(segment.data[segment.data.size() - 4], segment.data[segment.data.size() - 3]);
|
||||
const auto break_address = word(segment.data[segment.data.size() - 2], segment.data[segment.data.size() - 1]);
|
||||
|
||||
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
|
||||
if(!(address & 0x1000)) return size_t(-1);
|
||||
@@ -130,27 +152,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
||||
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||
const auto disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||
|
||||
switch(segment.data.size()) {
|
||||
case 8192:
|
||||
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 10495:
|
||||
target.paging_model = Target::PagingModel::Pitfall2;
|
||||
break;
|
||||
case 12288:
|
||||
target.paging_model = Target::PagingModel::CBSRamPlus;
|
||||
break;
|
||||
case 16384:
|
||||
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 32768:
|
||||
target.paging_model = Target::PagingModel::Atari32k;
|
||||
break;
|
||||
case 65536:
|
||||
DeterminePagingFor64kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 8192: DeterminePagingFor8kCartridge(target, segment, disassembly); break;
|
||||
case 10495: target.paging_model = Target::PagingModel::Pitfall2; break;
|
||||
case 12288: target.paging_model = Target::PagingModel::CBSRamPlus; break;
|
||||
case 16384: DeterminePagingFor16kCartridge(target, segment, disassembly); break;
|
||||
case 32768: target.paging_model = Target::PagingModel::Atari32k; break;
|
||||
case 65536: DeterminePagingFor64kCartridge(target, segment, disassembly); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -177,7 +188,12 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
}
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// TODO: sanity checking; is this image really for an Atari 2600?
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -20,11 +20,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
FourMegabytes);
|
||||
MemorySize memory_size = MemorySize::OneMegabyte;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(memory_size);
|
||||
AnnounceEnum(MemorySize);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(memory_size);
|
||||
AnnounceEnum(MemorySize);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -9,7 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
@@ -52,11 +52,20 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return coleco_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
const bool is_confident
|
||||
) {
|
||||
TargetList targets;
|
||||
auto target = std::make_unique<Target>(Machine::ColecoVision);
|
||||
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
if(is_confident) {
|
||||
target->media = media;
|
||||
} else {
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
}
|
||||
if(!target->media.empty())
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Coleco {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -14,159 +14,161 @@
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
public:
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
emplace_drive(4000000, 300, 2);
|
||||
set_drive(1);
|
||||
get_drive().set_motor_on(true);
|
||||
}
|
||||
public:
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
emplace_drive(4000000, 300, 2);
|
||||
set_drive(1);
|
||||
get_drive().set_motor_on(true);
|
||||
}
|
||||
|
||||
struct Sector {
|
||||
uint8_t sector, track;
|
||||
std::array<uint8_t, 256> data;
|
||||
bool header_checksum_matched;
|
||||
bool data_checksum_matched;
|
||||
};
|
||||
struct Sector {
|
||||
uint8_t sector, track;
|
||||
std::array<uint8_t, 256> data;
|
||||
bool header_checksum_matched;
|
||||
bool data_checksum_matched;
|
||||
};
|
||||
|
||||
/*!
|
||||
Attempts to read the sector located at @c track and @c sector.
|
||||
/*!
|
||||
Attempts to read the sector located at @c track and @c sector.
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> sector(uint8_t track, uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
const Sector *sector(const uint8_t track, const uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
|
||||
if(difference) {
|
||||
int direction = difference < 0 ? -1 : 1;
|
||||
difference *= direction;
|
||||
if(difference) {
|
||||
const int direction = difference < 0 ? -1 : 1;
|
||||
difference *= direction;
|
||||
|
||||
for(int c = 0; c < difference; c++) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
}
|
||||
|
||||
unsigned int zone = 3;
|
||||
if(track >= 18) zone = 2;
|
||||
else if(track >= 25) zone = 1;
|
||||
else if(track >= 31) zone = 0;
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
|
||||
for(int c = 0; c < difference; c++) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
}
|
||||
|
||||
return get_sector(sector);
|
||||
unsigned int zone = 3;
|
||||
if(track >= 18) zone = 2;
|
||||
else if(track >= 25) zone = 1;
|
||||
else if(track >= 31) zone = 0;
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
|
||||
}
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
get_drive().set_disk(disk);
|
||||
return get_sector(sector);
|
||||
}
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::unordered_map<uint16_t, std::unique_ptr<Sector>> sector_cache_;
|
||||
|
||||
void process_input_bit(const int value) override {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block(const int max_index_count) {
|
||||
// find GCR lead-in
|
||||
proceed_to_shift_value(0x3ff);
|
||||
if(shift_register_ != 0x3ff) return 0xff;
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(int value) {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block(int max_index_count) {
|
||||
// find GCR lead-in
|
||||
proceed_to_shift_value(0x3ff);
|
||||
if(shift_register_ != 0x3ff) return 0xff;
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
}
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
unsigned int get_next_byte() {
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 10) run_for(Cycles(1));
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
}
|
||||
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
void proceed_to_shift_value(const unsigned int shift_value) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int get_next_byte() {
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 10) run_for(Cycles(1));
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
void process_index_hole() override {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
const Sector *get_sector(const uint8_t sector) {
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
auto existing = sector_cache_.find(sector_address);
|
||||
if(existing != sector_cache_.end()) return existing->second.get();
|
||||
|
||||
const auto first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(true) {
|
||||
const auto next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
}
|
||||
|
||||
void proceed_to_shift_value(unsigned int shift_value) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
|
||||
void process_index_hole() {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_sector(uint8_t sector) {
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
|
||||
const std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
const Sector *get_next_sector() {
|
||||
auto sector = std::make_unique<Sector>();
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < max_index_count) {
|
||||
// look for a sector header
|
||||
while(1) {
|
||||
const std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
uint8_t checksum = uint8_t(get_next_byte());
|
||||
sector->sector = uint8_t(get_next_byte());
|
||||
sector->track = uint8_t(get_next_byte());
|
||||
uint8_t disk_id[2];
|
||||
disk_id[0] = uint8_t(get_next_byte());
|
||||
disk_id[1] = uint8_t(get_next_byte());
|
||||
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
|
||||
|
||||
// look for the following data
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = uint8_t(get_next_byte());
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
auto pair = sector_cache_.emplace(sector_address, std::move(sector));
|
||||
return pair.first->second.get();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_next_sector() {
|
||||
auto sector = std::make_shared<Sector>();
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < max_index_count) {
|
||||
// look for a sector header
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
uint8_t checksum = uint8_t(get_next_byte());
|
||||
sector->sector = uint8_t(get_next_byte());
|
||||
sector->track = uint8_t(get_next_byte());
|
||||
uint8_t disk_id[2];
|
||||
disk_id[0] = uint8_t(get_next_byte());
|
||||
disk_id[1] = uint8_t(get_next_byte());
|
||||
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
|
||||
|
||||
// look for the following data
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = uint8_t(get_next_byte());
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
@@ -174,20 +176,26 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
CommodoreGCRParser parser;
|
||||
parser.set_disk(disk);
|
||||
|
||||
// find any sector whatsoever to establish the current track
|
||||
std::shared_ptr<CommodoreGCRParser::Sector> sector;
|
||||
|
||||
// assemble directory
|
||||
// Assemble directory.
|
||||
std::vector<uint8_t> directory;
|
||||
uint8_t next_track = 18;
|
||||
uint8_t next_sector = 1;
|
||||
while(1) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
directory.reserve(20 * 1024); // Probably more than plenty.
|
||||
std::set<std::pair<uint8_t, uint8_t>> visited;
|
||||
while(true) {
|
||||
// Don't be fooled by disks that are encoded with a looping directory.
|
||||
const auto key = std::make_pair(next_track, next_sector);
|
||||
if(visited.find(key) != visited.end()) break;
|
||||
visited.insert(key);
|
||||
|
||||
// Append sector to directory and follow next link.
|
||||
const auto sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
|
||||
// Check for end-of-directory.
|
||||
if(!next_track) break;
|
||||
}
|
||||
|
||||
@@ -216,24 +224,36 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
}
|
||||
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
|
||||
std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
const std::size_t number_of_sectors =
|
||||
size_t(directory[header_pointer + 0x1e]) +
|
||||
(size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
if(number_of_sectors) {
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
const auto sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
|
||||
else
|
||||
new_file.data.insert(new_file.data.end(), sector->data.begin() + 2, sector->data.begin() + next_sector);
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + (is_first_sector ? 4 : 2),
|
||||
sector->data.end()
|
||||
);
|
||||
else
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + 2,
|
||||
sector->data.begin() + next_sector
|
||||
);
|
||||
|
||||
is_first_sector = false;
|
||||
is_first_sector = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!next_track) files.push_back(new_file);
|
||||
|
@@ -15,6 +15,6 @@
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
|
||||
}
|
||||
|
@@ -1,47 +0,0 @@
|
||||
//
|
||||
// File.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/09/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "File.hpp"
|
||||
|
||||
bool Analyser::Static::Commodore::File::is_basic() {
|
||||
// BASIC files are always relocatable (?)
|
||||
if(type != File::RelocatableProgram) return false;
|
||||
|
||||
uint16_t line_address = starting_address;
|
||||
int line_number = -1;
|
||||
|
||||
// decide whether this is a BASIC file based on the proposition that:
|
||||
// (1) they're always relocatable; and
|
||||
// (2) they have a per-line structure of:
|
||||
// [4 bytes: address of start of next line]
|
||||
// [4 bytes: this line number]
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)
|
||||
while(1) {
|
||||
if(size_t(line_address - starting_address) >= data.size() + 2) break;
|
||||
|
||||
uint16_t next_line_address = data[line_address - starting_address];
|
||||
next_line_address |= data[line_address - starting_address + 1] << 8;
|
||||
|
||||
if(!next_line_address) {
|
||||
return true;
|
||||
}
|
||||
if(next_line_address < line_address + 5) break;
|
||||
|
||||
if(size_t(line_address - starting_address) >= data.size() + 5) break;
|
||||
uint16_t next_line_number = data[line_address - starting_address + 2];
|
||||
next_line_number |= data[line_address - starting_address + 3] << 8;
|
||||
|
||||
if(next_line_number <= line_number) break;
|
||||
|
||||
line_number = uint16_t(next_line_number);
|
||||
line_address = next_line_address;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@@ -29,8 +29,6 @@ struct File {
|
||||
Relative
|
||||
} type;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
bool is_basic();
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -15,23 +15,30 @@
|
||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
namespace {
|
||||
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
// only one mapped item is allowed
|
||||
// Only one mapped item is allowed ...
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 16 kb in size
|
||||
// ... which must be 16 kb in size.
|
||||
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||
if(segment.start_address != 0xa000) continue;
|
||||
if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
|
||||
@@ -39,126 +46,313 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
vic20_cartridges.push_back(cartridge);
|
||||
}
|
||||
|
||||
// TODO: other machines?
|
||||
|
||||
return vic20_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
struct BASICAnalysis {
|
||||
enum class Version {
|
||||
NotBASIC,
|
||||
BASIC2,
|
||||
BASIC4,
|
||||
BASIC3_5,
|
||||
} minimum_version = Version::NotBASIC;
|
||||
std::vector<uint16_t> machine_code_addresses;
|
||||
};
|
||||
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::Vic20; // TODO: machine estimation
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
std::optional<BASICAnalysis> analyse(const File &file) {
|
||||
BASICAnalysis analysis;
|
||||
|
||||
switch(file.type) {
|
||||
// For 'program' types, proceed with analysis below.
|
||||
case File::RelocatableProgram:
|
||||
case File::NonRelocatableProgram:
|
||||
break;
|
||||
|
||||
// For sequential and relative data stop right now.
|
||||
case File::DataSequence:
|
||||
case File::Relative:
|
||||
return std::nullopt;
|
||||
|
||||
// For user data, try decoding from the starting point.
|
||||
case File::User:
|
||||
analysis.machine_code_addresses.push_back(file.starting_address);
|
||||
return analysis;
|
||||
}
|
||||
|
||||
// Don't form an opinion if file is empty.
|
||||
if(file.data.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uint16_t line_address = file.starting_address;
|
||||
// int previous_line_number = -1;
|
||||
|
||||
const auto byte = [&](uint16_t address) {
|
||||
return file.data[address - file.starting_address];
|
||||
};
|
||||
const auto word = [&](uint16_t address) {
|
||||
return uint16_t(byte(address) | byte(address + 1) << 8);
|
||||
};
|
||||
|
||||
// BASIC programs have a per-line structure of:
|
||||
// [2 bytes: address of start of next line]
|
||||
// [2 bytes: this line number]
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)
|
||||
//
|
||||
// If a SYS is encountered that jumps into the BASIC program then treat that as
|
||||
// a machine code entry point.
|
||||
|
||||
std::unordered_set<uint16_t> visited_lines;
|
||||
while(true) {
|
||||
// Analysis has failed if there isn't at least one complete BASIC line from here.
|
||||
// Fall back on guessing the start address as a machine code entrypoint.
|
||||
if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) {
|
||||
analysis.machine_code_addresses.push_back(file.starting_address);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto next_line_address = word(line_address);
|
||||
// const auto line_number = word(line_address + 2);
|
||||
|
||||
uint16_t code = line_address + 4;
|
||||
const auto next = [&]() -> uint8_t {
|
||||
if(code >= file.starting_address + file.data.size()) {
|
||||
return 0;
|
||||
}
|
||||
return byte(code++);
|
||||
};
|
||||
|
||||
// TODO: sanity check on apparent line contents.
|
||||
// TODO: observe token set (and possibly parameters?) to guess BASIC version.
|
||||
while(true) {
|
||||
const auto token = next();
|
||||
if(!token || token == 0x8f) break;
|
||||
|
||||
switch(token) {
|
||||
case 0x9e: { // SYS; parse following ASCII argument.
|
||||
uint16_t address = 0;
|
||||
while(true) {
|
||||
const auto c = next();
|
||||
if(c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
address = (address * 10) + (c - '0');
|
||||
};
|
||||
analysis.machine_code_addresses.push_back(address);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if a formal end of the program has been declared or if, as some copy protections do,
|
||||
// the linked list of line contents has been made circular.
|
||||
visited_lines.insert(line_address);
|
||||
if(!next_line_address || visited_lines.find(next_line_address) != visited_lines.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// previous_line_number = line_number;
|
||||
line_address = next_line_address;
|
||||
}
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
template <typename TargetT>
|
||||
void set_loading_command(TargetT &target) {
|
||||
if(target.media.disks.empty()) {
|
||||
target.loading_command = "LOAD\"\",1,1\nRUN\n";
|
||||
} else {
|
||||
target.loading_command = "LOAD\"*\",8,1\nRUN\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool obviously_uses_ted(const File &file) {
|
||||
const auto analysis = analyse(file);
|
||||
if(!analysis) return false;
|
||||
|
||||
// Disassemble.
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
file.data,
|
||||
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
|
||||
analysis->machine_code_addresses
|
||||
);
|
||||
|
||||
// If FF3E or FF3F is touched, this is for the +4.
|
||||
// TODO: probably require a very early touch.
|
||||
for(const auto address: {0xff3e, 0xff3f}) {
|
||||
for(const auto &collection: {
|
||||
disassembly.external_loads,
|
||||
disassembly.external_stores,
|
||||
disassembly.external_modifies
|
||||
}) {
|
||||
if(collection.find(uint16_t(address)) != collection.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct FileAnalysis {
|
||||
int device = 0;
|
||||
std::vector<File> files;
|
||||
bool is_disk = false;
|
||||
Analyser::Static::Media media;
|
||||
};
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
|
||||
template <TargetPlatform::Type platform>
|
||||
FileAnalysis analyse_files(const Analyser::Static::Media &media) {
|
||||
FileAnalysis analysis;
|
||||
|
||||
// check disks
|
||||
// Find all valid Commodore files on disks.
|
||||
for(auto &disk : media.disks) {
|
||||
std::vector<File> disk_files = GetFiles(disk);
|
||||
if(!disk_files.empty()) {
|
||||
is_disk = true;
|
||||
files.insert(files.end(), disk_files.begin(), disk_files.end());
|
||||
target->media.disks.push_back(disk);
|
||||
if(!device) device = 8;
|
||||
analysis.is_disk = true;
|
||||
analysis.files.insert(
|
||||
analysis.files.end(),
|
||||
std::make_move_iterator(disk_files.begin()),
|
||||
std::make_move_iterator(disk_files.end())
|
||||
);
|
||||
analysis.media.disks.push_back(disk);
|
||||
if(!analysis.device) analysis.device = 8;
|
||||
}
|
||||
}
|
||||
|
||||
// check tapes
|
||||
// Find all valid Commodore files on tapes.
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> tape_files = GetFiles(tape);
|
||||
tape->reset();
|
||||
auto serialiser = tape->serialiser();
|
||||
std::vector<File> tape_files = GetFiles(*serialiser, platform);
|
||||
if(!tape_files.empty()) {
|
||||
files.insert(files.end(), tape_files.begin(), tape_files.end());
|
||||
target->media.tapes.push_back(tape);
|
||||
if(!device) device = 1;
|
||||
analysis.files.insert(
|
||||
analysis.files.end(),
|
||||
std::make_move_iterator(tape_files.begin()),
|
||||
std::make_move_iterator(tape_files.end())
|
||||
);
|
||||
analysis.media.tapes.push_back(tape);
|
||||
if(!analysis.device) analysis.device = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(!files.empty()) {
|
||||
auto memory_model = Target::MemoryModel::Unexpanded;
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
|
||||
if(files.front().is_basic()) {
|
||||
string_stream << "0";
|
||||
} else {
|
||||
string_stream << "1";
|
||||
return analysis;
|
||||
}
|
||||
|
||||
std::string loading_command(const FileAnalysis &file_analysis) {
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "LOAD\"" << (file_analysis.is_disk ? "*" : "") << "\"," << file_analysis.device;
|
||||
|
||||
const auto analysis = analyse(file_analysis.files[0]);
|
||||
if(analysis && !analysis->machine_code_addresses.empty()) {
|
||||
string_stream << ",1";
|
||||
}
|
||||
string_stream << "\nRUN\n";
|
||||
return string_stream.str();
|
||||
}
|
||||
|
||||
std::pair<TargetPlatform::IntType, std::optional<Vic20Target::MemoryModel>>
|
||||
analyse_starting_address(uint16_t starting_address) {
|
||||
switch(starting_address) {
|
||||
case 0x1c01:
|
||||
// TODO: assume C128.
|
||||
default:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append(
|
||||
"Unrecognised loading address for Commodore program: %04x", starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
return std::make_pair(TargetPlatform::Vic20 | TargetPlatform::Plus4, Vic20Target::MemoryModel::Unexpanded);
|
||||
|
||||
case 0x1201: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::ThirtyTwoKB);
|
||||
case 0x0401: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::EightKB);
|
||||
case 0x0801: return std::make_pair(TargetPlatform::C64, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
template <TargetPlatform::IntType platform>
|
||||
std::unique_ptr<Analyser::Static::Target> get_target(
|
||||
const Analyser::Static::Media &media,
|
||||
const std::string &file_name,
|
||||
bool is_confident
|
||||
);
|
||||
|
||||
template<>
|
||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Plus4>(
|
||||
const Analyser::Static::Media &media,
|
||||
const std::string &,
|
||||
bool is_confident
|
||||
) {
|
||||
auto target = std::make_unique<Plus4Target>();
|
||||
if(is_confident) {
|
||||
target->media = media;
|
||||
set_loading_command(*target);
|
||||
} else {
|
||||
const auto files = analyse_files<TargetPlatform::Plus4>(media);
|
||||
if(!files.files.empty()) {
|
||||
target->loading_command = loading_command(files);
|
||||
}
|
||||
string_stream << "\nRUN\n";
|
||||
target->loading_command = string_stream.str();
|
||||
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append("Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
memory_model = Target::MemoryModel::Unexpanded;
|
||||
break;
|
||||
case 0x1201:
|
||||
memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
break;
|
||||
case 0x0401:
|
||||
memory_model = Target::MemoryModel::EightKB;
|
||||
break;
|
||||
}
|
||||
|
||||
target->set_memory_model(memory_model);
|
||||
|
||||
// General approach: increase memory size conservatively such that the largest file found will fit.
|
||||
// for(File &file : files) {
|
||||
// std::size_t file_size = file.data.size();
|
||||
// bool is_basic = file.is_basic();
|
||||
|
||||
/*if(is_basic)
|
||||
{
|
||||
// BASIC files may be relocated, so the only limit is size.
|
||||
//
|
||||
// An unexpanded machine has 3583 bytes free for BASIC;
|
||||
// a 3kb expanded machine has 6655 bytes free.
|
||||
if(file_size > 6655)
|
||||
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
|
||||
target->vic20.memory_model = Vic20MemoryModel::EightKB;
|
||||
}
|
||||
else
|
||||
{*/
|
||||
// if(!file.type == File::NonRelocatableProgram)
|
||||
// {
|
||||
// Non-BASIC files may be relocatable but, if so, by what logic?
|
||||
// Given that this is unknown, take starting address as literal
|
||||
// and check against memory windows.
|
||||
//
|
||||
// (ignoring colour memory...)
|
||||
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
|
||||
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
|
||||
// A 32kb expanded Vic has memory in the entire low 32kb.
|
||||
// uint16_t starting_address = file.starting_address;
|
||||
|
||||
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
|
||||
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
|
||||
// if(starting_address + file_size > 0x2000)
|
||||
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
// }
|
||||
// }
|
||||
target->media.disks = media.disks;
|
||||
target->media.tapes = media.tapes;
|
||||
}
|
||||
|
||||
// Attach a 1541 if there are any disks here.
|
||||
target->has_c1541 = !target->media.disks.empty();
|
||||
return std::move(target);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
|
||||
const Analyser::Static::Media &media,
|
||||
const std::string &file_name,
|
||||
bool is_confident
|
||||
) {
|
||||
auto target = std::make_unique<Vic20Target>();
|
||||
const auto files = analyse_files<TargetPlatform::Vic20>(media);
|
||||
if(!files.files.empty()) {
|
||||
target->loading_command = loading_command(files);
|
||||
|
||||
const auto model = analyse_starting_address(files.files[0].starting_address);
|
||||
if(model.second.has_value()) {
|
||||
target->set_memory_model(*model.second);
|
||||
}
|
||||
}
|
||||
|
||||
if(is_confident) {
|
||||
target->media = media;
|
||||
set_loading_command(*target);
|
||||
} else {
|
||||
// Strip out inappropriate cartridges but retain all tapes and disks.
|
||||
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
|
||||
target->media.disks = media.disks;
|
||||
target->media.tapes = media.tapes;
|
||||
}
|
||||
|
||||
for(const auto &file : files.files) {
|
||||
// The Vic-20 never has RAM after 0x8000.
|
||||
if(file.ending_address >= 0x8000) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(obviously_uses_ted(file)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect filename for configuration hints.
|
||||
if(!target->media.empty()) {
|
||||
// Inspect filename for configuration hints.
|
||||
using Region = Analyser::Static::Commodore::Vic20Target::Region;
|
||||
|
||||
std::string lowercase_name = file_name;
|
||||
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
|
||||
|
||||
// Hint 1: 'ntsc' anywhere in the name implies America.
|
||||
if(lowercase_name.find("ntsc") != std::string::npos) {
|
||||
target->region = Analyser::Static::Commodore::Target::Region::American;
|
||||
target->region = Region::American;
|
||||
}
|
||||
|
||||
// Potential additional hints: check for TheC64 tags.
|
||||
// Potential additional hints: check for TheC64 tags; these are Vic-20 exclusive.
|
||||
auto final_underscore = lowercase_name.find_last_of('_');
|
||||
if(final_underscore != std::string::npos) {
|
||||
auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
|
||||
@@ -180,10 +374,10 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
|
||||
target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
|
||||
if(!strcmp(next_tag, "tn")) { // i.e. NTSC.
|
||||
target->region = Analyser::Static::Commodore::Target::Region::American;
|
||||
target->region = Region::American;
|
||||
}
|
||||
if(!strcmp(next_tag, "tp")) { // i.e. PAL.
|
||||
target->region = Analyser::Static::Commodore::Target::Region::European;
|
||||
target->region = Region::European;
|
||||
}
|
||||
|
||||
// Unhandled:
|
||||
@@ -194,11 +388,36 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
// RO: this disk image should be treated as read-only.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach a 1540 if there are any disks here.
|
||||
target->has_c1540 = !target->media.disks.empty();
|
||||
// Attach a 1540 if there are any disks here.
|
||||
target->has_c1540 = !target->media.disks.empty();
|
||||
return std::move(target);
|
||||
}
|
||||
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType platforms,
|
||||
bool is_confident
|
||||
) {
|
||||
TargetList destination;
|
||||
|
||||
if(platforms & TargetPlatform::Vic20) {
|
||||
auto vic20 = get_target<TargetPlatform::Vic20>(media, file_name, is_confident);
|
||||
if(vic20) {
|
||||
destination.push_back(std::move(vic20));
|
||||
}
|
||||
}
|
||||
|
||||
if(platforms & TargetPlatform::Plus4) {
|
||||
auto plus4 = get_target<TargetPlatform::Plus4>(media, file_name, is_confident);
|
||||
if(plus4) {
|
||||
destination.push_back(std::move(plus4));
|
||||
}
|
||||
}
|
||||
|
||||
return destination;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -12,21 +12,21 @@
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) {
|
||||
Storage::Tape::Commodore::Parser parser(type);
|
||||
std::vector<File> file_list;
|
||||
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(serialiser);
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
while(!serialiser.is_at_end()) {
|
||||
if(!header) {
|
||||
header = parser.get_next_header(tape);
|
||||
header = parser.get_next_header(serialiser);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(header->type) {
|
||||
case Storage::Tape::Commodore::Header::DataSequenceHeader: {
|
||||
File new_file;
|
||||
File &new_file = file_list.emplace_back();
|
||||
new_file.name = header->name;
|
||||
new_file.raw_name = header->raw_name;
|
||||
new_file.starting_address = header->starting_address;
|
||||
@@ -34,38 +34,36 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
new_file.type = File::DataSequence;
|
||||
|
||||
new_file.data.swap(header->data);
|
||||
while(!tape->is_at_end()) {
|
||||
header = parser.get_next_header(tape);
|
||||
while(!serialiser.is_at_end()) {
|
||||
header = parser.get_next_header(serialiser);
|
||||
if(!header) continue;
|
||||
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
|
||||
std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
|
||||
}
|
||||
|
||||
file_list.push_back(new_file);
|
||||
}
|
||||
break;
|
||||
|
||||
case Storage::Tape::Commodore::Header::RelocatableProgram:
|
||||
case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(serialiser);
|
||||
if(data) {
|
||||
File new_file;
|
||||
File &new_file = file_list.emplace_back();
|
||||
new_file.name = header->name;
|
||||
new_file.raw_name = header->raw_name;
|
||||
new_file.starting_address = header->starting_address;
|
||||
new_file.ending_address = header->ending_address;
|
||||
new_file.data.swap(data->data);
|
||||
new_file.type = (header->type == Storage::Tape::Commodore::Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram;
|
||||
|
||||
file_list.push_back(new_file);
|
||||
new_file.type =
|
||||
header->type == Storage::Tape::Commodore::Header::RelocatableProgram
|
||||
? File::RelocatableProgram : File::NonRelocatableProgram;
|
||||
}
|
||||
|
||||
header = parser.get_next_header(tape);
|
||||
header = parser.get_next_header(serialiser);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
header = parser.get_next_header(tape);
|
||||
header = parser.get_next_header(serialiser);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &, TargetPlatform::Type);
|
||||
|
||||
}
|
||||
|
@@ -15,7 +15,21 @@
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
struct Plus4Target: public Analyser::Static::Target, public Reflection::StructImpl<Plus4Target> {
|
||||
// TODO: region, etc.
|
||||
std::string loading_command;
|
||||
bool has_c1541 = false;
|
||||
|
||||
Plus4Target() : Analyser::Static::Target(Machine::Plus4) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Plus4Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_c1541);
|
||||
}
|
||||
};
|
||||
|
||||
struct Vic20Target: public Analyser::Static::Target, public Reflection::StructImpl<Vic20Target> {
|
||||
enum class MemoryModel {
|
||||
Unexpanded,
|
||||
EightKB,
|
||||
@@ -54,17 +68,19 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
bool has_c1540 = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Vic20) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(enabled_ram.bank0);
|
||||
DeclareField(enabled_ram.bank1);
|
||||
DeclareField(enabled_ram.bank2);
|
||||
DeclareField(enabled_ram.bank3);
|
||||
DeclareField(enabled_ram.bank5);
|
||||
DeclareField(region);
|
||||
DeclareField(has_c1540);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
Vic20Target() : Analyser::Static::Target(Machine::Vic20) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Vic20Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(enabled_ram.bank0);
|
||||
DeclareField(enabled_ram.bank1);
|
||||
DeclareField(enabled_ram.bank2);
|
||||
DeclareField(enabled_ram.bank3);
|
||||
DeclareField(enabled_ram.bank5);
|
||||
DeclareField(region);
|
||||
DeclareField(has_c1540);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -17,7 +17,12 @@ using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Dis
|
||||
|
||||
struct MOS6502Disassembler {
|
||||
|
||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
|
||||
static void AddToDisassembly(
|
||||
PartialDisassembly &disassembly,
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
uint16_t entry_point
|
||||
) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
uint16_t address = entry_point;
|
||||
while(true) {
|
||||
@@ -75,23 +80,25 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
}
|
||||
|
||||
// Decode operation.
|
||||
#define RM_INSTRUCTION(base, op) \
|
||||
case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
|
||||
instruction.operation = op; \
|
||||
#define RM_INSTRUCTION(base, op) \
|
||||
case base+0x09: case base+0x05: case base+0x15: case base+0x01: \
|
||||
case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define URM_INSTRUCTION(base, op) \
|
||||
#define URM_INSTRUCTION(base, op) \
|
||||
case base+0x07: case base+0x17: case base+0x03: case base+0x13: case base+0x0f: case base+0x1f: case base+0x1b: \
|
||||
instruction.operation = op; \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define M_INSTRUCTION(base, op) \
|
||||
#define M_INSTRUCTION(base, op) \
|
||||
case base+0x0a: case base+0x06: case base+0x16: case base+0x0e: case base+0x1e: \
|
||||
instruction.operation = op; \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define IM_INSTRUCTION(base, op) \
|
||||
#define IM_INSTRUCTION(base, op) \
|
||||
case base: instruction.operation = op; break;
|
||||
|
||||
switch(operation) {
|
||||
default:
|
||||
instruction.operation = Instruction::KIL;
|
||||
@@ -259,7 +266,10 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
|
||||
|
||||
// TODO: something wider-ranging than this
|
||||
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
|
||||
if(
|
||||
instruction.addressing_mode == Instruction::Absolute ||
|
||||
instruction.addressing_mode == Instruction::ZeroPage
|
||||
) {
|
||||
const size_t mapped_address = address_mapper(instruction.operand);
|
||||
const bool is_external = mapped_address >= memory.size();
|
||||
|
||||
@@ -272,20 +282,23 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
case Instruction::ADC: case Instruction::SBC:
|
||||
case Instruction::LAS:
|
||||
case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
|
||||
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
|
||||
case Instruction::STY: case Instruction::STX: case Instruction::STA:
|
||||
case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
|
||||
case Instruction::TAS:
|
||||
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
|
||||
case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
|
||||
case Instruction::DCP: case Instruction::ISC:
|
||||
case Instruction::INC: case Instruction::DEC:
|
||||
case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
|
||||
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -330,5 +343,10 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points) {
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(
|
||||
memory,
|
||||
address_mapper,
|
||||
entry_points,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ namespace Analyser::Static::Disassembler {
|
||||
Provides an address mapper that relocates a chunk of memory so that it starts at
|
||||
address @c start_address.
|
||||
*/
|
||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
|
||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(const T start_address) {
|
||||
return [start_address](T argument) {
|
||||
return size_t(argument - start_address);
|
||||
};
|
||||
|
@@ -16,44 +16,48 @@ namespace {
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
class Accessor {
|
||||
public:
|
||||
Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
|
||||
memory_(memory), address_mapper_(address_mapper), address_(address) {}
|
||||
public:
|
||||
Accessor(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
uint16_t address
|
||||
) :
|
||||
memory_(memory), address_mapper_(address_mapper), address_(address) {}
|
||||
|
||||
uint8_t byte() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
address_++;
|
||||
if(mapped_address >= memory_.size()) {
|
||||
overrun_ = true;
|
||||
return 0xff;
|
||||
}
|
||||
return memory_[mapped_address];
|
||||
uint8_t byte() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
++address_;
|
||||
if(mapped_address >= memory_.size()) {
|
||||
overrun_ = true;
|
||||
return 0xff;
|
||||
}
|
||||
return memory_[mapped_address];
|
||||
}
|
||||
|
||||
uint16_t word() {
|
||||
uint8_t low = byte();
|
||||
uint8_t high = byte();
|
||||
return uint16_t(low | (high << 8));
|
||||
}
|
||||
uint16_t word() {
|
||||
uint8_t low = byte();
|
||||
uint8_t high = byte();
|
||||
return uint16_t(low | (high << 8));
|
||||
}
|
||||
|
||||
bool overrun() {
|
||||
return overrun_;
|
||||
}
|
||||
bool overrun() const {
|
||||
return overrun_;
|
||||
}
|
||||
|
||||
bool at_end() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
return mapped_address >= memory_.size();
|
||||
}
|
||||
bool at_end() const {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
return mapped_address >= memory_.size();
|
||||
}
|
||||
|
||||
uint16_t address() {
|
||||
return address_;
|
||||
}
|
||||
uint16_t address() const {
|
||||
return address_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<uint8_t> &memory_;
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper_;
|
||||
uint16_t address_;
|
||||
bool overrun_ = false;
|
||||
private:
|
||||
const std::vector<uint8_t> &memory_;
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper_;
|
||||
uint16_t address_;
|
||||
bool overrun_ = false;
|
||||
};
|
||||
|
||||
constexpr uint8_t x(uint8_t v) { return v >> 6; }
|
||||
@@ -83,8 +87,12 @@ Instruction::Location register_pair_table2[] = {
|
||||
Instruction::Location::AF
|
||||
};
|
||||
|
||||
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
Instruction::Location register_table[] = {
|
||||
Instruction::Location RegisterTableEntry(
|
||||
const int offset, Accessor &accessor,
|
||||
Instruction &instruction,
|
||||
const bool needs_indirect_offset
|
||||
) {
|
||||
constexpr Instruction::Location register_table[] = {
|
||||
Instruction::Location::B, Instruction::Location::C,
|
||||
Instruction::Location::D, Instruction::Location::E,
|
||||
Instruction::Location::H, Instruction::Location::L,
|
||||
@@ -92,7 +100,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
|
||||
Instruction::Location::A
|
||||
};
|
||||
|
||||
Instruction::Location location = register_table[offset];
|
||||
const Instruction::Location location = register_table[offset];
|
||||
if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
|
||||
instruction.offset = accessor.byte() - 128;
|
||||
}
|
||||
@@ -100,7 +108,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
|
||||
return location;
|
||||
}
|
||||
|
||||
Instruction::Operation alu_table[] = {
|
||||
constexpr Instruction::Operation alu_table[] = {
|
||||
Instruction::Operation::ADD,
|
||||
Instruction::Operation::ADC,
|
||||
Instruction::Operation::SUB,
|
||||
@@ -111,7 +119,7 @@ Instruction::Operation alu_table[] = {
|
||||
Instruction::Operation::CP
|
||||
};
|
||||
|
||||
Instruction::Operation rotation_table[] = {
|
||||
constexpr Instruction::Operation rotation_table[] = {
|
||||
Instruction::Operation::RLC,
|
||||
Instruction::Operation::RRC,
|
||||
Instruction::Operation::RL,
|
||||
@@ -122,19 +130,32 @@ Instruction::Operation rotation_table[] = {
|
||||
Instruction::Operation::SRL
|
||||
};
|
||||
|
||||
Instruction::Operation block_table[][4] = {
|
||||
{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
|
||||
{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
|
||||
{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
|
||||
{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
|
||||
constexpr Instruction::Operation block_table[][4] = {
|
||||
{
|
||||
Instruction::Operation::LDI, Instruction::Operation::CPI,
|
||||
Instruction::Operation::INI, Instruction::Operation::OUTI
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDD, Instruction::Operation::CPD,
|
||||
Instruction::Operation::IND, Instruction::Operation::OUTD
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDIR, Instruction::Operation::CPIR,
|
||||
Instruction::Operation::INIR, Instruction::Operation::OTIR
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDDR, Instruction::Operation::CPDR,
|
||||
Instruction::Operation::INDR, Instruction::Operation::OTDR
|
||||
},
|
||||
};
|
||||
|
||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
if(!x(operation)) {
|
||||
instruction.operation = rotation_table[y(operation)];
|
||||
instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
@@ -148,7 +169,7 @@ void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
}
|
||||
}
|
||||
|
||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
@@ -170,7 +191,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
if(y(operation) == 6) {
|
||||
instruction.destination = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@@ -179,7 +201,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
if(y(operation) == 6) {
|
||||
instruction.source = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@@ -190,11 +213,13 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
if(q(operation)) {
|
||||
instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand_Indirect;
|
||||
} else {
|
||||
instruction.destination = Instruction::Location::Operand_Indirect;
|
||||
instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
instruction.operand = accessor.word();
|
||||
break;
|
||||
@@ -202,7 +227,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
instruction.operation = Instruction::Operation::NEG;
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
|
||||
instruction.operation =
|
||||
y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN;
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::IM;
|
||||
@@ -253,7 +279,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
} hl_substitution = None;
|
||||
|
||||
while(true) {
|
||||
uint8_t operation = accessor.byte();
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
case 0:
|
||||
@@ -343,15 +369,18 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::INC;
|
||||
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = Instruction::Operation::DEC;
|
||||
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = accessor.byte();
|
||||
break;
|
||||
@@ -374,8 +403,10 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
instruction.operation = Instruction::Operation::HALT;
|
||||
} else {
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@@ -517,10 +548,14 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
instruction.destination == Instruction::Location::HL_Indirect) {
|
||||
|
||||
if(instruction.source == Instruction::Location::HL_Indirect) {
|
||||
instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
instruction.source =
|
||||
hl_substitution == IX ?
|
||||
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
}
|
||||
if(instruction.destination == Instruction::Location::HL_Indirect) {
|
||||
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
instruction.destination =
|
||||
hl_substitution == IX ?
|
||||
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -542,7 +577,12 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
}
|
||||
|
||||
struct Z80Disassembler {
|
||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
|
||||
static void AddToDisassembly(
|
||||
PartialDisassembly &disassembly,
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
const uint16_t entry_point
|
||||
) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
Accessor accessor(memory, address_mapper, entry_point);
|
||||
|
||||
|
@@ -47,7 +47,12 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@@ -62,7 +67,8 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
}
|
||||
|
||||
// Grab track 0, sector 0: the boot sector.
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto track_zero =
|
||||
disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
|
||||
|
||||
@@ -89,7 +95,8 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
// If the boot sector looks like it's intended for the Oric, create an Oric.
|
||||
// Otherwise go with the Apple II.
|
||||
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
|
||||
bool did_read_shift_register = false;
|
||||
bool is_oric = false;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::DiskII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -26,7 +26,12 @@ bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@@ -72,7 +77,8 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi
|
||||
|
||||
if(!has_exdos_ini) {
|
||||
if(did_pick_file) {
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
target->loading_command =
|
||||
std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
} else {
|
||||
target->loading_command = ":dir\n";
|
||||
}
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -30,20 +30,22 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Speed speed = Speed::FourMHz;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
AnnounceEnum(Speed);
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {}
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
DeclareField(speed);
|
||||
}
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
AnnounceEnum(Speed);
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
DeclareField(speed);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -16,7 +16,12 @@
|
||||
#include "../../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
|
||||
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType platforms,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@@ -35,11 +40,12 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
// If the disk image is very small or large, map it to the PC. That's the only option old enough
|
||||
// to have used 5.25" media.
|
||||
if(disk->get_maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
|
||||
}
|
||||
|
||||
// Attempt to grab MFM track 0, sector 1: the boot sector.
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto track_zero =
|
||||
disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::MFM::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(
|
||||
*track_zero,
|
||||
@@ -48,7 +54,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
|
||||
// If no sectors were found, assume this disk was either single density or high density, which both imply the PC.
|
||||
if(sector_map.empty() || sector_map.size() > 10) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
|
||||
}
|
||||
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
|
||||
@@ -77,7 +83,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
if(
|
||||
std::search(sample.begin(), sample.end(), string.begin(), string.end()) != sample.end()
|
||||
) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,5 +102,5 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
// could redirect to an MSX2 with MSX-DOS2? Though it'd be nicer if I had a machine that was pure CP/M.
|
||||
|
||||
// Being unable to prove that this is a PC disk, throw it to the Enterprise.
|
||||
return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms);
|
||||
return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms, false);
|
||||
}
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::FAT12 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||
};
|
||||
const Type type;
|
||||
|
||||
Cartridge(const std::vector<Segment> &segments, Type type) :
|
||||
Cartridge(const std::vector<Segment> &segments, const Type type) :
|
||||
Storage::Cartridge::Cartridge(segments), type(type) {}
|
||||
};
|
||||
|
||||
|
@@ -27,7 +27,8 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
if(segment.data.size() & 0x1fff) {
|
||||
std::vector<uint8_t> truncated_data;
|
||||
std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
|
||||
const auto truncated_size =
|
||||
std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
|
||||
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||
output_segments.emplace_back(start_address, truncated_data);
|
||||
} else {
|
||||
@@ -82,7 +83,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
|
||||
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
const size_t data_size = segment.data.size();
|
||||
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
|
||||
|
||||
@@ -101,7 +102,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// Reject cartridge if the ROM header wasn't found.
|
||||
if(!found_start) continue;
|
||||
|
||||
uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
|
||||
const uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
@@ -137,10 +138,12 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
}
|
||||
|
||||
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
|
||||
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_8kb_total =
|
||||
address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
|
||||
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
|
||||
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
const auto konami_with_scc_total =
|
||||
address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
|
||||
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
|
||||
|
||||
@@ -182,7 +185,12 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
return targets;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
|
||||
// Append targets for any cartridges that look correct.
|
||||
@@ -194,7 +202,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
|
||||
|
||||
// Check tapes for loadable files.
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> files_on_tape = GetFiles(tape);
|
||||
const std::vector<File> files_on_tape = GetFiles(tape);
|
||||
if(!files_on_tape.empty()) {
|
||||
switch(files_on_tape.front().type) {
|
||||
case File::Type::ASCII: target->loading_command = "RUN\"CAS:\r"; break;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -29,12 +29,12 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player(1000000);
|
||||
tape_player.set_motor_control(true);
|
||||
tape_player.set_tape(tape);
|
||||
tape_player.set_tape(tape, TargetPlatform::MSX);
|
||||
|
||||
using Parser = Storage::Tape::MSX::Parser;
|
||||
|
||||
// Get all recognisable files from the tape.
|
||||
while(!tape->is_at_end()) {
|
||||
while(!tape_player.is_at_end()) {
|
||||
// Try to locate and measure a header.
|
||||
std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player);
|
||||
if(!file_speed) continue;
|
||||
|
@@ -32,6 +32,6 @@ struct File {
|
||||
File();
|
||||
};
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
|
||||
|
||||
}
|
||||
|
@@ -33,15 +33,17 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
);
|
||||
Region region = Region::USA;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(has_msx_music);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(has_msx_music);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -9,9 +9,14 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool is_confident
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty() && !is_confident) return {};
|
||||
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -18,12 +18,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
|
||||
Model model = Model::MacPlus;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Macintosh) {
|
||||
// Boilerplate for declaring fields and potential values.
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::Macintosh) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -22,12 +22,22 @@ using namespace Analyser::Static::Oric;
|
||||
|
||||
namespace {
|
||||
|
||||
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
||||
int score(
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly,
|
||||
const std::set<uint16_t> &rom_functions,
|
||||
const std::set<uint16_t> &variable_locations
|
||||
) {
|
||||
int score = 0;
|
||||
|
||||
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.outward_calls) {
|
||||
score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||
}
|
||||
for(const auto address : disassembly.external_stores) {
|
||||
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
}
|
||||
for(const auto address : disassembly.external_loads) {
|
||||
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
@@ -35,19 +45,32 @@ int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::
|
||||
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
const std::set<uint16_t> rom_functions = {
|
||||
0x0228, 0x022b,
|
||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f, 0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
||||
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f, 0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
|
||||
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0, 0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
|
||||
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
|
||||
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f, 0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
|
||||
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
|
||||
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e, 0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
|
||||
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563, 0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
|
||||
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974, 0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
|
||||
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf, 0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
|
||||
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a, 0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
||||
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7, 0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524,
|
||||
0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f,
|
||||
0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
||||
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f,
|
||||
0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
|
||||
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0,
|
||||
0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
|
||||
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a,
|
||||
0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
|
||||
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f,
|
||||
0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
|
||||
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f,
|
||||
0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
|
||||
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e,
|
||||
0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
|
||||
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563,
|
||||
0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
|
||||
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974,
|
||||
0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
|
||||
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf,
|
||||
0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
|
||||
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a,
|
||||
0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
||||
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7,
|
||||
0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
||||
};
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
||||
@@ -59,19 +82,32 @@ int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
const std::set<uint16_t> rom_functions = {
|
||||
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915, 0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
||||
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c, 0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
|
||||
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c, 0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
|
||||
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
|
||||
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de, 0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
|
||||
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
|
||||
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f, 0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
|
||||
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
|
||||
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1, 0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
|
||||
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab, 0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
|
||||
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561, 0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
|
||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524,
|
||||
0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915,
|
||||
0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
||||
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c,
|
||||
0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
|
||||
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c,
|
||||
0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
|
||||
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767,
|
||||
0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
|
||||
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de,
|
||||
0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
|
||||
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4,
|
||||
0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
|
||||
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f,
|
||||
0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
|
||||
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab,
|
||||
0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
|
||||
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1,
|
||||
0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
|
||||
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab,
|
||||
0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
|
||||
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561,
|
||||
0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
|
||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14,
|
||||
0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||
0xfc18
|
||||
};
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
@@ -102,7 +138,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
|
||||
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
||||
}
|
||||
|
||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
|
||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range_start, const uint16_t range_end) {
|
||||
/*
|
||||
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
|
||||
use disassembly to test for likely matches.
|
||||
@@ -120,8 +156,8 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start
|
||||
}
|
||||
|
||||
// Grab a disassembly.
|
||||
const auto disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
|
||||
|
||||
// Check for references to the Jasmin registers.
|
||||
int register_hits = 0;
|
||||
@@ -145,7 +181,12 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
||||
@@ -153,14 +194,18 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
||||
int basic11_votes = 0;
|
||||
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> tape_files = GetFiles(tape);
|
||||
tape->reset();
|
||||
auto serialiser = tape->serialiser();
|
||||
std::vector<File> tape_files = GetFiles(*serialiser);
|
||||
if(!tape_files.empty()) {
|
||||
for(const auto &file : tape_files) {
|
||||
if(file.data_type == File::MachineCode) {
|
||||
std::vector<uint16_t> entry_points = {file.starting_address};
|
||||
const Analyser::Static::MOS6502::Disassembly disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
||||
Analyser::Static::MOS6502::Disassemble(
|
||||
file.data,
|
||||
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
|
||||
entry_points
|
||||
);
|
||||
|
||||
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
|
||||
}
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -11,57 +11,57 @@
|
||||
|
||||
using namespace Analyser::Static::Oric;
|
||||
|
||||
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> Analyser::Static::Oric::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
std::vector<File> files;
|
||||
Storage::Tape::Oric::Parser parser;
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
while(!serialiser.is_at_end()) {
|
||||
// sync to next lead-in, check that it's one of three 0x16s
|
||||
bool is_fast = parser.sync_and_get_encoding_speed(tape);
|
||||
bool is_fast = parser.sync_and_get_encoding_speed(serialiser);
|
||||
int next_bytes[2];
|
||||
next_bytes[0] = parser.get_next_byte(tape, is_fast);
|
||||
next_bytes[1] = parser.get_next_byte(tape, is_fast);
|
||||
next_bytes[0] = parser.get_next_byte(serialiser, is_fast);
|
||||
next_bytes[1] = parser.get_next_byte(serialiser, is_fast);
|
||||
|
||||
if(next_bytes[0] != 0x16 || next_bytes[1] != 0x16) continue;
|
||||
|
||||
// get the first byte that isn't a 0x16, check it was a 0x24
|
||||
int byte = 0x16;
|
||||
while(!tape->is_at_end() && byte == 0x16) {
|
||||
byte = parser.get_next_byte(tape, is_fast);
|
||||
while(!serialiser.is_at_end() && byte == 0x16) {
|
||||
byte = parser.get_next_byte(serialiser, is_fast);
|
||||
}
|
||||
if(byte != 0x24) continue;
|
||||
|
||||
// skip two empty bytes
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
parser.get_next_byte(serialiser, is_fast);
|
||||
parser.get_next_byte(serialiser, is_fast);
|
||||
|
||||
// get data and launch types
|
||||
File new_file;
|
||||
switch(parser.get_next_byte(tape, is_fast)) {
|
||||
switch(parser.get_next_byte(serialiser, is_fast)) {
|
||||
case 0x00: new_file.data_type = File::ProgramType::BASIC; break;
|
||||
case 0x80: new_file.data_type = File::ProgramType::MachineCode; break;
|
||||
default: new_file.data_type = File::ProgramType::None; break;
|
||||
}
|
||||
switch(parser.get_next_byte(tape, is_fast)) {
|
||||
switch(parser.get_next_byte(serialiser, is_fast)) {
|
||||
case 0x80: new_file.launch_type = File::ProgramType::BASIC; break;
|
||||
case 0xc7: new_file.launch_type = File::ProgramType::MachineCode; break;
|
||||
default: new_file.launch_type = File::ProgramType::None; break;
|
||||
}
|
||||
|
||||
// read end and start addresses
|
||||
new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
|
||||
new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
|
||||
new_file.ending_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
|
||||
new_file.ending_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
|
||||
new_file.starting_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
|
||||
new_file.starting_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
|
||||
|
||||
// skip an empty byte
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
parser.get_next_byte(serialiser, is_fast);
|
||||
|
||||
// read file name, up to 16 characters and null terminated
|
||||
char file_name[17];
|
||||
int name_pos = 0;
|
||||
while(name_pos < 16) {
|
||||
file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
|
||||
file_name[name_pos] = char(parser.get_next_byte(serialiser, is_fast));
|
||||
if(!file_name[name_pos]) break;
|
||||
name_pos++;
|
||||
}
|
||||
@@ -72,11 +72,11 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
|
||||
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
|
||||
new_file.data.reserve(body_length);
|
||||
for(std::size_t c = 0; c < body_length; c++) {
|
||||
new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
|
||||
new_file.data.push_back(uint8_t(parser.get_next_byte(serialiser, is_fast)));
|
||||
}
|
||||
|
||||
// only one validation check: was there enough tape?
|
||||
if(!tape->is_at_end()) {
|
||||
if(!serialiser.is_at_end()) {
|
||||
files.push_back(new_file);
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,6 @@ struct File {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
|
||||
|
||||
}
|
||||
|
@@ -41,15 +41,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
std::string loading_command;
|
||||
bool should_start_jasmin = false;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::Oric) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(rom);
|
||||
DeclareField(disk_interface);
|
||||
DeclareField(processor);
|
||||
AnnounceEnum(ROM);
|
||||
AnnounceEnum(DiskInterface);
|
||||
AnnounceEnum(Processor);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::Oric) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(rom);
|
||||
DeclareField(disk_interface);
|
||||
DeclareField(processor);
|
||||
AnnounceEnum(ROM);
|
||||
AnnounceEnum(DiskInterface);
|
||||
AnnounceEnum(Processor);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::PCCompatible {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -24,13 +24,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Fast);
|
||||
Speed speed = Speed::Fast;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::PCCompatible) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(VideoAdaptor);
|
||||
AnnounceEnum(Speed);
|
||||
DeclareField(adaptor);
|
||||
DeclareField(speed);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::PCCompatible) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
AnnounceEnum(VideoAdaptor);
|
||||
AnnounceEnum(Speed);
|
||||
DeclareField(adaptor);
|
||||
DeclareField(speed);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -13,7 +13,12 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
if(media.cartridges.empty())
|
||||
return {};
|
||||
|
||||
@@ -54,7 +59,8 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
|
||||
if(lowercase_name.find("(jp)") == std::string::npos) {
|
||||
target->region =
|
||||
(lowercase_name.find("(us)") == std::string::npos &&
|
||||
lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
|
||||
lowercase_name.find("(ntsc)") == std::string::npos) ?
|
||||
Target::Region::Europe : Target::Region::USA;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
@@ -63,9 +69,9 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
|
||||
// If one is found, set the paging scheme appropriately.
|
||||
const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
|
||||
if(
|
||||
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
|
||||
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
|
||||
(inverse_checksum&0xff) == data[0x7fe8] &&
|
||||
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present
|
||||
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present.
|
||||
!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
|
||||
) {
|
||||
target->paging_scheme = Target::PagingScheme::Codemasters;
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -37,15 +37,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Region region = Region::Japan;
|
||||
PagingScheme paging_scheme = PagingScheme::Sega;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::MasterSystem) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::MasterSystem) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
|
||||
constexpr bool is_master_system(const Analyser::Static::Sega::Target::Model model) {
|
||||
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
|
@@ -104,14 +104,14 @@ std::string get_extension(const std::string &name) {
|
||||
}
|
||||
|
||||
class MediaAccumulator {
|
||||
public:
|
||||
public:
|
||||
MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
|
||||
file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
|
||||
|
||||
/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
|
||||
/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
|
||||
template <typename InstanceT>
|
||||
void insert(TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
|
||||
void insert(const TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
|
||||
if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
|
||||
media.disks.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
|
||||
@@ -127,20 +127,23 @@ class MediaAccumulator {
|
||||
potential_platforms_ |= platforms;
|
||||
|
||||
// Check whether the instance itself has any input on target platforms.
|
||||
TargetPlatform::TypeDistinguisher *const distinguisher =
|
||||
dynamic_cast<TargetPlatform::TypeDistinguisher *>(instance.get());
|
||||
if(distinguisher) potential_platforms_ &= distinguisher->target_platform_type();
|
||||
TargetPlatform::Distinguisher *const distinguisher =
|
||||
dynamic_cast<TargetPlatform::Distinguisher *>(instance.get());
|
||||
if(distinguisher) {
|
||||
was_distinguished = true;
|
||||
potential_platforms_ &= distinguisher->target_platforms();
|
||||
}
|
||||
}
|
||||
|
||||
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
void insert(const TargetPlatform::IntType platforms, Args &&... args) {
|
||||
insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void try_insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
void try_insert(const TargetPlatform::IntType platforms, Args &&... args) {
|
||||
try {
|
||||
insert<InstanceT>(platforms, std::forward<Args>(args)...);
|
||||
} catch(...) {}
|
||||
@@ -149,22 +152,23 @@ class MediaAccumulator {
|
||||
/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
|
||||
/// providing the file name as the only construction argument.
|
||||
template <typename InstanceT>
|
||||
void try_standard(TargetPlatform::IntType platforms, const char *extension) {
|
||||
void try_standard(const TargetPlatform::IntType platforms, const char *extension) {
|
||||
if(name_matches(extension)) {
|
||||
try_insert<InstanceT>(platforms, file_name_);
|
||||
}
|
||||
}
|
||||
|
||||
bool name_matches(const char *extension) {
|
||||
bool name_matches(const char *const extension) {
|
||||
return extension_ == extension;
|
||||
}
|
||||
|
||||
Media media;
|
||||
bool was_distinguished = false;
|
||||
|
||||
private:
|
||||
const std::string &file_name_;
|
||||
TargetPlatform::IntType &potential_platforms_;
|
||||
const std::string extension_;
|
||||
private:
|
||||
const std::string &file_name_;
|
||||
TargetPlatform::IntType &potential_platforms_;
|
||||
const std::string extension_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -209,7 +213,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
|
||||
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore, "d64");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
|
||||
accumulator.try_standard<MassStorage::DAT>(TargetPlatform::Acorn, "dat");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::DMK>>(TargetPlatform::MSX, "dmk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "do");
|
||||
@@ -223,11 +227,12 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::MSX, "dsk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::OricMFMDSK>>(TargetPlatform::Oric, "dsk");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore, "g64");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore8bit, "g64");
|
||||
|
||||
accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore |
|
||||
TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
|
||||
"hfe"); // TODO: switch to AllDisk once the MSX stops being so greedy.
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
|
||||
@@ -267,10 +272,10 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
if(accumulator.name_matches("prg")) {
|
||||
// Try instantiating as a ROM; failing that accept as a tape.
|
||||
try {
|
||||
accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore, file_name);
|
||||
accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore8bit, file_name);
|
||||
} catch(...) {
|
||||
try {
|
||||
accumulator.insert<Tape::PRG>(TargetPlatform::Commodore, file_name);
|
||||
accumulator.insert<Tape::PRG>(TargetPlatform::Commodore8bit, file_name);
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +290,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
|
||||
|
||||
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore, "tap");
|
||||
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore8bit, "tap");
|
||||
accumulator.try_standard<Tape::OricTAP>(TargetPlatform::Oric, "tap");
|
||||
accumulator.try_standard<Tape::ZXSpectrumTAP>(TargetPlatform::ZXSpectrum, "tap");
|
||||
accumulator.try_standard<Tape::TZX>(TargetPlatform::MSX, "tsx");
|
||||
@@ -334,14 +339,30 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
||||
// TODO: std::popcount here.
|
||||
int total_options = 0;
|
||||
TargetPlatform::IntType mask = 1;
|
||||
while(mask) {
|
||||
total_options += bool(potential_platforms & mask);
|
||||
mask <<= 1;
|
||||
}
|
||||
const bool is_confident = total_options == 1;
|
||||
// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
|
||||
// The machine-specific static analyser will still run in case it can provide meaningful annotations on
|
||||
// loading command, machine configuration, etc, but the flag will be passed onwards to mean "don't reject this".
|
||||
|
||||
// Hand off to platform-specific determination of whether these
|
||||
// things are actually compatible and, if so, how to load them.
|
||||
const auto append = [&](TargetPlatform::IntType platform, auto evaluator) {
|
||||
if(!(potential_platforms & platform)) {
|
||||
return;
|
||||
}
|
||||
auto new_targets = evaluator(media, file_name, potential_platforms);
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));
|
||||
auto new_targets = evaluator(media, file_name, potential_platforms, is_confident);
|
||||
targets.insert(
|
||||
targets.end(),
|
||||
std::make_move_iterator(new_targets.begin()),
|
||||
std::make_move_iterator(new_targets.end())
|
||||
);
|
||||
};
|
||||
|
||||
append(TargetPlatform::Acorn, Acorn::GetTargets);
|
||||
@@ -352,7 +373,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
append(TargetPlatform::Atari2600, Atari2600::GetTargets);
|
||||
append(TargetPlatform::AtariST, AtariST::GetTargets);
|
||||
append(TargetPlatform::Coleco, Coleco::GetTargets);
|
||||
append(TargetPlatform::Commodore, Commodore::GetTargets);
|
||||
append(TargetPlatform::Commodore8bit, Commodore::GetTargets);
|
||||
append(TargetPlatform::DiskII, DiskII::GetTargets);
|
||||
append(TargetPlatform::Enterprise, Enterprise::GetTargets);
|
||||
append(TargetPlatform::FAT12, FAT12::GetTargets);
|
||||
@@ -364,13 +385,6 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
append(TargetPlatform::ZX8081, ZX8081::GetTargets);
|
||||
append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
|
||||
|
||||
// Reset any tapes to their initial position.
|
||||
for(const auto &target : targets) {
|
||||
for(auto &tape : target->media.tapes) {
|
||||
tape->reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
|
||||
// picked their insertion order carefully.
|
||||
std::stable_sort(targets.begin(), targets.end(),
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/TargetPlatforms.hpp"
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
#include <memory>
|
||||
@@ -38,12 +39,15 @@ struct Media {
|
||||
}
|
||||
|
||||
Media &operator +=(const Media &rhs) {
|
||||
#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end());
|
||||
append(disks);
|
||||
append(tapes);
|
||||
append(cartridges);
|
||||
append(mass_storage_devices);
|
||||
#undef append
|
||||
const auto append = [&](auto &destination, auto &source) {
|
||||
destination.insert(destination.end(), source.begin(), source.end());
|
||||
};
|
||||
|
||||
append(disks, rhs.disks);
|
||||
append(tapes, rhs.tapes);
|
||||
append(cartridges, rhs.cartridges);
|
||||
append(mass_storage_devices, rhs.mass_storage_devices);
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
@@ -54,16 +58,16 @@ struct Media {
|
||||
*/
|
||||
struct Target {
|
||||
Target(Machine machine) : machine(machine) {}
|
||||
virtual ~Target() {}
|
||||
virtual ~Target() = default;
|
||||
|
||||
// This field is entirely optional.
|
||||
std::unique_ptr<Reflection::Struct> state;
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
float confidence = 0.0f;
|
||||
float confidence = 0.5f;
|
||||
};
|
||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
|
||||
using TargetList = std::vector<std::unique_ptr<Target>>;
|
||||
|
||||
/*!
|
||||
Attempts, through any available means, to return a list of potential targets for the file with the given name.
|
||||
|
@@ -14,25 +14,30 @@
|
||||
#include "Target.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
|
||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
std::vector<Storage::Data::ZX8081::File> files;
|
||||
Storage::Tape::ZX8081::Parser parser;
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
|
||||
if(next_file != nullptr) {
|
||||
files.push_back(*next_file);
|
||||
while(!serialiser.is_at_end()) {
|
||||
const auto next_file = parser.get_next_file(serialiser);
|
||||
if(next_file) {
|
||||
files.push_back(std::move(*next_file));
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType potential_platforms,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
if(!media.tapes.empty()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||
media.tapes.front()->reset();
|
||||
const auto serialiser = media.tapes.front()->serialiser();
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(*serialiser);
|
||||
if(!files.empty()) {
|
||||
Target *const target = new Target;
|
||||
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -27,13 +27,15 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
bool ZX80_uses_ZX81_ROM = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::ZX8081) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(memory_model);
|
||||
DeclareField(is_ZX81);
|
||||
DeclareField(ZX80_uses_ZX81_ROM);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::ZX8081) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(memory_model);
|
||||
DeclareField(is_ZX81);
|
||||
DeclareField(ZX80_uses_ZX81_ROM);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -8,14 +8,17 @@
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
bool IsSpectrumTape(Storage::Tape::TapeSerialiser &tape) {
|
||||
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
||||
Parser parser(Parser::MachineType::ZXSpectrum);
|
||||
|
||||
@@ -35,28 +38,77 @@ bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
|
||||
// Get logical sector 1; the Spectrum appears to support various physical
|
||||
// sectors as sector 1.
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
|
||||
uint8_t sector_mask = 0;
|
||||
while(!boot_sector) {
|
||||
boot_sector = parser.sector(0, 0, sector_mask + 1);
|
||||
sector_mask += 0x40;
|
||||
if(!sector_mask) break;
|
||||
}
|
||||
// Grab absolutely any sector from the first track to determine general encoding.
|
||||
const Storage::Encodings::MFM::Sector *any_sector = parser.any_sector(0, 0);
|
||||
if(!any_sector) return false;
|
||||
|
||||
// Determine the sector base and get logical sector 1.
|
||||
const uint8_t sector_base = any_sector->address.sector & 0xc0;
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, sector_base + 1);
|
||||
if(!boot_sector) return false;
|
||||
|
||||
// Test that the contents of the boot sector sum to 3, modulo 256.
|
||||
uint8_t byte_sum = 0;
|
||||
for(auto byte: boot_sector->samples[0]) {
|
||||
byte_sum += byte;
|
||||
Storage::Disk::CPM::ParameterBlock cpm_format{};
|
||||
switch(sector_base) {
|
||||
case 0x40: cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format(); break;
|
||||
case 0xc0: cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format(); break;
|
||||
|
||||
default: {
|
||||
// Check the first ten bytes of the first sector for the disk format; if these are all
|
||||
// the same value then instead substitute a default format.
|
||||
std::array<uint8_t, 10> format;
|
||||
std::copy(boot_sector->samples[0].begin(), boot_sector->samples[0].begin() + 10, format.begin());
|
||||
if(std::all_of(format.begin(), format.end(), [&](const uint8_t v) { return v == format[0]; })) {
|
||||
format = {0x00, 0x00, 0x28, 0x09, 0x02, 0x01, 0x03, 0x02, 0x2a, 0x52};
|
||||
}
|
||||
|
||||
// Parse those ten bytes as:
|
||||
//
|
||||
// Byte 0: disc type
|
||||
// Byte 1: sidedness
|
||||
// bits 0-6: arrangement
|
||||
// 0 => single sided
|
||||
// 1 => double sided, flip sides
|
||||
// 2 => double sided, up and over
|
||||
// bit 7: double-track
|
||||
// Byte 2: number of tracks per side
|
||||
// Byte 3: number of sectors per track
|
||||
// Byte 4: Log2(sector size) - 7
|
||||
// Byte 5: number of reserved tracks
|
||||
// Byte 6: Log2(block size) - 7
|
||||
// Byte 7: number of directory blocks
|
||||
// Byte 8: gap length (read/write)
|
||||
// Byte 9: gap length(format)
|
||||
cpm_format.sectors_per_track = format[3];
|
||||
cpm_format.tracks = format[2];
|
||||
cpm_format.block_size = 128 << format[6];
|
||||
cpm_format.first_sector = sector_base + 1;
|
||||
cpm_format.reserved_tracks = format[5];
|
||||
|
||||
// i.e. bits set downward from 0x4000 for as many blocks as form the catalogue.
|
||||
cpm_format.catalogue_allocation_bitmap = 0x8000 - (0x8000 >> format[7]);
|
||||
} break;
|
||||
}
|
||||
return byte_sum == 3;
|
||||
|
||||
// If the boot sector sums to 3 modulo 256 then this is a Spectrum disk.
|
||||
const auto byte_sum = static_cast<uint8_t>(
|
||||
std::accumulate(boot_sector->samples[0].begin(), boot_sector->samples[0].end(), 0));
|
||||
if(byte_sum == 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ... otherwise read a CPM directory and look for a BASIC program called "DISK".
|
||||
const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format, false);
|
||||
return catalogue && catalogue->is_zx_spectrum_booter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
@@ -64,7 +116,8 @@ Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Medi
|
||||
if(!media.tapes.empty()) {
|
||||
bool has_spectrum_tape = false;
|
||||
for(auto &tape: media.tapes) {
|
||||
has_spectrum_tape |= IsSpectrumTape(tape);
|
||||
auto serialiser = tape->serialiser();
|
||||
has_spectrum_tape |= IsSpectrumTape(*serialiser);
|
||||
}
|
||||
|
||||
if(has_spectrum_tape) {
|
||||
|
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
@@ -27,11 +27,13 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
Model model = Model::Plus2;
|
||||
bool should_hold_enter = false;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
};
|
||||
|
||||
|
99
BUILD.md
Normal file
99
BUILD.md
Normal file
@@ -0,0 +1,99 @@
|
||||

|
||||
# Building Clock Signal
|
||||
|
||||
Clock Signal is available as [a macOS native application using
|
||||
Metal](#macos-app) or as [a cross-platform command-line-driven SDL executable
|
||||
using OpenGL](#sdl-app).
|
||||
|
||||
## macOS app
|
||||
|
||||
The macOS native application requires a Metal-capable Mac running macOS 10.13 or
|
||||
later and has no prerequisites beyond the normal system libraries. It can be
|
||||
built [using Xcode](#building-the-macos-app-using-xcode) or on the command line
|
||||
[using `xcodebuild`](#building-the-macos-app-using-xcodebuild).
|
||||
|
||||
Machine ROMs are intended to be built into the application bundle; populate the
|
||||
dummy folders below ROMImages before building.
|
||||
|
||||
The Xcode project is configured to sign the application using the developer's
|
||||
certificate, but if you are not the developer then you will get a "No signing
|
||||
certificate" error. To avoid this, you'll specify that you want to sign the
|
||||
application to run locally.
|
||||
|
||||
### Building the macOS app using Xcode
|
||||
|
||||
Open the Clock Signal Xcode project in OSBindings/Mac.
|
||||
|
||||
To avoid signing errors, edit the project, select the Signing & Capabilities
|
||||
tab, and change the Signing Certificate drop-down menu from "Development" to
|
||||
"Sign to Run Locally".
|
||||
|
||||
To avoid crashes when running Clock Signal via Xcode on older Macs due to
|
||||
"unrecognized selector sent to instance" errors, edit the scheme, and in the Run
|
||||
section, scroll down to the Metal heading and uncheck the "API Validation"
|
||||
checkbox.
|
||||
|
||||
To build, choose "Build" from Xcode's Product menu or press
|
||||
<kbd>Command</kbd> + <kbd>B</kbd>.
|
||||
|
||||
To build and run, choose "Run" from the Product menu or press
|
||||
<kbd>Command</kbd> + <kbd>R</kbd>.
|
||||
|
||||
To see the folder where the Clock Signal application was built, choose "Show
|
||||
Build Folder in Finder" from the Product menu. Look in the "Products" folder for
|
||||
a folder named after the configuration (e.g. "Debug" or "Release").
|
||||
|
||||
### Building the macOS app using `xcodebuild`
|
||||
|
||||
To build, change to the OSBindings/Mac directory in the Terminal, then run
|
||||
`xcodebuild`, specifying `-` as the code sign identity to sign the application
|
||||
to run locally to avoid signing errors:
|
||||
|
||||
cd OSBindings/Mac
|
||||
xcodebuild CODE_SIGN_IDENTITY=-
|
||||
|
||||
`xcodebuild` will create a "build" folder in this directory which is where you
|
||||
can find the Clock Signal application after it's compiled, in a directory named
|
||||
after the configuration (e.g. "Debug" or "Release").
|
||||
|
||||
## SDL app
|
||||
|
||||
The SDL app can be built on Linux, BSD, macOS, and other Unix-like operating
|
||||
systems. Prerequisites are SDL 2, ZLib and OpenGL (or Mesa). OpenGL 3.2 or
|
||||
better is required at runtime. It can be built [using
|
||||
SCons](#building-the-sdl-app-using-scons).
|
||||
|
||||
### Building the SDL app using SCons
|
||||
|
||||
To build, change to the OSBindings/SDL directory and run `scons`. You can add a
|
||||
`-j` flag to build in parallel. For example, if you have 8 processor cores:
|
||||
|
||||
cd OSBindings/SDL
|
||||
scons -j8
|
||||
|
||||
The `clksignal` executable will be created in this directory. You can run it
|
||||
from here or install it by copying it where you want it, for example:
|
||||
|
||||
cp clksignal /usr/local/bin
|
||||
|
||||
To start an emulator with a particular disk image `file`, if you've installed
|
||||
`clksignal` to a directory in your `PATH`, run:
|
||||
|
||||
clksignal file
|
||||
|
||||
Or if you're running it from the current directory:
|
||||
|
||||
./clksignal file
|
||||
|
||||
Other options are availble. Run `clksignal` or `./clksignal` with no arguments
|
||||
to learn more.
|
||||
|
||||
Setting up `clksignal` as the associated program for supported file types in
|
||||
your favoured filesystem browser is recommended; it has no file navigation
|
||||
abilities of its own.
|
||||
|
||||
Some emulated systems require the provision of original machine ROMs. These are
|
||||
not included and may be located in either /usr/local/share/CLK/ or
|
||||
/usr/share/CLK/. You will be prompted for them if they are found to be missing.
|
||||
The structure should mirror that under OSBindings in the source archive; see the
|
||||
readme.txt in each folder to determine the proper files and names ahead of time.
|
30
BUILD.txt
30
BUILD.txt
@@ -1,30 +0,0 @@
|
||||
Linux, BSD
|
||||
==========
|
||||
|
||||
Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime.
|
||||
|
||||
Build:
|
||||
|
||||
cd OSBindings/SDL
|
||||
scons
|
||||
|
||||
Optionally:
|
||||
|
||||
cp clksignal /usr/bin
|
||||
|
||||
To launch:
|
||||
|
||||
clksignal file
|
||||
|
||||
Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own.
|
||||
|
||||
Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time.
|
||||
|
||||
macOS
|
||||
=====
|
||||
|
||||
There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application.
|
||||
|
||||
Build: open the Xcode project in OSBindings/Mac and press command+b.
|
||||
|
||||
Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building.
|
70
CMakeLists.txt
Normal file
70
CMakeLists.txt
Normal file
@@ -0,0 +1,70 @@
|
||||
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
|
||||
|
||||
project(CLK
|
||||
LANGUAGES CXX
|
||||
VERSION 24.01.22
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(CLK_UIS "SDL")
|
||||
#list(PREPEND CLK_UIS "QT")
|
||||
#if(APPLE)
|
||||
# list(PREPEND CLK_UIS "MAC")
|
||||
# set(CLK_DEFAULT_UI "MAC")
|
||||
#else()
|
||||
set(CLK_DEFAULT_UI "SDL")
|
||||
#endif()
|
||||
|
||||
set(CLK_UI ${CLK_DEFAULT_UI} CACHE STRING "User interface")
|
||||
set_property(CACHE CLK_UI PROPERTY STRINGS ${CLK_UIS})
|
||||
|
||||
if(NOT CLK_UI IN_LIST CLK_UIS)
|
||||
list(JOIN CLK_UIS ", " CLK_UIS_PRETTY)
|
||||
message(FATAL_ERROR "Invalid value for 'CLK_UI'; must be one of ${CLK_UIS_PRETTY}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Configuring for ${CLK_UI} UI")
|
||||
|
||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
include("CLK_SOURCES")
|
||||
|
||||
add_executable(clksignal ${CLK_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(clksignal PRIVATE /W4)
|
||||
else()
|
||||
# TODO: Add -Wpedandic.
|
||||
target_compile_options(clksignal PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(clksignal PRIVATE ZLIB::ZLIB)
|
||||
|
||||
if(CLK_UI STREQUAL "MAC")
|
||||
enable_language(OBJC OBJCXX SWIFT)
|
||||
# TODO: Build the Mac version.
|
||||
else()
|
||||
find_package(OpenGL REQUIRED)
|
||||
target_link_libraries(clksignal PRIVATE OpenGL::GL)
|
||||
if(APPLE)
|
||||
target_compile_definitions(clksignal PRIVATE "GL_SILENCE_DEPRECATION" "IGNORE_APPLE")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CLK_UI STREQUAL "QT")
|
||||
# TODO: Build the Qt version.
|
||||
elseif(APPLE)
|
||||
set(BLA_VENDOR Apple)
|
||||
find_package(BLAS REQUIRED)
|
||||
target_link_libraries(clksignal PRIVATE BLAS::BLAS)
|
||||
endif()
|
||||
|
||||
if(CLK_UI STREQUAL "SDL")
|
||||
find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2)
|
||||
target_link_libraries(clksignal PRIVATE SDL2::SDL2)
|
||||
endif()
|
||||
|
||||
# TODO: Investigate building on Windows.
|
@@ -56,205 +56,225 @@
|
||||
Boolean operators, but forcing callers and receivers to be explicit as to usage.
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
using IntType = int64_t;
|
||||
public:
|
||||
using IntType = int64_t;
|
||||
|
||||
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
|
||||
forceinline constexpr WrappedInt() noexcept : length_(0) {}
|
||||
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
|
||||
forceinline constexpr WrappedInt() noexcept : length_(0) {}
|
||||
|
||||
forceinline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
forceinline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
forceinline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator *=(const T &rhs) {
|
||||
length_ *= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator /=(const T &rhs) {
|
||||
length_ /= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int,
|
||||
// which is prone silently to permit misuse.
|
||||
|
||||
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const {
|
||||
if constexpr (sizeof(Type) == sizeof(IntType)) {
|
||||
if constexpr (std::is_same_v<Type, IntType>) {
|
||||
return length_;
|
||||
} else if constexpr (std::is_signed_v<Type>) {
|
||||
// Both integers are the same size, but a signed result is being asked for
|
||||
// from an unsigned original.
|
||||
return length_ > Type(std::numeric_limits<Type>::max()) ?
|
||||
Type(std::numeric_limits<Type>::max()) : Type(length_);
|
||||
} else {
|
||||
// An unsigned result is being asked for from a signed original.
|
||||
return length_ < 0 ? 0 : Type(length_);
|
||||
}
|
||||
}
|
||||
|
||||
forceinline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
const auto clamped = std::clamp(
|
||||
length_,
|
||||
IntType(std::numeric_limits<Type>::min()),
|
||||
IntType(std::numeric_limits<Type>::max())
|
||||
);
|
||||
return Type(clamped);
|
||||
}
|
||||
|
||||
forceinline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
/// @returns The underlying int, in its native form.
|
||||
forceinline constexpr IntType as_integral() const { return length_; }
|
||||
|
||||
forceinline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
template <typename Result = T> forceinline Result divide(const T &divisor) {
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r, divisor);
|
||||
return r;
|
||||
}
|
||||
|
||||
forceinline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
/*!
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
template <typename Result> Result flush() {
|
||||
// Jiggery pokery here; switching to function overloading avoids
|
||||
// the namespace-level requirement for template specialisation.
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
forceinline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
// operator int() is deliberately not provided, to avoid accidental subtitution of
|
||||
// classes that use this template.
|
||||
|
||||
forceinline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator *=(const T &rhs) {
|
||||
length_ *= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator /=(const T &rhs) {
|
||||
length_ /= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const {
|
||||
const auto clamped = std::clamp(length_, IntType(std::numeric_limits<Type>::min()), IntType(std::numeric_limits<Type>::max()));
|
||||
return Type(clamped);
|
||||
}
|
||||
|
||||
/// @returns The underlying int, in its native form.
|
||||
forceinline constexpr IntType as_integral() const { return length_; }
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
template <typename Result = T> forceinline Result divide(const T &divisor) {
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r, divisor);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*!
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
template <typename Result> Result flush() {
|
||||
// Jiggery pokery here; switching to function overloading avoids
|
||||
// the namespace-level requirement for template specialisation.
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
// operator int() is deliberately not provided, to avoid accidental subtitution of
|
||||
// classes that use this template.
|
||||
|
||||
protected:
|
||||
IntType length_;
|
||||
protected:
|
||||
IntType length_;
|
||||
};
|
||||
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline static constexpr Cycles max() {
|
||||
return Cycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
public:
|
||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline static constexpr Cycles max() {
|
||||
return Cycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
|
||||
void fill(Cycles &result, const Cycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
void fill(Cycles &result, const Cycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
forceinline static constexpr HalfCycles max() {
|
||||
return HalfCycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
public:
|
||||
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
forceinline static constexpr HalfCycles max() {
|
||||
return HalfCycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept :
|
||||
WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
const Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
const Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Equivalent to @c divide_cycles(Cycles(1)) but faster.
|
||||
*/
|
||||
forceinline Cycles divide_cycles() {
|
||||
const Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
/*!
|
||||
Equivalent to @c divide_cycles(Cycles(1)) but faster.
|
||||
*/
|
||||
forceinline Cycles divide_cycles() {
|
||||
const Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result = Cycles(length_ >> 1);
|
||||
length_ &= 1;
|
||||
}
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result = Cycles(length_ >> 1);
|
||||
length_ &= 1;
|
||||
}
|
||||
|
||||
void fill(HalfCycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
void fill(HalfCycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
|
||||
void fill(Cycles &result, const HalfCycles &divisor) {
|
||||
result = Cycles(length_ / (divisor.length_ << 1));
|
||||
length_ %= (divisor.length_ << 1);
|
||||
}
|
||||
void fill(Cycles &result, const HalfCycles &divisor) {
|
||||
result = Cycles(length_ / (divisor.length_ << 1));
|
||||
length_ %= (divisor.length_ << 1);
|
||||
}
|
||||
|
||||
void fill(HalfCycles &result, const HalfCycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
void fill(HalfCycles &result, const HalfCycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
};
|
||||
|
||||
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
|
||||
@@ -265,14 +285,14 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
automatically to gain run_for(HalfCycles).
|
||||
*/
|
||||
template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
forceinline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush<Cycles>());
|
||||
}
|
||||
forceinline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush<Cycles>());
|
||||
}
|
||||
|
||||
private:
|
||||
HalfCycles half_cycles_;
|
||||
private:
|
||||
HalfCycles half_cycles_;
|
||||
};
|
||||
|
@@ -58,28 +58,28 @@ struct Observer {
|
||||
The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency.
|
||||
*/
|
||||
class Source {
|
||||
public:
|
||||
/// Registers @c observer as the new clocking observer.
|
||||
void set_clocking_hint_observer(Observer *observer) {
|
||||
observer_ = observer;
|
||||
update_clocking_observer();
|
||||
}
|
||||
public:
|
||||
/// Registers @c observer as the new clocking observer.
|
||||
void set_clocking_hint_observer(Observer *observer) {
|
||||
observer_ = observer;
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() const = 0;
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() const = 0;
|
||||
|
||||
private:
|
||||
Observer *observer_ = nullptr;
|
||||
private:
|
||||
Observer *observer_ = nullptr;
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Provided for subclasses; call this whenever the clocking preference might have changed.
|
||||
This will notify the observer if there is one.
|
||||
*/
|
||||
void update_clocking_observer() {
|
||||
if(!observer_) return;
|
||||
observer_->set_component_prefers_clocking(this, preferred_clocking());
|
||||
}
|
||||
protected:
|
||||
/*!
|
||||
Provided for subclasses; call this whenever the clocking preference might have changed.
|
||||
This will notify the observer if there is one.
|
||||
*/
|
||||
void update_clocking_observer() {
|
||||
if(!observer_) return;
|
||||
observer_->set_component_prefers_clocking(this, preferred_clocking());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -15,73 +15,79 @@
|
||||
Provides the logic to insert into and traverse a list of future scheduled items.
|
||||
*/
|
||||
template <typename TimeUnit> class DeferredQueue {
|
||||
public:
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
*/
|
||||
void defer(TimeUnit delay, const std::function<void(void)> &action) {
|
||||
// Apply immediately if there's no delay (or a negative delay).
|
||||
if(delay <= TimeUnit(0)) {
|
||||
action();
|
||||
return;
|
||||
public:
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
*/
|
||||
void defer(TimeUnit delay, const std::function<void(void)> &action) {
|
||||
// Apply immediately if there's no delay (or a negative delay).
|
||||
if(delay <= TimeUnit(0)) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!pending_actions_.empty()) {
|
||||
// Otherwise enqueue, having subtracted the delay for any preceding events,
|
||||
// and subtracting from the subsequent, if any.
|
||||
auto insertion_point = pending_actions_.begin();
|
||||
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
|
||||
delay -= insertion_point->delay;
|
||||
++insertion_point;
|
||||
}
|
||||
if(insertion_point != pending_actions_.end()) {
|
||||
insertion_point->delay -= delay;
|
||||
}
|
||||
|
||||
if(!pending_actions_.empty()) {
|
||||
// Otherwise enqueue, having subtracted the delay for any preceding events,
|
||||
// and subtracting from the subsequent, if any.
|
||||
auto insertion_point = pending_actions_.begin();
|
||||
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
|
||||
delay -= insertion_point->delay;
|
||||
++insertion_point;
|
||||
}
|
||||
if(insertion_point != pending_actions_.end()) {
|
||||
insertion_point->delay -= delay;
|
||||
}
|
||||
pending_actions_.emplace(insertion_point, delay, action);
|
||||
} else {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
}
|
||||
}
|
||||
|
||||
pending_actions_.emplace(insertion_point, delay, action);
|
||||
/*!
|
||||
@returns The amount of time until the next enqueued action will occur,
|
||||
or TimeUnit(-1) if the queue is empty.
|
||||
*/
|
||||
TimeUnit time_until_next_action() const {
|
||||
if(pending_actions_.empty()) return TimeUnit(-1);
|
||||
return pending_actions_.front().delay;
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the queue the specified amount of time, performing any actions it reaches.
|
||||
*/
|
||||
void advance(TimeUnit time) {
|
||||
auto erase_iterator = pending_actions_.begin();
|
||||
while(erase_iterator != pending_actions_.end()) {
|
||||
erase_iterator->delay -= time;
|
||||
if(erase_iterator->delay <= TimeUnit(0)) {
|
||||
time = -erase_iterator->delay;
|
||||
erase_iterator->action();
|
||||
++erase_iterator;
|
||||
} else {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The amount of time until the next enqueued action will occur,
|
||||
or TimeUnit(-1) if the queue is empty.
|
||||
*/
|
||||
TimeUnit time_until_next_action() const {
|
||||
if(pending_actions_.empty()) return TimeUnit(-1);
|
||||
return pending_actions_.front().delay;
|
||||
if(erase_iterator != pending_actions_.begin()) {
|
||||
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the queue the specified amount of time, performing any actions it reaches.
|
||||
*/
|
||||
void advance(TimeUnit time) {
|
||||
auto erase_iterator = pending_actions_.begin();
|
||||
while(erase_iterator != pending_actions_.end()) {
|
||||
erase_iterator->delay -= time;
|
||||
if(erase_iterator->delay <= TimeUnit(0)) {
|
||||
time = -erase_iterator->delay;
|
||||
erase_iterator->action();
|
||||
++erase_iterator;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(erase_iterator != pending_actions_.begin()) {
|
||||
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
|
||||
}
|
||||
}
|
||||
/// @returns @c true if no actions are enqueued; @c false otherwise.
|
||||
bool empty() const {
|
||||
return pending_actions_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
TimeUnit delay;
|
||||
std::function<void(void)> action;
|
||||
private:
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
TimeUnit delay;
|
||||
std::function<void(void)> action;
|
||||
|
||||
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
|
||||
};
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) :
|
||||
delay(delay), action(std::move(action)) {}
|
||||
};
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -92,30 +98,28 @@ template <typename TimeUnit> class DeferredQueue {
|
||||
This list is efficient only for short queues.
|
||||
*/
|
||||
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
|
||||
public:
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
public:
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
|
||||
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
|
||||
target_(time_to_next);
|
||||
length -= time_to_next;
|
||||
DeferredQueue<TimeUnit>::advance(time_to_next);
|
||||
}
|
||||
|
||||
DeferredQueue<TimeUnit>::advance(length);
|
||||
target_(length);
|
||||
|
||||
// TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe?
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
|
||||
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
|
||||
target_(time_to_next);
|
||||
length -= time_to_next;
|
||||
DeferredQueue<TimeUnit>::advance(time_to_next);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
DeferredQueue<TimeUnit>::advance(length);
|
||||
target_(length);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user