mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
979 Commits
2017-08-27
...
2018-06-03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94359e9c75 | ||
|
|
076fa55651 | ||
|
|
d380595ad4 | ||
|
|
d84b8700a3 | ||
|
|
80b281d9f1 | ||
|
|
69dc3cc4d8 | ||
|
|
1a9cea050e | ||
|
|
0833412df9 | ||
|
|
35e84ff1a8 | ||
|
|
8dd7c6ef23 | ||
|
|
a26ab7086d | ||
|
|
b2464598d0 | ||
|
|
6812a001d8 | ||
|
|
6c16754a6b | ||
|
|
75f9e3caeb | ||
|
|
ad5afe21ee | ||
|
|
8a566cc1dd | ||
|
|
928aab13dc | ||
|
|
f3fe711542 | ||
|
|
db8d8d8404 | ||
|
|
6220ccb5d3 | ||
|
|
20843305dd | ||
|
|
8f6c0f6a8d | ||
|
|
ede2df7e70 | ||
|
|
d45231c1a8 | ||
|
|
772812b35f | ||
|
|
f443fd44b5 | ||
|
|
79c60b8984 | ||
|
|
2dc2c2ce79 | ||
|
|
523ca3264b | ||
|
|
4036c60b45 | ||
|
|
7d652e53e2 | ||
|
|
7c3dd55e5c | ||
|
|
1b43381be0 | ||
|
|
8f78e5039e | ||
|
|
57ee6d4e41 | ||
|
|
2868b1eca7 | ||
|
|
a4d7703efd | ||
|
|
ca4bc92c33 | ||
|
|
853261364e | ||
|
|
d3c5e4267f | ||
|
|
086b801c29 | ||
|
|
f9c25372c2 | ||
|
|
ea92363e6c | ||
|
|
015f692bd3 | ||
|
|
80d34f5511 | ||
|
|
e482929da8 | ||
|
|
4952657b31 | ||
|
|
46fae1a761 | ||
|
|
a09fb01d71 | ||
|
|
7cee3b7449 | ||
|
|
8263c48a1d | ||
|
|
ed06533e60 | ||
|
|
7b7beb13a3 | ||
|
|
c46007332a | ||
|
|
908d3b0ee5 | ||
|
|
8a031b1137 | ||
|
|
1aba9f807e | ||
|
|
4c49963988 | ||
|
|
821d40fe74 | ||
|
|
6ab1cf9325 | ||
|
|
076c0a48e9 | ||
|
|
fde613a5c4 | ||
|
|
44ad0970be | ||
|
|
b3f4d0ed8c | ||
|
|
bfdd3468ea | ||
|
|
f7decd80b6 | ||
|
|
7c2721d54d | ||
|
|
8907d0a9a7 | ||
|
|
6c8e6e9303 | ||
|
|
85c4e009f3 | ||
|
|
76802b5e38 | ||
|
|
9f2f388e5a | ||
|
|
729f53d84f | ||
|
|
d2d7ab5d04 | ||
|
|
5107c7c23d | ||
|
|
4dbd1f1358 | ||
|
|
7996040f35 | ||
|
|
0055efb720 | ||
|
|
dfa5eef20d | ||
|
|
3053acb4f3 | ||
|
|
dea9892a85 | ||
|
|
b9b6327707 | ||
|
|
84ae2964fd | ||
|
|
149b940f84 | ||
|
|
7226d8d4f7 | ||
|
|
ad9b0cd4e3 | ||
|
|
484e640d43 | ||
|
|
5d6b5d9f10 | ||
|
|
0b771ce61a | ||
|
|
72e07d4e83 | ||
|
|
2252c29495 | ||
|
|
39c0bc6c47 | ||
|
|
8f1a516a2c | ||
|
|
a6b8e88406 | ||
|
|
c19b50619f | ||
|
|
3747d96b22 | ||
|
|
8b23a08fc4 | ||
|
|
3fdefb94e4 | ||
|
|
49592ebaf3 | ||
|
|
f410dcb3f3 | ||
|
|
bd27f61a03 | ||
|
|
d703328114 | ||
|
|
afe222cb16 | ||
|
|
d0fd4dd4db | ||
|
|
3ba6b6f1ee | ||
|
|
bc464e247f | ||
|
|
c23f6d8d19 | ||
|
|
39d779edf0 | ||
|
|
0cb5362c6f | ||
|
|
a43ca0db35 | ||
|
|
9089bf6535 | ||
|
|
ef19a03efc | ||
|
|
85e1610627 | ||
|
|
d16ae84d0b | ||
|
|
95f859cf5c | ||
|
|
578a5b3e69 | ||
|
|
25f7e3af31 | ||
|
|
86192b18d1 | ||
|
|
c3144382c5 | ||
|
|
6bb9b7be04 | ||
|
|
8ee34fafa6 | ||
|
|
312171fa59 | ||
|
|
a8dbfb0569 | ||
|
|
b09b4b4433 | ||
|
|
45bd24ada0 | ||
|
|
c3a2f7717b | ||
|
|
70e6c3b2f6 | ||
|
|
d1b889aa61 | ||
|
|
f65c65569a | ||
|
|
1139caa83f | ||
|
|
d613c3c187 | ||
|
|
36f8b165cf | ||
|
|
d6e8b34942 | ||
|
|
4c4ab25d0e | ||
|
|
9ff34d90f4 | ||
|
|
9593e0f7fe | ||
|
|
1293d8b69e | ||
|
|
3e0055737e | ||
|
|
ba7fbc4032 | ||
|
|
c36d7b4972 | ||
|
|
1c0b5bb02b | ||
|
|
0dece80b5d | ||
|
|
e3b4aebf1a | ||
|
|
2e20191c01 | ||
|
|
59718e132b | ||
|
|
4d070fbfe3 | ||
|
|
723ee88043 | ||
|
|
65ba9ee6e7 | ||
|
|
fcc750784a | ||
|
|
3787d094ec | ||
|
|
4b4ea4a103 | ||
|
|
af0cf0d40a | ||
|
|
eecea93b3b | ||
|
|
ac4948c4b1 | ||
|
|
5e34c1b6b8 | ||
|
|
05e31d7594 | ||
|
|
f4097290c2 | ||
|
|
9da481b060 | ||
|
|
79002d6962 | ||
|
|
dbd9282efc | ||
|
|
b32538f3c8 | ||
|
|
e7618bb32e | ||
|
|
aacf26f05d | ||
|
|
265bc80d44 | ||
|
|
10c0e687f5 | ||
|
|
a9d4fe0b41 | ||
|
|
5cd15147eb | ||
|
|
c62db6665a | ||
|
|
fabcb261dc | ||
|
|
45cf28e0eb | ||
|
|
5b35c88be2 | ||
|
|
7f03f5d02f | ||
|
|
b98d5b790a | ||
|
|
5c74044e62 | ||
|
|
992a99d792 | ||
|
|
41075356e2 | ||
|
|
850a394eb5 | ||
|
|
244721a6f8 | ||
|
|
d59db504a3 | ||
|
|
c90e122eb2 | ||
|
|
4c6dc597f4 | ||
|
|
b4f6dee954 | ||
|
|
2685e9087e | ||
|
|
376b26c1e4 | ||
|
|
7061537ff5 | ||
|
|
2f2390b5aa | ||
|
|
99de8f1c5c | ||
|
|
af61bbc3e2 | ||
|
|
56d88f23ef | ||
|
|
4bff44377a | ||
|
|
7463edaa1b | ||
|
|
e92e06a5f4 | ||
|
|
4cbe5068a9 | ||
|
|
38b2302b59 | ||
|
|
bce0702745 | ||
|
|
d447e81abd | ||
|
|
6592745e53 | ||
|
|
e87a3cffd4 | ||
|
|
fa0b6e8a08 | ||
|
|
074b4c3500 | ||
|
|
5968c9a391 | ||
|
|
72bc5f8d7b | ||
|
|
0a0d81cd5a | ||
|
|
75e9c3678b | ||
|
|
aebe8a64a2 | ||
|
|
1aacf437b5 | ||
|
|
7e8e3fdd39 | ||
|
|
b8ae283049 | ||
|
|
6621e54952 | ||
|
|
e03a403a51 | ||
|
|
ba43b3e6b8 | ||
|
|
b4a2d1395c | ||
|
|
f5ae8d0f79 | ||
|
|
5f1c210746 | ||
|
|
f6c2f6e896 | ||
|
|
6547560e52 | ||
|
|
a167e3849b | ||
|
|
f22c23cb4c | ||
|
|
a07c99d778 | ||
|
|
1c605d58e3 | ||
|
|
6a79ce9eb1 | ||
|
|
465c38f03c | ||
|
|
be05d51e07 | ||
|
|
9bc470027e | ||
|
|
335c633884 | ||
|
|
cd26f11818 | ||
|
|
abe47b6ed8 | ||
|
|
61659faeaa | ||
|
|
71adb964e5 | ||
|
|
e599e65087 | ||
|
|
7efee9b52b | ||
|
|
079dc671e1 | ||
|
|
a32a7d1374 | ||
|
|
467cd5450f | ||
|
|
1580874a55 | ||
|
|
15f7cbe8c1 | ||
|
|
428b6145fa | ||
|
|
3ad0b31db8 | ||
|
|
8d4d5d1f46 | ||
|
|
4c8a68c6a4 | ||
|
|
0b4b6f4aec | ||
|
|
bb4db6b382 | ||
|
|
94b1c37fb2 | ||
|
|
cf6f6c5c15 | ||
|
|
f541986333 | ||
|
|
44513d6912 | ||
|
|
b20cbcd5fe | ||
|
|
1c5972f7b0 | ||
|
|
28947bb3c4 | ||
|
|
865c47a1ac | ||
|
|
3821679efd | ||
|
|
506b4da6c3 | ||
|
|
10f637d2cf | ||
|
|
0bab7c88f0 | ||
|
|
78c612ca17 | ||
|
|
e1c4035812 | ||
|
|
eb6d6c8033 | ||
|
|
7bf88565ce | ||
|
|
ee10155296 | ||
|
|
cc49140f6f | ||
|
|
3e846f89a1 | ||
|
|
5782cab2a0 | ||
|
|
8c511e2b76 | ||
|
|
ec72fb3baf | ||
|
|
bab1440f5c | ||
|
|
60c1da6a66 | ||
|
|
a849b3f2e4 | ||
|
|
dbe3c5c3f8 | ||
|
|
60cf6b3cfd | ||
|
|
5044aac337 | ||
|
|
36e0cb29c0 | ||
|
|
c0b4dd65da | ||
|
|
d061ea232b | ||
|
|
49feca4ddf | ||
|
|
46b1c57bf4 | ||
|
|
eaf1482182 | ||
|
|
d3418550eb | ||
|
|
3ffa9e2751 | ||
|
|
c697dd78f0 | ||
|
|
7dac791290 | ||
|
|
cde2faeda6 | ||
|
|
69f520428d | ||
|
|
80c84ddd75 | ||
|
|
fca8a58b36 | ||
|
|
33084899d0 | ||
|
|
7b381a8b6b | ||
|
|
9c75689a8d | ||
|
|
0ee40e8556 | ||
|
|
8b45377b89 | ||
|
|
f6fb368d88 | ||
|
|
183a5379de | ||
|
|
912791d3d4 | ||
|
|
163a61dd63 | ||
|
|
207d462dbf | ||
|
|
33281b9d89 | ||
|
|
389979923e | ||
|
|
067174965e | ||
|
|
286259c83b | ||
|
|
e1aa3e5a7f | ||
|
|
78e1c2851a | ||
|
|
0869213c55 | ||
|
|
f3fe16215a | ||
|
|
ec353ce663 | ||
|
|
b7ff5ef9dd | ||
|
|
3b26e0a7c5 | ||
|
|
6d464557a0 | ||
|
|
a776bec46a | ||
|
|
a2da51c30b | ||
|
|
8067bf548a | ||
|
|
62b0645ed0 | ||
|
|
39a94874ae | ||
|
|
e15d6717a1 | ||
|
|
37ef46e7bb | ||
|
|
70c09b3031 | ||
|
|
9378fbb0df | ||
|
|
2118b9c0cd | ||
|
|
d0c53de250 | ||
|
|
d98507eab0 | ||
|
|
760c75103e | ||
|
|
4407fd2f1f | ||
|
|
7fcd243be0 | ||
|
|
3165e9d82e | ||
|
|
6656a08c60 | ||
|
|
76661c0b51 | ||
|
|
3bb496f9ae | ||
|
|
45be1c19df | ||
|
|
a301964bd0 | ||
|
|
eea6858121 | ||
|
|
2a320fdf56 | ||
|
|
4695296ef2 | ||
|
|
0fdbbeca1d | ||
|
|
34cc39ad65 | ||
|
|
3d0c832a21 | ||
|
|
1acdab9448 | ||
|
|
93e85c5c4a | ||
|
|
ab98189d25 | ||
|
|
cd0fb7624b | ||
|
|
bae38497bb | ||
|
|
29921bfa8d | ||
|
|
2712702461 | ||
|
|
a3fa9440d1 | ||
|
|
6419b0e619 | ||
|
|
58e5b6e3f1 | ||
|
|
682c3d8079 | ||
|
|
da3d65c18f | ||
|
|
ece3a05504 | ||
|
|
927697b0f0 | ||
|
|
74dfc80b0f | ||
|
|
a7f229bc4b | ||
|
|
89bec2919f | ||
|
|
78eaecb29e | ||
|
|
d410aea856 | ||
|
|
6b1eef572b | ||
|
|
719f5d79c2 | ||
|
|
48737a32a7 | ||
|
|
53f05efb2d | ||
|
|
0e73ba4b3e | ||
|
|
f0f9d5a6af | ||
|
|
03501df9e5 | ||
|
|
dd6f85d4db | ||
|
|
1804ea6849 | ||
|
|
c8657e08f4 | ||
|
|
a942e1319b | ||
|
|
9e0a56b4f0 | ||
|
|
9abc020818 | ||
|
|
2dade8d353 | ||
|
|
1100dc6993 | ||
|
|
f212b18511 | ||
|
|
a6ca69550f | ||
|
|
2452641844 | ||
|
|
c82af4b814 | ||
|
|
fdef914137 | ||
|
|
dfcc502a88 | ||
|
|
1c6faaae88 | ||
|
|
35c8a0dd8c | ||
|
|
38feedaf6a | ||
|
|
0a2f908af4 | ||
|
|
705d53cc21 | ||
|
|
35b18d58af | ||
|
|
3c5a8d9ff3 | ||
|
|
7ca02be578 | ||
|
|
ea13c7dd32 | ||
|
|
fdfd72a42c | ||
|
|
da97bf95c0 | ||
|
|
bdfc36427c | ||
|
|
74dfe56d2b | ||
|
|
6cce9aa54e | ||
|
|
ba68b7247b | ||
|
|
b02e4fbbf6 | ||
|
|
59b4c7314d | ||
|
|
d328589bd0 | ||
|
|
b05d2b26bf | ||
|
|
86239469e7 | ||
|
|
7890506b16 | ||
|
|
83f73c3f02 | ||
|
|
87760297fc | ||
|
|
5b854d51e7 | ||
|
|
d4df101ab6 | ||
|
|
0ad2676640 | ||
|
|
a074ee2071 | ||
|
|
204d5cc964 | ||
|
|
23d15a4d6c | ||
|
|
23c47e21de | ||
|
|
5530b96446 | ||
|
|
99d28a172b | ||
|
|
d83178f29d | ||
|
|
d9d5ffdaa2 | ||
|
|
cabad6fc05 | ||
|
|
a4dc9c0403 | ||
|
|
270723ae72 | ||
|
|
b215cf83d5 | ||
|
|
f237dcf904 | ||
|
|
fc81bfa59b | ||
|
|
832ac173ae | ||
|
|
3673cfe9be | ||
|
|
6aaef97158 | ||
|
|
b0ab617393 | ||
|
|
6780b0bf11 | ||
|
|
9c0a440c38 | ||
|
|
2439f5aee5 | ||
|
|
8265f289bd | ||
|
|
9728bea0a7 | ||
|
|
fc9e84c72e | ||
|
|
7d75e864b1 | ||
|
|
a005dabbe3 | ||
|
|
c8a4432c63 | ||
|
|
7b420d56e3 | ||
|
|
ddf1bf3cbf | ||
|
|
7ea4ca00dc | ||
|
|
6b8c223804 | ||
|
|
23105956d6 | ||
|
|
d751b7e2cb | ||
|
|
f02989649c | ||
|
|
dcf313a833 | ||
|
|
9960121b08 | ||
|
|
8eea55b51c | ||
|
|
e1cab52c84 | ||
|
|
eb39617ad0 | ||
|
|
43b682a5af | ||
|
|
043fd5d404 | ||
|
|
d63a95983d | ||
|
|
4cf258f952 | ||
|
|
4e720d57b2 | ||
|
|
c12aaea747 | ||
|
|
ca48497e87 | ||
|
|
d493ea4bca | ||
|
|
e025674eb2 | ||
|
|
f2519f4fd7 | ||
|
|
db914d8c56 | ||
|
|
66faed4008 | ||
|
|
11abc99ef8 | ||
|
|
21efb32b6f | ||
|
|
622a04aec8 | ||
|
|
d360b2c62d | ||
|
|
6a112edc18 | ||
|
|
8fb4409ebb | ||
|
|
d213341d9c | ||
|
|
c2f1306d85 | ||
|
|
2143ea6f12 | ||
|
|
edb30b3c6c | ||
|
|
234e4f6f66 | ||
|
|
ce2d3c6e82 | ||
|
|
46c76b9c07 | ||
|
|
583c3cfe7d | ||
|
|
e13312dcc5 | ||
|
|
d9e49c0d5f | ||
|
|
8a370cc1ac | ||
|
|
cdae0fa593 | ||
|
|
765c0d4ff8 | ||
|
|
4cf2e16b5c | ||
|
|
9cbd61e709 | ||
|
|
0202c7afb2 | ||
|
|
c187c5a637 | ||
|
|
23c34a8c14 | ||
|
|
93ece2aec7 | ||
|
|
e12ab8fe2e | ||
|
|
2fe0ceb52a | ||
|
|
f354c12c81 | ||
|
|
def82cba49 | ||
|
|
e7bc7b94c9 | ||
|
|
aafdff49be | ||
|
|
4ef583813a | ||
|
|
9f97fb738e | ||
|
|
4e124047c6 | ||
|
|
6eb56a1564 | ||
|
|
35fc0a5c16 | ||
|
|
b36c917810 | ||
|
|
a5ac8c824e | ||
|
|
0ccc104027 | ||
|
|
8be6cb827b | ||
|
|
2f59226300 | ||
|
|
793ef68206 | ||
|
|
513c067f94 | ||
|
|
999a0c22d4 | ||
|
|
5d0832613f | ||
|
|
2ffde4c3c2 | ||
|
|
57ddfcd645 | ||
|
|
fc16e8eb8c | ||
|
|
655b971976 | ||
|
|
3e1d8ea082 | ||
|
|
772c320d5a | ||
|
|
bcc7ad0c30 | ||
|
|
73b4e1722b | ||
|
|
185cd3c123 | ||
|
|
ed564cb810 | ||
|
|
b78ece1f1e | ||
|
|
c8367a017f | ||
|
|
344a12566b | ||
|
|
c07113ea95 | ||
|
|
bc2879c412 | ||
|
|
1d47b55729 | ||
|
|
db25b4554b | ||
|
|
05b95ea2e0 | ||
|
|
250f7bf6b0 | ||
|
|
34db35b500 | ||
|
|
f75590253d | ||
|
|
4f6abc9059 | ||
|
|
c70dbc6a49 | ||
|
|
1c255b9e7d | ||
|
|
188bfa9c18 | ||
|
|
c7f8f37822 | ||
|
|
4a19dbb8cb | ||
|
|
bf0601123b | ||
|
|
9339f3413f | ||
|
|
c18517be4b | ||
|
|
eef34adcbd | ||
|
|
769d9dfbb9 | ||
|
|
6a0bb83716 | ||
|
|
6da8a3e24b | ||
|
|
e349161a53 | ||
|
|
d5b1a9d918 | ||
|
|
76af0228dd | ||
|
|
2cc1a2684a | ||
|
|
98a9d57c0b | ||
|
|
c481293aca | ||
|
|
5fd0a2b9ea | ||
|
|
11b73a9c0b | ||
|
|
c4950574ea | ||
|
|
0b297f2972 | ||
|
|
f9f870ad2d | ||
|
|
cbba6a5595 | ||
|
|
0a079b0f94 | ||
|
|
9a7e974579 | ||
|
|
f4d414d6e4 | ||
|
|
b4bfcd4279 | ||
|
|
e8ddff0ee0 | ||
|
|
b61fab9df7 | ||
|
|
28fb1ce2ae | ||
|
|
b9b107ee85 | ||
|
|
f17758e7f9 | ||
|
|
0bb24075b6 | ||
|
|
db6d9b59d0 | ||
|
|
51e82c10c5 | ||
|
|
2d892da225 | ||
|
|
b99ba2bc02 | ||
|
|
d36e9d0b0d | ||
|
|
2dc1d4443e | ||
|
|
f8a2459c91 | ||
|
|
ac80d10cd8 | ||
|
|
eb6b612052 | ||
|
|
d66a33f249 | ||
|
|
ec4c259695 | ||
|
|
ad50b6b1fb | ||
|
|
3da323c657 | ||
|
|
aca7842ca4 | ||
|
|
38c912b968 | ||
|
|
7a52e7d6d2 | ||
|
|
c36de4f640 | ||
|
|
504772bcda | ||
|
|
5d0c33d545 | ||
|
|
7bc1bcd493 | ||
|
|
b0616ee10c | ||
|
|
da57df55e8 | ||
|
|
4daea1121b | ||
|
|
afcdd64d5e | ||
|
|
798cdba979 | ||
|
|
f957344ac4 | ||
|
|
b3fbd0f352 | ||
|
|
042edc72f7 | ||
|
|
943418c434 | ||
|
|
7d7e2538bd | ||
|
|
7a544731e2 | ||
|
|
e1914b4f16 | ||
|
|
202958303e | ||
|
|
57b060ac3c | ||
|
|
8653eb8b55 | ||
|
|
a4f0a260fd | ||
|
|
d4a53e82bb | ||
|
|
6eedc99286 | ||
|
|
ec266d6c8e | ||
|
|
e3a5218e78 | ||
|
|
a473338abe | ||
|
|
ae21782adc | ||
|
|
ee44d671e7 | ||
|
|
3766bef962 | ||
|
|
ad3df36c20 | ||
|
|
38b11893e8 | ||
|
|
e4534775b0 | ||
|
|
fe7fc6b22e | ||
|
|
fe0cdc8d69 | ||
|
|
7f8a13a409 | ||
|
|
ca26ce8400 | ||
|
|
d3dd8f3f2a | ||
|
|
3c8d2d579d | ||
|
|
edcbb3dfed | ||
|
|
9c8158753e | ||
|
|
5da9cb2957 | ||
|
|
54c845b6e2 | ||
|
|
ee84f33ab5 | ||
|
|
f0f149c018 | ||
|
|
7dfbe4bb93 | ||
|
|
aa4eef41d8 | ||
|
|
69ec8a362e | ||
|
|
ecd7d4731b | ||
|
|
563aa051e4 | ||
|
|
642bb8333f | ||
|
|
c558e86e03 | ||
|
|
dbb14ea2e2 | ||
|
|
173e16b107 | ||
|
|
7d2adad67e | ||
|
|
d33612def5 | ||
|
|
9cb6ca3440 | ||
|
|
e957e40b14 | ||
|
|
7a8a43a96a | ||
|
|
0eb5dd9688 | ||
|
|
a14b53a9ab | ||
|
|
576d554a2c | ||
|
|
68a2895753 | ||
|
|
f90b3f06aa | ||
|
|
f067fa9923 | ||
|
|
ee9f89ccb5 | ||
|
|
573a9c6fb2 | ||
|
|
a46a37fba9 | ||
|
|
324b57c054 | ||
|
|
ae50ca9ab2 | ||
|
|
6e4bde00d3 | ||
|
|
d4d0dd87c9 | ||
|
|
221c05ca76 | ||
|
|
ff21ff90eb | ||
|
|
fcf295fd68 | ||
|
|
2008dec1ed | ||
|
|
b4f3c41aae | ||
|
|
90c4e3726f | ||
|
|
c83b3cefbc | ||
|
|
a8ac51da73 | ||
|
|
bc65ba3f9b | ||
|
|
79674fdbd3 | ||
|
|
adea4711f1 | ||
|
|
bded406caa | ||
|
|
85085a6375 | ||
|
|
d122d598d3 | ||
|
|
d6192b8c58 | ||
|
|
f02d4dbb59 | ||
|
|
f3818991f6 | ||
|
|
c7dd6247f0 | ||
|
|
99e17600d7 | ||
|
|
1d821ad459 | ||
|
|
c60a9ee3c3 | ||
|
|
ffcbd1e94d | ||
|
|
6c8b503402 | ||
|
|
55e1d25966 | ||
|
|
0bdd776114 | ||
|
|
c1b7bceec8 | ||
|
|
dc4f58e40c | ||
|
|
3b8cdd620c | ||
|
|
3365ff0200 | ||
|
|
89c3e2ba5a | ||
|
|
c6306db47c | ||
|
|
8ddc64c82a | ||
|
|
b887cb7255 | ||
|
|
d54ee2af82 | ||
|
|
723c113186 | ||
|
|
c368c4443e | ||
|
|
7b25b03cd5 | ||
|
|
9961d13e2d | ||
|
|
29b5ccc767 | ||
|
|
90af395df2 | ||
|
|
6f8d4d6c5c | ||
|
|
63381ff505 | ||
|
|
2ea050556b | ||
|
|
8dcac6561e | ||
|
|
90d33949f9 | ||
|
|
d3e68914dd | ||
|
|
82ad0354c4 | ||
|
|
073e439518 | ||
|
|
27b123549b | ||
|
|
de9db724a7 | ||
|
|
532ea35ee9 | ||
|
|
e9ddec35d6 | ||
|
|
7647f8089b | ||
|
|
f00f0353a6 | ||
|
|
e19ae5d43d | ||
|
|
0a9622435c | ||
|
|
f704932475 | ||
|
|
d0f096a20b | ||
|
|
949d0f3928 | ||
|
|
a2d48223c3 | ||
|
|
fc080c773f | ||
|
|
adb3811847 | ||
|
|
dbbea78b76 | ||
|
|
fd96e3e657 | ||
|
|
06d81b3a97 | ||
|
|
88551607a6 | ||
|
|
2a9dccff26 | ||
|
|
1027f85683 | ||
|
|
9bb9cb4a65 | ||
|
|
2de80646ec | ||
|
|
bf4ed57f68 | ||
|
|
9578f3dc44 | ||
|
|
a97c478a34 | ||
|
|
e0113d5dce | ||
|
|
980cf541d2 | ||
|
|
69c983f9ee | ||
|
|
70039d22f1 | ||
|
|
ebdb80c908 | ||
|
|
0eaac99d74 | ||
|
|
792061a82b | ||
|
|
d2ba7d7430 | ||
|
|
8713cfa613 | ||
|
|
aa77be1c10 | ||
|
|
e6aa2321cd | ||
|
|
c827d14d97 | ||
|
|
2979d19621 | ||
|
|
282e5c9d3e | ||
|
|
ede47d4ba7 | ||
|
|
5408efe9b5 | ||
|
|
d6141cb020 | ||
|
|
198d0fd1de | ||
|
|
6d80856f02 | ||
|
|
4778616fd7 | ||
|
|
2e025d85eb | ||
|
|
61f2191c86 | ||
|
|
c1eab8d5f3 | ||
|
|
91d2d59ae5 | ||
|
|
5aef81cf24 | ||
|
|
3550196bed | ||
|
|
bce58683fa | ||
|
|
c91a5875b2 | ||
|
|
2e15fab651 | ||
|
|
6a176082a0 | ||
|
|
fd346bac3e | ||
|
|
25e9dcc800 | ||
|
|
792cbb1536 | ||
|
|
2e12370251 | ||
|
|
7adc25694a | ||
|
|
ca80da7fbe | ||
|
|
f853d87884 | ||
|
|
524087805f | ||
|
|
916eb96b47 | ||
|
|
4add2c1051 | ||
|
|
cb0f58ab7a | ||
|
|
d9e56711ce | ||
|
|
d60692b6fd | ||
|
|
5b6ea35d96 | ||
|
|
4cbc87a17d | ||
|
|
46e7c199b2 | ||
|
|
ff7ba526fb | ||
|
|
a825da3715 | ||
|
|
fabaf4e607 | ||
|
|
153067c018 | ||
|
|
f7f2736d4d | ||
|
|
a16ca65825 | ||
|
|
cb015c83e1 | ||
|
|
2203499215 | ||
|
|
c0055a5a5f | ||
|
|
62218e81bf | ||
|
|
c45d4831ec | ||
|
|
9fd33bdfde | ||
|
|
6e1d69581c | ||
|
|
f95515ae81 | ||
|
|
09c855a659 | ||
|
|
16c96b605a | ||
|
|
e10d369e53 | ||
|
|
0d1b63a8c5 | ||
|
|
ddcdd07dd0 | ||
|
|
35da3edf60 | ||
|
|
d605022ea3 | ||
|
|
0da78065ce | ||
|
|
4b68c372c6 | ||
|
|
13406fedd8 | ||
|
|
a209ae76ca | ||
|
|
0116d7f071 | ||
|
|
512e877d06 | ||
|
|
1e1efcdcb8 | ||
|
|
bc2f58e9de | ||
|
|
fd10c42433 | ||
|
|
794437f20f | ||
|
|
23d5849cda | ||
|
|
5070a8414f | ||
|
|
5a3ca0e447 | ||
|
|
e384c50580 | ||
|
|
b9734278f6 | ||
|
|
f807a6b608 | ||
|
|
833f8c02a4 | ||
|
|
0248c6a282 | ||
|
|
218b976dbc | ||
|
|
513903890e | ||
|
|
1157bde453 | ||
|
|
46345c6a3e | ||
|
|
c13f8e5390 | ||
|
|
ad9df4bb90 | ||
|
|
e983854e71 | ||
|
|
ec999446e8 | ||
|
|
5e3e91373a | ||
|
|
c52348d8d7 | ||
|
|
9e0907ee76 | ||
|
|
9ad4025138 | ||
|
|
405f58d6a3 | ||
|
|
afbd1c425c | ||
|
|
b2c1b83fcd | ||
|
|
8d2b9a581a | ||
|
|
1825af0dd3 | ||
|
|
c2f6799f0c | ||
|
|
b5b6219cb7 | ||
|
|
185a699279 | ||
|
|
96b8f9ae9f | ||
|
|
88e2350b8f | ||
|
|
5c141af734 | ||
|
|
da580e4186 | ||
|
|
57ee09dffb | ||
|
|
7c8e830b90 | ||
|
|
ba5f668338 | ||
|
|
2c1e99858b | ||
|
|
7f2febeec9 | ||
|
|
2d7a4fe5f0 | ||
|
|
91b867a7b3 | ||
|
|
3944e734d3 | ||
|
|
ce78d9d12c | ||
|
|
edbc60a3fb | ||
|
|
6ea3ff62df | ||
|
|
88959571f1 | ||
|
|
b4583e976e | ||
|
|
92d9805f09 | ||
|
|
0c2dd62328 | ||
|
|
3f4d90d775 | ||
|
|
542ec4312f | ||
|
|
18798c9886 | ||
|
|
7aaf27389c | ||
|
|
ee179aa7bd | ||
|
|
3a05ce36de | ||
|
|
4f289ab10b | ||
|
|
78ee46270b | ||
|
|
edb632af52 | ||
|
|
19c03a08a6 | ||
|
|
44cdc124af | ||
|
|
b37787a414 | ||
|
|
53b99ea248 | ||
|
|
97a2be71e3 | ||
|
|
f623bff5c3 | ||
|
|
2511fc8401 | ||
|
|
d37ec9e5b0 | ||
|
|
95c82f5b36 | ||
|
|
ec202ed8be | ||
|
|
7190225603 | ||
|
|
52e7cabd4e | ||
|
|
064f1dfdbc | ||
|
|
f40e1fd840 | ||
|
|
e194a2a015 | ||
|
|
c39759333a | ||
|
|
edb9fd301c | ||
|
|
ea5023ac26 | ||
|
|
0fb363ea0e | ||
|
|
1cc85615d5 | ||
|
|
7b01c1bee6 | ||
|
|
35705c5345 | ||
|
|
f41da83d97 | ||
|
|
cd1e5dea4d | ||
|
|
ef605eda51 | ||
|
|
2f48ee59fa | ||
|
|
f86729c4ac | ||
|
|
5f99f4442c | ||
|
|
326857a84d | ||
|
|
5dd3945695 | ||
|
|
19eb975c73 | ||
|
|
698ffca51b | ||
|
|
fe3cc5c57c | ||
|
|
f488854720 | ||
|
|
51c0c45e04 | ||
|
|
c3e1489a8e | ||
|
|
e3420f62c6 | ||
|
|
970c80f2e3 | ||
|
|
9f4a407f94 | ||
|
|
5dda897334 | ||
|
|
3982e375e3 | ||
|
|
a8524daecb | ||
|
|
d1ce764201 | ||
|
|
8875982e1f | ||
|
|
3319a4f589 | ||
|
|
c7f27b2db4 | ||
|
|
631f630549 | ||
|
|
2a08bd9ecc | ||
|
|
f789ee4ff0 | ||
|
|
a295b42497 | ||
|
|
d8337492cc | ||
|
|
15c8debc16 | ||
|
|
67af153c16 | ||
|
|
d72dad2d1a | ||
|
|
698e4fe550 | ||
|
|
b5406b90cd | ||
|
|
05a93ba237 | ||
|
|
77548d14db | ||
|
|
b85dd608e7 | ||
|
|
231f13d810 | ||
|
|
704bfa114c | ||
|
|
44a56724cb | ||
|
|
5fbea625ae | ||
|
|
ac57b37e96 | ||
|
|
e3e9baeaa4 | ||
|
|
e071123f90 | ||
|
|
98adb01721 | ||
|
|
d6a5f9a29e | ||
|
|
0d84b4b9dd | ||
|
|
a85909198f | ||
|
|
98751e6ac8 | ||
|
|
da082673d7 | ||
|
|
35fe4d50d4 | ||
|
|
b835cb73e2 | ||
|
|
662d031e3c | ||
|
|
bf20c717fb | ||
|
|
4d4a0cf1d2 | ||
|
|
b62f3e726a | ||
|
|
82b13e98f2 | ||
|
|
9ac831b09c | ||
|
|
42616da7ff | ||
|
|
2f13517f38 | ||
|
|
fb9fd26af7 | ||
|
|
d3c385b471 | ||
|
|
96bf133924 | ||
|
|
6d6cac429d | ||
|
|
dc0b65f9c9 | ||
|
|
8882aa496f | ||
|
|
0622187ddf | ||
|
|
523e1288fa | ||
|
|
1a96cce26f | ||
|
|
a4e275e1fc | ||
|
|
6075064400 | ||
|
|
ff6e65cca9 | ||
|
|
90d2347c90 | ||
|
|
90c7056d12 | ||
|
|
fed2bc9fc9 | ||
|
|
ff510f3b84 | ||
|
|
3b12fca417 | ||
|
|
8eeb7e73cd | ||
|
|
7fd6699e0b | ||
|
|
ed70b15fc9 | ||
|
|
ff24e1de31 | ||
|
|
6547102511 | ||
|
|
d538ff5039 | ||
|
|
a49594c6a3 | ||
|
|
3544c0f014 | ||
|
|
f26fe3756c | ||
|
|
a42ca290cb | ||
|
|
da09098e49 | ||
|
|
450712f39c | ||
|
|
24b3faa427 | ||
|
|
40d11ea0e3 | ||
|
|
ab2bcb939f | ||
|
|
45499050b6 | ||
|
|
0c9197df30 | ||
|
|
a1e200cc65 | ||
|
|
8a612bb6ab | ||
|
|
e6ac939ae0 | ||
|
|
b034d4e6f8 | ||
|
|
de218611e4 | ||
|
|
615f7ce176 | ||
|
|
b306776ba9 | ||
|
|
0f85cffc78 | ||
|
|
96648df5fe | ||
|
|
2c99a2d6ec | ||
|
|
4af333d5ec | ||
|
|
a5f9869769 | ||
|
|
f10be2a18a | ||
|
|
c88d627b4e | ||
|
|
b30bb2a234 | ||
|
|
d498080eb4 | ||
|
|
334afbc710 | ||
|
|
17c13624e5 | ||
|
|
113349d272 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -18,9 +18,14 @@ DerivedData
|
||||
*.xcuserstate
|
||||
.DS_Store
|
||||
|
||||
# Exclude system ROMs
|
||||
# Exclude system ROMs and unit test ROMs
|
||||
ROMImages/*
|
||||
OSBindings/Mac/Clock SignalTests/Atari\ ROMs
|
||||
OSBindings/Mac/Clock SignalTests/Atari ROMs
|
||||
OSBindings/Mac/Clock SignalTests/MSX ROMs
|
||||
|
||||
# Exclude intermediate build products
|
||||
*.o
|
||||
.sconsign.dblite
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
|
||||
18
.travis.yml
18
.travis.yml
@@ -1,5 +1,13 @@
|
||||
language: objective-c
|
||||
osx_image: xcode8.2
|
||||
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
|
||||
xcode_scheme: Clock Signal
|
||||
xcode_sdk: macosx10.12
|
||||
# language: objective-c
|
||||
# osx_image: xcode8.2
|
||||
# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
|
||||
# xcode_scheme: Clock Signal
|
||||
# xcode_sdk: macosx10.12
|
||||
|
||||
language: cpp
|
||||
before_install:
|
||||
- sudo apt-get install libsdl2-dev
|
||||
script: cd OSBindings/SDL && scons
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
51
Activity/Observer.hpp
Normal file
51
Activity/Observer.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// ActivityObserver.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/05/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ActivityObserver_h
|
||||
#define ActivityObserver_h
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Activity {
|
||||
|
||||
/*!
|
||||
Provides a purely virtual base class for anybody that wants to receive notifications of
|
||||
'activity': any feedback from an emulated system which a user could perceive other than
|
||||
through the machine's native audio and video outputs.
|
||||
|
||||
So: status LEDs, drive activity, etc. A receiver may choose to make appropriate noises
|
||||
and/or to show or unshow status indicators.
|
||||
*/
|
||||
class Observer {
|
||||
public:
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led(const std::string &name) = 0;
|
||||
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
virtual void register_drive(const std::string &name) = 0;
|
||||
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
virtual void set_led_status(const std::string &name, bool lit) = 0;
|
||||
|
||||
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(const std::string &name, DriveEvent event) = 0;
|
||||
|
||||
/// Informs the receiver of the motor-on status of the drive with name @c name.
|
||||
virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ActivityObserver_h */
|
||||
24
Activity/Source.hpp
Normal file
24
Activity/Source.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// ActivitySource.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/05/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ActivitySource_h
|
||||
#define ActivitySource_h
|
||||
|
||||
#include "Observer.hpp"
|
||||
|
||||
namespace Activity {
|
||||
|
||||
class Source {
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *observer) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* ActivitySource_h */
|
||||
30
Analyser/Dynamic/ConfidenceCounter.cpp
Normal file
30
Analyser/Dynamic/ConfidenceCounter.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// ConfidenceCounter.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ConfidenceCounter.hpp"
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
float ConfidenceCounter::get_confidence() {
|
||||
return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
|
||||
}
|
||||
|
||||
void ConfidenceCounter::add_hit() {
|
||||
hits_++;
|
||||
}
|
||||
|
||||
void ConfidenceCounter::add_miss() {
|
||||
misses_++;
|
||||
}
|
||||
|
||||
void ConfidenceCounter::add_equivocal() {
|
||||
if(hits_ > misses_) {
|
||||
hits_++;
|
||||
misses_++;
|
||||
}
|
||||
}
|
||||
47
Analyser/Dynamic/ConfidenceCounter.hpp
Normal file
47
Analyser/Dynamic/ConfidenceCounter.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// ConfidenceCounter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ConfidenceCounter_hpp
|
||||
#define ConfidenceCounter_hpp
|
||||
|
||||
#include "ConfidenceSource.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a confidence source that calculates its probability by virtual of a history of events.
|
||||
|
||||
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() override;
|
||||
|
||||
/*! 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 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceCounter_hpp */
|
||||
28
Analyser/Dynamic/ConfidenceSource.hpp
Normal file
28
Analyser/Dynamic/ConfidenceSource.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ConfidenceSource.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ConfidenceSource_hpp
|
||||
#define ConfidenceSource_hpp
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides an abstract interface through which objects can declare the probability
|
||||
that they are the proper target for their input; e.g. if an Acorn Electron is asked
|
||||
to run an Atari 2600 program then its confidence should shrink towards 0.0; if the
|
||||
program is handed to an Atari 2600 then its confidence should grow towards 1.0.
|
||||
*/
|
||||
struct ConfidenceSource {
|
||||
virtual float get_confidence() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceSource_hpp */
|
||||
28
Analyser/Dynamic/ConfidenceSummary.cpp
Normal file
28
Analyser/Dynamic/ConfidenceSummary.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ConfidenceSummary.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ConfidenceSummary.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <numeric>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
float ConfidenceSummary::get_confidence() {
|
||||
float result = 0.0f;
|
||||
for(std::size_t index = 0; index < sources_.size(); ++index) {
|
||||
result += sources_[index]->get_confidence() * weights_[index];
|
||||
}
|
||||
return result / weight_sum_;
|
||||
}
|
||||
46
Analyser/Dynamic/ConfidenceSummary.hpp
Normal file
46
Analyser/Dynamic/ConfidenceSummary.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// ConfidenceSummary.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ConfidenceSummary_hpp
|
||||
#define ConfidenceSummary_hpp
|
||||
|
||||
#include "ConfidenceSource.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace 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.
|
||||
|
||||
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() override;
|
||||
|
||||
private:
|
||||
std::vector<ConfidenceSource *> sources_;
|
||||
std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceSummary_hpp */
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// MultiCRTMachine.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiCRTMachine.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
|
||||
speaker_ = MultiSpeaker::create(machines);
|
||||
}
|
||||
|
||||
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
|
||||
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
|
||||
// to a separate queue and this queue will block until all are done.
|
||||
volatile std::size_t outstanding_machines;
|
||||
std::condition_variable condition;
|
||||
std::mutex mutex;
|
||||
{
|
||||
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
outstanding_machines = machines_.size();
|
||||
|
||||
for(std::size_t index = 0; index < machines_.size(); ++index) {
|
||||
CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
|
||||
queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
|
||||
if(crt_machine) function(crt_machine);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
outstanding_machines--;
|
||||
condition.notify_all();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
|
||||
}
|
||||
|
||||
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
|
||||
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||
for(const auto &machine: machines_) {
|
||||
CRTMachine::Machine *crt_machine = machine->crt_machine();
|
||||
if(crt_machine) function(crt_machine);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiCRTMachine::setup_output(float aspect_ratio) {
|
||||
perform_serial([=](::CRTMachine::Machine *machine) {
|
||||
machine->setup_output(aspect_ratio);
|
||||
});
|
||||
}
|
||||
|
||||
void MultiCRTMachine::close_output() {
|
||||
perform_serial([=](::CRTMachine::Machine *machine) {
|
||||
machine->close_output();
|
||||
});
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *MultiCRTMachine::get_crt() {
|
||||
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
|
||||
return crt_machine ? crt_machine->get_crt() : nullptr;
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
void MultiCRTMachine::run_for(Time::Seconds duration) {
|
||||
perform_parallel([=](::CRTMachine::Machine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
||||
if(delegate_) delegate_->multi_crt_did_run_machines();
|
||||
}
|
||||
|
||||
void MultiCRTMachine::did_change_machine_order() {
|
||||
if(speaker_) {
|
||||
speaker_->set_new_front_machine(machines_.front().get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// MultiCRTMachine.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiCRTMachine_hpp
|
||||
#define MultiCRTMachine_hpp
|
||||
|
||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../../Machines/CRTMachine.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include "MultiSpeaker.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the CRT machine interface to multiple machines.
|
||||
|
||||
Keeps a reference to the original vector of machines; will access it only after
|
||||
acquiring a supplied mutex. The owner should also call did_change_machine_order()
|
||||
if the order of machines changes.
|
||||
*/
|
||||
class MultiCRTMachine: public CRTMachine::Machine {
|
||||
public:
|
||||
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
|
||||
|
||||
/*!
|
||||
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
|
||||
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();
|
||||
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void multi_crt_did_run_machines() = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
// Below is the standard CRTMachine::Machine interface; see there for documentation.
|
||||
void setup_output(float aspect_ratio) override;
|
||||
void close_output() override;
|
||||
Outputs::CRT::CRT *get_crt() override;
|
||||
Outputs::Speaker::Speaker *get_speaker() override;
|
||||
void run_for(Time::Seconds duration) override;
|
||||
|
||||
private:
|
||||
void run_for(const Cycles cycles) override {}
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::mutex &machines_mutex_;
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
Delegate *delegate_ = nullptr;
|
||||
|
||||
/*!
|
||||
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(::CRTMachine::Machine *)> &);
|
||||
|
||||
/*!
|
||||
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(::CRTMachine::Machine *)> &);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* MultiCRTMachine_hpp */
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// MultiConfigurable.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiConfigurable.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
for(const auto &machine: machines) {
|
||||
Configurable::Device *device = machine->configurable_device();
|
||||
if(device) devices_.push_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> options;
|
||||
|
||||
// Produce the list of unique options.
|
||||
for(const auto &device : devices_) {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
|
||||
for(auto &option : device_options) {
|
||||
if(std::find(options.begin(), options.end(), option) == options.end()) {
|
||||
options.push_back(std::move(option));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
|
||||
for(const auto &device : devices_) {
|
||||
device->set_selections(selection_by_option);
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
|
||||
Configurable::SelectionSet set;
|
||||
for(const auto &device : devices_) {
|
||||
Configurable::SelectionSet device_set = device->get_accurate_selections();
|
||||
for(auto &selection : device_set) {
|
||||
set.insert(std::move(selection));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
|
||||
Configurable::SelectionSet set;
|
||||
for(const auto &device : devices_) {
|
||||
Configurable::SelectionSet device_set = device->get_user_friendly_selections();
|
||||
for(auto &selection : device_set) {
|
||||
set.insert(std::move(selection));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// MultiConfigurable.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiConfigurable_hpp
|
||||
#define MultiConfigurable_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the configurable interface to multiple machines.
|
||||
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiConfigurable: public Configurable::Device {
|
||||
public:
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
|
||||
void set_selections(const Configurable::SelectionSet &selection_by_option) override;
|
||||
Configurable::SelectionSet get_accurate_selections() override;
|
||||
Configurable::SelectionSet get_user_friendly_selections() override;
|
||||
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiConfigurable_hpp */
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// MultiConfigurationTarget.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiConfigurationTarget.hpp"
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
for(const auto &machine: machines) {
|
||||
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
|
||||
if(configuration_target) targets_.push_back(configuration_target);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {
|
||||
}
|
||||
|
||||
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {
|
||||
bool inserted = false;
|
||||
for(const auto &target : targets_) {
|
||||
inserted |= target->insert_media(media);
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// MultiConfigurationTarget.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiConfigurationTarget_hpp
|
||||
#define MultiConfigurationTarget_hpp
|
||||
|
||||
#include "../../../../Machines/ConfigurationTarget.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the configuration target interface to multiple machines.
|
||||
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
|
||||
public:
|
||||
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
|
||||
void configure_as_target(const Analyser::Static::Target *target) override;
|
||||
bool insert_media(const Analyser::Static::Media &media) override;
|
||||
|
||||
private:
|
||||
std::vector<ConfigurationTarget::Machine *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiConfigurationTarget_hpp */
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// MultiJoystickMachine.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiJoystickMachine.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
namespace {
|
||||
|
||||
class MultiJoystick: public Inputs::Joystick {
|
||||
public:
|
||||
MultiJoystick(std::vector<JoystickMachine::Machine *> &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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DigitalInput> get_inputs() override {
|
||||
std::vector<DigitalInput> inputs;
|
||||
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<DigitalInput> 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_digital_input(const DigitalInput &digital_input, bool is_active) override {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_digital_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
void reset_all_inputs() override {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
std::size_t total_joysticks = 0;
|
||||
std::vector<JoystickMachine::Machine *> joystick_machines;
|
||||
for(const auto &machine: machines) {
|
||||
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
|
||||
if(joystick_machine) {
|
||||
joystick_machines.push_back(joystick_machine);
|
||||
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
|
||||
}
|
||||
}
|
||||
|
||||
for(std::size_t index = 0; index < total_joysticks; ++index) {
|
||||
joysticks_.emplace_back(new MultiJoystick(joystick_machines, index));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// MultiJoystickMachine.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiJoystickMachine_hpp
|
||||
#define MultiJoystickMachine_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the joystick machine interface to multiple machines.
|
||||
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiJoystickMachine: public JoystickMachine::Machine {
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiJoystickMachine_hpp */
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// MultiKeyboardMachine.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiKeyboardMachine.hpp"
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
for(const auto &machine: machines) {
|
||||
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
|
||||
if(keyboard_machine) machines_.push_back(keyboard_machine);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyboardMachine::clear_all_keys() {
|
||||
for(const auto &machine: machines_) {
|
||||
machine->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
|
||||
for(const auto &machine: machines_) {
|
||||
machine->set_key_state(key, is_pressed);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyboardMachine::type_string(const std::string &string) {
|
||||
for(const auto &machine: machines_) {
|
||||
machine->type_string(string);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
|
||||
for(const auto &machine: machines_) {
|
||||
uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key);
|
||||
if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// MultiKeyboardMachine.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiKeyboardMachine_hpp
|
||||
#define MultiKeyboardMachine_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Machines/KeyboardMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the keyboard machine interface to multiple machines.
|
||||
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiKeyboardMachine: public KeyboardMachine::Machine {
|
||||
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() override;
|
||||
void set_key_state(uint16_t key, bool is_pressed) override;
|
||||
void type_string(const std::string &) override;
|
||||
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
|
||||
|
||||
private:
|
||||
std::vector<::KeyboardMachine::Machine *> machines_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiKeyboardMachine_hpp */
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// MultiSpeaker.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiSpeaker.hpp"
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers;
|
||||
for(const auto &machine: machines) {
|
||||
Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
|
||||
if(speaker) speakers.push_back(speaker);
|
||||
}
|
||||
if(speakers.empty()) return nullptr;
|
||||
|
||||
return new MultiSpeaker(speakers);
|
||||
}
|
||||
|
||||
MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers) :
|
||||
speakers_(speakers), front_speaker_(speakers.front()) {
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_delegate(this);
|
||||
}
|
||||
}
|
||||
|
||||
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
float ideal = 0.0f;
|
||||
for(const auto &speaker: speakers_) {
|
||||
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
|
||||
}
|
||||
|
||||
return ideal / static_cast<float>(speakers_.size());
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_output_rate(cycles_per_second, buffer_size);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
if(!delegate_) return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
}
|
||||
delegate_->speaker_did_complete_samples(this, buffer);
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
if(!delegate_) return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
}
|
||||
delegate_->speaker_did_change_input_clock(this);
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
front_speaker_ = machine->crt_machine()->get_speaker();
|
||||
}
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_change_input_clock(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// MultiSpeaker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiSpeaker_hpp
|
||||
#define MultiSpeaker_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Outputs/Speaker/Speaker.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
|
||||
transparently to connect a single caller to multiple destinations.
|
||||
|
||||
Makes a static internal copy of the list of machines; expects the owner to keep it
|
||||
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);
|
||||
|
||||
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||
void set_new_front_machine(::Machine::DynamicMachine *machine);
|
||||
|
||||
// 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_output_rate(float cycles_per_second, int buffer_size) override;
|
||||
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) override;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiSpeaker_hpp */
|
||||
113
Analyser/Dynamic/MultiMachine/MultiMachine.cpp
Normal file
113
Analyser/Dynamic/MultiMachine/MultiMachine.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// MultiMachine.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiMachine.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
|
||||
machines_(std::move(machines)),
|
||||
configurable_(machines_),
|
||||
configuration_target_(machines_),
|
||||
crt_machine_(machines_, machines_mutex_),
|
||||
joystick_machine_(machines),
|
||||
keyboard_machine_(machines_) {
|
||||
crt_machine_.set_delegate(this);
|
||||
}
|
||||
|
||||
Activity::Source *MultiMachine::activity_source() {
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
ConfigurationTarget::Machine *MultiMachine::configuration_target() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->configuration_target();
|
||||
} else {
|
||||
return &configuration_target_;
|
||||
}
|
||||
}
|
||||
|
||||
CRTMachine::Machine *MultiMachine::crt_machine() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->crt_machine();
|
||||
} else {
|
||||
return &crt_machine_;
|
||||
}
|
||||
}
|
||||
|
||||
JoystickMachine::Machine *MultiMachine::joystick_machine() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->joystick_machine();
|
||||
} else {
|
||||
return &joystick_machine_;
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->keyboard_machine();
|
||||
} else {
|
||||
return &keyboard_machine_;
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::Device *MultiMachine::configurable_device() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->configurable_device();
|
||||
} else {
|
||||
return &configurable_;
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
|
||||
return
|
||||
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
|
||||
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
|
||||
}
|
||||
|
||||
void MultiMachine::multi_crt_did_run_machines() {
|
||||
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||
#ifdef DEBUG
|
||||
for(const auto &machine: machines_) {
|
||||
CRTMachine::Machine *crt = machine->crt_machine();
|
||||
printf("%0.2f ", crt->get_confidence());
|
||||
crt->print_type();
|
||||
printf("; ");
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
DynamicMachine *front = machines_.front().get();
|
||||
std::stable_sort(machines_.begin(), machines_.end(),
|
||||
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
|
||||
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
|
||||
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
|
||||
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
|
||||
});
|
||||
|
||||
if(machines_.front().get() != front) {
|
||||
crt_machine_.did_change_machine_order();
|
||||
}
|
||||
|
||||
if(would_collapse(machines_)) {
|
||||
pick_first();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMachine::pick_first() {
|
||||
has_picked_ = true;
|
||||
// machines_.erase(machines_.begin() + 1, machines_.end());
|
||||
// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
|
||||
// request a close_output while the context is active.
|
||||
}
|
||||
|
||||
void *MultiMachine::raw_pointer() {
|
||||
return nullptr;
|
||||
}
|
||||
80
Analyser/Dynamic/MultiMachine/MultiMachine.hpp
Normal file
80
Analyser/Dynamic/MultiMachine/MultiMachine.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// MultiMachine.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiMachine_hpp
|
||||
#define MultiMachine_hpp
|
||||
|
||||
#include "../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include "Implementation/MultiConfigurable.hpp"
|
||||
#include "Implementation/MultiConfigurationTarget.hpp"
|
||||
#include "Implementation/MultiCRTMachine.hpp"
|
||||
#include "Implementation/MultiJoystickMachine.hpp"
|
||||
#include "Implementation/MultiKeyboardMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides the same interface as to a single machine, while multiplexing all
|
||||
underlying calls to an array of real dynamic machines.
|
||||
|
||||
Calls to crt_machine->get_crt will return that for the frontmost machine;
|
||||
anything installed as the speaker's delegate will similarly receive
|
||||
feedback only from that machine.
|
||||
|
||||
Following each crt_machine->run_for, reorders the supplied machines by
|
||||
confidence.
|
||||
|
||||
If confidence for any machine becomes disproportionately low compared to
|
||||
the others in the set, that machine stops running.
|
||||
*/
|
||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
|
||||
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);
|
||||
|
||||
Activity::Source *activity_source() override;
|
||||
ConfigurationTarget::Machine *configuration_target() override;
|
||||
CRTMachine::Machine *crt_machine() override;
|
||||
JoystickMachine::Machine *joystick_machine() override;
|
||||
KeyboardMachine::Machine *keyboard_machine() override;
|
||||
Configurable::Device *configurable_device() override;
|
||||
void *raw_pointer() override;
|
||||
|
||||
private:
|
||||
void multi_crt_did_run_machines() override;
|
||||
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::mutex machines_mutex_;
|
||||
|
||||
MultiConfigurable configurable_;
|
||||
MultiConfigurationTarget configuration_target_;
|
||||
MultiCRTMachine crt_machine_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMachine_hpp */
|
||||
28
Analyser/Machines.hpp
Normal file
28
Analyser/Machines.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Machines.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_h
|
||||
#define Machines_h
|
||||
|
||||
namespace Analyser {
|
||||
|
||||
enum class Machine {
|
||||
AmstradCPC,
|
||||
AppleII,
|
||||
Atari2600,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
MSX,
|
||||
Oric,
|
||||
Vic20,
|
||||
ZX8081
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Machines_h */
|
||||
105
Analyser/Static/Acorn/Disk.cpp
Normal file
105
Analyser/Static/Acorn/Disk.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// Disk.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Disk.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
Storage::Encodings::MFM::Parser parser(false, disk);
|
||||
|
||||
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
|
||||
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
|
||||
|
||||
if(!names || !details) return nullptr;
|
||||
if(names->samples.empty() || details->samples.empty()) return nullptr;
|
||||
if(names->samples[0].size() != 256 || details->samples[0].size() != 256) return nullptr;
|
||||
|
||||
uint8_t final_file_offset = details->samples[0][5];
|
||||
if(final_file_offset&7) return nullptr;
|
||||
if(final_file_offset < 8) return nullptr;
|
||||
|
||||
char disk_name[13];
|
||||
snprintf(disk_name, 13, "%.8s%.4s", &names->samples[0][0], &details->samples[0][0]);
|
||||
catalogue->name = disk_name;
|
||||
|
||||
switch((details->samples[0][6] >> 4)&3) {
|
||||
case 0: 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;
|
||||
}
|
||||
|
||||
for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
|
||||
File new_file;
|
||||
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.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
|
||||
|
||||
long data_length = static_cast<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(static_cast<std::size_t>(data_length));
|
||||
|
||||
if(start_sector < 2) continue;
|
||||
while(data_length > 0) {
|
||||
uint8_t sector = static_cast<uint8_t>(start_sector % 10);
|
||||
uint8_t track = static_cast<uint8_t>(start_sector / 10);
|
||||
start_sector++;
|
||||
|
||||
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
|
||||
if(!next_sector) break;
|
||||
|
||||
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;
|
||||
}
|
||||
if(!data_length) catalogue->files.push_back(new_file);
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++) {
|
||||
Storage::Encodings::MFM::Sector *sector = parser.get_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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
@@ -3,22 +3,23 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Acorn_Disk_hpp
|
||||
#define StaticAnalyser_Acorn_Disk_hpp
|
||||
|
||||
#include "File.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
/// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
|
||||
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
|
||||
struct Catalogue {
|
||||
std::string name;
|
||||
std::list<File> files;
|
||||
std::vector<File> files;
|
||||
enum class BootOption {
|
||||
None,
|
||||
LoadBOOT,
|
||||
@@ -30,6 +31,7 @@ struct Catalogue {
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef File_hpp
|
||||
#define File_hpp
|
||||
#ifndef StaticAnalyser_Acorn_File_hpp
|
||||
#define StaticAnalyser_Acorn_File_hpp
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
struct File {
|
||||
@@ -38,9 +38,10 @@ struct File {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
std::list<Chunk> chunks;
|
||||
std::vector<Chunk> chunks;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,29 +3,30 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/08/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
using namespace StaticAnalyser::Acorn;
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
|
||||
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
|
||||
for(const auto &cartridge : cartridges) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
// only one mapped item is allowed
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 16 kb in size
|
||||
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||
if(segment.data.size() != 0x4000) continue;
|
||||
// which 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?
|
||||
uint8_t copyright_offset = segment.data[7];
|
||||
@@ -49,28 +50,28 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
// 1/(2^32) *
|
||||
// ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) *
|
||||
// 1/4
|
||||
// = something very improbable — around 1/16th of 1 in 2^32, but not exactly.
|
||||
// = something very improbable, around 1/16th of 1 in 2^32, but not exactly.
|
||||
acorn_cartridges.push_back(cartridge);
|
||||
}
|
||||
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &destination) {
|
||||
Target target;
|
||||
target.machine = Target::Electron;
|
||||
target.probability = 1.0; // TODO: a proper estimation
|
||||
target.acorn.has_dfs = false;
|
||||
target.acorn.has_adfs = false;
|
||||
target.acorn.should_shift_restart = false;
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::Electron;
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
target->has_dfs = false;
|
||||
target->has_adfs = false;
|
||||
target->should_shift_restart = false;
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target.media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
|
||||
// if there are any tapes, attempt to get data from the first
|
||||
if(media.tapes.size() > 0) {
|
||||
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
|
||||
std::list<File> files = GetFiles(tape);
|
||||
std::vector<File> files = GetFiles(tape);
|
||||
tape->reset();
|
||||
|
||||
// continue if there are any files
|
||||
@@ -82,9 +83,9 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
|
||||
|
||||
// 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
|
||||
size_t pointer = 0;
|
||||
std::size_t pointer = 0;
|
||||
uint8_t *data = &files.front().data[0];
|
||||
size_t data_size = files.front().data.size();
|
||||
std::size_t data_size = files.front().data.size();
|
||||
while(1) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 13) {
|
||||
is_basic = false;
|
||||
@@ -96,9 +97,9 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
|
||||
|
||||
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
||||
target.loadingCommand = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
|
||||
target.media.tapes = media.tapes;
|
||||
target->media.tapes = media.tapes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,18 +109,21 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
|
||||
dfs_catalogue = GetDFSCatalogue(disk);
|
||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||
if(dfs_catalogue || adfs_catalogue) {
|
||||
target.media.disks = media.disks;
|
||||
target.acorn.has_dfs = !!dfs_catalogue;
|
||||
target.acorn.has_adfs = !!adfs_catalogue;
|
||||
target->media.disks = media.disks;
|
||||
target->has_dfs = !!dfs_catalogue;
|
||||
target->has_adfs = !!adfs_catalogue;
|
||||
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None)
|
||||
target.acorn.should_shift_restart = true;
|
||||
target->should_shift_restart = true;
|
||||
else
|
||||
target.loadingCommand = "*CAT\n";
|
||||
target->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
|
||||
destination.push_back(target);
|
||||
TargetList targets;
|
||||
if(!target->media.empty()) {
|
||||
targets.push_back(std::move(target));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
26
Analyser/Static/Acorn/StaticAnalyser.hpp
Normal file
26
Analyser/Static/Acorn/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// AcornAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/08/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Acorn_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Acorn_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AcornAnalyser_hpp */
|
||||
@@ -3,16 +3,17 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/08/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
|
||||
#include <deque>
|
||||
#include "../../NumberTheory/CRC.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Acorn.hpp"
|
||||
|
||||
using namespace StaticAnalyser::Acorn;
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
|
||||
|
||||
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) {
|
||||
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
|
||||
@@ -38,7 +39,7 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
|
||||
// read out name
|
||||
char name[11];
|
||||
size_t name_ptr = 0;
|
||||
std::size_t name_ptr = 0;
|
||||
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
|
||||
name[name_ptr] = (char)parser.get_next_byte(tape);
|
||||
if(!name[name_ptr]) break;
|
||||
@@ -50,14 +51,14 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
// 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->block_number = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape));
|
||||
new_chunk->next_address = (uint32_t)parser.get_next_word(tape);
|
||||
|
||||
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));
|
||||
uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8));
|
||||
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
|
||||
|
||||
if(!new_chunk->header_crc_matched) return nullptr;
|
||||
@@ -65,13 +66,13 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
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));
|
||||
new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape)));
|
||||
}
|
||||
|
||||
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));
|
||||
uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8));
|
||||
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
|
||||
} else {
|
||||
new_chunk->data_crc_matched = true;
|
||||
@@ -118,7 +119,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
||||
return file;
|
||||
}
|
||||
|
||||
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
Storage::Tape::Acorn::Parser parser;
|
||||
|
||||
// populate chunk list
|
||||
@@ -131,7 +132,7 @@ std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::T
|
||||
}
|
||||
|
||||
// decompose into file list
|
||||
std::list<File> file_list;
|
||||
std::vector<File> file_list;
|
||||
|
||||
while(chunk_list.size()) {
|
||||
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/08/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Acorn_Tape_hpp
|
||||
@@ -12,13 +12,15 @@
|
||||
#include <memory>
|
||||
|
||||
#include "File.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
Analyser/Static/Acorn/Target.hpp
Normal file
30
Analyser/Static/Acorn/Target.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/03/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Acorn_Target_h
|
||||
#define Analyser_Static_Acorn_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
bool has_adfs = false;
|
||||
bool has_dfs = false;
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Acorn_Target_h */
|
||||
247
Analyser/Static/AmstradCPC/StaticAnalyser.cpp
Normal file
247
Analyser/Static/AmstradCPC/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// AmstradCPC.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/07/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
|
||||
static bool strcmp_insensitive(const char *a, const char *b) {
|
||||
if(std::strlen(a) != std::strlen(b)) return false;
|
||||
while(*a) {
|
||||
if(std::tolower(*a) != std::tolower(*b)) return false;
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_implied_extension(const std::string &extension) {
|
||||
return
|
||||
extension == " " ||
|
||||
strcmp_insensitive(extension.c_str(), "BAS") ||
|
||||
strcmp_insensitive(extension.c_str(), "BIN");
|
||||
}
|
||||
|
||||
static void right_trim(std::string &string) {
|
||||
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), string.end());
|
||||
}
|
||||
|
||||
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
// Trim spaces from the name.
|
||||
std::string name = file.name;
|
||||
right_trim(name);
|
||||
|
||||
// Form the basic command.
|
||||
std::string command = "run\"" + name;
|
||||
|
||||
// Consider whether the extension is required.
|
||||
if(!is_implied_extension(file.type)) {
|
||||
std::string type = file.type;
|
||||
right_trim(type);
|
||||
command += "." + type;
|
||||
}
|
||||
|
||||
// Add a newline and return.
|
||||
return command + "\n";
|
||||
}
|
||||
|
||||
static void InspectCatalogue(
|
||||
const Storage::Disk::CPM::Catalogue &catalogue,
|
||||
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) {
|
||||
candidate_files.push_back(&file);
|
||||
}
|
||||
|
||||
// Remove all files with untypable characters.
|
||||
candidate_files.erase(
|
||||
std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
|
||||
for(const auto c : file->name + file->type) {
|
||||
if(c < 32) return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
candidate_files.end());
|
||||
|
||||
// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files.
|
||||
bool are_all_system = true;
|
||||
for(const auto &file : candidate_files) {
|
||||
if(!file->system) {
|
||||
are_all_system = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!are_all_system) {
|
||||
candidate_files.erase(
|
||||
std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
|
||||
return file->system;
|
||||
}),
|
||||
candidate_files.end());
|
||||
}
|
||||
|
||||
// If there's just one file, run that.
|
||||
if(candidate_files.size() == 1) {
|
||||
target->loading_command = RunCommandFor(*candidate_files[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
|
||||
// that AMSDOS allows to be omitted, pick that one.
|
||||
int basic_files = 0;
|
||||
int implicit_suffixed_files = 0;
|
||||
|
||||
std::size_t last_basic_file = 0;
|
||||
std::size_t last_implicit_suffixed_file = 0;
|
||||
|
||||
for(std::size_t c = 0; c < candidate_files.size(); c++) {
|
||||
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
|
||||
if(candidate_files[c]->type == " " && candidate_files[c]->name == " ")
|
||||
continue;
|
||||
|
||||
// Check for whether this is [potentially] BASIC.
|
||||
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
|
||||
basic_files++;
|
||||
last_basic_file = c;
|
||||
}
|
||||
|
||||
// Check suffix for emptiness.
|
||||
if(is_implied_extension(candidate_files[c]->type)) {
|
||||
implicit_suffixed_files++;
|
||||
last_implicit_suffixed_file = c;
|
||||
}
|
||||
}
|
||||
if(basic_files == 1 || implicit_suffixed_files == 1) {
|
||||
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
|
||||
target->loading_command = RunCommandFor(*candidate_files[selected_file]);
|
||||
return;
|
||||
}
|
||||
|
||||
// One more guess: if only one remaining candidate file has a different name than the others,
|
||||
// assume it is intended to stand out.
|
||||
std::map<std::string, int> name_counts;
|
||||
std::map<std::string, std::size_t> indices_by_name;
|
||||
std::size_t index = 0;
|
||||
for(const auto &file : candidate_files) {
|
||||
name_counts[file->name]++;
|
||||
indices_by_name[file->name] = index;
|
||||
index++;
|
||||
}
|
||||
if(name_counts.size() == 2) {
|
||||
for(const auto &pair : name_counts) {
|
||||
if(pair.second == 1) {
|
||||
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Desperation.
|
||||
target->loading_command = "cat\n";
|
||||
}
|
||||
|
||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
|
||||
// this disk was formatted and the filler byte never replaced.
|
||||
bool matched = true;
|
||||
for(std::size_t c = 1; c < 64; c++) {
|
||||
if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This is a system disk, then launch it as though it were CP/M.
|
||||
if(!matched) {
|
||||
target->loading_command = "|cpm\n";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::AmstradCPC;
|
||||
target->confidence = 0.5;
|
||||
|
||||
target->model = Target::Model::CPC6128;
|
||||
|
||||
if(!media.tapes.empty()) {
|
||||
// TODO: which of these are actually potentially CPC tapes?
|
||||
target->media.tapes = media.tapes;
|
||||
|
||||
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||
// a while. Yuck!
|
||||
target->loading_command = "|tape\nrun\"\n1234567890";
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
InspectCatalogue(*data_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Failing that check for a boot sector.
|
||||
if(CheckBootSector(disk, target)) {
|
||||
target->media.disks.push_back(disk);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
InspectCatalogue(*system_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If any media survived, add the target.
|
||||
if(!target->media.empty())
|
||||
destination.push_back(std::move(target));
|
||||
|
||||
return destination;
|
||||
}
|
||||
26
Analyser/Static/AmstradCPC/StaticAnalyser.hpp
Normal file
26
Analyser/Static/AmstradCPC/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/07/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */
|
||||
35
Analyser/Static/AmstradCPC/Target.hpp
Normal file
35
Analyser/Static/AmstradCPC/Target.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/03/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_AmstradCPC_Target_h
|
||||
#define Analyser_Static_AmstradCPC_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
CPC464,
|
||||
CPC664,
|
||||
CPC6128
|
||||
};
|
||||
|
||||
Model model = Model::CPC464;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_Target_h */
|
||||
23
Analyser/Static/AppleII/StaticAnalyser.cpp
Normal file
23
Analyser/Static/AppleII/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
auto target = std::unique_ptr<Target>(new Target);
|
||||
target->machine = Machine::AppleII;
|
||||
target->media = media;
|
||||
|
||||
if(!target->media.disks.empty())
|
||||
target->disk_controller = Target::DiskController::SixteenSector;
|
||||
|
||||
TargetList targets;
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
}
|
||||
26
Analyser/Static/AppleII/StaticAnalyser.hpp
Normal file
26
Analyser/Static/AppleII/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */
|
||||
37
Analyser/Static/AppleII/Target.hpp
Normal file
37
Analyser/Static/AppleII/Target.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/04/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Target_h
|
||||
#define Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
II,
|
||||
IIplus
|
||||
};
|
||||
enum class DiskController {
|
||||
None,
|
||||
SixteenSector,
|
||||
ThirteenSector
|
||||
};
|
||||
|
||||
Model model = Model::IIplus;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Target_h */
|
||||
204
Analyser/Static/Atari/StaticAnalyser.cpp
Normal file
204
Analyser/Static/Atari/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/09/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/6502.hpp"
|
||||
|
||||
using namespace Analyser::Static::Atari;
|
||||
|
||||
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
||||
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
|
||||
uint16_t entry_address, break_address;
|
||||
|
||||
entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
|
||||
break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
|
||||
|
||||
// a CommaVid start address needs to be outside of its RAM
|
||||
if(entry_address < 0x1800 || break_address < 0x1800) return;
|
||||
|
||||
std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
|
||||
address &= 0x1fff;
|
||||
return static_cast<std::size_t>(address - 0x1800);
|
||||
};
|
||||
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
|
||||
if(has_wide_area_store) break;
|
||||
}
|
||||
}
|
||||
|
||||
// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
|
||||
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
|
||||
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
|
||||
// attempts to modify itself but it probably doesn't
|
||||
if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::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[0] == 0x78
|
||||
) {
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
|
||||
return;
|
||||
}
|
||||
|
||||
// make an assumption that this is the Atari paging model
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
|
||||
|
||||
std::set<uint16_t> internal_accesses;
|
||||
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
||||
internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
|
||||
internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
|
||||
|
||||
int atari_access_count = 0;
|
||||
int parker_access_count = 0;
|
||||
int tigervision_access_count = 0;
|
||||
for(uint16_t address : internal_accesses) {
|
||||
uint16_t masked_address = address & 0x1fff;
|
||||
atari_access_count += masked_address >= 0x1ff8 && masked_address < 0x1ffa;
|
||||
parker_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ff8;
|
||||
}
|
||||
for(uint16_t address: disassembly.external_stores) {
|
||||
uint16_t masked_address = address & 0x1fff;
|
||||
tigervision_access_count += masked_address == 0x3f;
|
||||
}
|
||||
|
||||
if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
|
||||
else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
// make an assumption that this is the Atari paging model
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
|
||||
|
||||
std::set<uint16_t> internal_accesses;
|
||||
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
||||
internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
|
||||
internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
|
||||
|
||||
int atari_access_count = 0;
|
||||
int mnetwork_access_count = 0;
|
||||
for(uint16_t address : internal_accesses) {
|
||||
uint16_t masked_address = address & 0x1fff;
|
||||
atari_access_count += masked_address >= 0x1ff6 && masked_address < 0x1ffa;
|
||||
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
|
||||
}
|
||||
|
||||
if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &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()) ?
|
||||
Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
|
||||
}
|
||||
|
||||
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
||||
if(segment.data.size() == 2048) {
|
||||
DeterminePagingFor2kCartridge(target, segment);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t entry_address, break_address;
|
||||
|
||||
entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
||||
break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
||||
|
||||
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
|
||||
if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
|
||||
return static_cast<std::size_t>(address & 0xfff);
|
||||
};
|
||||
|
||||
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});
|
||||
|
||||
switch(segment.data.size()) {
|
||||
case 8192:
|
||||
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 10495:
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
|
||||
break;
|
||||
case 12288:
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
|
||||
break;
|
||||
case 16384:
|
||||
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 32768:
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
|
||||
break;
|
||||
case 65536:
|
||||
DeterminePagingFor64kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
|
||||
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
|
||||
// next 128 bytes. So check for that.
|
||||
if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
|
||||
target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
|
||||
bool has_superchip = true;
|
||||
for(std::size_t address = 0; address < 128; address++) {
|
||||
if(segment.data[address] != segment.data[address+128]) {
|
||||
has_superchip = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
target.uses_superchip = has_superchip;
|
||||
}
|
||||
|
||||
// check for a Tigervision or Tigervision-esque scheme
|
||||
if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
|
||||
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
|
||||
if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
|
||||
}
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
// TODO: sanity checking; is this image really for an Atari 2600?
|
||||
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
|
||||
target->machine = Machine::Atari2600;
|
||||
target->confidence = 0.5;
|
||||
target->media.cartridges = media.cartridges;
|
||||
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
|
||||
target->uses_superchip = false;
|
||||
|
||||
// try to figure out the paging scheme
|
||||
if(!media.cartridges.empty()) {
|
||||
const auto &segments = media.cartridges.front()->get_segments();
|
||||
|
||||
if(segments.size() == 1) {
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
DeterminePagingForCartridge(*target, segment);
|
||||
}
|
||||
}
|
||||
TargetList destinations;
|
||||
destinations.push_back(std::move(target));
|
||||
return destinations;
|
||||
}
|
||||
@@ -3,19 +3,23 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Atari_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Atari_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari {
|
||||
|
||||
void AddTargets(const Media &media, std::list<Target> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
Analyser/Static/Atari/Target.hpp
Normal file
43
Analyser/Static/Atari/Target.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/03/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Atari_Target_h
|
||||
#define Analyser_Static_Atari_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class PagingModel {
|
||||
None,
|
||||
CommaVid,
|
||||
Atari8k,
|
||||
Atari16k,
|
||||
Atari32k,
|
||||
ActivisionStack,
|
||||
ParkerBros,
|
||||
Tigervision,
|
||||
CBSRamPlus,
|
||||
MNetwork,
|
||||
MegaBoy,
|
||||
Pitfall2
|
||||
};
|
||||
|
||||
// TODO: shouldn't these be properties of the cartridge?
|
||||
PagingModel paging_model = PagingModel::None;
|
||||
bool uses_superchip = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Atari_Target_h */
|
||||
64
Analyser/Static/Coleco/StaticAnalyser.cpp
Normal file
64
Analyser/Static/Coleco/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
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) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
// only one mapped item is allowed
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 8, 12, 16, 24 or 32 kb in size
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
const std::size_t data_size = segment.data.size();
|
||||
const std::size_t overflow = data_size&8191;
|
||||
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
|
||||
if(data_size < 8192) continue;
|
||||
|
||||
// the two bytes that will be first must be 0xaa and 0x55, either way around
|
||||
auto *start = &segment.data[0];
|
||||
if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
|
||||
start = &segment.data[segment.data.size() - 16384];
|
||||
}
|
||||
if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
|
||||
if(start[0] == start[1]) continue;
|
||||
|
||||
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
|
||||
if(!overflow) {
|
||||
coleco_cartridges.push_back(cartridge);
|
||||
} else {
|
||||
// Size down to a multiple of 8kb and apply the start address.
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
|
||||
std::vector<uint8_t> truncated_data;
|
||||
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
|
||||
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||
output_segments.emplace_back(0x8000, truncated_data);
|
||||
|
||||
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
}
|
||||
}
|
||||
|
||||
return coleco_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList targets;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::ColecoVision;
|
||||
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
if(!target->media.empty())
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/Coleco/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Coleco/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/02/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Coleco {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
@@ -3,28 +3,28 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 13/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "../../Storage/Disk/DiskController.hpp"
|
||||
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||
#include "../../Storage/Data/Commodore.hpp"
|
||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||
#include "../../../Storage/Data/Commodore.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
using namespace StaticAnalyser::Commodore;
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
public:
|
||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) {
|
||||
drive.reset(new Storage::Disk::Drive);
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
drive.reset(new Storage::Disk::Drive(4000000, 300, 2));
|
||||
set_drive(drive);
|
||||
set_motor_on(true);
|
||||
drive->set_motor_on(true);
|
||||
}
|
||||
|
||||
struct Sector {
|
||||
@@ -40,14 +40,16 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
|
||||
int difference = (int)track - (int)track_;
|
||||
int difference = static_cast<int>(track) - static_cast<int>(track_);
|
||||
track_ = track;
|
||||
|
||||
if(difference) {
|
||||
int direction = difference < 0 ? -1 : 1;
|
||||
difference *= 2 * direction;
|
||||
difference *= direction;
|
||||
|
||||
for(int c = 0; c < difference; c++) step(direction);
|
||||
for(int c = 0; c < difference; c++) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
}
|
||||
|
||||
unsigned int zone = 3;
|
||||
if(track >= 18) zone = 2;
|
||||
@@ -66,24 +68,24 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff;
|
||||
void process_input_bit(int value) {
|
||||
shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block() {
|
||||
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;
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < 2) {
|
||||
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < 2) {
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
@@ -97,8 +99,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
}
|
||||
|
||||
void proceed_to_shift_value(unsigned int shift_value) {
|
||||
index_count_ = 0;
|
||||
while(shift_register_ != shift_value && index_count_ < 2) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
@@ -108,7 +110,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_sector(uint8_t sector) {
|
||||
uint16_t sector_address = (uint16_t)((track_ << 8) | sector);
|
||||
uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
|
||||
std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
@@ -124,38 +126,38 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
|
||||
std::shared_ptr<Sector> get_next_sector() {
|
||||
std::shared_ptr<Sector> sector(new Sector);
|
||||
index_count_ = 0;
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < 2) {
|
||||
while(index_count_ < max_index_count) {
|
||||
// look for a sector header
|
||||
while(1) {
|
||||
if(proceed_to_next_block() == 0x08) break;
|
||||
if(index_count_ >= 2) return nullptr;
|
||||
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 checksum = static_cast<uint8_t>(get_next_byte());
|
||||
sector->sector = static_cast<uint8_t>(get_next_byte());
|
||||
sector->track = static_cast<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();
|
||||
disk_id[0] = static_cast<uint8_t>(get_next_byte());
|
||||
disk_id[1] = static_cast<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() == 0x07) break;
|
||||
if(index_count_ >= 2) return nullptr;
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
for(size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = (uint8_t)get_next_byte();
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = static_cast<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);
|
||||
uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
}
|
||||
@@ -165,8 +167,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
}
|
||||
};
|
||||
|
||||
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
std::list<File> files;
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
std::vector<File> files;
|
||||
CommodoreGCRParser parser;
|
||||
parser.drive->set_disk(disk);
|
||||
|
||||
@@ -188,7 +190,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
}
|
||||
|
||||
// parse directory
|
||||
size_t header_pointer = (size_t)-32;
|
||||
std::size_t header_pointer = static_cast<std::size_t>(-32);
|
||||
while(header_pointer+32+31 < directory.size()) {
|
||||
header_pointer += 32;
|
||||
|
||||
@@ -207,12 +209,12 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
next_sector = directory[header_pointer + 4];
|
||||
|
||||
new_file.raw_name.reserve(16);
|
||||
for(size_t c = 0; c < 16; c++) {
|
||||
for(std::size_t c = 0; c < 16; c++) {
|
||||
new_file.raw_name.push_back(directory[header_pointer + 5 + c]);
|
||||
}
|
||||
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
|
||||
size_t number_of_sectors = (size_t)directory[header_pointer + 0x1e] + ((size_t)directory[header_pointer + 0x1f] << 8);
|
||||
std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8);
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
|
||||
bool is_first_sector = true;
|
||||
@@ -223,7 +225,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
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(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<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
|
||||
@@ -3,23 +3,25 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 13/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Commodore_Disk_hpp
|
||||
#define StaticAnalyser_Commodore_Disk_hpp
|
||||
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "File.hpp"
|
||||
#include <list>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
||||
@@ -3,12 +3,12 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "File.hpp"
|
||||
|
||||
bool StaticAnalyser::Commodore::File::is_basic() {
|
||||
bool Analyser::Static::Commodore::File::is_basic() {
|
||||
// BASIC files are always relocatable (?)
|
||||
if(type != File::RelocatableProgram) return false;
|
||||
|
||||
@@ -21,9 +21,9 @@ bool StaticAnalyser::Commodore::File::is_basic() {
|
||||
// [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)ß
|
||||
// (with a next line address of 0000 indicating end of program)
|
||||
while(1) {
|
||||
if(line_address - starting_address >= data.size() + 2) break;
|
||||
if(static_cast<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;
|
||||
@@ -33,13 +33,13 @@ bool StaticAnalyser::Commodore::File::is_basic() {
|
||||
}
|
||||
if(next_line_address < line_address + 5) break;
|
||||
|
||||
if(line_address - starting_address >= data.size() + 5) break;
|
||||
if(static_cast<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_number = static_cast<uint16_t>(next_line_number);
|
||||
line_address = next_line_address;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef File_hpp
|
||||
@@ -12,18 +12,17 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
struct File {
|
||||
File() : is_closed(false), is_locked(false) {}
|
||||
|
||||
std::wstring name;
|
||||
std::vector<uint8_t> raw_name;
|
||||
uint16_t starting_address;
|
||||
uint16_t ending_address;
|
||||
bool is_locked;
|
||||
bool is_closed;
|
||||
bool is_locked = false;
|
||||
bool is_closed = false;
|
||||
enum {
|
||||
RelocatableProgram,
|
||||
NonRelocatableProgram,
|
||||
@@ -36,6 +35,7 @@ struct File {
|
||||
bool is_basic();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
161
Analyser/Static/Commodore/StaticAnalyser.cpp
Normal file
161
Analyser/Static/Commodore/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// CommodoreAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/09/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "File.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
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) {
|
||||
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
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// 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;
|
||||
|
||||
vic20_cartridges.push_back(cartridge);
|
||||
}
|
||||
|
||||
return vic20_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::Vic20; // TODO: machine estimation
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
|
||||
int device = 0;
|
||||
std::vector<File> files;
|
||||
bool is_disk = false;
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
|
||||
|
||||
// check 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;
|
||||
}
|
||||
}
|
||||
|
||||
// check tapes
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> tape_files = GetFiles(tape);
|
||||
tape->reset();
|
||||
if(!tape_files.empty()) {
|
||||
files.insert(files.end(), tape_files.begin(), tape_files.end());
|
||||
target->media.tapes.push_back(tape);
|
||||
if(!device) device = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(!files.empty()) {
|
||||
target->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";
|
||||
}
|
||||
string_stream << "\nRUN\n";
|
||||
target->loading_command = string_stream.str();
|
||||
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
printf("Starting address %04x?\n", files.front().starting_address);
|
||||
case 0x1001:
|
||||
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||
break;
|
||||
case 0x1201:
|
||||
target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
break;
|
||||
case 0x0401:
|
||||
target->memory_model = Target::MemoryModel::EightKB;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
if(!target->media.empty()) {
|
||||
// Inspect filename for a region hint.
|
||||
std::string lowercase_name = file_name;
|
||||
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
|
||||
if(lowercase_name.find("ntsc") != std::string::npos) {
|
||||
target->region = Analyser::Static::Commodore::Target::Region::American;
|
||||
}
|
||||
|
||||
// Attach a 1540 if there are any disks here.
|
||||
target->has_c1540 = !target->media.disks.empty();
|
||||
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
@@ -3,19 +3,23 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
void AddTargets(const Media &media, std::list<Target> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/08/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Parsers/Commodore.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
|
||||
|
||||
using namespace StaticAnalyser::Commodore;
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::list<File> file_list;
|
||||
std::vector<File> file_list;
|
||||
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
|
||||
|
||||
@@ -3,21 +3,22 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/08/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Commodore_Tape_hpp
|
||||
#define StaticAnalyser_Commodore_Tape_hpp
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "File.hpp"
|
||||
#include <list>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
Analyser/Static/Commodore/Target.hpp
Normal file
44
Analyser/Static/Commodore/Target.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/03/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Commodore_Target_h
|
||||
#define Analyser_Static_Commodore_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class MemoryModel {
|
||||
Unexpanded,
|
||||
EightKB,
|
||||
ThirtyTwoKB
|
||||
};
|
||||
|
||||
enum class Region {
|
||||
American,
|
||||
Danish,
|
||||
Japanese,
|
||||
European,
|
||||
Swedish
|
||||
};
|
||||
|
||||
MemoryModel memory_model = MemoryModel::Unexpanded;
|
||||
Region region = Region::European;
|
||||
bool has_c1540 = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Commodore_Target_h */
|
||||
@@ -3,27 +3,28 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/11/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Disassembler6502.hpp"
|
||||
#include <map>
|
||||
#include "6502.hpp"
|
||||
|
||||
using namespace StaticAnalyser::MOS6502;
|
||||
#include "Kernel.hpp"
|
||||
|
||||
struct PartialDisassembly {
|
||||
Disassembly disassembly;
|
||||
std::vector<uint16_t> remaining_entry_points;
|
||||
};
|
||||
using namespace Analyser::Static::MOS6502;
|
||||
namespace {
|
||||
|
||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
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) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
uint16_t address = entry_point;
|
||||
while(1) {
|
||||
size_t local_address = address_mapper(address);
|
||||
while(true) {
|
||||
std::size_t local_address = address_mapper(address);
|
||||
if(local_address >= memory.size()) return;
|
||||
|
||||
struct Instruction instruction;
|
||||
Instruction instruction;
|
||||
instruction.address = address;
|
||||
address++;
|
||||
|
||||
@@ -233,7 +234,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
|
||||
case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
|
||||
case Instruction::Relative: {
|
||||
size_t operand_address = address_mapper(address);
|
||||
std::size_t operand_address = address_mapper(address);
|
||||
if(operand_address >= memory.size()) return;
|
||||
address++;
|
||||
|
||||
@@ -244,12 +245,12 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
// two-byte operands
|
||||
case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
|
||||
case Instruction::Indirect: {
|
||||
size_t low_operand_address = address_mapper(address);
|
||||
size_t high_operand_address = address_mapper(address + 1);
|
||||
std::size_t low_operand_address = address_mapper(address);
|
||||
std::size_t high_operand_address = address_mapper(address + 1);
|
||||
if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
|
||||
address += 2;
|
||||
|
||||
instruction.operand = memory[low_operand_address] | (uint16_t)(memory[high_operand_address] << 8);
|
||||
instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -259,7 +260,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
|
||||
// TODO: something wider-ranging than this
|
||||
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
|
||||
size_t mapped_address = address_mapper(instruction.operand);
|
||||
std::size_t mapped_address = address_mapper(instruction.operand);
|
||||
bool is_external = mapped_address >= memory.size();
|
||||
|
||||
switch(instruction.operation) {
|
||||
@@ -301,37 +302,19 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
return;
|
||||
}
|
||||
if(instruction.addressing_mode == Instruction::Relative) {
|
||||
uint16_t destination = (uint16_t)(address + (int8_t)instruction.operand);
|
||||
uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand);
|
||||
disassembly.remaining_entry_points.push_back(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, std::vector<uint16_t> entry_points) {
|
||||
PartialDisassembly partialDisassembly;
|
||||
partialDisassembly.remaining_entry_points = entry_points;
|
||||
};
|
||||
|
||||
while(!partialDisassembly.remaining_entry_points.empty()) {
|
||||
// pull the next entry point from the back of the vector
|
||||
uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back();
|
||||
partialDisassembly.remaining_entry_points.pop_back();
|
||||
} // end of anonymous namespace
|
||||
|
||||
// if that address has already bene visited, forget about it
|
||||
if(partialDisassembly.disassembly.instructions_by_address.find(next_entry_point) != partialDisassembly.disassembly.instructions_by_address.end()) continue;
|
||||
|
||||
// if it's outgoing, log it as such and forget about it; otherwise disassemble
|
||||
size_t mapped_entry_point = address_mapper(next_entry_point);
|
||||
if(mapped_entry_point >= memory.size())
|
||||
partialDisassembly.disassembly.outward_calls.insert(next_entry_point);
|
||||
else
|
||||
AddToDisassembly(partialDisassembly, memory, address_mapper, next_entry_point);
|
||||
}
|
||||
|
||||
return std::move(partialDisassembly.disassembly);
|
||||
}
|
||||
|
||||
std::function<size_t(uint16_t)> StaticAnalyser::MOS6502::OffsetMapper(uint16_t start_address) {
|
||||
return [start_address](uint16_t argument) {
|
||||
return (size_t)(argument - start_address);
|
||||
};
|
||||
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);
|
||||
}
|
||||
101
Analyser/Static/Disassembler/6502.hpp
Normal file
101
Analyser/Static/Disassembler/6502.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// 6502.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/11/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Disassembler_6502_hpp
|
||||
#define StaticAnalyser_Disassembler_6502_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MOS6502 {
|
||||
|
||||
/*!
|
||||
Describes a 6502 instruciton: its address, the operation it performs, its addressing mode
|
||||
and its operand, if any.
|
||||
*/
|
||||
struct Instruction {
|
||||
/*! The address this instruction starts at. This is a mapped address. */
|
||||
uint16_t address = 0;
|
||||
/*! The operation this instruction performs. */
|
||||
enum {
|
||||
BRK, JSR, RTI, RTS, JMP,
|
||||
CLC, SEC, CLD, SED, CLI, SEI, CLV,
|
||||
NOP,
|
||||
|
||||
SLO, RLA, SRE, RRA, ALR, ARR,
|
||||
SAX, LAX, DCP, ISC,
|
||||
ANC, XAA, AXS,
|
||||
AND, EOR, ORA, BIT,
|
||||
ADC, SBC,
|
||||
AHX, SHY, SHX, TAS, LAS,
|
||||
|
||||
LDA, STA, LDX, STX, LDY, STY,
|
||||
|
||||
BPL, BMI, BVC, BVS, BCC, BCS, BNE, BEQ,
|
||||
|
||||
CMP, CPX, CPY,
|
||||
INC, DEC, DEX, DEY, INX, INY,
|
||||
ASL, ROL, LSR, ROR,
|
||||
TAX, TXA, TAY, TYA, TSX, TXS,
|
||||
PLA, PHA, PLP, PHP,
|
||||
|
||||
KIL
|
||||
} operation = NOP;
|
||||
/*! The addressing mode used by the instruction. */
|
||||
enum {
|
||||
Absolute,
|
||||
AbsoluteX,
|
||||
AbsoluteY,
|
||||
Immediate,
|
||||
Implied,
|
||||
ZeroPage,
|
||||
ZeroPageX,
|
||||
ZeroPageY,
|
||||
Indirect,
|
||||
IndexedIndirectX,
|
||||
IndirectIndexedY,
|
||||
Relative,
|
||||
} addressing_mode = Implied;
|
||||
/*! The instruction's operand, if any. */
|
||||
uint16_t operand = 0;
|
||||
};
|
||||
|
||||
/*! Represents the disassembled form of a program. */
|
||||
struct Disassembly {
|
||||
/*! All instructions found, mapped by address. */
|
||||
std::map<uint16_t, Instruction> instructions_by_address;
|
||||
/*! The set of all calls or jumps that land outside of the area covered by the data provided for disassembly. */
|
||||
std::set<uint16_t> outward_calls;
|
||||
/*! The set of all calls or jumps that land inside of the area covered by the data provided for disassembly. */
|
||||
std::set<uint16_t> internal_calls;
|
||||
/*! The sets of all stores, loads and modifies that occur to data outside of the area covered by the data provided for disassembly. */
|
||||
std::set<uint16_t> external_stores, external_loads, external_modifies;
|
||||
/*! The sets of all stores, loads and modifies that occur to data inside of the area covered by the data provided for disassembly. */
|
||||
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
|
||||
};
|
||||
|
||||
/*!
|
||||
Disassembles the data provided as @c memory, mapping it into the 6502's full address range via the @c address_mapper,
|
||||
starting disassembly from each of the @c entry_points.
|
||||
*/
|
||||
Disassembly Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disassembler6502_hpp */
|
||||
9
Analyser/Static/Disassembler/AddressMapper.cpp
Normal file
9
Analyser/Static/Disassembler/AddressMapper.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// AddressMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AddressMapper.hpp"
|
||||
32
Analyser/Static/Disassembler/AddressMapper.hpp
Normal file
32
Analyser/Static/Disassembler/AddressMapper.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// AddressMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AddressMapper_hpp
|
||||
#define AddressMapper_hpp
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace 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) {
|
||||
return [start_address](T argument) {
|
||||
return static_cast<std::size_t>(argument - start_address);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AddressMapper_hpp */
|
||||
52
Analyser/Static/Disassembler/Kernel.hpp
Normal file
52
Analyser/Static/Disassembler/Kernel.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Kernel.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Kernel_hpp
|
||||
#define Kernel_hpp
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Disassembly {
|
||||
|
||||
template <typename D, typename S> struct PartialDisassembly {
|
||||
D disassembly;
|
||||
std::vector<S> remaining_entry_points;
|
||||
};
|
||||
|
||||
template <typename D, typename S, typename Disassembler> D Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(S)> &address_mapper,
|
||||
std::vector<S> entry_points) {
|
||||
PartialDisassembly<D, S> partial_disassembly;
|
||||
partial_disassembly.remaining_entry_points = entry_points;
|
||||
|
||||
while(!partial_disassembly.remaining_entry_points.empty()) {
|
||||
// pull the next entry point from the back of the vector
|
||||
S next_entry_point = partial_disassembly.remaining_entry_points.back();
|
||||
partial_disassembly.remaining_entry_points.pop_back();
|
||||
|
||||
// if that address has already been visited, forget about it
|
||||
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
|
||||
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
|
||||
|
||||
// if it's outgoing, log it as such and forget about it; otherwise disassemble
|
||||
std::size_t mapped_entry_point = address_mapper(next_entry_point);
|
||||
if(mapped_entry_point >= memory.size())
|
||||
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
|
||||
else
|
||||
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
|
||||
}
|
||||
|
||||
return partial_disassembly.disassembly;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Kernel_hpp */
|
||||
619
Analyser/Static/Disassembler/Z80.cpp
Normal file
619
Analyser/Static/Disassembler/Z80.cpp
Normal file
@@ -0,0 +1,619 @@
|
||||
//
|
||||
// Z80.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Z80.hpp"
|
||||
|
||||
#include "Kernel.hpp"
|
||||
|
||||
using namespace Analyser::Static::Z80;
|
||||
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) {}
|
||||
|
||||
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 static_cast<uint16_t>(low | (high << 8));
|
||||
}
|
||||
|
||||
bool overrun() {
|
||||
return overrun_;
|
||||
}
|
||||
|
||||
bool at_end() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
return mapped_address >= memory_.size();
|
||||
}
|
||||
|
||||
uint16_t address() {
|
||||
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;
|
||||
};
|
||||
|
||||
#define x(v) (v >> 6)
|
||||
#define y(v) ((v >> 3) & 7)
|
||||
#define q(v) ((v >> 3) & 1)
|
||||
#define p(v) ((v >> 4) & 3)
|
||||
#define z(v) (v & 7)
|
||||
|
||||
Instruction::Condition condition_table[] = {
|
||||
Instruction::Condition::NZ, Instruction::Condition::Z,
|
||||
Instruction::Condition::NC, Instruction::Condition::C,
|
||||
Instruction::Condition::PO, Instruction::Condition::PE,
|
||||
Instruction::Condition::P, Instruction::Condition::M
|
||||
};
|
||||
|
||||
Instruction::Location register_pair_table[] = {
|
||||
Instruction::Location::BC,
|
||||
Instruction::Location::DE,
|
||||
Instruction::Location::HL,
|
||||
Instruction::Location::SP
|
||||
};
|
||||
|
||||
Instruction::Location register_pair_table2[] = {
|
||||
Instruction::Location::BC,
|
||||
Instruction::Location::DE,
|
||||
Instruction::Location::HL,
|
||||
Instruction::Location::AF
|
||||
};
|
||||
|
||||
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
Instruction::Location register_table[] = {
|
||||
Instruction::Location::B, Instruction::Location::C,
|
||||
Instruction::Location::D, Instruction::Location::E,
|
||||
Instruction::Location::H, Instruction::Location::L,
|
||||
Instruction::Location::HL_Indirect,
|
||||
Instruction::Location::A
|
||||
};
|
||||
|
||||
Instruction::Location location = register_table[offset];
|
||||
if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
|
||||
instruction.offset = accessor.byte() - 128;
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
Instruction::Operation alu_table[] = {
|
||||
Instruction::Operation::ADD,
|
||||
Instruction::Operation::ADC,
|
||||
Instruction::Operation::SUB,
|
||||
Instruction::Operation::SBC,
|
||||
Instruction::Operation::AND,
|
||||
Instruction::Operation::XOR,
|
||||
Instruction::Operation::OR,
|
||||
Instruction::Operation::CP
|
||||
};
|
||||
|
||||
Instruction::Operation rotation_table[] = {
|
||||
Instruction::Operation::RLC,
|
||||
Instruction::Operation::RRC,
|
||||
Instruction::Operation::RL,
|
||||
Instruction::Operation::RR,
|
||||
Instruction::Operation::SLA,
|
||||
Instruction::Operation::SRA,
|
||||
Instruction::Operation::SLL,
|
||||
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},
|
||||
};
|
||||
|
||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, 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);
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = y(operation);
|
||||
|
||||
switch(x(operation)) {
|
||||
case 1: instruction.operation = Instruction::Operation::BIT; break;
|
||||
case 2: instruction.operation = Instruction::Operation::RES; break;
|
||||
case 3: instruction.operation = Instruction::Operation::SET; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
default:
|
||||
instruction.operation = Instruction::Operation::Invalid;
|
||||
break;
|
||||
case 2:
|
||||
if(z(operation) < 4 && y(operation) >= 4) {
|
||||
instruction.operation = block_table[y(operation)-4][z(operation)];
|
||||
} else {
|
||||
instruction.operation = Instruction::Operation::Invalid;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
switch(z(operation)) {
|
||||
case 0:
|
||||
instruction.operation = Instruction::Operation::IN;
|
||||
instruction.source = Instruction::Location::BC_Indirect;
|
||||
if(y(operation) == 6) {
|
||||
instruction.destination = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
instruction.operation = Instruction::Operation::OUT;
|
||||
instruction.destination = Instruction::Location::BC_Indirect;
|
||||
if(y(operation) == 6) {
|
||||
instruction.source = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
instruction.operation = (y(operation)&1) ? Instruction::Operation::ADC : Instruction::Operation::SBC;
|
||||
instruction.destination = Instruction::Location::HL;
|
||||
instruction.source = register_pair_table[y(operation) >> 1];
|
||||
break;
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
if(q(operation)) {
|
||||
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.operand = accessor.word();
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::NEG;
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::IM;
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
switch(y(operation)&3) {
|
||||
case 0: instruction.operand = 0; break;
|
||||
case 1: instruction.operand = 0; break;
|
||||
case 2: instruction.operand = 1; break;
|
||||
case 3: instruction.operand = 2; break;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
switch(y(operation)) {
|
||||
case 0:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::I;
|
||||
instruction.source = Instruction::Location::A;
|
||||
break;
|
||||
case 1:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::R;
|
||||
instruction.source = Instruction::Location::A;
|
||||
break;
|
||||
case 2:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::A;
|
||||
instruction.source = Instruction::Location::I;
|
||||
break;
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::A;
|
||||
instruction.source = Instruction::Location::R;
|
||||
break;
|
||||
case 4: instruction.operation = Instruction::Operation::RRD; break;
|
||||
case 5: instruction.operation = Instruction::Operation::RLD; break;
|
||||
default: instruction.operation = Instruction::Operation::NOP; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
bool needs_indirect_offset = false;
|
||||
enum HLSubstitution {
|
||||
None, IX, IY
|
||||
} hl_substitution = None;
|
||||
|
||||
while(true) {
|
||||
uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
case 0:
|
||||
switch(z(operation)) {
|
||||
case 0:
|
||||
switch(y(operation)) {
|
||||
case 0: instruction.operation = Instruction::Operation::NOP; break;
|
||||
case 1: instruction.operation = Instruction::Operation::EXAFAFd; break;
|
||||
case 2:
|
||||
instruction.operation = Instruction::Operation::DJNZ;
|
||||
instruction.operand = accessor.byte() - 128;
|
||||
break;
|
||||
default:
|
||||
instruction.operation = Instruction::Operation::JR;
|
||||
instruction.operand = accessor.byte() - 128;
|
||||
if(y(operation) >= 4) instruction.condition = condition_table[y(operation) - 4];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(y(operation)&1) {
|
||||
instruction.operation = Instruction::Operation::ADD;
|
||||
instruction.destination = Instruction::Location::HL;
|
||||
instruction.source = register_pair_table[y(operation) >> 1];
|
||||
} else {
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = register_pair_table[y(operation) >> 1];
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = accessor.word();
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
switch(y(operation)) {
|
||||
case 0:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::BC_Indirect;
|
||||
instruction.source = Instruction::Location::A;
|
||||
break;
|
||||
case 1:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::A;
|
||||
instruction.source = Instruction::Location::BC_Indirect;
|
||||
break;
|
||||
case 2:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::DE_Indirect;
|
||||
instruction.source = Instruction::Location::A;
|
||||
break;
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::A;
|
||||
instruction.source = Instruction::Location::DE_Indirect;
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::Operand_Indirect;
|
||||
instruction.source = Instruction::Location::HL;
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::HL;
|
||||
instruction.source = Instruction::Location::Operand_Indirect;
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::Operand_Indirect;
|
||||
instruction.source = Instruction::Location::A;
|
||||
break;
|
||||
case 7:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::A;
|
||||
instruction.source = Instruction::Location::Operand_Indirect;
|
||||
break;
|
||||
}
|
||||
|
||||
if(y(operation) > 3) {
|
||||
instruction.operand = accessor.word();
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if(y(operation)&1) {
|
||||
instruction.operation = Instruction::Operation::DEC;
|
||||
} else {
|
||||
instruction.operation = Instruction::Operation::INC;
|
||||
}
|
||||
instruction.source = instruction.destination = register_pair_table[y(operation) >> 1];
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::INC;
|
||||
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);
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = accessor.byte();
|
||||
break;
|
||||
case 7:
|
||||
switch(y(operation)) {
|
||||
case 0: instruction.operation = Instruction::Operation::RLCA; break;
|
||||
case 1: instruction.operation = Instruction::Operation::RRCA; break;
|
||||
case 2: instruction.operation = Instruction::Operation::RLA; break;
|
||||
case 3: instruction.operation = Instruction::Operation::RRA; break;
|
||||
case 4: instruction.operation = Instruction::Operation::DAA; break;
|
||||
case 5: instruction.operation = Instruction::Operation::CPL; break;
|
||||
case 6: instruction.operation = Instruction::Operation::SCF; break;
|
||||
case 7: instruction.operation = Instruction::Operation::CCF; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(y(operation) == 6 && z(operation) == 6) {
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
instruction.operation = alu_table[y(operation)];
|
||||
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination = Instruction::Location::A;
|
||||
break;
|
||||
case 3:
|
||||
switch(z(operation)) {
|
||||
case 0:
|
||||
instruction.operation = Instruction::Operation::RET;
|
||||
instruction.condition = condition_table[y(operation)];
|
||||
break;
|
||||
case 1:
|
||||
switch(y(operation)) {
|
||||
default:
|
||||
instruction.operation = Instruction::Operation::POP;
|
||||
instruction.source = register_pair_table2[y(operation) >> 1];
|
||||
break;
|
||||
case 1:
|
||||
instruction.operation = Instruction::Operation::RET;
|
||||
break;
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::EXX;
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = Instruction::Operation::JP;
|
||||
instruction.source = Instruction::Location::HL;
|
||||
break;
|
||||
case 7:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = Instruction::Location::SP;
|
||||
instruction.source = Instruction::Location::HL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
instruction.operation = Instruction::Operation::JP;
|
||||
instruction.condition = condition_table[y(operation)];
|
||||
instruction.operand = accessor.word();
|
||||
break;
|
||||
case 3:
|
||||
switch(y(operation)) {
|
||||
case 0:
|
||||
instruction.operation = Instruction::Operation::JP;
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = accessor.word();
|
||||
break;
|
||||
case 1:
|
||||
DisassembleCBPage(accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 2:
|
||||
instruction.operation = Instruction::Operation::OUT;
|
||||
instruction.source = Instruction::Location::A;
|
||||
instruction.destination = Instruction::Location::Operand_Indirect;
|
||||
instruction.operand = accessor.byte();
|
||||
break;
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::IN;
|
||||
instruction.destination = Instruction::Location::A;
|
||||
instruction.source = Instruction::Location::Operand_Indirect;
|
||||
instruction.operand = accessor.byte();
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::EX;
|
||||
instruction.destination = Instruction::Location::SP_Indirect;
|
||||
instruction.source = Instruction::Location::HL;
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = Instruction::Operation::EX;
|
||||
instruction.destination = Instruction::Location::DE;
|
||||
instruction.source = Instruction::Location::HL;
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::DI;
|
||||
break;
|
||||
case 7:
|
||||
instruction.operation = Instruction::Operation::EI;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::CALL;
|
||||
instruction.source = Instruction::Location::Operand_Indirect;
|
||||
instruction.operand = accessor.word();
|
||||
instruction.condition = condition_table[y(operation)];
|
||||
break;
|
||||
case 5:
|
||||
switch(y(operation)) {
|
||||
default:
|
||||
instruction.operation = Instruction::Operation::PUSH;
|
||||
instruction.source = register_pair_table2[y(operation) >> 1];
|
||||
break;
|
||||
case 1:
|
||||
instruction.operation = Instruction::Operation::CALL;
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = accessor.word();
|
||||
break;
|
||||
case 3:
|
||||
needs_indirect_offset = true;
|
||||
hl_substitution = IX;
|
||||
continue; // i.e. repeat loop.
|
||||
case 5:
|
||||
DisassembleEDPage(accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 7:
|
||||
needs_indirect_offset = true;
|
||||
hl_substitution = IY;
|
||||
continue; // i.e. repeat loop.
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = alu_table[y(operation)];
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.destination = Instruction::Location::A;
|
||||
instruction.operand = accessor.byte();
|
||||
break;
|
||||
case 7:
|
||||
instruction.operation = Instruction::Operation::RST;
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = y(operation) << 3;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// This while(true) isn't an infinite loop for everything except those paths that opt in
|
||||
// via continue.
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform IX/IY substitution for HL, if applicable.
|
||||
if(hl_substitution != None) {
|
||||
// EX DE, HL is not affected.
|
||||
if(instruction.operation == Instruction::Operation::EX) return;
|
||||
|
||||
// If an (HL) is involved, switch it for IX+d or IY+d.
|
||||
if( instruction.source == Instruction::Location::HL_Indirect ||
|
||||
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;
|
||||
}
|
||||
if(instruction.destination == Instruction::Location::HL_Indirect) {
|
||||
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, switch either of H or L for I[X/Y]h and I[X/Y]l.
|
||||
if(instruction.source == Instruction::Location::H) {
|
||||
instruction.source = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
|
||||
}
|
||||
if(instruction.source == Instruction::Location::L) {
|
||||
instruction.source = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
|
||||
}
|
||||
if(instruction.destination == Instruction::Location::H) {
|
||||
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
|
||||
}
|
||||
if(instruction.destination == Instruction::Location::L) {
|
||||
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
Accessor accessor(memory, address_mapper, entry_point);
|
||||
|
||||
while(!accessor.at_end()) {
|
||||
Instruction instruction;
|
||||
instruction.address = accessor.address();
|
||||
|
||||
DisassembleMainPage(accessor, instruction);
|
||||
|
||||
// If any memory access was invalid, end disassembly.
|
||||
if(accessor.overrun()) return;
|
||||
|
||||
// Store the instruction away.
|
||||
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
|
||||
|
||||
// Update access tables.
|
||||
int access_type =
|
||||
((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
|
||||
((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
|
||||
uint16_t address = static_cast<uint16_t>(instruction.operand);
|
||||
bool is_internal = address_mapper(address) < memory.size();
|
||||
switch(access_type) {
|
||||
default: break;
|
||||
case 1:
|
||||
if(is_internal) {
|
||||
disassembly.disassembly.internal_loads.insert(address);
|
||||
} else {
|
||||
disassembly.disassembly.external_loads.insert(address);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if(is_internal) {
|
||||
disassembly.disassembly.internal_stores.insert(address);
|
||||
} else {
|
||||
disassembly.disassembly.external_stores.insert(address);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if(is_internal) {
|
||||
disassembly.disassembly.internal_modifies.insert(address);
|
||||
} else {
|
||||
disassembly.disassembly.internal_modifies.insert(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Add any (potentially) newly discovered entry point.
|
||||
if( instruction.operation == Instruction::Operation::JP ||
|
||||
instruction.operation == Instruction::Operation::JR ||
|
||||
instruction.operation == Instruction::Operation::CALL ||
|
||||
instruction.operation == Instruction::Operation::RST) {
|
||||
disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand));
|
||||
}
|
||||
|
||||
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
|
||||
if(instruction.condition != Instruction::Condition::None) continue;
|
||||
|
||||
if(instruction.operation == Instruction::Operation::RET) return;
|
||||
if(instruction.operation == Instruction::Operation::RETI) return;
|
||||
if(instruction.operation == Instruction::Operation::RETN) return;
|
||||
if(instruction.operation == Instruction::Operation::JP) return;
|
||||
if(instruction.operation == Instruction::Operation::JR) return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
Disassembly Analyser::Static::Z80::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, Z80Disassembler>(memory, address_mapper, entry_points);
|
||||
}
|
||||
90
Analyser/Static/Disassembler/Z80.hpp
Normal file
90
Analyser/Static/Disassembler/Z80.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// Z80.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Disassembler_Z80_hpp
|
||||
#define StaticAnalyser_Disassembler_Z80_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Z80 {
|
||||
|
||||
struct Instruction {
|
||||
/*! The address this instruction starts at. This is a mapped address. */
|
||||
uint16_t address = 0;
|
||||
|
||||
/*! The operation this instruction performs. */
|
||||
enum class Operation {
|
||||
NOP,
|
||||
EXAFAFd, EXX, EX,
|
||||
LD, HALT,
|
||||
ADD, ADC, SUB, SBC, AND, XOR, OR, CP,
|
||||
INC, DEC,
|
||||
RLCA, RRCA, RLA, RRA, DAA, CPL, SCF, CCF,
|
||||
RLD, RRD,
|
||||
DJNZ, JR, JP, CALL, RST, RET, RETI, RETN,
|
||||
PUSH, POP,
|
||||
IN, OUT,
|
||||
EI, DI,
|
||||
RLC, RRC, RL, RR, SLA, SRA, SLL, SRL,
|
||||
BIT, RES, SET,
|
||||
LDI, CPI, INI, OUTI,
|
||||
LDD, CPD, IND, OUTD,
|
||||
LDIR, CPIR, INIR, OTIR,
|
||||
LDDR, CPDR, INDR, OTDR,
|
||||
NEG,
|
||||
IM,
|
||||
Invalid
|
||||
} operation = Operation::NOP;
|
||||
|
||||
/*! The condition required for this instruction to take effect. */
|
||||
enum class Condition {
|
||||
None, NZ, Z, NC, C, PO, PE, P, M
|
||||
} condition = Condition::None;
|
||||
|
||||
enum class Location {
|
||||
B, C, D, E, H, L, HL_Indirect, A, I, R,
|
||||
BC, DE, HL, SP, AF, Operand,
|
||||
IX_Indirect_Offset, IY_Indirect_Offset, IXh, IXl, IYh, IYl,
|
||||
Operand_Indirect,
|
||||
BC_Indirect, DE_Indirect, SP_Indirect,
|
||||
None
|
||||
};
|
||||
/*! The locations of source data for this instruction. */
|
||||
Location source = Location::None;
|
||||
/*! The locations of destination data from this instruction. */
|
||||
Location destination = Location::None;
|
||||
/*! The operand, if any; if this is used then it'll be referenced by either the source or destination location. */
|
||||
int operand = 0;
|
||||
/*! The offset to apply, if any; applies to IX_Indirect_Offset and IY_Indirect_Offset locations. */
|
||||
int offset = 0;
|
||||
};
|
||||
|
||||
struct Disassembly {
|
||||
std::map<uint16_t, Instruction> instructions_by_address;
|
||||
std::set<uint16_t> outward_calls;
|
||||
std::set<uint16_t> internal_calls;
|
||||
std::set<uint16_t> external_stores, external_loads, external_modifies;
|
||||
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
|
||||
};
|
||||
|
||||
Disassembly Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_Disassembler_Z80_hpp */
|
||||
125
Analyser/Static/DiskII/StaticAnalyser.cpp
Normal file
125
Analyser/Static/DiskII/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../AppleII/Target.hpp"
|
||||
#include "../Oric/Target.hpp"
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::AppleII;
|
||||
|
||||
if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
|
||||
target->disk_controller = Target::DiskController::ThirteenSector;
|
||||
} else {
|
||||
target->disk_controller = Target::DiskController::SixteenSector;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||
using Target = Analyser::Static::Oric::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::Oric;
|
||||
target->rom = Target::ROM::Pravetz;
|
||||
target->disk_interface = Target::DiskInterface::Pravetz;
|
||||
target->loading_command = "CALL 800\n";
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// Grab track 0, sector 0: the boot sector.
|
||||
auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
|
||||
|
||||
const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
|
||||
for(const auto &pair: sector_map) {
|
||||
if(!pair.second.address.sector) {
|
||||
sector_zero = &pair.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no boot sector then if there are also no sectors at all,
|
||||
// decline to nominate a machine. Otherwise go with an Apple as the default.
|
||||
TargetList targets;
|
||||
if(!sector_zero) {
|
||||
if(sector_map.empty()) {
|
||||
return targets;
|
||||
} else {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(nullptr)));
|
||||
targets.back()->media = media;
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
|
||||
// If the boot sector looks like it's intended for the Oric, create an Oric.
|
||||
// Otherwise go with the Apple II.
|
||||
|
||||
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;
|
||||
|
||||
// Look for a tight BPL loop reading the Oric's shift register address of 0x31c. The Apple II just has RAM there,
|
||||
// so the probability of such a loop is infinitesimal.
|
||||
for(const auto &instruction: disassembly.instructions_by_address) {
|
||||
// Is this a read of the shift register?
|
||||
if(
|
||||
(
|
||||
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDA) ||
|
||||
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDX) ||
|
||||
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDY)
|
||||
) &&
|
||||
instruction.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Absolute &&
|
||||
instruction.second.address == 0x031c) {
|
||||
did_read_shift_register = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(did_read_shift_register) {
|
||||
if(
|
||||
instruction.second.operation == Analyser::Static::MOS6502::Instruction::BPL &&
|
||||
instruction.second.address == 0xfb) {
|
||||
is_oric = true;
|
||||
break;
|
||||
}
|
||||
|
||||
did_read_shift_register = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check also for calls into the 0x3xx page above 0x320, as that's where the Oric's boot ROM is.
|
||||
for(const auto address: disassembly.outward_calls) {
|
||||
is_oric |= address >= 0x320 && address < 0x400;
|
||||
}
|
||||
|
||||
if(is_oric) {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero)));
|
||||
} else {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(sector_zero)));
|
||||
}
|
||||
targets.back()->media = media;
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/DiskII/StaticAnalyser.hpp
Normal file
27
Analyser/Static/DiskII/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_DiskII_StaticAnalyser_hpp
|
||||
#define Analyser_Static_DiskII_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace DiskII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */
|
||||
40
Analyser/Static/MSX/Cartridge.hpp
Normal file
40
Analyser/Static/MSX/Cartridge.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Cartridge.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Cartridge_hpp
|
||||
#define Cartridge_hpp
|
||||
|
||||
#include "../../../Storage/Cartridge/Cartridge.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
/*!
|
||||
Extends the base cartridge class by adding a (guess at) the banking scheme.
|
||||
*/
|
||||
struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||
enum Type {
|
||||
None,
|
||||
Konami,
|
||||
KonamiWithSCC,
|
||||
ASCII8kb,
|
||||
ASCII16kb,
|
||||
FMPac
|
||||
};
|
||||
const Type type;
|
||||
|
||||
Cartridge(const std::vector<Segment> &segments, Type type) :
|
||||
Storage::Cartridge::Cartridge(segments), type(type) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Cartridge_hpp */
|
||||
299
Analyser/Static/MSX/StaticAnalyser.cpp
Normal file
299
Analyser/Static/MSX/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/11/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/Z80.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
const Storage::Cartridge::Cartridge::Segment &segment,
|
||||
uint16_t start_address,
|
||||
Analyser::Static::MSX::Cartridge::Type type,
|
||||
float confidence) {
|
||||
|
||||
// Size down to a multiple of 8kb in size and apply the start address.
|
||||
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 = static_cast<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 {
|
||||
output_segments.emplace_back(start_address, segment.data);
|
||||
}
|
||||
|
||||
std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
|
||||
target->machine = Analyser::Machine::MSX;
|
||||
target->confidence = confidence;
|
||||
|
||||
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
|
||||
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
} else {
|
||||
target->media.cartridges.emplace_back(new Analyser::Static::MSX::Cartridge(output_segments, type));
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/*
|
||||
Expected standard cartridge format:
|
||||
|
||||
DEFB "AB" ; expansion ROM header
|
||||
DEFW initcode ; start of the init code, 0 if no initcode
|
||||
DEFW callstat; pointer to CALL statement handler, 0 if no such handler
|
||||
DEFW device; pointer to expansion device handler, 0 if no such handler
|
||||
DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram
|
||||
DEFS 6,0 ; room reserved for future extensions
|
||||
|
||||
MSX cartridges often include banking hardware; those games were marketed as MegaROMs. The file
|
||||
format that the MSX community has decided upon doesn't retain the type of hardware included, so
|
||||
this analyser has to guess.
|
||||
|
||||
(additional audio hardware is also sometimes included, but it's implied by the banking hardware)
|
||||
*/
|
||||
static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
// No cartridges implies no targets.
|
||||
if(cartridges.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList targets;
|
||||
for(const auto &cartridge : cartridges) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
// Only one mapped item is allowed.
|
||||
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 size_t data_size = segment.data.size();
|
||||
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
|
||||
|
||||
// Check for a ROM header at address 0; if it's not found then try 0x4000
|
||||
// and adjust the start address;
|
||||
uint16_t start_address = 0;
|
||||
bool found_start = false;
|
||||
if(segment.data[0] == 0x41 && segment.data[1] == 0x42) {
|
||||
start_address = 0x4000;
|
||||
found_start = true;
|
||||
} else if(segment.data.size() >= 0x8000 && segment.data[0x4000] == 0x41 && segment.data[0x4001] == 0x42) {
|
||||
start_address = 0;
|
||||
found_start = true;
|
||||
}
|
||||
|
||||
// Reject cartridge if the ROM header wasn't found.
|
||||
if(!found_start) continue;
|
||||
|
||||
uint16_t init_address = static_cast<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.
|
||||
if(data_size <= 0xc000) {
|
||||
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this ROM is greater than 48kb in size then some sort of MegaROM scheme must
|
||||
// be at play; disassemble to try to figure it out.
|
||||
std::vector<uint8_t> first_8k;
|
||||
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
|
||||
Analyser::Static::Z80::Disassembly disassembly =
|
||||
Analyser::Static::Z80::Disassemble(
|
||||
first_8k,
|
||||
Analyser::Static::Disassembler::OffsetMapper(start_address),
|
||||
{ init_address }
|
||||
);
|
||||
|
||||
// // Look for a indirect store followed by an unconditional JP or CALL into another
|
||||
// // segment, that's a fairly explicit sign where found.
|
||||
using Instruction = Analyser::Static::Z80::Instruction;
|
||||
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
|
||||
bool is_ascii = false;
|
||||
// auto iterator = instructions.begin();
|
||||
// while(iterator != instructions.end()) {
|
||||
// auto next_iterator = iterator;
|
||||
// next_iterator++;
|
||||
// if(next_iterator == instructions.end()) break;
|
||||
//
|
||||
// if( iterator->second.operation == Instruction::Operation::LD &&
|
||||
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
|
||||
// (
|
||||
// iterator->second.operand == 0x5000 ||
|
||||
// iterator->second.operand == 0x6000 ||
|
||||
// iterator->second.operand == 0x6800 ||
|
||||
// iterator->second.operand == 0x7000 ||
|
||||
// iterator->second.operand == 0x77ff ||
|
||||
// iterator->second.operand == 0x7800 ||
|
||||
// iterator->second.operand == 0x8000 ||
|
||||
// iterator->second.operand == 0x9000 ||
|
||||
// iterator->second.operand == 0xa000
|
||||
// ) &&
|
||||
// (
|
||||
// next_iterator->second.operation == Instruction::Operation::CALL ||
|
||||
// next_iterator->second.operation == Instruction::Operation::JP
|
||||
// ) &&
|
||||
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
|
||||
// ) {
|
||||
// const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand);
|
||||
// switch(iterator->second.operand) {
|
||||
// case 0x6000:
|
||||
// if(address >= 0x6000 && address < 0x8000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0x6800:
|
||||
// if(address >= 0x6000 && address < 0x6800) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x7000:
|
||||
// if(address >= 0x6000 && address < 0x8000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// if(address >= 0x7000 && address < 0x7800) {
|
||||
// is_ascii = true;
|
||||
// }
|
||||
// break;
|
||||
// case 0x77ff:
|
||||
// if(address >= 0x7000 && address < 0x7800) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x7800:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x8000:
|
||||
// if(address >= 0x8000 && address < 0xa000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0x9000:
|
||||
// if(address >= 0x8000 && address < 0xa000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0xa000:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
|
||||
// }
|
||||
// break;
|
||||
// case 0xb000:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// iterator = next_iterator;
|
||||
|
||||
// Look for LD (nnnn), A instructions, and collate those addresses.
|
||||
std::map<uint16_t, int> address_counts;
|
||||
for(const auto &instruction_pair : instructions) {
|
||||
if( instruction_pair.second.operation == Instruction::Operation::LD &&
|
||||
instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
|
||||
instruction_pair.second.source == Instruction::Location::A) {
|
||||
address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Weight confidences by number of observed hits.
|
||||
float total_hits =
|
||||
static_cast<float>(
|
||||
address_counts[0x6000] + address_counts[0x6800] +
|
||||
address_counts[0x7000] + address_counts[0x7800] +
|
||||
address_counts[0x77ff] + address_counts[0x8000] +
|
||||
address_counts[0xa000] + address_counts[0x5000] +
|
||||
address_counts[0x9000] + address_counts[0xb000]
|
||||
);
|
||||
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||
static_cast<float>( address_counts[0x6000] +
|
||||
address_counts[0x6800] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x7800]) / total_hits));
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||
static_cast<float>( address_counts[0x6000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x77ff]) / total_hits));
|
||||
if(!is_ascii) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::Konami,
|
||||
static_cast<float>( address_counts[0x6000] +
|
||||
address_counts[0x8000] +
|
||||
address_counts[0xa000]) / total_hits));
|
||||
}
|
||||
if(!is_ascii) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
|
||||
static_cast<float>( address_counts[0x5000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x9000] +
|
||||
address_counts[0xb000]) / total_hits));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
|
||||
// Append targets for any cartridges that look correct.
|
||||
auto cartridge_targets = CartridgeTargetsFrom(media.cartridges);
|
||||
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
|
||||
|
||||
// Consider building a target for disks and/or tapes.
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
|
||||
// Check tapes for loadable files.
|
||||
for(auto &tape : media.tapes) {
|
||||
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;
|
||||
case File::Type::TokenisedBASIC: target->loading_command = "CLOAD\rRUN\r"; break;
|
||||
case File::Type::Binary: target->loading_command = "BLOAD\"CAS:\",R\r"; break;
|
||||
default: break;
|
||||
}
|
||||
target->media.tapes.push_back(tape);
|
||||
}
|
||||
}
|
||||
|
||||
// Blindly accept disks for now.
|
||||
target->media.disks = media.disks;
|
||||
target->has_disk_drive = !media.disks.empty();
|
||||
|
||||
if(!target->media.empty()) {
|
||||
target->machine = Machine::MSX;
|
||||
target->confidence = 0.5;
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
26
Analyser/Static/MSX/StaticAnalyser.hpp
Normal file
26
Analyser/Static/MSX/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/11/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_MSX_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */
|
||||
165
Analyser/Static/MSX/Tape.cpp
Normal file
165
Analyser/Static/MSX/Tape.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// Tape.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
|
||||
#include "../../../Storage/Tape/Parsers/MSX.hpp"
|
||||
|
||||
using namespace Analyser::Static::MSX;
|
||||
|
||||
File::File(File &&rhs) :
|
||||
name(std::move(rhs.name)),
|
||||
type(rhs.type),
|
||||
data(std::move(rhs.data)),
|
||||
starting_address(rhs.starting_address),
|
||||
entry_address(rhs.entry_address) {}
|
||||
|
||||
File::File() :
|
||||
type(Type::Binary),
|
||||
starting_address(0),
|
||||
entry_address(0) {} // For the sake of initialising in a defined state.
|
||||
|
||||
std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> files;
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player(1000000);
|
||||
tape_player.set_motor_control(true);
|
||||
tape_player.set_tape(tape);
|
||||
|
||||
using Parser = Storage::Tape::MSX::Parser;
|
||||
|
||||
// Get all recognisable files from the tape.
|
||||
while(!tape->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;
|
||||
|
||||
// Check whether what follows is a recognisable file type.
|
||||
uint8_t header[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||
for(std::size_t c = 0; c < sizeof(header); ++c) {
|
||||
int next_byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(next_byte == -1) break;
|
||||
header[c] = static_cast<uint8_t>(next_byte);
|
||||
}
|
||||
|
||||
bool bytes_are_same = true;
|
||||
for(std::size_t c = 1; c < sizeof(header); ++c)
|
||||
bytes_are_same &= (header[c] == header[0]);
|
||||
|
||||
if(!bytes_are_same) continue;
|
||||
if(header[0] != 0xd0 && header[0] != 0xd3 && header[0] != 0xea) continue;
|
||||
|
||||
File file;
|
||||
|
||||
// Determine file type from information already collected.
|
||||
switch(header[0]) {
|
||||
case 0xd0: file.type = File::Type::Binary; break;
|
||||
case 0xd3: file.type = File::Type::TokenisedBASIC; break;
|
||||
case 0xea: file.type = File::Type::ASCII; break;
|
||||
default: break; // Unreachable.
|
||||
}
|
||||
|
||||
// Read file name.
|
||||
char name[7];
|
||||
for(std::size_t c = 1; c < 6; ++c)
|
||||
name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player));
|
||||
name[6] = '\0';
|
||||
file.name = name;
|
||||
|
||||
// ASCII: Read 256-byte segments until one ends with an end-of-file character.
|
||||
if(file.type == File::Type::ASCII) {
|
||||
while(true) {
|
||||
file_speed = Parser::find_header(tape_player);
|
||||
if(!file_speed) break;
|
||||
int c = 256;
|
||||
bool contains_end_of_file = false;
|
||||
while(c--) {
|
||||
int byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(byte == -1) break;
|
||||
contains_end_of_file |= (byte == 0x1a);
|
||||
file.data.push_back(static_cast<uint8_t>(byte));
|
||||
}
|
||||
if(c != -1) break;
|
||||
if(contains_end_of_file) {
|
||||
files.push_back(std::move(file));
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read a single additional segment, using the information at the begging to determine length.
|
||||
file_speed = Parser::find_header(tape_player);
|
||||
if(!file_speed) continue;
|
||||
|
||||
// Binary: read start address, end address, entry address, then that many bytes.
|
||||
if(file.type == File::Type::Binary) {
|
||||
uint8_t locations[6];
|
||||
uint16_t end_address;
|
||||
std::size_t c;
|
||||
for(c = 0; c < sizeof(locations); ++c) {
|
||||
int byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(byte == -1) break;
|
||||
locations[c] = static_cast<uint8_t>(byte);
|
||||
}
|
||||
if(c != sizeof(locations)) continue;
|
||||
|
||||
file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8));
|
||||
end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8));
|
||||
file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8));
|
||||
|
||||
if(end_address < file.starting_address) continue;
|
||||
|
||||
std::size_t length = end_address - file.starting_address;
|
||||
while(length--) {
|
||||
int byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(byte == -1) continue;
|
||||
file.data.push_back(static_cast<uint8_t>(byte));
|
||||
}
|
||||
|
||||
files.push_back(std::move(file));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tokenised BASIC, then: keep following 'next line' links from a hypothetical start of
|
||||
// 0x8001, until finding the final line.
|
||||
uint16_t current_address = 0x8001;
|
||||
while(current_address) {
|
||||
int next_address_buffer[2];
|
||||
next_address_buffer[0] = Parser::get_byte(*file_speed, tape_player);
|
||||
next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
|
||||
|
||||
if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
|
||||
file.data.push_back(static_cast<uint8_t>(next_address_buffer[0]));
|
||||
file.data.push_back(static_cast<uint8_t>(next_address_buffer[1]));
|
||||
|
||||
uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8));
|
||||
if(!next_address) {
|
||||
files.push_back(std::move(file));
|
||||
break;
|
||||
}
|
||||
if(next_address < current_address+2) break;
|
||||
|
||||
// This line makes sense, so push it all in.
|
||||
std::size_t length = next_address - current_address - 2;
|
||||
current_address = next_address;
|
||||
bool found_error = false;
|
||||
while(length--) {
|
||||
int byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(byte == -1) {
|
||||
found_error = true;
|
||||
break;
|
||||
}
|
||||
file.data.push_back(static_cast<uint8_t>(byte));
|
||||
}
|
||||
if(found_error) break;
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
44
Analyser/Static/MSX/Tape.hpp
Normal file
44
Analyser/Static/MSX/Tape.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Tape.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_MSX_Tape_hpp
|
||||
#define StaticAnalyser_MSX_Tape_hpp
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
enum Type {
|
||||
Binary,
|
||||
TokenisedBASIC,
|
||||
ASCII
|
||||
} type;
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
uint16_t starting_address; // Provided only for Type::Binary files.
|
||||
uint16_t entry_address; // Provided only for Type::Binary files.
|
||||
|
||||
File(File &&rhs);
|
||||
File();
|
||||
};
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_Tape_hpp */
|
||||
28
Analyser/Static/MSX/Target.hpp
Normal file
28
Analyser/Static/MSX/Target.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/04/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_MSX_Target_h
|
||||
#define Analyser_Static_MSX_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
bool has_disk_drive = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_MSX_Target_h */
|
||||
@@ -3,28 +3,35 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/10/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Tape.hpp"
|
||||
#include "../Disassembler/Disassembler6502.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
using namespace StaticAnalyser::Oric;
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
|
||||
static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace Analyser::Static::Oric;
|
||||
|
||||
static 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(auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||
for(auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
for(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;
|
||||
}
|
||||
|
||||
static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
|
||||
std::set<uint16_t> rom_functions = {
|
||||
static int Basic10Score(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,
|
||||
@@ -40,15 +47,15 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
|
||||
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
|
||||
};
|
||||
std::set<uint16_t> variable_locations = {
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
||||
};
|
||||
|
||||
return Score(disassembly, rom_functions, variable_locations);
|
||||
}
|
||||
|
||||
static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
|
||||
std::set<uint16_t> rom_functions = {
|
||||
static int Basic11Score(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,
|
||||
@@ -65,30 +72,51 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
|
||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||
0xfc18
|
||||
};
|
||||
std::set<uint16_t> variable_locations = {
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
|
||||
};
|
||||
|
||||
return Score(disassembly, rom_functions, variable_locations);
|
||||
}
|
||||
|
||||
void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &destination) {
|
||||
Target target;
|
||||
target.machine = Target::Oric;
|
||||
target.probability = 1.0;
|
||||
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
|
||||
/*
|
||||
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
|
||||
*/
|
||||
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
|
||||
if(!sector) return false;
|
||||
if(sector->samples.empty()) return false;
|
||||
|
||||
const std::vector<uint8_t> &first_sample = sector->samples[0];
|
||||
if(first_sample.size() != 256) return false;
|
||||
|
||||
const uint8_t signature[] = {
|
||||
0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0,
|
||||
0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00,
|
||||
0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6,
|
||||
0x12, 0x00
|
||||
};
|
||||
|
||||
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::Oric;
|
||||
target->confidence = 0.5;
|
||||
|
||||
int basic10_votes = 0;
|
||||
int basic11_votes = 0;
|
||||
|
||||
for(auto &tape : media.tapes) {
|
||||
std::list<File> tape_files = GetFiles(tape);
|
||||
std::vector<File> tape_files = GetFiles(tape);
|
||||
tape->reset();
|
||||
if(tape_files.size()) {
|
||||
for(auto file : tape_files) {
|
||||
for(const auto &file : tape_files) {
|
||||
if(file.data_type == File::MachineCode) {
|
||||
std::vector<uint16_t> entry_points = {file.starting_address};
|
||||
StaticAnalyser::MOS6502::Disassembly disassembly =
|
||||
StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::MOS6502::OffsetMapper(file.starting_address), entry_points);
|
||||
Analyser::Static::MOS6502::Disassembly disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
||||
|
||||
int basic10_score = Basic10Score(disassembly);
|
||||
int basic11_score = Basic11Score(disassembly);
|
||||
@@ -96,23 +124,32 @@ void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &des
|
||||
}
|
||||
}
|
||||
|
||||
target.media.tapes.push_back(tape);
|
||||
target.loadingCommand = "CLOAD\"\"\n";
|
||||
target->media.tapes.push_back(tape);
|
||||
target->loading_command = "CLOAD\"\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
// trust that any disk supplied can be handled by the Microdisc. TODO: check.
|
||||
if(!media.disks.empty()) {
|
||||
target.oric.has_microdisc = true;
|
||||
target.media.disks = media.disks;
|
||||
} else {
|
||||
target.oric.has_microdisc = false;
|
||||
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
|
||||
for(auto &disk: media.disks) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
if(IsMicrodisc(parser)) {
|
||||
target->disk_interface = Target::DiskInterface::Microdisc;
|
||||
target->media.disks.push_back(disk);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
target->disk_interface = Target::DiskInterface::None;
|
||||
|
||||
// TODO: really this should add two targets if not all votes agree
|
||||
target.oric.use_atmos_rom = basic11_votes >= basic10_votes;
|
||||
if(target.oric.has_microdisc) target.oric.use_atmos_rom = true;
|
||||
if(basic11_votes >= basic10_votes || target->disk_interface == Target::DiskInterface::Microdisc)
|
||||
target->rom = Target::ROM::BASIC11;
|
||||
else
|
||||
target->rom = Target::ROM::BASIC10;
|
||||
|
||||
if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
|
||||
destination.push_back(target);
|
||||
TargetList targets;
|
||||
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
}
|
||||
26
Analyser/Static/Oric/StaticAnalyser.hpp
Normal file
26
Analyser/Static/Oric/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/10/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Oric_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Oric_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
@@ -3,16 +3,16 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/11/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Oric.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Oric.hpp"
|
||||
|
||||
using namespace StaticAnalyser::Oric;
|
||||
using namespace Analyser::Static::Oric;
|
||||
|
||||
std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::list<File> files;
|
||||
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> files;
|
||||
Storage::Tape::Oric::Parser parser;
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
@@ -49,10 +49,10 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
|
||||
}
|
||||
|
||||
// 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 = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
|
||||
new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
|
||||
|
||||
// skip an empty byte
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
@@ -69,10 +69,10 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
|
||||
new_file.name = file_name;
|
||||
|
||||
// read body
|
||||
size_t body_length = new_file.ending_address - new_file.starting_address + 1;
|
||||
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
|
||||
new_file.data.reserve(body_length);
|
||||
for(size_t c = 0; c < body_length; c++) {
|
||||
new_file.data.push_back((uint8_t)parser.get_next_byte(tape, is_fast));
|
||||
for(std::size_t c = 0; c < body_length; c++) {
|
||||
new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
|
||||
}
|
||||
|
||||
// only one validation check: was there enough tape?
|
||||
@@ -3,18 +3,19 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/11/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Oric_Tape_hpp
|
||||
#define StaticAnalyser_Oric_Tape_hpp
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include <list>
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
struct File {
|
||||
@@ -30,8 +31,9 @@ struct File {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
Analyser/Static/Oric/Target.hpp
Normal file
41
Analyser/Static/Oric/Target.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/03/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Oric_Target_h
|
||||
#define Analyser_Static_Oric_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class ROM {
|
||||
BASIC10,
|
||||
BASIC11,
|
||||
Pravetz
|
||||
};
|
||||
|
||||
enum class DiskInterface {
|
||||
Microdisc,
|
||||
Pravetz,
|
||||
None
|
||||
};
|
||||
|
||||
ROM rom = ROM::BASIC11;
|
||||
DiskInterface disk_interface = DiskInterface::None;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Oric_Target_h */
|
||||
193
Analyser/Static/StaticAnalyser.cpp
Normal file
193
Analyser/Static/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/08/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
|
||||
// Analysers
|
||||
#include "Acorn/StaticAnalyser.hpp"
|
||||
#include "AmstradCPC/StaticAnalyser.hpp"
|
||||
#include "AppleII/StaticAnalyser.hpp"
|
||||
#include "Atari/StaticAnalyser.hpp"
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "ZX8081/StaticAnalyser.hpp"
|
||||
|
||||
// Cartridges
|
||||
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
|
||||
#include "../../Storage/Cartridge/Formats/PRG.hpp"
|
||||
|
||||
// Disks
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// Tapes
|
||||
#include "../../Storage/Tape/Formats/CAS.hpp"
|
||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||
#include "../../Storage/Tape/Formats/CSW.hpp"
|
||||
#include "../../Storage/Tape/Formats/OricTAP.hpp"
|
||||
#include "../../Storage/Tape/Formats/TapePRG.hpp"
|
||||
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
|
||||
#include "../../Storage/Tape/Formats/TZX.hpp"
|
||||
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
|
||||
|
||||
// Target Platform Types
|
||||
#include "../../Storage/TargetPlatforms.hpp"
|
||||
|
||||
using namespace Analyser::Static;
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
Media result;
|
||||
|
||||
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
|
||||
// test as to file format.
|
||||
std::string::size_type final_dot = file_name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return result;
|
||||
std::string extension = file_name.substr(final_dot + 1);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
|
||||
#define Insert(list, class, platforms) \
|
||||
list.emplace_back(new Storage::class(file_name));\
|
||||
potential_platforms |= platforms;\
|
||||
TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
|
||||
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
|
||||
|
||||
#define TryInsert(list, class, platforms) \
|
||||
try {\
|
||||
Insert(list, class, platforms) \
|
||||
} catch(...) {}
|
||||
|
||||
#define Format(ext, list, class, platforms) \
|
||||
if(extension == ext) { \
|
||||
TryInsert(list, class, platforms) \
|
||||
}
|
||||
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
|
||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
|
||||
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||
|
||||
// PRG
|
||||
if(extension == "prg") {
|
||||
// try instantiating as a ROM; failing that accept as a tape
|
||||
try {
|
||||
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
|
||||
} catch(...) {
|
||||
try {
|
||||
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
Format( "rom",
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
|
||||
|
||||
#undef Format
|
||||
#undef Insert
|
||||
#undef TryInsert
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
TargetPlatform::IntType throwaway;
|
||||
return GetMediaAndPlatforms(file_name, throwaway);
|
||||
}
|
||||
|
||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetList targets;
|
||||
|
||||
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
|
||||
// union of all platforms this file might be a target for.
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
||||
// Hand off to platform-specific determination of whether these things are actually compatible and,
|
||||
// if so, how to load them.
|
||||
#define Append(x) {\
|
||||
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||
}
|
||||
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
|
||||
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
|
||||
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
|
||||
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari);
|
||||
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
|
||||
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
|
||||
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
|
||||
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
|
||||
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
|
||||
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
|
||||
#undef Append
|
||||
|
||||
// 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(),
|
||||
[] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) {
|
||||
return a->confidence > b->confidence;
|
||||
});
|
||||
|
||||
return targets;
|
||||
}
|
||||
66
Analyser/Static/StaticAnalyser.hpp
Normal file
66
Analyser/Static/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/08/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_hpp
|
||||
#define StaticAnalyser_hpp
|
||||
|
||||
#include "../Machines.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/Cartridge/Cartridge.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges.
|
||||
*/
|
||||
struct Media {
|
||||
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
|
||||
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
|
||||
|
||||
bool empty() const {
|
||||
return disks.empty() && tapes.empty() && cartridges.empty();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
||||
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||
*/
|
||||
struct Target {
|
||||
virtual ~Target() {}
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
float confidence;
|
||||
};
|
||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
|
||||
|
||||
/*!
|
||||
Attempts, through any available means, to return a list of potential targets for the file with the given name.
|
||||
|
||||
@returns The list of potential targets, sorted from most to least probable.
|
||||
*/
|
||||
TargetList GetTargets(const std::string &file_name);
|
||||
|
||||
/*!
|
||||
Inspects the supplied file and determines the media included.
|
||||
*/
|
||||
Media GetMedia(const std::string &file_name);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
@@ -11,7 +11,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
#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) {
|
||||
std::vector<Storage::Data::ZX8081::File> files;
|
||||
@@ -27,41 +28,43 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
|
||||
return files;
|
||||
}
|
||||
|
||||
void StaticAnalyser::ZX8081::AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
if(!media.tapes.empty()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||
media.tapes.front()->reset();
|
||||
if(!files.empty()) {
|
||||
StaticAnalyser::Target target;
|
||||
target.machine = Target::ZX8081;
|
||||
Target *target = new Target;
|
||||
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
|
||||
target->machine = Machine::ZX8081;
|
||||
|
||||
// Guess the machine type from the file only if it isn't already known.
|
||||
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
|
||||
default:
|
||||
target.zx8081.isZX81 = files.front().isZX81;
|
||||
target->is_ZX81 = files.front().isZX81;
|
||||
break;
|
||||
|
||||
case TargetPlatform::ZX80: target.zx8081.isZX81 = false; break;
|
||||
case TargetPlatform::ZX81: target.zx8081.isZX81 = true; break;
|
||||
case TargetPlatform::ZX80: target->is_ZX81 = false; break;
|
||||
case TargetPlatform::ZX81: target->is_ZX81 = true; break;
|
||||
}
|
||||
|
||||
/*if(files.front().data.size() > 16384) {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
|
||||
target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
|
||||
} else*/ if(files.front().data.size() > 1024) {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
|
||||
target->memory_model = Target::MemoryModel::SixteenKB;
|
||||
} else {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
|
||||
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||
}
|
||||
target.media.tapes = media.tapes;
|
||||
target->media.tapes = media.tapes;
|
||||
|
||||
// TODO: how to run software once loaded? Might require a BASIC detokeniser.
|
||||
if(target.zx8081.isZX81) {
|
||||
target.loadingCommand = "J\"\"\n";
|
||||
if(target->is_ZX81) {
|
||||
target->loading_command = "J\"\"\n";
|
||||
} else {
|
||||
target.loadingCommand = "W\n";
|
||||
target->loading_command = "W\n";
|
||||
}
|
||||
|
||||
destination.push_back(target);
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
26
Analyser/Static/ZX8081/StaticAnalyser.hpp
Normal file
26
Analyser/Static/ZX8081/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||
#define Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
36
Analyser/Static/ZX8081/Target.hpp
Normal file
36
Analyser/Static/ZX8081/Target.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/03/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_ZX8081_Target_h
|
||||
#define Analyser_Static_ZX8081_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class MemoryModel {
|
||||
Unexpanded,
|
||||
SixteenKB,
|
||||
SixtyFourKB
|
||||
};
|
||||
|
||||
MemoryModel memory_model = MemoryModel::Unexpanded;
|
||||
bool is_ZX81 = false;
|
||||
bool ZX80_uses_ZX81_ROM = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_ZX8081_Target_h */
|
||||
30
BUILD.txt
Normal file
30
BUILD.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
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.
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockReceiver_hpp
|
||||
@@ -52,79 +52,79 @@
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
inline WrappedInt(int l) : length_(l) {}
|
||||
inline WrappedInt() : length_(0) {}
|
||||
constexpr WrappedInt(int l) : length_(l) {}
|
||||
constexpr WrappedInt() : length_(0) {}
|
||||
|
||||
inline T &operator =(const T &rhs) {
|
||||
T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T &operator +=(const T &rhs) {
|
||||
T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator -=(const T &rhs) {
|
||||
T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++() {
|
||||
T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++(int) {
|
||||
T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --() {
|
||||
T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --(int) {
|
||||
T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator %=(const T &rhs) {
|
||||
T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator &=(const T &rhs) {
|
||||
T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
inline T operator -() const { return T(- length_); }
|
||||
constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
inline bool operator !() const { return !length_; }
|
||||
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
|
||||
|
||||
inline int as_int() const { return length_; }
|
||||
constexpr int as_int() const { return length_; }
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor — @c this will end up with
|
||||
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.
|
||||
*/
|
||||
inline T divide(const T &divisor) {
|
||||
T divide(const T &divisor) {
|
||||
T result(length_ / divisor.length_);
|
||||
length_ %= divisor.length_;
|
||||
return result;
|
||||
@@ -134,7 +134,7 @@ template <class T> class WrappedInt {
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
inline T flush() {
|
||||
T flush() {
|
||||
T result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@@ -147,40 +147,47 @@ template <class T> class WrappedInt {
|
||||
int length_;
|
||||
};
|
||||
|
||||
/// Describes an integer number of whole cycles — pairs of clock signal transitions.
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
inline Cycles() : WrappedInt<Cycles>() {}
|
||||
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
constexpr Cycles() : WrappedInt<Cycles>() {}
|
||||
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles — single clock signal transitions.
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
|
||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() << 1) {}
|
||||
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
inline Cycles cycles() {
|
||||
constexpr Cycles cycles() {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
///Flushes the whole cycles in @c this, subtracting that many from the total stored here.
|
||||
inline Cycles flush_cycles() {
|
||||
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
|
||||
Cycles flush_cycles() {
|
||||
Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
|
||||
HalfCycles flush() {
|
||||
HalfCycles result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor — @c this will end up with
|
||||
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.
|
||||
*/
|
||||
inline Cycles divide_cycles(const Cycles &divisor) {
|
||||
Cycles divide_cycles(const Cycles &divisor) {
|
||||
HalfCycles half_divisor = HalfCycles(divisor);
|
||||
Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
@@ -196,7 +203,6 @@ template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
using T::run_for;
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush_cycles());
|
||||
|
||||
88
ClockReceiver/ClockingHintSource.hpp
Normal file
88
ClockReceiver/ClockingHintSource.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// ClockingHintSource.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/08/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockingHintSource_hpp
|
||||
#define ClockingHintSource_hpp
|
||||
|
||||
namespace ClockingHint {
|
||||
|
||||
enum class Preference {
|
||||
/// The component doesn't currently require a clock signal.
|
||||
None,
|
||||
/// The component can be clocked only immediate prior to (explicit) accesses.
|
||||
JustInTime,
|
||||
/// The component require real-time clocking.
|
||||
RealTime
|
||||
};
|
||||
|
||||
class Source;
|
||||
|
||||
struct Observer {
|
||||
/// Called to inform an observer that the component @c component has changed its clocking requirements.
|
||||
virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
An clocking hint source is any component that can provide hints as to the type of
|
||||
clocking required for accurate emulation. A disk controller is an archetypal example.
|
||||
|
||||
Types of clocking are:
|
||||
|
||||
- none:
|
||||
a component that acts and reacts to direct contact but does not have a state that autonomously evolves.
|
||||
E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command.
|
||||
|
||||
- just-in-time:
|
||||
a component that has an evolving state but can receive clock updates only immediately before a
|
||||
direct contact. This is possibly the most common kind of component.
|
||||
|
||||
- real-time:
|
||||
a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example
|
||||
so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of
|
||||
the machine rather than waiting to be called upon, or because there's some other non-obvious relationship
|
||||
at play.
|
||||
|
||||
A clocking hint source can signal changes in preferred clocking to an observer.
|
||||
|
||||
This is intended to allow for performance improvements to machines with components that can be messaged selectively.
|
||||
The observer callout is virtual so the intended use case is that a machine holds a component that might go through
|
||||
periods of different clocking requirements.
|
||||
|
||||
Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that
|
||||
the saved or deferred ::run_fors add up to a substantial amount.
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() = 0;
|
||||
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ClockingHintSource_h */
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ForceInline_hpp
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
//
|
||||
// Sleeper.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Sleeper_hpp
|
||||
#define Sleeper_hpp
|
||||
|
||||
/*!
|
||||
A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing
|
||||
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
|
||||
|
||||
A sleeper will signal sleeps and wakes to an observer.
|
||||
|
||||
This is intended to allow for performance improvements to machines with components that can sleep. The observer
|
||||
callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
|
||||
into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
|
||||
the saved ::run_fors add up to a substantial amount.
|
||||
|
||||
By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
|
||||
not a command.
|
||||
*/
|
||||
class Sleeper {
|
||||
public:
|
||||
Sleeper() : sleep_observer_(nullptr) {}
|
||||
|
||||
class SleepObserver {
|
||||
public:
|
||||
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
|
||||
virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0;
|
||||
};
|
||||
|
||||
/// Registers @c observer as the new sleep observer;
|
||||
void set_sleep_observer(SleepObserver *observer) {
|
||||
sleep_observer_ = observer;
|
||||
}
|
||||
|
||||
/// @returns @c true if the component is currently sleeping; @c false otherwise.
|
||||
virtual bool is_sleeping() = 0;
|
||||
|
||||
protected:
|
||||
/// Provided for subclasses; send sleep announcements to the sleep_observer_.
|
||||
SleepObserver *sleep_observer_;
|
||||
|
||||
/*!
|
||||
Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
|
||||
if one exists.
|
||||
|
||||
@c is_sleeping will be called only if there is an observer.
|
||||
*/
|
||||
void update_sleep_observer() {
|
||||
if(!sleep_observer_) return;
|
||||
sleep_observer_->set_component_is_sleeping(this, is_sleeping());
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* Sleeper_h */
|
||||
18
ClockReceiver/TimeTypes.hpp
Normal file
18
ClockReceiver/TimeTypes.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// TimeTypes.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/03/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TimeTypes_h
|
||||
#define TimeTypes_h
|
||||
|
||||
namespace Time {
|
||||
|
||||
typedef double Seconds;
|
||||
|
||||
}
|
||||
|
||||
#endif /* TimeTypes_h */
|
||||
@@ -3,51 +3,39 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "1770.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
|
||||
using namespace WD;
|
||||
|
||||
WD1770::Status::Status() :
|
||||
type(Status::One),
|
||||
write_protect(false),
|
||||
record_type(false),
|
||||
spin_up(false),
|
||||
record_not_found(false),
|
||||
crc_error(false),
|
||||
seek_error(false),
|
||||
lost_data(false),
|
||||
data_request(false),
|
||||
interrupt_request(false),
|
||||
busy(false) {}
|
||||
|
||||
WD1770::WD1770(Personality p) :
|
||||
Storage::Disk::MFMController(8000000, 16, 300),
|
||||
interesting_event_mask_((int)Event1770::Command),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
index_hole_count_target_(-1),
|
||||
delegate_(nullptr),
|
||||
Storage::Disk::MFMController(8000000),
|
||||
personality_(p),
|
||||
head_is_loaded_(false) {
|
||||
interesting_event_mask_(static_cast<int>(Event1770::Command)) {
|
||||
set_is_double_density(false);
|
||||
posit_event((int)Event1770::Command);
|
||||
posit_event(static_cast<int>(Event1770::Command));
|
||||
}
|
||||
|
||||
void WD1770::set_register(int address, uint8_t value) {
|
||||
switch(address&3) {
|
||||
case 0: {
|
||||
if((value&0xf0) == 0xd0) {
|
||||
if(value == 0xd0) {
|
||||
// Force interrupt **immediately**.
|
||||
printf("Force interrupt immediately\n");
|
||||
posit_event(static_cast<int>(Event1770::ForceInterrupt));
|
||||
} else {
|
||||
printf("!!!TODO: force interrupt!!!\n");
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
command_ = value;
|
||||
posit_event((int)Event1770::Command);
|
||||
posit_event(static_cast<int>(Event1770::Command));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -75,7 +63,7 @@ uint8_t WD1770::get_register(int address) {
|
||||
switch(status_.type) {
|
||||
case Status::One:
|
||||
status |=
|
||||
(get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||
(status_.seek_error ? Flag::SeekError : 0);
|
||||
// TODO: index hole
|
||||
break;
|
||||
@@ -91,11 +79,11 @@ uint8_t WD1770::get_register(int address) {
|
||||
}
|
||||
|
||||
if(!has_motor_on_line()) {
|
||||
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
|
||||
status |= get_drive().get_is_ready() ? 0 : Flag::NotReady;
|
||||
if(status_.type == Status::One)
|
||||
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
||||
} else {
|
||||
status |= (get_motor_on() ? Flag::MotorOn : 0);
|
||||
status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0);
|
||||
if(status_.type == Status::One)
|
||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
||||
}
|
||||
@@ -115,24 +103,24 @@ void WD1770::run_for(const Cycles cycles) {
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
|
||||
if(delay_time_) {
|
||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
||||
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
|
||||
if(delay_time_ <= number_of_cycles) {
|
||||
delay_time_ = 0;
|
||||
posit_event((int)Event1770::Timer);
|
||||
posit_event(static_cast<int>(Event1770::Timer));
|
||||
} else {
|
||||
delay_time_ -= number_of_cycles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
|
||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = (int)Event::Token; return; }
|
||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() 0; }
|
||||
#define END_SECTION() (void)0; }
|
||||
|
||||
#define READ_ID() \
|
||||
if(new_event_type == (int)Event::Token) { \
|
||||
if(new_event_type == static_cast<int>(Event::Token)) { \
|
||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } \
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
|
||||
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
|
||||
@@ -169,10 +157,10 @@ void WD1770::run_for(const Cycles cycles) {
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
void WD1770::posit_event(int new_event_type) {
|
||||
if(new_event_type == (int)Event::IndexHole) {
|
||||
if(new_event_type == static_cast<int>(Event::IndexHole)) {
|
||||
index_hole_count_++;
|
||||
if(index_hole_count_target_ == index_hole_count_) {
|
||||
posit_event((int)Event1770::IndexHoleTarget);
|
||||
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
|
||||
index_hole_count_target_ = -1;
|
||||
}
|
||||
|
||||
@@ -187,13 +175,23 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
}
|
||||
|
||||
if(!(interesting_event_mask_ & (int)new_event_type)) return;
|
||||
if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
|
||||
interesting_event_mask_ = 0;
|
||||
resume_point_ = 0;
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
status.data_request = false;
|
||||
});
|
||||
} else {
|
||||
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
|
||||
interesting_event_mask_ &= ~new_event_type;
|
||||
}
|
||||
|
||||
Status new_status;
|
||||
BEGIN_SECTION()
|
||||
|
||||
// Wait for a new command, branch to the appropriate handler.
|
||||
case 0:
|
||||
wait_for_command:
|
||||
printf("Idle...\n");
|
||||
set_data_mode(DataMode::Scanning);
|
||||
@@ -257,7 +255,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto test_type1_type;
|
||||
|
||||
begin_type1_spin_up:
|
||||
if((command_&0x08) || get_motor_on()) goto test_type1_type;
|
||||
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
|
||||
SPIN_UP();
|
||||
|
||||
test_type1_type:
|
||||
@@ -280,11 +278,11 @@ void WD1770::posit_event(int new_event_type) {
|
||||
if(step_direction_) track_++; else track_--;
|
||||
|
||||
perform_step:
|
||||
if(!step_direction_ && get_is_track_zero()) {
|
||||
if(!step_direction_ && get_drive().get_is_track_zero()) {
|
||||
track_ = 0;
|
||||
goto verify;
|
||||
}
|
||||
step(step_direction_ ? 1 : -1);
|
||||
get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
|
||||
unsigned int time_to_wait;
|
||||
switch(command_ & 3) {
|
||||
default:
|
||||
@@ -310,7 +308,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
verify_read_data:
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6) {
|
||||
@@ -376,7 +374,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto test_type2_delay;
|
||||
|
||||
begin_type2_spin_up:
|
||||
if(get_motor_on()) goto test_type2_delay;
|
||||
if(get_drive().get_motor_on()) goto test_type2_delay;
|
||||
// Perform spin up.
|
||||
SPIN_UP();
|
||||
|
||||
@@ -386,7 +384,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
WAIT_FOR_TIME(30);
|
||||
|
||||
test_type2_write_protection:
|
||||
if(command_&0x20 && get_drive_is_read_only()) {
|
||||
if(command_&0x20 && get_drive().get_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
@@ -394,7 +392,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
|
||||
type2_get_header:
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5) {
|
||||
@@ -478,7 +476,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
printf("Read sector %d\n", sector_);
|
||||
printf("Finished reading sector %d\n", sector_);
|
||||
goto wait_for_command;
|
||||
}
|
||||
goto type2_check_crc;
|
||||
@@ -524,7 +522,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
type2_write_loop:
|
||||
/*
|
||||
This deviates from the data sheet slightly since that would prima facie request one more byte
|
||||
of data than is actually written — the last time around the loop it has transferred from the
|
||||
of data than is actually written; the last time around the loop it has transferred from the
|
||||
data register to the data shift register, set data request, written the byte, checked that data
|
||||
request has been satified, then finally considers whether all bytes are done. Based on both
|
||||
natural expectations and the way that emulated machines responded, I believe that to be a
|
||||
@@ -594,7 +592,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto type3_test_delay;
|
||||
|
||||
begin_type3_spin_up:
|
||||
if((command_&0x08) || get_motor_on()) goto type3_test_delay;
|
||||
if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay;
|
||||
SPIN_UP();
|
||||
|
||||
type3_test_delay:
|
||||
@@ -611,8 +609,8 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
read_address_get_header:
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
if(new_event_type == (int)Event::Token) {
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||
if(new_event_type == static_cast<int>(Event::Token)) {
|
||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
|
||||
if(status_.data_request) {
|
||||
@@ -652,7 +650,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
index_hole_count_ = 0;
|
||||
|
||||
read_track_read_byte:
|
||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
if(index_hole_count_) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
@@ -674,8 +672,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
status.lost_data = false;
|
||||
});
|
||||
|
||||
write_track_test_write_protect:
|
||||
if(get_drive_is_read_only()) {
|
||||
if(get_drive().get_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
@@ -720,7 +717,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
case 0xfd: case 0xfe:
|
||||
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
||||
write_raw_short(
|
||||
(uint16_t)(
|
||||
static_cast<uint16_t>(
|
||||
0xa022 |
|
||||
((data_ & 0x80) << 7) |
|
||||
((data_ & 0x40) << 6) |
|
||||
@@ -781,8 +778,9 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
|
||||
}
|
||||
|
||||
void WD1770::set_head_load_request(bool head_load) {}
|
||||
void WD1770::set_motor_on(bool motor_on) {}
|
||||
|
||||
void WD1770::set_head_loaded(bool head_loaded) {
|
||||
head_is_loaded_ = head_loaded;
|
||||
if(head_loaded) posit_event((int)Event1770::HeadLoad);
|
||||
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _770_hpp
|
||||
#define _770_hpp
|
||||
|
||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
||||
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
|
||||
|
||||
namespace WD {
|
||||
|
||||
@@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
|
||||
/// Runs the controller for @c number_of_cycles cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Disk::Controller::run_for;
|
||||
|
||||
enum Flag: uint8_t {
|
||||
NotReady = 0x80,
|
||||
@@ -76,6 +75,7 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
|
||||
protected:
|
||||
virtual void set_head_load_request(bool head_load);
|
||||
virtual void set_motor_on(bool motor_on);
|
||||
void set_head_loaded(bool head_loaded);
|
||||
|
||||
private:
|
||||
@@ -84,20 +84,19 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
inline bool has_head_load_line() { return (personality_ == P1793 ); }
|
||||
|
||||
struct Status {
|
||||
Status();
|
||||
bool write_protect;
|
||||
bool record_type;
|
||||
bool spin_up;
|
||||
bool record_not_found;
|
||||
bool crc_error;
|
||||
bool seek_error;
|
||||
bool lost_data;
|
||||
bool data_request;
|
||||
bool interrupt_request;
|
||||
bool busy;
|
||||
bool write_protect = false;
|
||||
bool record_type = false;
|
||||
bool spin_up = false;
|
||||
bool record_not_found = false;
|
||||
bool crc_error = false;
|
||||
bool seek_error = false;
|
||||
bool lost_data = false;
|
||||
bool data_request = false;
|
||||
bool interrupt_request = false;
|
||||
bool busy = false;
|
||||
enum {
|
||||
One, Two, Three
|
||||
} type;
|
||||
} type = One;
|
||||
} status_;
|
||||
uint8_t track_;
|
||||
uint8_t sector_;
|
||||
@@ -105,7 +104,7 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
uint8_t command_;
|
||||
|
||||
int index_hole_count_;
|
||||
int index_hole_count_target_;
|
||||
int index_hole_count_target_ = -1;
|
||||
int distance_into_section_;
|
||||
|
||||
int step_direction_;
|
||||
@@ -116,21 +115,22 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
Command = (1 << 3), // Indicates receipt of a new command.
|
||||
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
|
||||
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
|
||||
IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_.
|
||||
IndexHoleTarget = (1 << 6), // Indicates that index_hole_count_ has reached index_hole_count_target_.
|
||||
ForceInterrupt = (1 << 7) // Indicates a forced interrupt.
|
||||
};
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_;
|
||||
unsigned int delay_time_;
|
||||
int resume_point_ = 0;
|
||||
unsigned int delay_time_ = 0;
|
||||
|
||||
// ID buffer
|
||||
uint8_t header_[6];
|
||||
|
||||
// 1793 head-loading logic
|
||||
bool head_is_loaded_;
|
||||
bool head_is_loaded_ = false;
|
||||
|
||||
// delegate
|
||||
Delegate *delegate_;
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _522_hpp
|
||||
@@ -13,9 +13,83 @@
|
||||
#include <typeinfo>
|
||||
#include <cstdio>
|
||||
|
||||
#include "Implementation/6522Storage.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
enum Line {
|
||||
One = 0,
|
||||
Two = 1
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a
|
||||
6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring.
|
||||
*/
|
||||
class PortHandler {
|
||||
public:
|
||||
/// Requests the current input value of @c port from the port handler.
|
||||
uint8_t get_port_input(Port port) { return 0xff; }
|
||||
|
||||
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
|
||||
|
||||
/// Sets the current logical output level for line @c line on port @c port.
|
||||
void set_control_line_output(Port port, Line line, bool value) {}
|
||||
|
||||
/// Sets the current logical value of the interrupt line.
|
||||
void set_interrupt_status(bool status) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds
|
||||
a virtual level of indirection for receiving changes to the interrupt line.
|
||||
*/
|
||||
class IRQDelegatePortHandler: public PortHandler {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
|
||||
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
|
||||
};
|
||||
|
||||
/// Sets the delegate that will receive notification of changes in the interrupt line.
|
||||
void set_interrupt_delegate(Delegate *delegate);
|
||||
|
||||
/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
|
||||
void set_interrupt_status(bool new_status);
|
||||
|
||||
private:
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MOS6522Base: public MOS6522Storage {
|
||||
public:
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
|
||||
/// Runs for a specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Runs for a specified number of cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
inline void do_phase1();
|
||||
inline void do_phase2();
|
||||
virtual void reevaluate_interrupts() = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
|
||||
@@ -28,353 +102,30 @@ namespace MOS {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6522 {
|
||||
private:
|
||||
enum InterruptFlag: uint8_t {
|
||||
CA2ActiveEdge = 1 << 0,
|
||||
CA1ActiveEdge = 1 << 1,
|
||||
ShiftRegister = 1 << 2,
|
||||
CB2ActiveEdge = 1 << 3,
|
||||
CB1ActiveEdge = 1 << 4,
|
||||
Timer2 = 1 << 5,
|
||||
Timer1 = 1 << 6,
|
||||
};
|
||||
|
||||
template <class T> class MOS6522: public MOS6522Base {
|
||||
public:
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
enum Line {
|
||||
One = 0,
|
||||
Two = 1
|
||||
};
|
||||
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(const MOS6522 &) = delete;
|
||||
|
||||
/*! Sets a register value. */
|
||||
inline void set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.output[1] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
registers_.output[0] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
// // No handshake, so write directly
|
||||
// registers_.output[0] = value;
|
||||
// static_cast<T *>(this)->set_port_output(0, value);
|
||||
// break;
|
||||
|
||||
case 0x2:
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3:
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
// Timer 1
|
||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x5: case 0x7:
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
}
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Timer 2
|
||||
case 0x8: registers_.timer_latch[1] = value; break;
|
||||
case 0x9:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
|
||||
timer_is_running_[1] = true;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa: registers_.shift = value; break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
registers_.auxiliary_control = value;
|
||||
break;
|
||||
case 0xc:
|
||||
// printf("Peripheral control %02x\n", value);
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08) {
|
||||
switch(value & 0x0e) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
||||
case 0x0c: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
if(value & 0x80) {
|
||||
switch(value & 0xe0) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
||||
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
registers_.interrupt_flags &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xe:
|
||||
if(value&0x80)
|
||||
registers_.interrupt_enable |= value;
|
||||
else
|
||||
registers_.interrupt_enable &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
void set_register(int address, uint8_t value);
|
||||
|
||||
/*! Gets a register value. */
|
||||
inline uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
// printf("6522 %p: %d\n", this, address);
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0x1:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
||||
uint8_t get_register(int address);
|
||||
|
||||
case 0x2: return registers_.data_direction[1];
|
||||
case 0x3: return registers_.data_direction[0];
|
||||
|
||||
// Timer 1
|
||||
case 0x4:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[0] & 0x00ff;
|
||||
case 0x5: return registers_.timer[0] >> 8;
|
||||
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
||||
case 0x7: return registers_.timer_latch[0] >> 8;
|
||||
|
||||
// Timer 2
|
||||
case 0x8:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
|
||||
case 0xa: return registers_.shift;
|
||||
|
||||
case 0xb: return registers_.auxiliary_control;
|
||||
case 0xc: return registers_.peripheral_control;
|
||||
|
||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||
case 0xe: return registers_.interrupt_enable | 0x80;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO: output modes, but probably elsewhere?
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_two = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define phase2() \
|
||||
registers_.last_timer[0] = registers_.timer[0];\
|
||||
registers_.last_timer[1] = registers_.timer[1];\
|
||||
\
|
||||
if(registers_.timer_needs_reload) {\
|
||||
registers_.timer_needs_reload = false;\
|
||||
registers_.timer[0] = registers_.timer_latch[0];\
|
||||
}\
|
||||
else\
|
||||
registers_.timer[0] --;\
|
||||
\
|
||||
registers_.timer[1] --; \
|
||||
if(registers_.next_timer[0] >= 0) { registers_.timer[0] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
|
||||
if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
|
||||
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
#define phase1() \
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {\
|
||||
timer_is_running_[1] = false;\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;\
|
||||
reevaluate_interrupts();\
|
||||
}\
|
||||
\
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;\
|
||||
reevaluate_interrupts();\
|
||||
\
|
||||
if(registers_.auxiliary_control&0x40)\
|
||||
registers_.timer_needs_reload = true;\
|
||||
else\
|
||||
timer_is_running_[0] = false;\
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
|
||||
if(is_phase2_) {
|
||||
phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
phase1();
|
||||
phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
inline void run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
phase1();
|
||||
phase2();
|
||||
}
|
||||
}
|
||||
|
||||
#undef phase1
|
||||
#undef phase2
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
inline bool get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
|
||||
MOS6522() :
|
||||
timer_is_running_{false, false},
|
||||
last_posted_interrupt_status_(false),
|
||||
is_phase2_(false) {}
|
||||
/*! @returns the bus handler. */
|
||||
T &bus_handler();
|
||||
|
||||
private:
|
||||
// Expected to be overridden
|
||||
uint8_t get_port_input(Port port) { return 0xff; }
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
|
||||
void set_control_line_output(Port port, Line line, bool value) {}
|
||||
void set_interrupt_status(bool status) {}
|
||||
T &bus_handler_;
|
||||
|
||||
// Input/output multiplexer
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
|
||||
// Phase toggle
|
||||
bool is_phase2_;
|
||||
|
||||
// Delegate and communications
|
||||
bool last_posted_interrupt_status_;
|
||||
inline void reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
|
||||
// The registers
|
||||
struct Registers {
|
||||
uint8_t output[2], input[2], data_direction[2];
|
||||
uint16_t timer[2], timer_latch[2], last_timer[2];
|
||||
int next_timer[2];
|
||||
uint8_t shift;
|
||||
uint8_t auxiliary_control, peripheral_control;
|
||||
uint8_t interrupt_flags, interrupt_enable;
|
||||
bool timer_needs_reload;
|
||||
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
Registers() :
|
||||
output{0, 0}, input{0, 0}, data_direction{0, 0},
|
||||
auxiliary_control(0), peripheral_control(0),
|
||||
interrupt_flags(0), interrupt_enable(0),
|
||||
last_timer{0, 0}, timer_needs_reload(false),
|
||||
next_timer{-1, -1} {}
|
||||
} registers_;
|
||||
|
||||
// control state
|
||||
struct {
|
||||
bool line_one, line_two;
|
||||
} control_inputs_[2];
|
||||
|
||||
// Internal state other than the registers
|
||||
bool timer_is_running_[2];
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
|
||||
inline void reevaluate_interrupts();
|
||||
};
|
||||
|
||||
/*!
|
||||
Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate
|
||||
that will receive IRQ line change notifications.
|
||||
*/
|
||||
class MOS6522IRQDelegate {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
|
||||
};
|
||||
|
||||
inline void set_interrupt_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
inline void set_interrupt_status(bool new_status) {
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
|
||||
private:
|
||||
Delegate *delegate_;
|
||||
};
|
||||
#include "Implementation/6522Implementation.hpp"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _522_hpp */
|
||||
|
||||
116
Components/6522/Implementation/6522Base.cpp
Normal file
116
Components/6522/Implementation/6522Base.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// 6522Base.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "../6522.hpp"
|
||||
|
||||
using namespace MOS::MOS6522;
|
||||
|
||||
void MOS6522Base::set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO: output modes, but probably elsewhere?
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_two = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase2() {
|
||||
registers_.last_timer[0] = registers_.timer[0];
|
||||
registers_.last_timer[1] = registers_.timer[1];
|
||||
|
||||
if(registers_.timer_needs_reload) {
|
||||
registers_.timer_needs_reload = false;
|
||||
registers_.timer[0] = registers_.timer_latch[0];
|
||||
} else {
|
||||
registers_.timer[0] --;
|
||||
}
|
||||
|
||||
registers_.timer[1] --;
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
}
|
||||
if(registers_.next_timer[1] >= 0) {
|
||||
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||
registers_.next_timer[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase1() {
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
|
||||
timer_is_running_[1] = false;
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
|
||||
if(registers_.auxiliary_control&0x40)
|
||||
registers_.timer_needs_reload = true;
|
||||
else
|
||||
timer_is_running_[0] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
void MOS6522Base::run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
|
||||
if(is_phase2_) {
|
||||
do_phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
do_phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
void MOS6522Base::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
bool MOS6522Base::get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
159
Components/6522/Implementation/6522Implementation.hpp
Normal file
159
Components/6522/Implementation/6522Implementation.hpp
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// Implementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.output[1] = value;
|
||||
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
registers_.output[0] = value;
|
||||
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3:
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
// Timer 1
|
||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x5: case 0x7:
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
}
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Timer 2
|
||||
case 0x8: registers_.timer_latch[1] = value; break;
|
||||
case 0x9:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
|
||||
timer_is_running_[1] = true;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa: registers_.shift = value; break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
registers_.auxiliary_control = value;
|
||||
break;
|
||||
case 0xc:
|
||||
// printf("Peripheral control %02x\n", value);
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08) {
|
||||
switch(value & 0x0e) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
||||
case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
if(value & 0x80) {
|
||||
switch(value & 0xe0) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
||||
case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
registers_.interrupt_flags &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xe:
|
||||
if(value&0x80)
|
||||
registers_.interrupt_enable |= value;
|
||||
else
|
||||
registers_.interrupt_enable &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0x1:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
||||
|
||||
case 0x2: return registers_.data_direction[1];
|
||||
case 0x3: return registers_.data_direction[0];
|
||||
|
||||
// Timer 1
|
||||
case 0x4:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[0] & 0x00ff;
|
||||
case 0x5: return registers_.timer[0] >> 8;
|
||||
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
||||
case 0x7: return registers_.timer_latch[0] >> 8;
|
||||
|
||||
// Timer 2
|
||||
case 0x8:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
|
||||
case 0xa: return registers_.shift;
|
||||
|
||||
case 0xb: return registers_.auxiliary_control;
|
||||
case 0xc: return registers_.peripheral_control;
|
||||
|
||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||
case 0xe: return registers_.interrupt_enable | 0x80;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
uint8_t input = bus_handler_.get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
|
||||
template <typename T> T &MOS6522<T>::bus_handler() {
|
||||
return bus_handler_;
|
||||
}
|
||||
|
||||
// Delegate and communications
|
||||
template <typename T> void MOS6522<T>::reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
bus_handler_.set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
63
Components/6522/Implementation/6522Storage.hpp
Normal file
63
Components/6522/Implementation/6522Storage.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// 6522Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _522Storage_hpp
|
||||
#define _522Storage_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
class MOS6522Storage {
|
||||
protected:
|
||||
// Phase toggle
|
||||
bool is_phase2_ = false;
|
||||
|
||||
// The registers
|
||||
struct Registers {
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
uint8_t output[2] = {0, 0};
|
||||
uint8_t input[2] = {0, 0};
|
||||
uint8_t data_direction[2] = {0, 0};
|
||||
uint16_t timer[2] = {0, 0};
|
||||
uint16_t timer_latch[2] = {0, 0};
|
||||
uint16_t last_timer[2] = {0, 0};
|
||||
int next_timer[2] = {-1, -1};
|
||||
uint8_t shift = 0;
|
||||
uint8_t auxiliary_control = 0;
|
||||
uint8_t peripheral_control = 0;
|
||||
uint8_t interrupt_flags = 0;
|
||||
uint8_t interrupt_enable = 0;
|
||||
bool timer_needs_reload = false;
|
||||
} registers_;
|
||||
|
||||
// control state
|
||||
struct {
|
||||
bool line_one = false;
|
||||
bool line_two = false;
|
||||
} control_inputs_[2];
|
||||
|
||||
bool timer_is_running_[2] = {false, false};
|
||||
bool last_posted_interrupt_status_ = false;
|
||||
|
||||
enum InterruptFlag: uint8_t {
|
||||
CA2ActiveEdge = 1 << 0,
|
||||
CA1ActiveEdge = 1 << 1,
|
||||
ShiftRegister = 1 << 2,
|
||||
CB2ActiveEdge = 1 << 3,
|
||||
CB1ActiveEdge = 1 << 4,
|
||||
Timer2 = 1 << 5,
|
||||
Timer1 = 1 << 6,
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _522Storage_hpp */
|
||||
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// IRQDelegatePortHandler.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "../6522.hpp"
|
||||
|
||||
using namespace MOS::MOS6522;
|
||||
|
||||
void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _532_hpp
|
||||
@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
if(address & 0x10) {
|
||||
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||
timer_.value = ((unsigned int)value << timer_.activeShift) ;
|
||||
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
|
||||
|
||||
// Timer and interrupt control
|
||||
case 0x04: case 0x06: {
|
||||
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
||||
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
|
||||
inline void run_for(const Cycles cycles) {
|
||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
||||
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
|
||||
|
||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
@@ -121,12 +121,9 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
}
|
||||
|
||||
MOS6532() :
|
||||
interrupt_status_(0),
|
||||
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
||||
a7_interrupt_({.last_port_value = 0, .enabled = false}),
|
||||
interrupt_line_(false),
|
||||
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
|
||||
MOS6532() {
|
||||
timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
|
||||
}
|
||||
|
||||
inline void set_port_did_change(int port) {
|
||||
if(!port) {
|
||||
@@ -154,26 +151,26 @@ template <class T> class MOS6532 {
|
||||
|
||||
struct {
|
||||
unsigned int value;
|
||||
unsigned int activeShift, writtenShift;
|
||||
bool interrupt_enabled;
|
||||
unsigned int activeShift = 10, writtenShift = 10;
|
||||
bool interrupt_enabled = false;
|
||||
} timer_;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
bool active_on_positive;
|
||||
uint8_t last_port_value;
|
||||
bool enabled = false;
|
||||
bool active_on_positive = false;
|
||||
uint8_t last_port_value = 0;
|
||||
} a7_interrupt_;
|
||||
|
||||
struct {
|
||||
uint8_t output_mask, output;
|
||||
uint8_t output_mask = 0, output = 0;
|
||||
} port_[2];
|
||||
|
||||
uint8_t interrupt_status_;
|
||||
uint8_t interrupt_status_ = 0;
|
||||
enum InterruptFlag: uint8_t {
|
||||
Timer = 0x80,
|
||||
PA7 = 0x40
|
||||
};
|
||||
bool interrupt_line_;
|
||||
bool interrupt_line_ = false;
|
||||
|
||||
// expected to be overridden
|
||||
uint8_t get_port_input(int port) { return 0xff; }
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "6560.hpp"
|
||||
|
||||
using namespace MOS;
|
||||
#include <cstring>
|
||||
|
||||
Speaker::Speaker() :
|
||||
volume_(0),
|
||||
control_registers_{0, 0, 0, 0},
|
||||
shift_registers_{0, 0, 0, 0},
|
||||
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
|
||||
using namespace MOS::MOS6560;
|
||||
|
||||
void Speaker::set_volume(uint8_t volume) {
|
||||
enqueue([=]() {
|
||||
volume_ = volume;
|
||||
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
|
||||
void AudioGenerator::set_volume(uint8_t volume) {
|
||||
audio_queue_.defer([=]() {
|
||||
volume_ = static_cast<int16_t>(volume) * range_multiplier_;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_control(int channel, uint8_t value) {
|
||||
enqueue([=]() {
|
||||
void AudioGenerator::set_control(int channel, uint8_t value) {
|
||||
audio_queue_.defer([=]() {
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
}
|
||||
@@ -98,15 +98,15 @@ static uint8_t noise_pattern[] = {
|
||||
|
||||
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
|
||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
|
||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
|
||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
|
||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
|
||||
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
|
||||
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
|
||||
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
|
||||
// means every second cycle, etc.
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; ++c) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
@@ -114,17 +114,17 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
|
||||
// this sums the output of all three sounds channels plus a DC offset for volume;
|
||||
// TODO: what's the real ratio of this stuff?
|
||||
target[c] = (
|
||||
target[c] = static_cast<int16_t>(
|
||||
(shift_registers_[0]&1) +
|
||||
(shift_registers_[1]&1) +
|
||||
(shift_registers_[2]&1) +
|
||||
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
|
||||
) * volume_ * 700 + volume_ * 44;
|
||||
) * volume_ + (volume_ >> 4);
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
||||
for(unsigned int c = 0; c < number_of_samples; ++c) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
@@ -132,6 +132,10 @@ void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
|
||||
range_multiplier_ = static_cast<int16_t>(range / 64);
|
||||
}
|
||||
|
||||
#undef shift
|
||||
#undef increment
|
||||
#undef update
|
||||
|
||||
@@ -3,34 +3,53 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _560_hpp
|
||||
#define _560_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6560 {
|
||||
|
||||
// audio state
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Speaker();
|
||||
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
|
||||
void set_volume(uint8_t volume);
|
||||
void set_control(int channel, uint8_t value);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
void skip_samples(unsigned int number_of_samples);
|
||||
// For ::SampleSource.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void skip_samples(std::size_t number_of_samples);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
unsigned int counters_[4];
|
||||
unsigned int shift_registers_[4];
|
||||
uint8_t control_registers_[4];
|
||||
uint8_t volume_;
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
|
||||
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||
uint8_t control_registers_[4] = {0, 0, 0, 0};
|
||||
int16_t volume_ = 0;
|
||||
int16_t range_multiplier_ = 1;
|
||||
};
|
||||
|
||||
struct BusHandler {
|
||||
void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
|
||||
*pixel_data = 0xff;
|
||||
*colour_data = 0xff;
|
||||
}
|
||||
};
|
||||
|
||||
enum class OutputMode {
|
||||
PAL, NTSC
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -41,40 +60,46 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
|
||||
@c set_register and @c get_register provide register access.
|
||||
*/
|
||||
template <class T> class MOS6560 {
|
||||
template <class BusHandler> class MOS6560 {
|
||||
public:
|
||||
MOS6560() :
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)),
|
||||
speaker_(new Speaker),
|
||||
horizontal_counter_(0),
|
||||
vertical_counter_(0),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_odd_frame_(false),
|
||||
is_odd_line_(false) {
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
MOS6560(BusHandler &bus_handler) :
|
||||
bus_handler_(bus_handler),
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
|
||||
audio_generator_(audio_queue_),
|
||||
speaker_(audio_generator_)
|
||||
{
|
||||
crt_->set_svideo_sampling_function(
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
|
||||
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
||||
|
||||
"float chroma = cos(phase + phaseOffset);"
|
||||
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
|
||||
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
||||
"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
|
||||
|
||||
"return vec2(yc.x, chroma);"
|
||||
"}");
|
||||
|
||||
// default to s-video output
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
|
||||
|
||||
// default to NTSC
|
||||
set_output_mode(OutputMode::NTSC);
|
||||
}
|
||||
|
||||
void set_clock_rate(double clock_rate) {
|
||||
speaker_->set_input_rate((float)(clock_rate / 4.0));
|
||||
~MOS6560() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
void set_clock_rate(double clock_rate) {
|
||||
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
enum OutputMode {
|
||||
PAL, NTSC
|
||||
};
|
||||
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
|
||||
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
|
||||
|
||||
void set_high_frequency_cutoff(float cutoff) {
|
||||
speaker_.set_high_frequency_cutoff(cutoff);
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the output mode to either PAL or NTSC.
|
||||
@@ -82,66 +107,67 @@ template <class T> class MOS6560 {
|
||||
void set_output_mode(OutputMode output_mode) {
|
||||
output_mode_ = output_mode;
|
||||
|
||||
// Lumunances are encoded trivially: on a 0–255 scale.
|
||||
// Luminances are encoded trivially: on a 0-255 scale.
|
||||
const uint8_t luminances[16] = {
|
||||
0, 255, 109, 189,
|
||||
199, 144, 159, 161,
|
||||
126, 227, 227, 207,
|
||||
235, 173, 188, 196
|
||||
0, 255, 64, 192,
|
||||
128, 128, 64, 192,
|
||||
128, 192, 128, 255,
|
||||
192, 192, 128, 255
|
||||
};
|
||||
|
||||
// Chrominances are encoded such that 0–128 is a complete revolution of phase;
|
||||
// Chrominances are encoded such that 0-128 is a complete revolution of phase;
|
||||
// anything above 191 disables the colour subcarrier. Phase is relative to the
|
||||
// colour burst, so 0 is green.
|
||||
const uint8_t pal_chrominances[16] = {
|
||||
255, 255, 40, 112,
|
||||
8, 88, 120, 56,
|
||||
40, 48, 40, 112,
|
||||
8, 88, 120, 56,
|
||||
255, 255, 37, 101,
|
||||
19, 86, 123, 59,
|
||||
46, 53, 37, 101,
|
||||
19, 86, 123, 59,
|
||||
};
|
||||
const uint8_t ntsc_chrominances[16] = {
|
||||
255, 255, 8, 72,
|
||||
32, 88, 48, 112,
|
||||
0, 0, 8, 72,
|
||||
32, 88, 48, 112,
|
||||
255, 255, 121, 57,
|
||||
103, 42, 80, 16,
|
||||
0, 9, 121, 57,
|
||||
103, 42, 80, 16,
|
||||
};
|
||||
const uint8_t *chrominances;
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
switch(output_mode) {
|
||||
case OutputMode::PAL:
|
||||
default:
|
||||
chrominances = pal_chrominances;
|
||||
display_type = Outputs::CRT::PAL50;
|
||||
display_type = Outputs::CRT::DisplayType::PAL50;
|
||||
timing_.cycles_per_line = 71;
|
||||
timing_.line_counter_increment_offset = 0;
|
||||
timing_.line_counter_increment_offset = 4;
|
||||
timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset;
|
||||
timing_.lines_per_progressive_field = 312;
|
||||
timing_.supports_interlacing = false;
|
||||
break;
|
||||
|
||||
case OutputMode::NTSC:
|
||||
chrominances = ntsc_chrominances;
|
||||
display_type = Outputs::CRT::NTSC60;
|
||||
display_type = Outputs::CRT::DisplayType::NTSC60;
|
||||
timing_.cycles_per_line = 65;
|
||||
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
|
||||
timing_.line_counter_increment_offset = 40;
|
||||
timing_.final_line_increment_position = 58;
|
||||
timing_.lines_per_progressive_field = 261;
|
||||
timing_.supports_interlacing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
|
||||
|
||||
// switch(output_mode) {
|
||||
// case OutputMode::PAL:
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// case OutputMode::NTSC:
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// }
|
||||
switch(output_mode) {
|
||||
case OutputMode::PAL:
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.07f, 0.9f, 0.9f));
|
||||
break;
|
||||
case OutputMode::NTSC:
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
break;
|
||||
}
|
||||
|
||||
for(int c = 0; c < 16; c++) {
|
||||
uint8_t *colour = (uint8_t *)&colours_[c];
|
||||
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
|
||||
colour[0] = luminances[c];
|
||||
colour[1] = chrominances[c];
|
||||
}
|
||||
@@ -161,7 +187,6 @@ template <class T> class MOS6560 {
|
||||
|
||||
// keep track of internal time relative to this scanline
|
||||
horizontal_counter_++;
|
||||
full_frame_counter_++;
|
||||
if(horizontal_counter_ == timing_.cycles_per_line) {
|
||||
if(horizontal_drawing_latch_) {
|
||||
current_character_row_++;
|
||||
@@ -183,9 +208,8 @@ template <class T> class MOS6560 {
|
||||
horizontal_drawing_latch_ = false;
|
||||
|
||||
vertical_counter_ ++;
|
||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
|
||||
if(vertical_counter_ == lines_this_field()) {
|
||||
vertical_counter_ = 0;
|
||||
full_frame_counter_ = 0;
|
||||
|
||||
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
|
||||
current_row_ = 0;
|
||||
@@ -218,7 +242,7 @@ template <class T> class MOS6560 {
|
||||
if(column_counter_&1) {
|
||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
||||
} else {
|
||||
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
video_matrix_address_counter_++;
|
||||
if(
|
||||
(current_character_row_ == 15) ||
|
||||
@@ -233,7 +257,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
uint8_t pixel_data;
|
||||
uint8_t colour_data;
|
||||
static_cast<T *>(this)->perform_read(fetch_address, &pixel_data, &colour_data);
|
||||
bus_handler_.perform_read(fetch_address, &pixel_data, &colour_data);
|
||||
|
||||
// TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should
|
||||
// divide the byte it is set for 3:1 and then continue as usual.
|
||||
@@ -247,7 +271,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
// apply vertical sync
|
||||
if(
|
||||
(vertical_counter_ < 3 && (is_odd_frame_ || !registers_.interlaced)) ||
|
||||
(vertical_counter_ < 3 && is_odd_frame()) ||
|
||||
(registers_.interlaced &&
|
||||
(
|
||||
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
|
||||
@@ -263,19 +287,22 @@ template <class T> class MOS6560 {
|
||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
|
||||
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
|
||||
case State::Pixels: crt_->output_data(cycles_in_state_ * 4); break;
|
||||
}
|
||||
output_state_ = this_state_;
|
||||
cycles_in_state_ = 0;
|
||||
|
||||
pixel_pointer = nullptr;
|
||||
if(output_state_ == State::Pixels) {
|
||||
pixel_pointer = (uint16_t *)crt_->allocate_write_area(260);
|
||||
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
|
||||
}
|
||||
}
|
||||
cycles_in_state_++;
|
||||
|
||||
if(this_state_ == State::Pixels) {
|
||||
// TODO: palette changes can happen within half-characters; the below needs to be divided.
|
||||
// Also: a perfect opportunity to rearrange this inner loop for no longer needing to be
|
||||
// two parts with a cooperative owner?
|
||||
if(column_counter_&1) {
|
||||
character_value_ = pixel_data;
|
||||
|
||||
@@ -316,7 +343,10 @@ template <class T> class MOS6560 {
|
||||
character_code_ = pixel_data;
|
||||
character_colour_ = colour_data;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep counting columns even if sync or the colour burst have interceded.
|
||||
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
|
||||
column_counter_++;
|
||||
}
|
||||
}
|
||||
@@ -325,7 +355,10 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
||||
*/
|
||||
inline void flush() { update_audio(); speaker_->flush(); }
|
||||
inline void flush() {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes to a 6560 register.
|
||||
@@ -345,7 +378,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
case 0x2:
|
||||
registers_.number_of_columns = value & 0x7f;
|
||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
@@ -354,8 +387,8 @@ template <class T> class MOS6560 {
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
|
||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
|
||||
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
break;
|
||||
|
||||
case 0xa:
|
||||
@@ -363,13 +396,13 @@ template <class T> class MOS6560 {
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
update_audio();
|
||||
speaker_->set_control(address - 0xa, value);
|
||||
audio_generator_.set_control(address - 0xa, value);
|
||||
break;
|
||||
|
||||
case 0xe:
|
||||
update_audio();
|
||||
registers_.auxiliary_colour = colours_[value >> 4];
|
||||
speaker_->set_volume(value & 0xf);
|
||||
audio_generator_.set_volume(value & 0xf);
|
||||
break;
|
||||
|
||||
case 0xf: {
|
||||
@@ -396,21 +429,24 @@ template <class T> class MOS6560 {
|
||||
*/
|
||||
uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
switch(address) {
|
||||
default: return registers_.direct_values[address];
|
||||
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (current_line >> 1) & 0xff;
|
||||
case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (raster_value() >> 1) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
BusHandler &bus_handler_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
|
||||
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
}
|
||||
|
||||
// register state
|
||||
@@ -432,7 +468,29 @@ template <class T> class MOS6560 {
|
||||
unsigned int cycles_in_state_;
|
||||
|
||||
// counters that cover an entire field
|
||||
int horizontal_counter_, vertical_counter_, full_frame_counter_;
|
||||
int horizontal_counter_ = 0, vertical_counter_ = 0;
|
||||
const int lines_this_field() {
|
||||
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
|
||||
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
|
||||
}
|
||||
const int raster_value() {
|
||||
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
const int line = vertical_counter_ + bonus_line;
|
||||
const int final_line = lines_this_field();
|
||||
|
||||
if(line < final_line)
|
||||
return line;
|
||||
|
||||
if(is_odd_frame()) {
|
||||
return (horizontal_counter_ >= timing_.final_line_increment_position) ? 0 : final_line - 1;
|
||||
} else {
|
||||
return line % final_line;
|
||||
}
|
||||
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
|
||||
}
|
||||
bool is_odd_frame() {
|
||||
return is_odd_frame_ || !registers_.interlaced;
|
||||
}
|
||||
|
||||
// latches dictating start and length of drawing
|
||||
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
||||
@@ -447,14 +505,14 @@ template <class T> class MOS6560 {
|
||||
// data latched from the bus
|
||||
uint8_t character_code_, character_colour_, character_value_;
|
||||
|
||||
bool is_odd_frame_, is_odd_line_;
|
||||
bool is_odd_frame_ = false, is_odd_line_ = false;
|
||||
|
||||
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
||||
uint16_t colours_[16];
|
||||
|
||||
uint16_t *pixel_pointer;
|
||||
void output_border(unsigned int number_of_cycles) {
|
||||
uint16_t *colour_pointer = (uint16_t *)crt_->allocate_write_area(1);
|
||||
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
|
||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||
crt_->output_level(number_of_cycles);
|
||||
}
|
||||
@@ -462,12 +520,14 @@ template <class T> class MOS6560 {
|
||||
struct {
|
||||
int cycles_per_line;
|
||||
int line_counter_increment_offset;
|
||||
int final_line_increment_position;
|
||||
int lines_per_progressive_field;
|
||||
bool supports_interlacing;
|
||||
} timing_;
|
||||
OutputMode output_mode_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _560_hpp */
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CRTC6845_hpp
|
||||
@@ -18,12 +18,12 @@ namespace Motorola {
|
||||
namespace CRTC {
|
||||
|
||||
struct BusState {
|
||||
bool display_enable;
|
||||
bool hsync;
|
||||
bool vsync;
|
||||
bool cursor;
|
||||
uint16_t refresh_address;
|
||||
uint16_t row_address;
|
||||
bool display_enable = false;
|
||||
bool hsync = false;
|
||||
bool vsync = false;
|
||||
bool cursor = false;
|
||||
uint16_t refresh_address = 0;
|
||||
uint16_t row_address = 0;
|
||||
};
|
||||
|
||||
class BusHandler {
|
||||
@@ -35,7 +35,7 @@ class BusHandler {
|
||||
void perform_bus_cycle_phase1(const BusState &) {}
|
||||
|
||||
/*!
|
||||
Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated
|
||||
Performs the second phase of a 6845 bus cycle. Some bus state, including sync, is updated
|
||||
directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore
|
||||
implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without
|
||||
having to wait until the next cycle has begun.
|
||||
@@ -44,27 +44,39 @@ class BusHandler {
|
||||
};
|
||||
|
||||
enum Personality {
|
||||
HD6845S, //
|
||||
UM6845R, //
|
||||
MC6845, //
|
||||
AMS40226 //
|
||||
HD6845S, // Type 0 in CPC parlance. Zero-width HSYNC available, no status, programmable VSYNC length.
|
||||
// Considered exactly identical to the UM6845, so this enum covers both.
|
||||
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
|
||||
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
AMS40226 // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
};
|
||||
|
||||
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
|
||||
|
||||
template <class T> class CRTC6845 {
|
||||
public:
|
||||
|
||||
CRTC6845(Personality p, T &bus_handler) noexcept :
|
||||
personality_(p), bus_handler_(bus_handler) {}
|
||||
personality_(p), bus_handler_(bus_handler), status_(0) {}
|
||||
|
||||
void select_register(uint8_t r) {
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
uint8_t get_status() const {
|
||||
uint8_t get_status() {
|
||||
switch(personality_) {
|
||||
case UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
|
||||
case AMS40226: return get_register();
|
||||
default: return 0xff;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
uint8_t get_register() const {
|
||||
uint8_t get_register() {
|
||||
if(selected_register_ == 31) status_ &= ~0x80;
|
||||
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
|
||||
|
||||
if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_;
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||
return registers_[selected_register_];
|
||||
}
|
||||
@@ -75,14 +87,27 @@ template <class T> class CRTC6845 {
|
||||
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
|
||||
};
|
||||
|
||||
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
|
||||
if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) {
|
||||
switch((value >> 4)&3) {
|
||||
default: display_skew_mask_ = 1; break;
|
||||
case 1: display_skew_mask_ = 2; break;
|
||||
case 2: display_skew_mask_ = 4; break;
|
||||
}
|
||||
}
|
||||
|
||||
if(selected_register_ < 16) {
|
||||
registers_[selected_register_] = value & masks[selected_register_];
|
||||
}
|
||||
if(selected_register_ == 31 && personality_ == UM6845R) {
|
||||
dummy_register_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_light_pen() {
|
||||
registers_[17] = bus_state_.refresh_address & 0xff;
|
||||
registers_[16] = bus_state_.refresh_address >> 8;
|
||||
status_ |= 0x40;
|
||||
}
|
||||
|
||||
void run_for(Cycles cycles) {
|
||||
@@ -115,10 +140,20 @@ template <class T> class CRTC6845 {
|
||||
}
|
||||
|
||||
// check for end of horizontal sync; note that a sync time of zero will result in an immediate
|
||||
// cancellation of the plan to perform sync
|
||||
// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero
|
||||
// will end up counting as 16 as it won't be checked until after overflow.
|
||||
if(bus_state_.hsync) {
|
||||
switch(personality_) {
|
||||
case HD6845S:
|
||||
case UM6845R:
|
||||
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||
break;
|
||||
default:
|
||||
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
perform_bus_cycle_phase2();
|
||||
@@ -131,12 +166,13 @@ template <class T> class CRTC6845 {
|
||||
|
||||
private:
|
||||
inline void perform_bus_cycle_phase1() {
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
|
||||
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
|
||||
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
|
||||
bus_handler_.perform_bus_cycle_phase1(bus_state_);
|
||||
}
|
||||
|
||||
inline void perform_bus_cycle_phase2() {
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
bus_handler_.perform_bus_cycle_phase2(bus_state_);
|
||||
}
|
||||
|
||||
@@ -144,8 +180,16 @@ template <class T> class CRTC6845 {
|
||||
// check for end of vertical sync
|
||||
if(bus_state_.vsync) {
|
||||
vsync_counter_ = (vsync_counter_ + 1) & 15;
|
||||
if(vsync_counter_ == (registers_[3] >> 4)) {
|
||||
bus_state_.vsync = false;
|
||||
// on the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs
|
||||
// always use a vertical sync count of 16.
|
||||
switch(personality_) {
|
||||
case HD6845S:
|
||||
case AMS40226:
|
||||
bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4);
|
||||
break;
|
||||
default:
|
||||
bus_state_.vsync = vsync_counter_ != 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +240,7 @@ template <class T> class CRTC6845 {
|
||||
inline void do_end_of_frame() {
|
||||
line_counter_ = 0;
|
||||
line_is_visible_ = true;
|
||||
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
|
||||
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
|
||||
bus_state_.refresh_address = line_address_;
|
||||
}
|
||||
|
||||
@@ -204,20 +248,25 @@ template <class T> class CRTC6845 {
|
||||
T &bus_handler_;
|
||||
BusState bus_state_;
|
||||
|
||||
uint8_t registers_[18];
|
||||
int selected_register_;
|
||||
uint8_t registers_[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t dummy_register_ = 0;
|
||||
int selected_register_ = 0;
|
||||
|
||||
uint8_t character_counter_;
|
||||
uint8_t line_counter_;
|
||||
uint8_t character_counter_ = 0;
|
||||
uint8_t line_counter_ = 0;
|
||||
|
||||
bool character_is_visible_, line_is_visible_;
|
||||
bool character_is_visible_ = false, line_is_visible_ = false;
|
||||
|
||||
int hsync_counter_;
|
||||
int vsync_counter_;
|
||||
bool is_in_adjustment_period_;
|
||||
int hsync_counter_ = 0;
|
||||
int vsync_counter_ = 0;
|
||||
bool is_in_adjustment_period_ = false;
|
||||
|
||||
uint16_t line_address_;
|
||||
uint16_t end_of_line_address_;
|
||||
uint16_t line_address_ = 0;
|
||||
uint16_t end_of_line_address_ = 0;
|
||||
uint8_t status_ = 0;
|
||||
|
||||
int display_skew_mask_ = 1;
|
||||
unsigned int character_is_visible_shifter_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef i8255_hpp
|
||||
@@ -76,8 +76,8 @@ template <class T> class i8255 {
|
||||
|
||||
private:
|
||||
void update_outputs() {
|
||||
port_handler_.set_value(0, outputs_[0]);
|
||||
port_handler_.set_value(1, outputs_[1]);
|
||||
if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
|
||||
if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
|
||||
port_handler_.set_value(2, outputs_[2]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "i8272.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
@@ -34,6 +34,7 @@ using namespace Intel::i8272;
|
||||
#define SetSeekEnd() (status_[0] |= 0x20)
|
||||
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
||||
#define SetNotReady() (status_[0] |= 0x08)
|
||||
#define SetSide2() (status_[0] |= 0x04)
|
||||
|
||||
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
||||
#define SetDataError() (status_[1] |= 0x20)
|
||||
@@ -43,6 +44,7 @@ using namespace Intel::i8272;
|
||||
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
||||
|
||||
#define SetControlMark() (status_[2] |= 0x40)
|
||||
#define ClearControlMark() (status_[2] &= ~0x40)
|
||||
#define ControlMark() (status_[2] & 0x40)
|
||||
|
||||
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
||||
@@ -75,21 +77,16 @@ namespace {
|
||||
const uint8_t CommandSenseDriveStatus = 0x04;
|
||||
}
|
||||
|
||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
||||
bus_handler_(bus_handler),
|
||||
main_status_(0),
|
||||
interesting_event_mask_((int)Event8272::CommandByte),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
head_timers_running_(0),
|
||||
expects_input_(false),
|
||||
drives_seeking_(0) {
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
|
||||
Storage::Disk::MFMController(clock_rate),
|
||||
bus_handler_(bus_handler) {
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
}
|
||||
|
||||
bool i8272::is_sleeping() {
|
||||
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
|
||||
ClockingHint::Preference i8272::preferred_clocking() {
|
||||
const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
|
||||
if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
|
||||
return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
void i8272::run_for(Cycles cycles) {
|
||||
@@ -101,7 +98,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
if(delay_time_ > 0) {
|
||||
if(cycles.as_int() >= delay_time_) {
|
||||
delay_time_ = 0;
|
||||
posit_event((int)Event8272::Timer);
|
||||
posit_event(static_cast<int>(Event8272::Timer));
|
||||
} else {
|
||||
delay_time_ -= cycles.as_int();
|
||||
}
|
||||
@@ -119,11 +116,12 @@ void i8272::run_for(Cycles cycles) {
|
||||
// Perform a step.
|
||||
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
||||
drives_[c].drive->step(direction);
|
||||
select_drive(c);
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||
|
||||
// Check for completion.
|
||||
if(drives_[c].seek_is_satisfied()) {
|
||||
if(seek_is_satisfied(c)) {
|
||||
drives_[c].phase = Drive::CompletedSeeking;
|
||||
drives_seeking_--;
|
||||
break;
|
||||
@@ -157,8 +155,13 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
// check for busy plus ready disabled
|
||||
if(is_executing_ && !get_drive().get_is_ready()) {
|
||||
posit_event(static_cast<int>(Event8272::NoLongerReady));
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
if(is_sleeping_) update_sleep_observer();
|
||||
if(is_sleeping_) update_clocking_observer();
|
||||
}
|
||||
|
||||
void i8272::set_register(int address, uint8_t value) {
|
||||
@@ -175,7 +178,7 @@ void i8272::set_register(int address, uint8_t value) {
|
||||
} else {
|
||||
// accumulate latest byte in the command byte sequence
|
||||
command_.push_back(value);
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +187,7 @@ uint8_t i8272::get_register(int address) {
|
||||
if(result_stack_.empty()) return 0xff;
|
||||
uint8_t result = result_stack_.back();
|
||||
result_stack_.pop_back();
|
||||
if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty);
|
||||
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
|
||||
|
||||
return result;
|
||||
} else {
|
||||
@@ -192,35 +195,29 @@ uint8_t i8272::get_register(int address) {
|
||||
}
|
||||
}
|
||||
|
||||
void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(drive < 4 && drive >= 0) {
|
||||
drives_[drive].drive->set_disk(disk);
|
||||
}
|
||||
}
|
||||
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() }
|
||||
|
||||
#define MS_TO_CYCLES(x) x * 8000
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
|
||||
|
||||
#define PASTE(x, y) x##y
|
||||
#define CONCAT(x, y) PASTE(x, y)
|
||||
|
||||
#define FIND_HEADER() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||
if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; } \
|
||||
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||
\
|
||||
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||
CONCAT(header_found, __LINE__): 0;\
|
||||
CONCAT(header_found, __LINE__): (void)0;\
|
||||
|
||||
#define FIND_DATA() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::Token) { \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||
if(event_type == static_cast<int>(Event::Token)) { \
|
||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
||||
}
|
||||
|
||||
@@ -235,10 +232,10 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
active_drive_ = command_[1]&3; \
|
||||
active_head_ = (command_[1] >> 2)&1; \
|
||||
set_drive(drives_[active_drive_].drive); \
|
||||
drives_[active_drive_].drive->set_head((unsigned int)active_head_); \
|
||||
set_is_double_density(command_[0] & 0x40); \
|
||||
invalidate_track();
|
||||
status_[0] = (command_[1]&7); \
|
||||
select_drive(active_drive_); \
|
||||
get_drive().set_head(active_head_); \
|
||||
set_is_double_density(command_[0] & 0x40);
|
||||
|
||||
#define WAIT_FOR_BYTES(n) \
|
||||
distance_into_section_ = 0; \
|
||||
@@ -262,13 +259,17 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
||||
head_timers_running_++; \
|
||||
is_sleeping_ = false; \
|
||||
update_sleep_observer(); \
|
||||
update_clocking_observer(); \
|
||||
} \
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
||||
}
|
||||
|
||||
void i8272::posit_event(int event_type) {
|
||||
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
||||
if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
|
||||
if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
|
||||
SetNotReady();
|
||||
goto abort;
|
||||
}
|
||||
if(!(interesting_event_mask_ & event_type)) return;
|
||||
interesting_event_mask_ &= ~event_type;
|
||||
|
||||
@@ -292,7 +293,7 @@ void i8272::posit_event(int event_type) {
|
||||
WAIT_FOR_EVENT(Event8272::CommandByte)
|
||||
SetBusy();
|
||||
|
||||
static const size_t required_lengths[32] = {
|
||||
static const std::size_t required_lengths[32] = {
|
||||
0, 0, 9, 3, 2, 9, 9, 2,
|
||||
1, 9, 2, 0, 9, 6, 0, 3,
|
||||
0, 9, 0, 0, 0, 0, 0, 0,
|
||||
@@ -338,9 +339,14 @@ void i8272::posit_event(int event_type) {
|
||||
}
|
||||
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||
// cylinder, head, sector and size registers from the command stream.
|
||||
is_executing_ = true;
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
LOAD_HEAD();
|
||||
if(!get_drive().get_is_ready()) {
|
||||
SetNotReady();
|
||||
goto abort;
|
||||
}
|
||||
}
|
||||
|
||||
// Jump to the proper place.
|
||||
@@ -427,9 +433,10 @@ void i8272::posit_event(int event_type) {
|
||||
// flag doesn't match the sort the command was looking for.
|
||||
read_data_found_header:
|
||||
FIND_DATA();
|
||||
if(event_type == (int)Event::Token) {
|
||||
ClearControlMark();
|
||||
if(event_type == static_cast<int>(Event::Token)) {
|
||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||
// Something other than a data mark came next — impliedly an ID or index mark.
|
||||
// Something other than a data mark came next, impliedly an ID or index mark.
|
||||
SetMissingAddressMark();
|
||||
SetMissingDataAddressMark();
|
||||
goto abort; // TODO: or read_next_data?
|
||||
@@ -458,24 +465,24 @@ void i8272::posit_event(int event_type) {
|
||||
//
|
||||
// TODO: consider DTL.
|
||||
read_data_get_byte:
|
||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
||||
if(event_type == (int)Event::Token) {
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
if(event_type == static_cast<int>(Event::Token)) {
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
}
|
||||
switch(event_type) {
|
||||
case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal.
|
||||
case static_cast<int>(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
|
||||
break;
|
||||
case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
|
||||
case static_cast<int>(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
|
||||
SetOverrun();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::IndexHole:
|
||||
case static_cast<int>(Event::IndexHole):
|
||||
SetEndOfCylinder();
|
||||
goto abort;
|
||||
break;
|
||||
@@ -504,7 +511,7 @@ void i8272::posit_event(int event_type) {
|
||||
write_data:
|
||||
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
if(get_drive().get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort;
|
||||
}
|
||||
@@ -527,7 +534,6 @@ void i8272::posit_event(int event_type) {
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(!has_input_) {
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
}
|
||||
write_byte(input_);
|
||||
@@ -559,7 +565,6 @@ void i8272::posit_event(int event_type) {
|
||||
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
|
||||
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
|
||||
index_hole_limit_ = 2;
|
||||
read_id_find_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
SetMissingAddressMark();
|
||||
@@ -607,7 +612,7 @@ void i8272::posit_event(int event_type) {
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
|
||||
|
||||
@@ -619,7 +624,7 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs format [/write] track.
|
||||
format_track:
|
||||
printf("Format track\n");
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
if(get_drive().get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort;
|
||||
}
|
||||
@@ -644,14 +649,13 @@ void i8272::posit_event(int event_type) {
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
format_track_write_header:
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||
switch(event_type) {
|
||||
case (int)Event::IndexHole:
|
||||
case static_cast<int>(Event::IndexHole):
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::DataWritten:
|
||||
case static_cast<int>(Event::DataWritten):
|
||||
header_[distance_into_section_] = input_;
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
@@ -682,8 +686,8 @@ void i8272::posit_event(int event_type) {
|
||||
// Otherwise, pad out to the index hole.
|
||||
format_track_pad:
|
||||
write_byte(get_is_double_density() ? 0x4e : 0xff);
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
if(event_type != (int)Event::IndexHole) goto format_track_pad;
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||
if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
|
||||
|
||||
end_writing();
|
||||
|
||||
@@ -712,12 +716,13 @@ void i8272::posit_event(int event_type) {
|
||||
seek:
|
||||
{
|
||||
int drive = command_[1]&3;
|
||||
select_drive(drive);
|
||||
|
||||
// Increment the seeking count if this drive wasn't already seeking.
|
||||
if(drives_[drive].phase != Drive::Seeking) {
|
||||
drives_seeking_++;
|
||||
is_sleeping_ = false;
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
|
||||
@@ -741,7 +746,7 @@ void i8272::posit_event(int event_type) {
|
||||
}
|
||||
|
||||
// Check whether any steps are even needed; if not then mark as completed already.
|
||||
if(drives_[drive].seek_is_satisfied()) {
|
||||
if(seek_is_satisfied(drive)) {
|
||||
drives_[drive].phase = Drive::CompletedSeeking;
|
||||
drives_seeking_--;
|
||||
}
|
||||
@@ -764,14 +769,13 @@ void i8272::posit_event(int event_type) {
|
||||
// If a drive was found, return its results. Otherwise return a single 0x80.
|
||||
if(found_drive != -1) {
|
||||
drives_[found_drive].phase = Drive::NotSeeking;
|
||||
status_[0] = (uint8_t)found_drive;
|
||||
status_[0] = static_cast<uint8_t>(found_drive);
|
||||
main_status_ &= ~(1 << found_drive);
|
||||
SetSeekEnd();
|
||||
|
||||
result_stack_.push_back(drives_[found_drive].head_position);
|
||||
result_stack_.push_back(status_[0]);
|
||||
result_stack_ = { drives_[found_drive].head_position, status_[0]};
|
||||
} else {
|
||||
result_stack_.push_back(0x80);
|
||||
result_stack_ = { 0x80 };
|
||||
}
|
||||
}
|
||||
goto post_result;
|
||||
@@ -793,24 +797,28 @@ void i8272::posit_event(int event_type) {
|
||||
printf("Sense drive status\n");
|
||||
{
|
||||
int drive = command_[1] & 3;
|
||||
result_stack_.push_back(
|
||||
select_drive(drive);
|
||||
result_stack_= {
|
||||
static_cast<uint8_t>(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
|
||||
(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00)
|
||||
);
|
||||
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(get_drive().get_is_ready() ? 0x20 : 0x00) |
|
||||
(get_drive().get_is_read_only() ? 0x40 : 0x00)
|
||||
)
|
||||
};
|
||||
}
|
||||
goto post_result;
|
||||
|
||||
// Performs any invalid command.
|
||||
invalid:
|
||||
// A no-op, but posts ST0 (but which ST0?)
|
||||
result_stack_.push_back(0x80);
|
||||
result_stack_ = {0x80};
|
||||
goto post_result;
|
||||
|
||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||
abort:
|
||||
end_writing();
|
||||
SetAbnormalTermination();
|
||||
goto post_st012chrn;
|
||||
|
||||
@@ -818,27 +826,21 @@ void i8272::posit_event(int event_type) {
|
||||
post_st012chrn:
|
||||
SCHEDULE_HEAD_UNLOAD();
|
||||
|
||||
result_stack_.push_back(size_);
|
||||
result_stack_.push_back(sector_);
|
||||
result_stack_.push_back(head_);
|
||||
result_stack_.push_back(cylinder_);
|
||||
|
||||
result_stack_.push_back(status_[2]);
|
||||
result_stack_.push_back(status_[1]);
|
||||
result_stack_.push_back(status_[0]);
|
||||
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
|
||||
|
||||
goto post_result;
|
||||
|
||||
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the
|
||||
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
|
||||
// last thing in it will be returned first.
|
||||
post_result:
|
||||
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
||||
for(size_t c = 0; c < result_stack_.size(); c++) {
|
||||
for(std::size_t c = 0; c < result_stack_.size(); c++) {
|
||||
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||
is_executing_ = false;
|
||||
ResetNonDMAExecution();
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
@@ -853,9 +855,9 @@ void i8272::posit_event(int event_type) {
|
||||
END_SECTION()
|
||||
}
|
||||
|
||||
bool i8272::Drive::seek_is_satisfied() {
|
||||
return (target_head_position == head_position) ||
|
||||
(target_head_position == -1 && drive->get_is_track_zero());
|
||||
bool i8272::seek_is_satisfied(int drive) {
|
||||
return (drives_[drive].target_head_position == drives_[drive].head_position) ||
|
||||
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
|
||||
}
|
||||
|
||||
void i8272::set_dma_acknowledge(bool dack) {
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef i8272_hpp
|
||||
#define i8272_hpp
|
||||
|
||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
||||
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -26,7 +26,7 @@ class BusHandler {
|
||||
|
||||
class i8272: public Storage::Disk::MFMController {
|
||||
public:
|
||||
i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||
i8272(BusHandler &bus_handler, Cycles clock_rate);
|
||||
|
||||
void run_for(Cycles);
|
||||
|
||||
@@ -39,9 +39,10 @@ class i8272: public Storage::Disk::MFMController {
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
bool is_sleeping();
|
||||
protected:
|
||||
virtual void select_drive(int number) = 0;
|
||||
|
||||
private:
|
||||
// The bus handler, for interrupt and DMA-driven usage.
|
||||
@@ -49,89 +50,83 @@ class i8272: public Storage::Disk::MFMController {
|
||||
std::unique_ptr<BusHandler> allocated_bus_handler_;
|
||||
|
||||
// Status registers.
|
||||
uint8_t main_status_;
|
||||
uint8_t status_[3];
|
||||
uint8_t main_status_ = 0;
|
||||
uint8_t status_[3] = {0, 0, 0};
|
||||
|
||||
// A buffer for accumulating the incoming command, and one for accumulating the result.
|
||||
std::vector<uint8_t> command_;
|
||||
std::vector<uint8_t> result_stack_;
|
||||
uint8_t input_;
|
||||
bool has_input_;
|
||||
bool expects_input_;
|
||||
uint8_t input_ = 0;
|
||||
bool has_input_ = false;
|
||||
bool expects_input_ = false;
|
||||
|
||||
// Event stream: the 8272-specific events, plus the current event state.
|
||||
enum class Event8272: int {
|
||||
CommandByte = (1 << 3),
|
||||
Timer = (1 << 4),
|
||||
ResultEmpty = (1 << 5),
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_;
|
||||
bool is_access_command_;
|
||||
void posit_event(int type) override;
|
||||
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
||||
// The counter used for ::Timer events.
|
||||
int delay_time_;
|
||||
int delay_time_ = 0;
|
||||
|
||||
// The connected drives.
|
||||
struct Drive {
|
||||
uint8_t head_position;
|
||||
uint8_t head_position = 0;
|
||||
|
||||
// Seeking: persistent state.
|
||||
enum Phase {
|
||||
NotSeeking,
|
||||
Seeking,
|
||||
CompletedSeeking
|
||||
} phase;
|
||||
bool did_seek;
|
||||
bool seek_failed;
|
||||
} phase = NotSeeking;
|
||||
bool did_seek = false;
|
||||
bool seek_failed = false;
|
||||
|
||||
// Seeking: transient state.
|
||||
int step_rate_counter;
|
||||
int steps_taken;
|
||||
int target_head_position; // either an actual number, or -1 to indicate to step until track zero
|
||||
|
||||
/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be.
|
||||
bool seek_is_satisfied();
|
||||
int step_rate_counter = 0;
|
||||
int steps_taken = 0;
|
||||
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
|
||||
|
||||
// Head state.
|
||||
int head_unload_delay[2];
|
||||
bool head_is_loaded[2];
|
||||
int head_unload_delay[2] = {0, 0};
|
||||
bool head_is_loaded[2] = {false, false};
|
||||
|
||||
// The connected drive.
|
||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||
|
||||
Drive() :
|
||||
head_position(0), phase(NotSeeking),
|
||||
drive(new Storage::Disk::Drive),
|
||||
head_is_loaded{false, false},
|
||||
head_unload_delay{0, 0} {};
|
||||
} drives_[4];
|
||||
int drives_seeking_;
|
||||
int drives_seeking_ = 0;
|
||||
|
||||
/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
|
||||
bool seek_is_satisfied(int drive);
|
||||
|
||||
// User-supplied parameters; as per the specify command.
|
||||
int step_rate_time_;
|
||||
int head_unload_time_;
|
||||
int head_load_time_;
|
||||
bool dma_mode_;
|
||||
int step_rate_time_ = 1;
|
||||
int head_unload_time_ = 1;
|
||||
int head_load_time_ = 1;
|
||||
bool dma_mode_ = false;
|
||||
bool is_executing_ = false;
|
||||
|
||||
// A count of head unload timers currently running.
|
||||
int head_timers_running_;
|
||||
int head_timers_running_ = 0;
|
||||
|
||||
// Transient storage and counters used while reading the disk.
|
||||
uint8_t header_[6];
|
||||
int distance_into_section_;
|
||||
int index_hole_count_, index_hole_limit_;
|
||||
uint8_t header_[6] = {0, 0, 0, 0, 0, 0};
|
||||
int distance_into_section_ = 0;
|
||||
int index_hole_count_ = 0, index_hole_limit_ = 0;
|
||||
|
||||
// Keeps track of the drive and head in use during commands.
|
||||
int active_drive_;
|
||||
int active_head_;
|
||||
int active_drive_ = 0;
|
||||
int active_head_ = 0;
|
||||
|
||||
// Internal registers.
|
||||
uint8_t cylinder_, head_, sector_, size_;
|
||||
uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
|
||||
|
||||
// Master switch on not performing any work.
|
||||
bool is_sleeping_;
|
||||
bool is_sleeping_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
713
Components/9918/9918.cpp
Normal file
713
Components/9918/9918.cpp
Normal file
@@ -0,0 +1,713 @@
|
||||
//
|
||||
// 9918.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/11/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "9918.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
using namespace TI;
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
|
||||
uint32_t result = 0;
|
||||
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
|
||||
result_ptr[0] = r;
|
||||
result_ptr[1] = g;
|
||||
result_ptr[2] = b;
|
||||
result_ptr[3] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
const uint32_t palette[16] = {
|
||||
palette_pack(0, 0, 0),
|
||||
palette_pack(0, 0, 0),
|
||||
palette_pack(33, 200, 66),
|
||||
palette_pack(94, 220, 120),
|
||||
|
||||
palette_pack(84, 85, 237),
|
||||
palette_pack(125, 118, 252),
|
||||
palette_pack(212, 82, 77),
|
||||
palette_pack(66, 235, 245),
|
||||
|
||||
palette_pack(252, 85, 84),
|
||||
palette_pack(255, 121, 120),
|
||||
palette_pack(212, 193, 84),
|
||||
palette_pack(230, 206, 128),
|
||||
|
||||
palette_pack(33, 176, 59),
|
||||
palette_pack(201, 91, 186),
|
||||
palette_pack(204, 204, 204),
|
||||
palette_pack(255, 255, 255)
|
||||
};
|
||||
|
||||
const uint8_t StatusInterrupt = 0x80;
|
||||
const uint8_t StatusFifthSprite = 0x40;
|
||||
|
||||
const int StatusSpriteCollisionShift = 5;
|
||||
const uint8_t StatusSpriteCollision = 0x20;
|
||||
|
||||
struct ReverseTable {
|
||||
std::uint8_t map[256];
|
||||
|
||||
ReverseTable() {
|
||||
for(int c = 0; c < 256; ++c) {
|
||||
map[c] = static_cast<uint8_t>(
|
||||
((c & 0x80) >> 7) |
|
||||
((c & 0x40) >> 5) |
|
||||
((c & 0x20) >> 3) |
|
||||
((c & 0x10) >> 1) |
|
||||
((c & 0x08) << 1) |
|
||||
((c & 0x04) << 3) |
|
||||
((c & 0x02) << 5) |
|
||||
((c & 0x01) << 7)
|
||||
);
|
||||
}
|
||||
}
|
||||
} reverse_table;
|
||||
|
||||
// Bits are reversed in the internal mode value; they're stored
|
||||
// in the order M1 M2 M3. Hence the definitions below.
|
||||
enum ScreenMode {
|
||||
Text = 4,
|
||||
MultiColour = 2,
|
||||
ColouredText = 0,
|
||||
Graphics = 1
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TMS9918Base::TMS9918Base() :
|
||||
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
|
||||
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
|
||||
crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {}
|
||||
|
||||
TMS9918::TMS9918(Personality p) {
|
||||
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
|
||||
// into whether there's a more natural form.
|
||||
crt_->set_rgb_sampling_function(
|
||||
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
||||
"{"
|
||||
"return texture(sampler, coordinate).rgb / vec3(255.0);"
|
||||
"}");
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
|
||||
crt_->set_input_gamma(2.8f);
|
||||
|
||||
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
|
||||
// intended to produce the correct relationship between the hard edges between pixels and
|
||||
// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
|
||||
// colour burst generator because I've yet to find any.
|
||||
crt_->set_immediate_default_phase(0.85f);
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *TMS9918::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
void TMS9918Base::test_sprite(int sprite_number, int screen_row) {
|
||||
if(!(status_ & StatusFifthSprite)) {
|
||||
status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number);
|
||||
}
|
||||
if(sprites_stopped_)
|
||||
return;
|
||||
|
||||
const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)];
|
||||
// A sprite Y of 208 means "don't scan the list any further".
|
||||
if(sprite_position == 208) {
|
||||
sprites_stopped_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const int sprite_row = (screen_row - sprite_position)&255;
|
||||
if(sprite_row < 0 || sprite_row >= sprite_height_) return;
|
||||
|
||||
const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot;
|
||||
if(active_sprite_slot == 4) {
|
||||
status_ |= StatusFifthSprite;
|
||||
return;
|
||||
}
|
||||
|
||||
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot];
|
||||
sprite.index = sprite_number;
|
||||
sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0);
|
||||
sprite_sets_[active_sprite_set_].active_sprite_slot++;
|
||||
}
|
||||
|
||||
void TMS9918Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
|
||||
int sprite_id = field / 6;
|
||||
field %= 6;
|
||||
|
||||
while(true) {
|
||||
const int cycles_in_sprite = std::min(cycles_left, 6 - field);
|
||||
cycles_left -= cycles_in_sprite;
|
||||
const int final_field = cycles_in_sprite + field;
|
||||
|
||||
assert(sprite_id < 4);
|
||||
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id];
|
||||
|
||||
if(field < 4) {
|
||||
std::memcpy(
|
||||
&sprite.info[field],
|
||||
&ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field],
|
||||
static_cast<size_t>(std::min(4, final_field) - field));
|
||||
}
|
||||
|
||||
field = std::min(4, final_field);
|
||||
const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0);
|
||||
const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?)
|
||||
while(field < final_field) {
|
||||
sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)];
|
||||
field++;
|
||||
}
|
||||
|
||||
if(!cycles_left) return;
|
||||
field = 0;
|
||||
sprite_id++;
|
||||
}
|
||||
}
|
||||
|
||||
void TMS9918::run_for(const HalfCycles cycles) {
|
||||
// As specific as I've been able to get:
|
||||
// Scanline time is always 228 cycles.
|
||||
// PAL output is 313 lines total. NTSC output is 262 lines total.
|
||||
// Interrupt is signalled upon entering the lower border.
|
||||
|
||||
// Keep a count of cycles separate from internal counts to avoid
|
||||
// potential errors mapping back and forth.
|
||||
half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2);
|
||||
|
||||
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
|
||||
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
|
||||
// for this part. So multiply by three quarters.
|
||||
int int_cycles = (cycles.as_int() * 3) + cycles_error_;
|
||||
cycles_error_ = int_cycles & 3;
|
||||
int_cycles >>= 2;
|
||||
if(!int_cycles) return;
|
||||
|
||||
while(int_cycles) {
|
||||
// Determine how much time has passed in the remainder of this line, and proceed.
|
||||
int cycles_left = std::min(342 - column_, int_cycles);
|
||||
|
||||
|
||||
|
||||
// ------------------------------------
|
||||
// Potentially perform a memory access.
|
||||
// ------------------------------------
|
||||
if(queued_access_ != MemoryAccess::None) {
|
||||
int time_until_access_slot = 0;
|
||||
switch(line_mode_) {
|
||||
case LineMode::Refresh:
|
||||
if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1;
|
||||
else time_until_access_slot = 3 - ((column_ - 53)&3);
|
||||
// i.e. 53 -> 3, 52 -> 2, 51 -> 1, 50 -> 0, etc
|
||||
break;
|
||||
case LineMode::Text:
|
||||
if(column_ < 59 || column_ >= 299) time_until_access_slot = column_&1;
|
||||
else time_until_access_slot = 5 - ((column_ + 3)%6);
|
||||
// i.e. 59 -> 3, 60 -> 2, 61 -> 1, etc
|
||||
break;
|
||||
case LineMode::Character:
|
||||
if(column_ < 9) time_until_access_slot = column_&1;
|
||||
else if(column_ < 30) time_until_access_slot = 30 - column_;
|
||||
else if(column_ < 37) time_until_access_slot = column_&1;
|
||||
else if(column_ < 311) time_until_access_slot = 31 - ((column_ + 7)&31);
|
||||
// i.e. 53 -> 3, 54 -> 2, 55 -> 1, 56 -> 0, 57 -> 31, etc
|
||||
else if(column_ < 313) time_until_access_slot = column_&1;
|
||||
else time_until_access_slot = 342 - column_;
|
||||
break;
|
||||
}
|
||||
|
||||
if(cycles_left >= time_until_access_slot) {
|
||||
if(queued_access_ == MemoryAccess::Write) {
|
||||
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
|
||||
} else {
|
||||
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
|
||||
}
|
||||
ram_pointer_++;
|
||||
queued_access_ = MemoryAccess::None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
column_ += cycles_left; // column_ is now the column that has been reached in this line.
|
||||
int_cycles -= cycles_left; // Count down duration to run for.
|
||||
|
||||
|
||||
|
||||
// ------------------------------
|
||||
// Perform video memory accesses.
|
||||
// ------------------------------
|
||||
if(((row_ < 192) || (row_ == frame_lines_-1)) && !blank_screen_) {
|
||||
const int sprite_row = (row_ < 192) ? row_ : -1;
|
||||
const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line.
|
||||
switch(line_mode_) {
|
||||
default: break;
|
||||
|
||||
case LineMode::Text:
|
||||
access_pointer_ = std::min(30, access_slot);
|
||||
if(access_pointer_ >= 30 && access_pointer_ < 150) {
|
||||
const int row_base = pattern_name_address_ + (row_ >> 3) * 40;
|
||||
const int end = std::min(150, access_slot);
|
||||
|
||||
// Pattern names are collected every third window starting from window 30.
|
||||
const int pattern_names_start = (access_pointer_ - 30 + 2) / 3;
|
||||
const int pattern_names_end = (end - 30 + 2) / 3;
|
||||
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
|
||||
|
||||
// Patterns are collected every third window starting from window 32.
|
||||
const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3;
|
||||
const int pattern_buffer_end = (end - 32 + 2) / 3;
|
||||
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
|
||||
pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LineMode::Character:
|
||||
// Four access windows: no collection.
|
||||
if(access_pointer_ < 5)
|
||||
access_pointer_ = std::min(5, access_slot);
|
||||
|
||||
// Then ten access windows are filled with collection of sprite 3 and 4 details.
|
||||
if(access_pointer_ >= 5 && access_pointer_ < 15) {
|
||||
int end = std::min(15, access_slot);
|
||||
get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, sprite_row - 1);
|
||||
access_pointer_ = std::min(15, access_slot);
|
||||
}
|
||||
|
||||
// Four more access windows: no collection.
|
||||
if(access_pointer_ >= 15 && access_pointer_ < 19) {
|
||||
access_pointer_ = std::min(19, access_slot);
|
||||
|
||||
// Start new sprite set if this is location 19.
|
||||
if(access_pointer_ == 19) {
|
||||
active_sprite_set_ ^= 1;
|
||||
sprite_sets_[active_sprite_set_].active_sprite_slot = 0;
|
||||
sprites_stopped_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Then eight access windows fetch the y position for the first eight sprites.
|
||||
while(access_pointer_ < 27 && access_pointer_ < access_slot) {
|
||||
test_sprite(access_pointer_ - 19, sprite_row);
|
||||
access_pointer_++;
|
||||
}
|
||||
|
||||
// The next 128 access slots are video and sprite collection interleaved.
|
||||
if(access_pointer_ >= 27 && access_pointer_ < 155) {
|
||||
int end = std::min(155, access_slot);
|
||||
|
||||
int row_base = pattern_name_address_;
|
||||
int pattern_base = pattern_generator_table_address_;
|
||||
int colour_base = colour_table_address_;
|
||||
if(screen_mode_ == ScreenMode::Graphics) {
|
||||
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
|
||||
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
||||
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
||||
}
|
||||
row_base += (row_ << 2)&~31;
|
||||
|
||||
// Pattern names are collected every fourth window starting from window 27.
|
||||
const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2;
|
||||
const int pattern_names_end = (end - 27 + 3) >> 2;
|
||||
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
|
||||
|
||||
// Colours are collected every fourth window starting from window 29.
|
||||
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
|
||||
const int colours_end = (end - 29 + 3) >> 2;
|
||||
if(screen_mode_ != 1) {
|
||||
for(int column = colours_start; column < colours_end; ++column) {
|
||||
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)];
|
||||
}
|
||||
} else {
|
||||
for(int column = colours_start; column < colours_end; ++column) {
|
||||
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)];
|
||||
}
|
||||
}
|
||||
|
||||
// Patterns are collected ever fourth window starting from window 30.
|
||||
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
|
||||
const int pattern_buffer_end = (end - 30 + 3) >> 2;
|
||||
|
||||
// Multicolour mode uss a different function of row to pick bytes
|
||||
const int row = (screen_mode_ != 2) ? (row_ & 7) : ((row_ >> 2) & 7);
|
||||
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
|
||||
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row];
|
||||
}
|
||||
|
||||
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
|
||||
const int sprite_start = (access_pointer_ - 28 + 3) >> 2;
|
||||
const int sprite_end = (end - 28 + 3) >> 2;
|
||||
for(int column = sprite_start; column < sprite_end; ++column) {
|
||||
if(column&3) {
|
||||
test_sprite(7 + column - (column >> 2), sprite_row);
|
||||
}
|
||||
}
|
||||
|
||||
access_pointer_ = std::min(155, access_slot);
|
||||
}
|
||||
|
||||
// Two access windows: no collection.
|
||||
if(access_pointer_ < 157)
|
||||
access_pointer_ = std::min(157, access_slot);
|
||||
|
||||
// Fourteen access windows: collect initial sprite information.
|
||||
if(access_pointer_ >= 157 && access_pointer_ < 171) {
|
||||
int end = std::min(171, access_slot);
|
||||
get_sprite_contents(access_pointer_ - 157, end - access_pointer_, sprite_row);
|
||||
access_pointer_ = std::min(171, access_slot);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// --------------------------
|
||||
// End video memory accesses.
|
||||
// --------------------------
|
||||
|
||||
|
||||
|
||||
// --------------------
|
||||
// Output video stream.
|
||||
// --------------------
|
||||
if(row_ < 192 && !blank_screen_) {
|
||||
// ----------------------
|
||||
// Output horizontal sync
|
||||
// ----------------------
|
||||
if(!output_column_ && column_ >= 26) {
|
||||
crt_->output_sync(13 * 4);
|
||||
crt_->output_default_colour_burst(13 * 4);
|
||||
output_column_ = 26;
|
||||
}
|
||||
|
||||
// -------------------
|
||||
// Output left border.
|
||||
// -------------------
|
||||
if(output_column_ >= 26) {
|
||||
int pixels_end = std::min(first_pixel_column_, column_);
|
||||
if(output_column_ < pixels_end) {
|
||||
output_border(pixels_end - output_column_);
|
||||
output_column_ = pixels_end;
|
||||
|
||||
// Grab a pointer for drawing pixels to, if the moment has arrived.
|
||||
if(pixels_end == first_pixel_column_) {
|
||||
pixel_base_ = pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------
|
||||
// Output pixels.
|
||||
// --------------
|
||||
if(output_column_ >= first_pixel_column_) {
|
||||
int pixels_end = std::min(first_right_border_column_, column_);
|
||||
|
||||
if(output_column_ < pixels_end) {
|
||||
switch(line_mode_) {
|
||||
default: break;
|
||||
|
||||
case LineMode::Text: {
|
||||
const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] };
|
||||
|
||||
const int shift = (output_column_ - first_pixel_column_) % 6;
|
||||
int byte_column = (output_column_ - first_pixel_column_) / 6;
|
||||
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
|
||||
int pixels_left = pixels_end - output_column_;
|
||||
int length = std::min(pixels_left, 6 - shift);
|
||||
while(true) {
|
||||
pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[pattern&0x01];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!pixels_left) break;
|
||||
length = std::min(6, pixels_left);
|
||||
byte_column++;
|
||||
pattern = reverse_table.map[pattern_buffer_[byte_column]];
|
||||
}
|
||||
output_column_ = pixels_end;
|
||||
} break;
|
||||
|
||||
case LineMode::Character: {
|
||||
// If this is the start of the visible area, seed sprite shifter positions.
|
||||
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
|
||||
if(output_column_ == first_pixel_column_) {
|
||||
int c = sprite_set.active_sprite_slot;
|
||||
while(c--) {
|
||||
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
|
||||
sprite.shift_position = -sprite.info[1];
|
||||
if(sprite.info[3] & 0x80) {
|
||||
sprite.shift_position += 32;
|
||||
if(sprite.shift_position > 0 && !sprites_magnified_)
|
||||
sprite.shift_position *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Paint the background tiles.
|
||||
const int pixels_left = pixels_end - output_column_;
|
||||
if(screen_mode_ == ScreenMode::MultiColour) {
|
||||
int pixel_location = output_column_ - first_pixel_column_;
|
||||
for(int c = 0; c < pixels_left; ++c) {
|
||||
pixel_target_[c] = palette[
|
||||
(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15
|
||||
];
|
||||
}
|
||||
pixel_target_ += pixels_left;
|
||||
} else {
|
||||
const int shift = (output_column_ - first_pixel_column_) & 7;
|
||||
int byte_column = (output_column_ - first_pixel_column_) >> 3;
|
||||
|
||||
int length = std::min(pixels_left, 8 - shift);
|
||||
|
||||
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
|
||||
uint8_t colour = colour_buffer_[byte_column];
|
||||
uint32_t colours[2] = {
|
||||
palette[(colour & 15) ? (colour & 15) : background_colour_],
|
||||
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
|
||||
};
|
||||
|
||||
int background_pixels_left = pixels_left;
|
||||
while(true) {
|
||||
background_pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[pattern&0x01];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!background_pixels_left) break;
|
||||
length = std::min(8, background_pixels_left);
|
||||
byte_column++;
|
||||
|
||||
pattern = reverse_table.map[pattern_buffer_[byte_column]];
|
||||
colour = colour_buffer_[byte_column];
|
||||
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
|
||||
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
|
||||
}
|
||||
}
|
||||
|
||||
// Paint sprites and check for collisions.
|
||||
if(sprite_set.active_sprite_slot) {
|
||||
int sprite_pixels_left = pixels_left;
|
||||
const int shift_advance = sprites_magnified_ ? 1 : 2;
|
||||
|
||||
const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
|
||||
while(sprite_pixels_left--) {
|
||||
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
|
||||
int sprite_mask = 0;
|
||||
int c = sprite_set.active_sprite_slot;
|
||||
while(c--) {
|
||||
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
|
||||
|
||||
if(sprite.shift_position < 0) {
|
||||
sprite.shift_position++;
|
||||
continue;
|
||||
} else if(sprite.shift_position < 32) {
|
||||
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
|
||||
mask = (mask >> 7) & 1;
|
||||
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
|
||||
sprite_mask |= mask;
|
||||
sprite.shift_position += shift_advance;
|
||||
|
||||
mask &= colour_masks[sprite.info[3]&15];
|
||||
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
|
||||
}
|
||||
}
|
||||
|
||||
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
|
||||
output_column_++;
|
||||
}
|
||||
}
|
||||
|
||||
output_column_ = pixels_end;
|
||||
} break;
|
||||
}
|
||||
|
||||
if(output_column_ == first_right_border_column_) {
|
||||
const unsigned int data_length = static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_);
|
||||
crt_->output_data(data_length * 4, data_length);
|
||||
pixel_target_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Output right border.
|
||||
// --------------------
|
||||
if(output_column_ >= first_right_border_column_) {
|
||||
output_border(column_ - output_column_);
|
||||
output_column_ = column_;
|
||||
}
|
||||
} else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) {
|
||||
// Vertical sync.
|
||||
if(column_ == 342) {
|
||||
crt_->output_sync(342 * 4);
|
||||
}
|
||||
} else {
|
||||
// Blank.
|
||||
if(!output_column_ && column_ >= 26) {
|
||||
crt_->output_sync(13 * 4);
|
||||
crt_->output_default_colour_burst(13 * 4);
|
||||
output_column_ = 26;
|
||||
}
|
||||
if(output_column_ >= 26) {
|
||||
output_border(column_ - output_column_);
|
||||
output_column_ = column_;
|
||||
}
|
||||
}
|
||||
// -----------------
|
||||
// End video stream.
|
||||
// -----------------
|
||||
|
||||
|
||||
|
||||
// -----------------------------------
|
||||
// Prepare for next line, potentially.
|
||||
// -----------------------------------
|
||||
if(column_ == 342) {
|
||||
access_pointer_ = column_ = output_column_ = 0;
|
||||
row_ = (row_ + 1) % frame_lines_;
|
||||
if(row_ == 192) status_ |= StatusInterrupt;
|
||||
|
||||
screen_mode_ = next_screen_mode_;
|
||||
blank_screen_ = next_blank_screen_;
|
||||
switch(screen_mode_) {
|
||||
case ScreenMode::Text:
|
||||
line_mode_ = LineMode::Text;
|
||||
first_pixel_column_ = 69;
|
||||
first_right_border_column_ = 309;
|
||||
break;
|
||||
default:
|
||||
line_mode_ = LineMode::Character;
|
||||
first_pixel_column_ = 63;
|
||||
first_right_border_column_ = 319;
|
||||
break;
|
||||
}
|
||||
if(blank_screen_ || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TMS9918Base::output_border(int cycles) {
|
||||
pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||
if(pixel_target_) *pixel_target_ = palette[background_colour_];
|
||||
crt_->output_level(static_cast<unsigned int>(cycles) * 4);
|
||||
}
|
||||
|
||||
void TMS9918::set_register(int address, uint8_t value) {
|
||||
// Writes to address 0 are writes to the video RAM. Store
|
||||
// the value and return.
|
||||
if(!(address & 1)) {
|
||||
write_phase_ = false;
|
||||
|
||||
// Enqueue the write to occur at the next available slot.
|
||||
read_ahead_buffer_ = value;
|
||||
queued_access_ = MemoryAccess::Write;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Writes to address 1 are performed in pairs; if this is the
|
||||
// low byte of a value, store it and wait for the high byte.
|
||||
if(!write_phase_) {
|
||||
low_write_ = value;
|
||||
write_phase_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
write_phase_ = false;
|
||||
if(value & 0x80) {
|
||||
// This is a write to a register.
|
||||
switch(value & 7) {
|
||||
case 0:
|
||||
next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
next_blank_screen_ = !(low_write_ & 0x40);
|
||||
generate_interrupts_ = !!(low_write_ & 0x20);
|
||||
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 2);
|
||||
sprites_16x16_ = !!(low_write_ & 0x02);
|
||||
sprites_magnified_ = !!(low_write_ & 0x01);
|
||||
|
||||
sprite_height_ = 8;
|
||||
if(sprites_16x16_) sprite_height_ <<= 1;
|
||||
if(sprites_magnified_) sprite_height_ <<= 1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
pattern_name_address_ = static_cast<uint16_t>((low_write_ & 0xf) << 10);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
colour_table_address_ = static_cast<uint16_t>(low_write_ << 6);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
pattern_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
sprite_attribute_table_address_ = static_cast<uint16_t>((low_write_ & 0x7f) << 7);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
sprite_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
text_colour_ = low_write_ >> 4;
|
||||
background_colour_ = low_write_ & 0xf;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// This is a write to the RAM pointer.
|
||||
ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8));
|
||||
if(!(value & 0x40)) {
|
||||
// Officially a 'read' set, so perform lookahead.
|
||||
queued_access_ = MemoryAccess::Read;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t TMS9918::get_register(int address) {
|
||||
write_phase_ = false;
|
||||
|
||||
// Reads from address 0 read video RAM, via the read-ahead buffer.
|
||||
if(!(address & 1)) {
|
||||
// Enqueue the write to occur at the next available slot.
|
||||
uint8_t result = read_ahead_buffer_;
|
||||
queued_access_ = MemoryAccess::Read;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reads from address 1 get the status register.
|
||||
uint8_t result = status_;
|
||||
status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision);
|
||||
return result;
|
||||
}
|
||||
|
||||
HalfCycles TMS9918::get_time_until_interrupt() {
|
||||
if(!generate_interrupts_) return HalfCycles(-1);
|
||||
if(get_interrupt_line()) return HalfCycles(0);
|
||||
|
||||
const int half_cycles_per_frame = frame_lines_ * 228 * 2;
|
||||
int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame;
|
||||
return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame);
|
||||
}
|
||||
|
||||
bool TMS9918::get_interrupt_line() {
|
||||
return (status_ & StatusInterrupt) && generate_interrupts_;
|
||||
}
|
||||
86
Components/9918/9918.hpp
Normal file
86
Components/9918/9918.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// 9918.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/11/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TMS9918_hpp
|
||||
#define TMS9918_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace TI {
|
||||
|
||||
/*!
|
||||
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
|
||||
vessel for emulation of sufficiently close derivatives, such as the Master System VDP.
|
||||
|
||||
The TMS9918 and descendants are video display generators that own their own RAM, making it
|
||||
accessible through an implicitly-timed register interface, and (depending on model) can generate
|
||||
PAL and NTSC component and composite video.
|
||||
|
||||
These chips have only one non-on-demand interaction with the outside world: an interrupt line.
|
||||
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
|
||||
*/
|
||||
class TMS9918: public TMS9918Base {
|
||||
public:
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
};
|
||||
|
||||
/*!
|
||||
Constructs an instance of the drive controller that behaves according to personality @c p.
|
||||
@param p The type of controller to emulate.
|
||||
*/
|
||||
TMS9918(Personality p);
|
||||
|
||||
enum TVStandard {
|
||||
/*! i.e. 50Hz output at around 312.5 lines/field */
|
||||
PAL,
|
||||
/*! i.e. 60Hz output at around 262.5 lines/field */
|
||||
NTSC
|
||||
};
|
||||
|
||||
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||
void set_tv_standard(TVStandard standard);
|
||||
|
||||
/*! Provides the CRT this TMS is connected to. */
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
/*!
|
||||
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
|
||||
*/
|
||||
void run_for(const HalfCycles cycles);
|
||||
|
||||
/*! Sets a register value. */
|
||||
void set_register(int address, uint8_t value);
|
||||
|
||||
/*! Gets a register value. */
|
||||
uint8_t get_register(int address);
|
||||
|
||||
/*!
|
||||
Returns the amount of time until get_interrupt_line would next return true if
|
||||
there are no interceding calls to set_register or get_register.
|
||||
|
||||
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
|
||||
never return true, returns -1.
|
||||
*/
|
||||
HalfCycles get_time_until_interrupt();
|
||||
|
||||
/*!
|
||||
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||
*/
|
||||
bool get_interrupt_line();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* TMS9918_hpp */
|
||||
101
Components/9918/Implementation/9918Base.hpp
Normal file
101
Components/9918/Implementation/9918Base.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// 9918Base.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TMS9918Base_hpp
|
||||
#define TMS9918Base_hpp
|
||||
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace TI {
|
||||
|
||||
class TMS9918Base {
|
||||
protected:
|
||||
TMS9918Base();
|
||||
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
uint8_t ram_[16384];
|
||||
|
||||
uint16_t ram_pointer_ = 0;
|
||||
uint8_t read_ahead_buffer_ = 0;
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
} queued_access_ = MemoryAccess::None;
|
||||
|
||||
uint8_t status_ = 0;
|
||||
|
||||
bool write_phase_ = false;
|
||||
uint8_t low_write_ = 0;
|
||||
|
||||
// The various register flags.
|
||||
int next_screen_mode_ = 0, screen_mode_ = 0;
|
||||
bool next_blank_screen_ = true, blank_screen_ = true;
|
||||
bool sprites_16x16_ = false;
|
||||
bool sprites_magnified_ = false;
|
||||
bool generate_interrupts_ = false;
|
||||
int sprite_height_ = 8;
|
||||
uint16_t pattern_name_address_ = 0;
|
||||
uint16_t colour_table_address_ = 0;
|
||||
uint16_t pattern_generator_table_address_ = 0;
|
||||
uint16_t sprite_attribute_table_address_ = 0;
|
||||
uint16_t sprite_generator_table_address_ = 0;
|
||||
|
||||
uint8_t text_colour_ = 0;
|
||||
uint8_t background_colour_ = 0;
|
||||
|
||||
HalfCycles half_cycles_into_frame_;
|
||||
int column_ = 0, row_ = 0, output_column_ = 0;
|
||||
int cycles_error_ = 0;
|
||||
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
|
||||
|
||||
void output_border(int cycles);
|
||||
|
||||
// Vertical timing details.
|
||||
int frame_lines_ = 262;
|
||||
int first_vsync_line_ = 227;
|
||||
|
||||
// Horizontal selections.
|
||||
enum class LineMode {
|
||||
Text = 0,
|
||||
Character = 1,
|
||||
Refresh = 2
|
||||
} line_mode_ = LineMode::Text;
|
||||
int first_pixel_column_, first_right_border_column_;
|
||||
|
||||
uint8_t pattern_names_[40];
|
||||
uint8_t pattern_buffer_[40];
|
||||
uint8_t colour_buffer_[40];
|
||||
|
||||
struct SpriteSet {
|
||||
struct ActiveSprite {
|
||||
int index = 0;
|
||||
int row = 0;
|
||||
|
||||
uint8_t info[4];
|
||||
uint8_t image[2];
|
||||
|
||||
int shift_position = 0;
|
||||
} active_sprites[4];
|
||||
int active_sprite_slot = 0;
|
||||
} sprite_sets_[2];
|
||||
int active_sprite_set_ = 0;
|
||||
bool sprites_stopped_ = false;
|
||||
|
||||
int access_pointer_ = 0;
|
||||
|
||||
inline void test_sprite(int sprite_number, int screen_row);
|
||||
inline void get_sprite_contents(int start, int cycles, int screen_row);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TMS9918Base_hpp */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user