mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	Compare commits
	
		
			543 Commits
		
	
	
		
			2017-10-24
			...
			2018-04-07
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | 
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -18,9 +18,14 @@ DerivedData
 | 
				
			|||||||
*.xcuserstate
 | 
					*.xcuserstate
 | 
				
			||||||
.DS_Store
 | 
					.DS_Store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Exclude system ROMs
 | 
					# Exclude system ROMs and unit test ROMs
 | 
				
			||||||
ROMImages/*
 | 
					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
 | 
					# CocoaPods
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							@@ -1,5 +1,13 @@
 | 
				
			|||||||
language: objective-c
 | 
					# language: objective-c
 | 
				
			||||||
osx_image: xcode8.2
 | 
					# osx_image: xcode8.2
 | 
				
			||||||
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
 | 
					# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
 | 
				
			||||||
xcode_scheme: Clock Signal
 | 
					# xcode_scheme: Clock Signal
 | 
				
			||||||
xcode_sdk: macosx10.12
 | 
					# xcode_sdk: macosx10.12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					language: cpp
 | 
				
			||||||
 | 
					before_install:
 | 
				
			||||||
 | 
						- sudo apt-get install libsdl2-dev
 | 
				
			||||||
 | 
					script: cd OSBindings/SDL && scons
 | 
				
			||||||
 | 
					compiler:
 | 
				
			||||||
 | 
						- clang
 | 
				
			||||||
 | 
						- gcc
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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 */
 | 
				
			||||||
							
								
								
									
										109
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										79
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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 */
 | 
				
			||||||
							
								
								
									
										27
									
								
								Analyser/Machines.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Machines.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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,
 | 
				
			||||||
 | 
						Atari2600,
 | 
				
			||||||
 | 
						ColecoVision,
 | 
				
			||||||
 | 
						Electron,
 | 
				
			||||||
 | 
						MSX,
 | 
				
			||||||
 | 
						Oric,
 | 
				
			||||||
 | 
						Vic20,
 | 
				
			||||||
 | 
						ZX8081
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Machines_h */
 | 
				
			||||||
@@ -7,14 +7,16 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Disk.hpp"
 | 
					#include "Disk.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/Controller/DiskController.hpp"
 | 
					
 | 
				
			||||||
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
					#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
				
			||||||
#include "../../NumberTheory/CRC.hpp"
 | 
					#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
				
			||||||
 | 
					#include "../../../NumberTheory/CRC.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace StaticAnalyser::Acorn;
 | 
					using namespace Analyser::Static::Acorn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
					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
 | 
						// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | 
				
			||||||
	std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
						std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
				
			||||||
	Storage::Encodings::MFM::Parser parser(false, disk);
 | 
						Storage::Encodings::MFM::Parser parser(false, disk);
 | 
				
			||||||
@@ -23,37 +25,36 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
 | 
				
			|||||||
	Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
 | 
						Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!names || !details) return nullptr;
 | 
						if(!names || !details) return nullptr;
 | 
				
			||||||
	if(names->data.size() != 256 || details->data.size() != 256) 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->data[5];
 | 
						uint8_t final_file_offset = details->samples[0][5];
 | 
				
			||||||
	if(final_file_offset&7) return nullptr;
 | 
						if(final_file_offset&7) return nullptr;
 | 
				
			||||||
	if(final_file_offset < 8) return nullptr;
 | 
						if(final_file_offset < 8) return nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	char disk_name[13];
 | 
						char disk_name[13];
 | 
				
			||||||
	snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]);
 | 
						snprintf(disk_name, 13, "%.8s%.4s", &names->samples[0][0], &details->samples[0][0]);
 | 
				
			||||||
	catalogue->name = disk_name;
 | 
						catalogue->name = disk_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch((details->data[6] >> 4)&3) {
 | 
						switch((details->samples[0][6] >> 4)&3) {
 | 
				
			||||||
		case 0: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
							case 0: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
				
			||||||
		case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
							case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
				
			||||||
		case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
							case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
				
			||||||
		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
							case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// DFS files are stored contiguously, and listed in descending order of distance from track 0.
 | 
						for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
 | 
				
			||||||
	// So iterating backwards implies the least amount of seeking.
 | 
					 | 
				
			||||||
	for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) {
 | 
					 | 
				
			||||||
		File new_file;
 | 
							File new_file;
 | 
				
			||||||
		char name[10];
 | 
							char name[10];
 | 
				
			||||||
		snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]);
 | 
							snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
 | 
				
			||||||
		new_file.name = name;
 | 
							new_file.name = name;
 | 
				
			||||||
		new_file.load_address = (uint32_t)(details->data[file_offset] | (details->data[file_offset+1] << 8) | ((details->data[file_offset+6]&0x0c) << 14));
 | 
							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->data[file_offset+2] | (details->data[file_offset+3] << 8) | ((details->data[file_offset+6]&0xc0) << 10));
 | 
							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->data[file_offset + 7] & 0x80);
 | 
							new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		long data_length = static_cast<long>(details->data[file_offset+4] | (details->data[file_offset+5] << 8) | ((details->data[file_offset+6]&0x30) << 12));
 | 
							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->data[file_offset+7] | ((details->data[file_offset+6]&0x03) << 8);
 | 
							int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
 | 
				
			||||||
		new_file.data.reserve(static_cast<size_t>(data_length));
 | 
							new_file.data.reserve(static_cast<std::size_t>(data_length));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(start_sector < 2) continue;
 | 
							if(start_sector < 2) continue;
 | 
				
			||||||
		while(data_length > 0) {
 | 
							while(data_length > 0) {
 | 
				
			||||||
@@ -65,15 +66,15 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
 | 
				
			|||||||
			if(!next_sector) break;
 | 
								if(!next_sector) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			long length_from_sector = std::min(data_length, 256l);
 | 
								long length_from_sector = std::min(data_length, 256l);
 | 
				
			||||||
			new_file.data.insert(new_file.data.end(), next_sector->data.begin(), next_sector->data.begin() + length_from_sector);
 | 
								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;
 | 
								data_length -= length_from_sector;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(!data_length) catalogue->files.push_front(new_file);
 | 
							if(!data_length) catalogue->files.push_back(new_file);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return catalogue;
 | 
						return catalogue;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
					std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
				
			||||||
	std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
						std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
				
			||||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
						Storage::Encodings::MFM::Parser parser(true, disk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,7 +86,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
 | 
				
			|||||||
	for(uint8_t c = 2; c < 7; c++) {
 | 
						for(uint8_t c = 2; c < 7; c++) {
 | 
				
			||||||
		Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
 | 
							Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
 | 
				
			||||||
		if(!sector) return nullptr;
 | 
							if(!sector) return nullptr;
 | 
				
			||||||
		root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
 | 
							root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Quick sanity checks.
 | 
						// Quick sanity checks.
 | 
				
			||||||
@@ -93,7 +94,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
 | 
				
			|||||||
	if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') 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;
 | 
						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->data[0xfd]) {
 | 
						switch(free_space_map_second_half->samples[0][0xfd]) {
 | 
				
			||||||
		default: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
							default: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
				
			||||||
		case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
							case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
				
			||||||
		case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
							case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
				
			||||||
@@ -10,15 +10,16 @@
 | 
				
			|||||||
#define StaticAnalyser_Acorn_Disk_hpp
 | 
					#define StaticAnalyser_Acorn_Disk_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "File.hpp"
 | 
					#include "File.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/Disk.hpp"
 | 
					#include "../../../Storage/Disk/Disk.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Acorn {
 | 
					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 {
 | 
					struct Catalogue {
 | 
				
			||||||
	std::string name;
 | 
						std::string name;
 | 
				
			||||||
	std::list<File> files;
 | 
						std::vector<File> files;
 | 
				
			||||||
	enum class BootOption {
 | 
						enum class BootOption {
 | 
				
			||||||
		None,
 | 
							None,
 | 
				
			||||||
		LoadBOOT,
 | 
							LoadBOOT,
 | 
				
			||||||
@@ -30,6 +31,7 @@ struct Catalogue {
 | 
				
			|||||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
					std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
				
			||||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
					std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,12 +9,12 @@
 | 
				
			|||||||
#ifndef StaticAnalyser_Acorn_File_hpp
 | 
					#ifndef StaticAnalyser_Acorn_File_hpp
 | 
				
			||||||
#define StaticAnalyser_Acorn_File_hpp
 | 
					#define StaticAnalyser_Acorn_File_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <list>
 | 
					 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Acorn {
 | 
					namespace Acorn {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct File {
 | 
					struct File {
 | 
				
			||||||
@@ -38,9 +38,10 @@ struct File {
 | 
				
			|||||||
		std::vector<uint8_t> data;
 | 
							std::vector<uint8_t> data;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::list<Chunk> chunks;
 | 
						std::vector<Chunk> chunks;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,22 +10,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "Disk.hpp"
 | 
					#include "Disk.hpp"
 | 
				
			||||||
#include "Tape.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>>
 | 
					static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			||||||
		AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
							AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
				
			||||||
	std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
 | 
						std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
 | 
						for(const auto &cartridge : cartridges) {
 | 
				
			||||||
		const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
 | 
							const auto &segments = cartridge->get_segments();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// only one mapped item is allowed
 | 
							// only one mapped item is allowed
 | 
				
			||||||
		if(segments.size() != 1) continue;
 | 
							if(segments.size() != 1) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// which must be 16 kb in size
 | 
							// which must be 8 or 16 kb in size
 | 
				
			||||||
		Storage::Cartridge::Cartridge::Segment segment = segments.front();
 | 
							const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
				
			||||||
		if(segment.data.size() != 0x4000) continue;
 | 
							if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// is a copyright string present?
 | 
							// is a copyright string present?
 | 
				
			||||||
		uint8_t copyright_offset = segment.data[7];
 | 
							uint8_t copyright_offset = segment.data[7];
 | 
				
			||||||
@@ -56,21 +57,21 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
	return acorn_cartridges;
 | 
						return acorn_cartridges;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &destination) {
 | 
					void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
 | 
				
			||||||
	Target target;
 | 
						std::unique_ptr<Target> target(new Target);
 | 
				
			||||||
	target.machine = Target::Electron;
 | 
						target->machine = Machine::Electron;
 | 
				
			||||||
	target.probability = 1.0; // TODO: a proper estimation
 | 
						target->confidence = 0.5; // TODO: a proper estimation
 | 
				
			||||||
	target.acorn.has_dfs = false;
 | 
						target->has_dfs = false;
 | 
				
			||||||
	target.acorn.has_adfs = false;
 | 
						target->has_adfs = false;
 | 
				
			||||||
	target.acorn.should_shift_restart = false;
 | 
						target->should_shift_restart = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// strip out inappropriate cartridges
 | 
						// 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 there are any tapes, attempt to get data from the first
 | 
				
			||||||
	if(media.tapes.size() > 0) {
 | 
						if(media.tapes.size() > 0) {
 | 
				
			||||||
		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
 | 
							std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
 | 
				
			||||||
		std::list<File> files = GetFiles(tape);
 | 
							std::vector<File> files = GetFiles(tape);
 | 
				
			||||||
		tape->reset();
 | 
							tape->reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// continue if there are any files
 | 
							// 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,
 | 
								// 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
 | 
								// so that's also justification to *RUN
 | 
				
			||||||
			size_t pointer = 0;
 | 
								std::size_t pointer = 0;
 | 
				
			||||||
			uint8_t *data = &files.front().data[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) {
 | 
								while(1) {
 | 
				
			||||||
				if(pointer >= data_size-1 || data[pointer] != 13) {
 | 
									if(pointer >= data_size-1 || data[pointer] != 13) {
 | 
				
			||||||
					is_basic = false;
 | 
										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
 | 
								// Inspect first file. If it's protected or doesn't look like BASIC
 | 
				
			||||||
			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | 
								// 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,19 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
 | 
				
			|||||||
		dfs_catalogue = GetDFSCatalogue(disk);
 | 
							dfs_catalogue = GetDFSCatalogue(disk);
 | 
				
			||||||
		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
 | 
							if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
 | 
				
			||||||
		if(dfs_catalogue || adfs_catalogue) {
 | 
							if(dfs_catalogue || adfs_catalogue) {
 | 
				
			||||||
			target.media.disks = media.disks;
 | 
								target->media.disks = media.disks;
 | 
				
			||||||
			target.acorn.has_dfs = !!dfs_catalogue;
 | 
								target->has_dfs = !!dfs_catalogue;
 | 
				
			||||||
			target.acorn.has_adfs = !!adfs_catalogue;
 | 
								target->has_adfs = !!adfs_catalogue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
								Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
				
			||||||
			if(bootOption != Catalogue::BootOption::None)
 | 
								if(bootOption != Catalogue::BootOption::None)
 | 
				
			||||||
				target.acorn.should_shift_restart = true;
 | 
									target->should_shift_restart = true;
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				target.loadingCommand = "*CAT\n";
 | 
									target->loading_command = "*CAT\n";
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
 | 
						if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) {
 | 
				
			||||||
		destination.push_back(target);
 | 
							destination.push_back(std::move(target));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -11,11 +11,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Acorn {
 | 
					namespace Acorn {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AddTargets(const Media &media, std::list<Target> &destination);
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,10 +9,11 @@
 | 
				
			|||||||
#include "Tape.hpp"
 | 
					#include "Tape.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <deque>
 | 
					#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) {
 | 
					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);
 | 
						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
 | 
						// read out name
 | 
				
			||||||
	char name[11];
 | 
						char name[11];
 | 
				
			||||||
	size_t name_ptr = 0;
 | 
						std::size_t name_ptr = 0;
 | 
				
			||||||
	while(!tape->is_at_end() && name_ptr < sizeof(name)) {
 | 
						while(!tape->is_at_end() && name_ptr < sizeof(name)) {
 | 
				
			||||||
		name[name_ptr] = (char)parser.get_next_byte(tape);
 | 
							name[name_ptr] = (char)parser.get_next_byte(tape);
 | 
				
			||||||
		if(!name[name_ptr]) break;
 | 
							if(!name[name_ptr]) break;
 | 
				
			||||||
@@ -118,7 +119,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
 | 
				
			|||||||
	return file;
 | 
						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;
 | 
						Storage::Tape::Acorn::Parser parser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// populate chunk list
 | 
						// populate chunk list
 | 
				
			||||||
@@ -131,7 +132,7 @@ std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::T
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// decompose into file list
 | 
						// decompose into file list
 | 
				
			||||||
	std::list<File> file_list;
 | 
						std::vector<File> file_list;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while(chunk_list.size()) {
 | 
						while(chunk_list.size()) {
 | 
				
			||||||
		std::unique_ptr<File> next_file = GetNextFile(chunk_list);
 | 
							std::unique_ptr<File> next_file = GetNextFile(chunk_list);
 | 
				
			||||||
@@ -12,13 +12,15 @@
 | 
				
			|||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "File.hpp"
 | 
					#include "File.hpp"
 | 
				
			||||||
#include "../../Storage/Tape/Tape.hpp"
 | 
					#include "../../../Storage/Tape/Tape.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Acorn {
 | 
					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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_Acorn_Target_h */
 | 
				
			||||||
@@ -8,13 +8,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Storage/Disk/Parsers/CPM.hpp"
 | 
					#include <algorithm>
 | 
				
			||||||
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
					#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) {
 | 
					static bool strcmp_insensitive(const char *a, const char *b) {
 | 
				
			||||||
	if(strlen(a) != strlen(b)) return false;
 | 
						if(std::strlen(a) != std::strlen(b)) return false;
 | 
				
			||||||
	while(*a) {
 | 
						while(*a) {
 | 
				
			||||||
		if(tolower(*a) != towlower(*b)) return false;
 | 
							if(std::tolower(*a) != std::tolower(*b)) return false;
 | 
				
			||||||
		a++;
 | 
							a++;
 | 
				
			||||||
		b++;
 | 
							b++;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -55,7 +60,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static void InspectCatalogue(
 | 
					static void InspectCatalogue(
 | 
				
			||||||
	const Storage::Disk::CPM::Catalogue &catalogue,
 | 
						const Storage::Disk::CPM::Catalogue &catalogue,
 | 
				
			||||||
	StaticAnalyser::Target &target) {
 | 
						const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::vector<const Storage::Disk::CPM::File *> candidate_files;
 | 
						std::vector<const Storage::Disk::CPM::File *> candidate_files;
 | 
				
			||||||
	candidate_files.reserve(catalogue.files.size());
 | 
						candidate_files.reserve(catalogue.files.size());
 | 
				
			||||||
@@ -92,7 +97,7 @@ static void InspectCatalogue(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// If there's just one file, run that.
 | 
						// If there's just one file, run that.
 | 
				
			||||||
	if(candidate_files.size() == 1) {
 | 
						if(candidate_files.size() == 1) {
 | 
				
			||||||
		target.loadingCommand = RunCommandFor(*candidate_files[0]);
 | 
							target->loading_command = RunCommandFor(*candidate_files[0]);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -101,10 +106,10 @@ static void InspectCatalogue(
 | 
				
			|||||||
	int basic_files = 0;
 | 
						int basic_files = 0;
 | 
				
			||||||
	int implicit_suffixed_files = 0;
 | 
						int implicit_suffixed_files = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	size_t last_basic_file = 0;
 | 
						std::size_t last_basic_file = 0;
 | 
				
			||||||
	size_t last_implicit_suffixed_file = 0;
 | 
						std::size_t last_implicit_suffixed_file = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for(size_t c = 0; c < candidate_files.size(); c++) {
 | 
						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.
 | 
							// 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 == "        ")
 | 
							if(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ")
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
@@ -122,16 +127,16 @@ static void InspectCatalogue(
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(basic_files == 1 || implicit_suffixed_files == 1) {
 | 
						if(basic_files == 1 || implicit_suffixed_files == 1) {
 | 
				
			||||||
		size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
 | 
							std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
 | 
				
			||||||
		target.loadingCommand = RunCommandFor(*candidate_files[selected_file]);
 | 
							target->loading_command = RunCommandFor(*candidate_files[selected_file]);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// One more guess: if only one remaining candidate file has a different name than the others,
 | 
						// One more guess: if only one remaining candidate file has a different name than the others,
 | 
				
			||||||
	// assume it is intended to stand out.
 | 
						// assume it is intended to stand out.
 | 
				
			||||||
	std::map<std::string, int> name_counts;
 | 
						std::map<std::string, int> name_counts;
 | 
				
			||||||
	std::map<std::string, size_t> indices_by_name;
 | 
						std::map<std::string, std::size_t> indices_by_name;
 | 
				
			||||||
	size_t index = 0;
 | 
						std::size_t index = 0;
 | 
				
			||||||
	for(auto file : candidate_files) {
 | 
						for(auto file : candidate_files) {
 | 
				
			||||||
		name_counts[file->name]++;
 | 
							name_counts[file->name]++;
 | 
				
			||||||
		indices_by_name[file->name] = index;
 | 
							indices_by_name[file->name] = index;
 | 
				
			||||||
@@ -140,25 +145,25 @@ static void InspectCatalogue(
 | 
				
			|||||||
	if(name_counts.size() == 2) {
 | 
						if(name_counts.size() == 2) {
 | 
				
			||||||
		for(auto &pair : name_counts) {
 | 
							for(auto &pair : name_counts) {
 | 
				
			||||||
			if(pair.second == 1) {
 | 
								if(pair.second == 1) {
 | 
				
			||||||
				target.loadingCommand = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
 | 
									target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Desperation.
 | 
						// Desperation.
 | 
				
			||||||
	target.loadingCommand = "cat\n";
 | 
						target->loading_command = "cat\n";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) {
 | 
					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::Parser parser(true, disk);
 | 
				
			||||||
	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
 | 
						Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
 | 
				
			||||||
	if(boot_sector != nullptr) {
 | 
						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
 | 
							// 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.
 | 
							// this disk was formatted and the filler byte never replaced.
 | 
				
			||||||
		bool matched = true;
 | 
							bool matched = true;
 | 
				
			||||||
		for(size_t c = 1; c < 64; c++) {
 | 
							for(std::size_t c = 1; c < 64; c++) {
 | 
				
			||||||
			if(boot_sector->data[c] != boot_sector->data[0]) {
 | 
								if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) {
 | 
				
			||||||
				matched = false;
 | 
									matched = false;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -166,7 +171,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// This is a system disk, then launch it as though it were CP/M.
 | 
							// This is a system disk, then launch it as though it were CP/M.
 | 
				
			||||||
		if(!matched) {
 | 
							if(!matched) {
 | 
				
			||||||
			target.loadingCommand = "|cpm\n";
 | 
								target->loading_command = "|cpm\n";
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -174,24 +179,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St
 | 
				
			|||||||
	return false;
 | 
						return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target> &destination) {
 | 
					void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
 | 
				
			||||||
	Target target;
 | 
						std::unique_ptr<Target> target(new Target);
 | 
				
			||||||
	target.machine = Target::AmstradCPC;
 | 
						target->machine = Machine::AmstradCPC;
 | 
				
			||||||
	target.probability = 1.0;
 | 
						target->confidence = 0.5;
 | 
				
			||||||
	target.media.disks = media.disks;
 | 
					 | 
				
			||||||
	target.media.tapes = media.tapes;
 | 
					 | 
				
			||||||
	target.media.cartridges = media.cartridges;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	target.amstradcpc.model = AmstradCPCModel::CPC6128;
 | 
						target->model = Target::Model::CPC6128;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(!media.tapes.empty()) {
 | 
				
			||||||
 | 
							// TODO: which of these are actually potentially CPC tapes?
 | 
				
			||||||
 | 
							target->media.tapes = media.tapes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!target.media.tapes.empty()) {
 | 
					 | 
				
			||||||
		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | 
							// 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
 | 
							// enter and responding to the follow-on prompt to press a key, so just type for
 | 
				
			||||||
		// a while. Yuck!
 | 
							// a while. Yuck!
 | 
				
			||||||
		target.loadingCommand = "|tape\nrun\"\n1234567890";
 | 
							target->loading_command = "|tape\nrun\"\n1234567890";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!target.media.disks.empty()) {
 | 
						if(!media.disks.empty()) {
 | 
				
			||||||
		Storage::Disk::CPM::ParameterBlock data_format;
 | 
							Storage::Disk::CPM::ParameterBlock data_format;
 | 
				
			||||||
		data_format.sectors_per_track = 9;
 | 
							data_format.sectors_per_track = 9;
 | 
				
			||||||
		data_format.tracks = 40;
 | 
							data_format.tracks = 40;
 | 
				
			||||||
@@ -200,11 +205,6 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target
 | 
				
			|||||||
		data_format.catalogue_allocation_bitmap = 0xc000;
 | 
							data_format.catalogue_allocation_bitmap = 0xc000;
 | 
				
			||||||
		data_format.reserved_tracks = 0;
 | 
							data_format.reserved_tracks = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), data_format);
 | 
					 | 
				
			||||||
		if(data_catalogue) {
 | 
					 | 
				
			||||||
			InspectCatalogue(*data_catalogue, target);
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			if(!CheckBootSector(target.media.disks.front(), target)) {
 | 
					 | 
				
			||||||
		Storage::Disk::CPM::ParameterBlock system_format;
 | 
							Storage::Disk::CPM::ParameterBlock system_format;
 | 
				
			||||||
		system_format.sectors_per_track = 9;
 | 
							system_format.sectors_per_track = 9;
 | 
				
			||||||
		system_format.tracks = 40;
 | 
							system_format.tracks = 40;
 | 
				
			||||||
@@ -213,13 +213,32 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target
 | 
				
			|||||||
		system_format.catalogue_allocation_bitmap = 0xc000;
 | 
							system_format.catalogue_allocation_bitmap = 0xc000;
 | 
				
			||||||
		system_format.reserved_tracks = 2;
 | 
							system_format.reserved_tracks = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), system_format);
 | 
							for(const 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) {
 | 
								if(system_catalogue) {
 | 
				
			||||||
				InspectCatalogue(*system_catalogue, target);
 | 
									InspectCatalogue(*system_catalogue, target);
 | 
				
			||||||
				}
 | 
									target->media.disks.push_back(disk);
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	destination.push_back(target);
 | 
						// If any media survived, add the target.
 | 
				
			||||||
 | 
						if(!target->media.empty())
 | 
				
			||||||
 | 
							destination.push_back(std::move(target));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -11,11 +11,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace AmstradCPC {
 | 
					namespace AmstradCPC {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AddTargets(const Media &media, std::list<Target> &destination);
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										33
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace AmstradCPC {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Target: public ::Analyser::Static::Target {
 | 
				
			||||||
 | 
						enum class Model {
 | 
				
			||||||
 | 
							CPC464,
 | 
				
			||||||
 | 
							CPC664,
 | 
				
			||||||
 | 
							CPC6128
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Model model = Model::CPC464;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_AmstradCPC_Target_h */
 | 
				
			||||||
@@ -8,11 +8,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../Disassembler/Disassembler6502.hpp"
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace StaticAnalyser::Atari;
 | 
					#include "../Disassembler/6502.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
					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
 | 
						// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | 
				
			||||||
	uint16_t entry_address, break_address;
 | 
						uint16_t entry_address, break_address;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,21 +24,21 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
 | 
				
			|||||||
	// a CommaVid start address needs to be outside of its RAM
 | 
						// a CommaVid start address needs to be outside of its RAM
 | 
				
			||||||
	if(entry_address < 0x1800 || break_address < 0x1800) return;
 | 
						if(entry_address < 0x1800 || break_address < 0x1800) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::function<size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
 | 
						std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
 | 
				
			||||||
		address &= 0x1fff;
 | 
							address &= 0x1fff;
 | 
				
			||||||
		return static_cast<size_t>(address - 0x1800);
 | 
							return static_cast<std::size_t>(address - 0x1800);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	StaticAnalyser::MOS6502::Disassembly high_location_disassembly =
 | 
						Analyser::Static::MOS6502::Disassembly high_location_disassembly =
 | 
				
			||||||
		StaticAnalyser::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
 | 
							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
 | 
						// assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 
				
			||||||
	// large amounts of memory
 | 
						// large amounts of memory
 | 
				
			||||||
	bool has_wide_area_store = false;
 | 
						bool has_wide_area_store = false;
 | 
				
			||||||
	for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
 | 
						for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
 | 
				
			||||||
		if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA) {
 | 
							if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
 | 
				
			||||||
			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect;
 | 
								has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
 | 
				
			||||||
			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX;
 | 
								has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
 | 
				
			||||||
			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY;
 | 
								has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(has_wide_area_store) break;
 | 
								if(has_wide_area_store) break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -46,10 +48,10 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
 | 
				
			|||||||
	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | 
						// 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
 | 
						// 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
 | 
						// attempts to modify itself but it probably doesn't
 | 
				
			||||||
	if(has_wide_area_store) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid;
 | 
						if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
 | 
					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
 | 
						// 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
 | 
						// 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?)
 | 
						// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | 
				
			||||||
@@ -58,12 +60,12 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
 | 
				
			|||||||
		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
 | 
							(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
 | 
				
			||||||
		segment.data[0] == 0x78
 | 
							segment.data[0] == 0x78
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ActivisionStack;
 | 
							target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// make an assumption that this is the Atari paging model
 | 
						// make an assumption that this is the Atari paging model
 | 
				
			||||||
	target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari8k;
 | 
						target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::set<uint16_t> internal_accesses;
 | 
						std::set<uint16_t> internal_accesses;
 | 
				
			||||||
	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
						internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
				
			||||||
@@ -83,13 +85,13 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
 | 
				
			|||||||
		tigervision_access_count += masked_address == 0x3f;
 | 
							tigervision_access_count += masked_address == 0x3f;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(parker_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ParkerBros;
 | 
						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.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
 | 
						else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
 | 
					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
 | 
						// make an assumption that this is the Atari paging model
 | 
				
			||||||
	target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari16k;
 | 
						target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::set<uint16_t> internal_accesses;
 | 
						std::set<uint16_t> internal_accesses;
 | 
				
			||||||
	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
						internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
				
			||||||
@@ -104,17 +106,17 @@ static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const
 | 
				
			|||||||
		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
 | 
							mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(mnetwork_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::MNetwork;
 | 
						if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor64kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
 | 
					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
 | 
						// make an assumption that this is a Tigervision if there is a write to 3F
 | 
				
			||||||
	target.atari.paging_model =
 | 
						target.paging_model =
 | 
				
			||||||
		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
 | 
							(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
 | 
				
			||||||
			StaticAnalyser::Atari2600PagingModel::Tigervision : StaticAnalyser::Atari2600PagingModel::MegaBoy;
 | 
								Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
					static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
				
			||||||
	if(segment.data.size() == 2048) {
 | 
						if(segment.data.size() == 2048) {
 | 
				
			||||||
		DeterminePagingFor2kCartridge(target, segment);
 | 
							DeterminePagingFor2kCartridge(target, segment);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
@@ -125,29 +127,29 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
 | 
				
			|||||||
	entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
 | 
						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));
 | 
						break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::function<size_t(uint16_t address)> address_mapper = [](uint16_t address) {
 | 
						std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
 | 
				
			||||||
		if(!(address & 0x1000)) return static_cast<size_t>(-1);
 | 
							if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
 | 
				
			||||||
		return static_cast<size_t>(address & 0xfff);
 | 
							return static_cast<std::size_t>(address & 0xfff);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
 | 
						std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
 | 
				
			||||||
	StaticAnalyser::MOS6502::Disassembly disassembly = StaticAnalyser::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
						Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch(segment.data.size()) {
 | 
						switch(segment.data.size()) {
 | 
				
			||||||
		case 8192:
 | 
							case 8192:
 | 
				
			||||||
			DeterminePagingFor8kCartridge(target, segment, disassembly);
 | 
								DeterminePagingFor8kCartridge(target, segment, disassembly);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 10495:
 | 
							case 10495:
 | 
				
			||||||
			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Pitfall2;
 | 
								target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 12288:
 | 
							case 12288:
 | 
				
			||||||
			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CBSRamPlus;
 | 
								target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 16384:
 | 
							case 16384:
 | 
				
			||||||
			DeterminePagingFor16kCartridge(target, segment, disassembly);
 | 
								DeterminePagingFor16kCartridge(target, segment, disassembly);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 32768:
 | 
							case 32768:
 | 
				
			||||||
			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari32k;
 | 
								target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 65536:
 | 
							case 65536:
 | 
				
			||||||
			DeterminePagingFor64kCartridge(target, segment, disassembly);
 | 
								DeterminePagingFor64kCartridge(target, segment, disassembly);
 | 
				
			||||||
@@ -159,43 +161,43 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
 | 
				
			|||||||
	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | 
						// 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
 | 
						// 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.
 | 
						// next 128 bytes. So check for that.
 | 
				
			||||||
	if(	target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::CBSRamPlus &&
 | 
						if(	target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
 | 
				
			||||||
		target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork) {
 | 
							target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
 | 
				
			||||||
		bool has_superchip = true;
 | 
							bool has_superchip = true;
 | 
				
			||||||
		for(size_t address = 0; address < 128; address++) {
 | 
							for(std::size_t address = 0; address < 128; address++) {
 | 
				
			||||||
			if(segment.data[address] != segment.data[address+128]) {
 | 
								if(segment.data[address] != segment.data[address+128]) {
 | 
				
			||||||
				has_superchip = false;
 | 
									has_superchip = false;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		target.atari.uses_superchip = has_superchip;
 | 
							target.uses_superchip = has_superchip;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check for a Tigervision or Tigervision-esque scheme
 | 
						// check for a Tigervision or Tigervision-esque scheme
 | 
				
			||||||
	if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096) {
 | 
						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();
 | 
							bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
 | 
				
			||||||
		if(looks_like_tigervision) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
 | 
							if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &destination) {
 | 
					void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
 | 
				
			||||||
	// TODO: sanity checking; is this image really for an Atari 2600.
 | 
						// TODO: sanity checking; is this image really for an Atari 2600?
 | 
				
			||||||
	Target target;
 | 
						std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
 | 
				
			||||||
	target.machine = Target::Atari2600;
 | 
						target->machine = Machine::Atari2600;
 | 
				
			||||||
	target.probability = 1.0;
 | 
						target->confidence = 0.5;
 | 
				
			||||||
	target.media.cartridges = media.cartridges;
 | 
						target->media.cartridges = media.cartridges;
 | 
				
			||||||
	target.atari.paging_model = Atari2600PagingModel::None;
 | 
						target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
 | 
				
			||||||
	target.atari.uses_superchip = false;
 | 
						target->uses_superchip = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// try to figure out the paging scheme
 | 
						// try to figure out the paging scheme
 | 
				
			||||||
	if(!media.cartridges.empty()) {
 | 
						if(!media.cartridges.empty()) {
 | 
				
			||||||
		const std::list<Storage::Cartridge::Cartridge::Segment> &segments = media.cartridges.front()->get_segments();
 | 
							const auto &segments = media.cartridges.front()->get_segments();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(segments.size() == 1) {
 | 
							if(segments.size() == 1) {
 | 
				
			||||||
			const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
								const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
				
			||||||
			DeterminePagingForCartridge(target, segment);
 | 
								DeterminePagingForCartridge(*target, segment);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	destination.push_back(target);
 | 
						destination.push_back(std::move(target));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -11,11 +11,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Atari {
 | 
					namespace Atari {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AddTargets(const Media &media, std::list<Target> &destination);
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										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 */
 | 
				
			||||||
							
								
								
									
										62
									
								
								Analyser/Static/Coleco/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Analyser/Static/Coleco/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
 | 
				
			||||||
 | 
						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())
 | 
				
			||||||
 | 
							destination.push_back(std::move(target));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								Analyser/Static/Coleco/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Analyser/Static/Coleco/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace Coleco {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* StaticAnalyser_hpp */
 | 
				
			||||||
@@ -7,15 +7,15 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Disk.hpp"
 | 
					#include "Disk.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/Controller/DiskController.hpp"
 | 
					#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
					#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
				
			||||||
#include "../../Storage/Data/Commodore.hpp"
 | 
					#include "../../../Storage/Data/Commodore.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <limits>
 | 
					#include <limits>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
#include <array>
 | 
					#include <array>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace StaticAnalyser::Commodore;
 | 
					using namespace Analyser::Static::Commodore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
					class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
@@ -71,19 +71,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
			bit_count_++;
 | 
								bit_count_++;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		unsigned int proceed_to_next_block() {
 | 
							unsigned int proceed_to_next_block(int max_index_count) {
 | 
				
			||||||
			// find GCR lead-in
 | 
								// find GCR lead-in
 | 
				
			||||||
			proceed_to_shift_value(0x3ff);
 | 
								proceed_to_shift_value(0x3ff);
 | 
				
			||||||
			if(shift_register_ != 0x3ff) return 0xff;
 | 
								if(shift_register_ != 0x3ff) return 0xff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// find end of lead-in
 | 
								// 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));
 | 
									run_for(Cycles(1));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// continue for a further nine bits
 | 
								// continue for a further nine bits
 | 
				
			||||||
			bit_count_ = 0;
 | 
								bit_count_ = 0;
 | 
				
			||||||
			while(bit_count_ < 9 && index_count_ < 2) {
 | 
								while(bit_count_ < 9 && index_count_ < max_index_count) {
 | 
				
			||||||
				run_for(Cycles(1));
 | 
									run_for(Cycles(1));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -97,8 +97,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void proceed_to_shift_value(unsigned int shift_value) {
 | 
							void proceed_to_shift_value(unsigned int shift_value) {
 | 
				
			||||||
			index_count_ = 0;
 | 
								const int max_index_count = index_count_ + 2;
 | 
				
			||||||
			while(shift_register_ != shift_value && index_count_ < 2) {
 | 
								while(shift_register_ != shift_value && index_count_ < max_index_count) {
 | 
				
			||||||
				run_for(Cycles(1));
 | 
									run_for(Cycles(1));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -124,13 +124,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		std::shared_ptr<Sector> get_next_sector() {
 | 
							std::shared_ptr<Sector> get_next_sector() {
 | 
				
			||||||
			std::shared_ptr<Sector> sector(new 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
 | 
									// look for a sector header
 | 
				
			||||||
				while(1) {
 | 
									while(1) {
 | 
				
			||||||
					if(proceed_to_next_block() == 0x08) break;
 | 
										if(proceed_to_next_block(max_index_count) == 0x08) break;
 | 
				
			||||||
					if(index_count_ >= 2) return nullptr;
 | 
										if(index_count_ >= max_index_count) return nullptr;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// get sector details, skip if this looks malformed
 | 
									// get sector details, skip if this looks malformed
 | 
				
			||||||
@@ -144,12 +144,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				// look for the following data
 | 
									// look for the following data
 | 
				
			||||||
				while(1) {
 | 
									while(1) {
 | 
				
			||||||
					if(proceed_to_next_block() == 0x07) break;
 | 
										if(proceed_to_next_block(max_index_count) == 0x07) break;
 | 
				
			||||||
					if(index_count_ >= 2) return nullptr;
 | 
										if(index_count_ >= max_index_count) return nullptr;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				checksum = 0;
 | 
									checksum = 0;
 | 
				
			||||||
				for(size_t c = 0; c < 256; c++) {
 | 
									for(std::size_t c = 0; c < 256; c++) {
 | 
				
			||||||
					sector->data[c] = static_cast<uint8_t>(get_next_byte());
 | 
										sector->data[c] = static_cast<uint8_t>(get_next_byte());
 | 
				
			||||||
					checksum ^= sector->data[c];
 | 
										checksum ^= sector->data[c];
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -165,8 +165,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
					std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
				
			||||||
	std::list<File> files;
 | 
						std::vector<File> files;
 | 
				
			||||||
	CommodoreGCRParser parser;
 | 
						CommodoreGCRParser parser;
 | 
				
			||||||
	parser.drive->set_disk(disk);
 | 
						parser.drive->set_disk(disk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -188,7 +188,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// parse directory
 | 
						// parse directory
 | 
				
			||||||
	size_t header_pointer = static_cast<size_t>(-32);
 | 
						std::size_t header_pointer = static_cast<std::size_t>(-32);
 | 
				
			||||||
	while(header_pointer+32+31 < directory.size()) {
 | 
						while(header_pointer+32+31 < directory.size()) {
 | 
				
			||||||
		header_pointer += 32;
 | 
							header_pointer += 32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -207,12 +207,12 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
 | 
				
			|||||||
		next_sector = directory[header_pointer + 4];
 | 
							next_sector = directory[header_pointer + 4];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		new_file.raw_name.reserve(16);
 | 
							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.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);
 | 
							new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		size_t number_of_sectors = static_cast<size_t>(directory[header_pointer + 0x1e]) + (static_cast<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);
 | 
							new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool is_first_sector = true;
 | 
							bool is_first_sector = true;
 | 
				
			||||||
@@ -9,17 +9,19 @@
 | 
				
			|||||||
#ifndef StaticAnalyser_Commodore_Disk_hpp
 | 
					#ifndef StaticAnalyser_Commodore_Disk_hpp
 | 
				
			||||||
#define StaticAnalyser_Commodore_Disk_hpp
 | 
					#define StaticAnalyser_Commodore_Disk_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Storage/Disk/Disk.hpp"
 | 
					#include "../../../Storage/Disk/Disk.hpp"
 | 
				
			||||||
#include "File.hpp"
 | 
					#include "File.hpp"
 | 
				
			||||||
#include <list>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Commodore {
 | 
					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 */
 | 
					#endif /* Disk_hpp */
 | 
				
			||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "File.hpp"
 | 
					#include "File.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool StaticAnalyser::Commodore::File::is_basic() {
 | 
					bool Analyser::Static::Commodore::File::is_basic() {
 | 
				
			||||||
	// BASIC files are always relocatable (?)
 | 
						// BASIC files are always relocatable (?)
 | 
				
			||||||
	if(type != File::RelocatableProgram) return false;
 | 
						if(type != File::RelocatableProgram) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,7 +23,7 @@ bool StaticAnalyser::Commodore::File::is_basic() {
 | 
				
			|||||||
	//		... null-terminated code ...
 | 
						//		... 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) {
 | 
						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];
 | 
							uint16_t next_line_address = data[line_address - starting_address];
 | 
				
			||||||
		next_line_address |= data[line_address - starting_address + 1] << 8;
 | 
							next_line_address |= data[line_address - starting_address + 1] << 8;
 | 
				
			||||||
@@ -33,7 +33,7 @@ bool StaticAnalyser::Commodore::File::is_basic() {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		if(next_line_address < line_address + 5) break;
 | 
							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];
 | 
							uint16_t next_line_number = data[line_address - starting_address + 2];
 | 
				
			||||||
		next_line_number |= data[line_address - starting_address + 3] << 8;
 | 
							next_line_number |= data[line_address - starting_address + 3] << 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,18 +12,17 @@
 | 
				
			|||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Commodore {
 | 
					namespace Commodore {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct File {
 | 
					struct File {
 | 
				
			||||||
	File() : is_closed(false), is_locked(false) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	std::wstring name;
 | 
						std::wstring name;
 | 
				
			||||||
	std::vector<uint8_t> raw_name;
 | 
						std::vector<uint8_t> raw_name;
 | 
				
			||||||
	uint16_t starting_address;
 | 
						uint16_t starting_address;
 | 
				
			||||||
	uint16_t ending_address;
 | 
						uint16_t ending_address;
 | 
				
			||||||
	bool is_locked;
 | 
						bool is_locked = false;
 | 
				
			||||||
	bool is_closed;
 | 
						bool is_closed = false;
 | 
				
			||||||
	enum {
 | 
						enum {
 | 
				
			||||||
		RelocatableProgram,
 | 
							RelocatableProgram,
 | 
				
			||||||
		NonRelocatableProgram,
 | 
							NonRelocatableProgram,
 | 
				
			||||||
@@ -36,6 +35,7 @@ struct File {
 | 
				
			|||||||
	bool is_basic();
 | 
						bool is_basic();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,21 +8,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Disk.hpp"
 | 
				
			||||||
#include "File.hpp"
 | 
					#include "File.hpp"
 | 
				
			||||||
#include "Tape.hpp"
 | 
					#include "Tape.hpp"
 | 
				
			||||||
#include "Disk.hpp"
 | 
					#include "Target.hpp"
 | 
				
			||||||
#include "../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
					#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace StaticAnalyser::Commodore;
 | 
					using namespace Analyser::Static::Commodore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
					static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			||||||
		Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
							Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
				
			||||||
	std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
 | 
						std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
 | 
						for(const auto &cartridge : cartridges) {
 | 
				
			||||||
		const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
 | 
							const auto &segments = cartridge->get_segments();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// only one mapped item is allowed
 | 
							// only one mapped item is allowed
 | 
				
			||||||
		if(segments.size() != 1) continue;
 | 
							if(segments.size() != 1) continue;
 | 
				
			||||||
@@ -38,42 +40,42 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
	return vic20_cartridges;
 | 
						return vic20_cartridges;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> &destination) {
 | 
					void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination, const std::string &file_name) {
 | 
				
			||||||
	Target target;
 | 
						std::unique_ptr<Target> target(new Target);
 | 
				
			||||||
	target.machine = Target::Vic20;	// TODO: machine estimation
 | 
						target->machine = Machine::Vic20;	// TODO: machine estimation
 | 
				
			||||||
	target.probability = 1.0; // TODO: a proper estimation
 | 
						target->confidence = 0.5; // TODO: a proper estimation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int device = 0;
 | 
						int device = 0;
 | 
				
			||||||
	std::list<File> files;
 | 
						std::vector<File> files;
 | 
				
			||||||
	bool is_disk = false;
 | 
						bool is_disk = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// strip out inappropriate cartridges
 | 
						// strip out inappropriate cartridges
 | 
				
			||||||
	target.media.cartridges = Vic20CartridgesFrom(media.cartridges);
 | 
						target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check disks
 | 
						// check disks
 | 
				
			||||||
	for(auto &disk : media.disks) {
 | 
						for(auto &disk : media.disks) {
 | 
				
			||||||
		std::list<File> disk_files = GetFiles(disk);
 | 
							std::vector<File> disk_files = GetFiles(disk);
 | 
				
			||||||
		if(!disk_files.empty()) {
 | 
							if(!disk_files.empty()) {
 | 
				
			||||||
			is_disk = true;
 | 
								is_disk = true;
 | 
				
			||||||
			files.splice(files.end(), disk_files);
 | 
								files.insert(files.end(), disk_files.begin(), disk_files.end());
 | 
				
			||||||
			target.media.disks.push_back(disk);
 | 
								target->media.disks.push_back(disk);
 | 
				
			||||||
			if(!device) device = 8;
 | 
								if(!device) device = 8;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check tapes
 | 
						// check tapes
 | 
				
			||||||
	for(auto &tape : media.tapes) {
 | 
						for(auto &tape : media.tapes) {
 | 
				
			||||||
		std::list<File> tape_files = GetFiles(tape);
 | 
							std::vector<File> tape_files = GetFiles(tape);
 | 
				
			||||||
		tape->reset();
 | 
							tape->reset();
 | 
				
			||||||
		if(!tape_files.empty()) {
 | 
							if(!tape_files.empty()) {
 | 
				
			||||||
			files.splice(files.end(), tape_files);
 | 
								files.insert(files.end(), tape_files.begin(), tape_files.end());
 | 
				
			||||||
			target.media.tapes.push_back(tape);
 | 
								target->media.tapes.push_back(tape);
 | 
				
			||||||
			if(!device) device = 1;
 | 
								if(!device) device = 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!files.empty()) {
 | 
						if(!files.empty()) {
 | 
				
			||||||
		target.vic20.memory_model = Vic20MemoryModel::Unexpanded;
 | 
							target->memory_model = Target::MemoryModel::Unexpanded;
 | 
				
			||||||
		std::ostringstream string_stream;
 | 
							std::ostringstream string_stream;
 | 
				
			||||||
		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
 | 
							string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
 | 
				
			||||||
  		if(files.front().is_basic()) {
 | 
					  		if(files.front().is_basic()) {
 | 
				
			||||||
@@ -82,23 +84,26 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target>
 | 
				
			|||||||
			string_stream << "1";
 | 
								string_stream << "1";
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		string_stream << "\nRUN\n";
 | 
							string_stream << "\nRUN\n";
 | 
				
			||||||
		target.loadingCommand = string_stream.str();
 | 
							target->loading_command = string_stream.str();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// make a first guess based on loading address
 | 
							// make a first guess based on loading address
 | 
				
			||||||
		switch(files.front().starting_address) {
 | 
							switch(files.front().starting_address) {
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									printf("Starting address %04x?\n", files.front().starting_address);
 | 
				
			||||||
			case 0x1001:
 | 
								case 0x1001:
 | 
				
			||||||
			default: break;
 | 
									target->memory_model = Target::MemoryModel::Unexpanded;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
			case 0x1201:
 | 
								case 0x1201:
 | 
				
			||||||
				target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
									target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
			case 0x0401:
 | 
								case 0x0401:
 | 
				
			||||||
				target.vic20.memory_model = Vic20MemoryModel::EightKB;
 | 
									target->memory_model = Target::MemoryModel::EightKB;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// General approach: increase memory size conservatively such that the largest file found will fit.
 | 
							// General approach: increase memory size conservatively such that the largest file found will fit.
 | 
				
			||||||
		for(File &file : files) {
 | 
					//		for(File &file : files) {
 | 
				
			||||||
			size_t file_size = file.data.size();
 | 
					//			std::size_t file_size = file.data.size();
 | 
				
			||||||
//			bool is_basic = file.is_basic();
 | 
					//			bool is_basic = file.is_basic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/*if(is_basic)
 | 
								/*if(is_basic)
 | 
				
			||||||
@@ -108,9 +113,9 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target>
 | 
				
			|||||||
				// An unexpanded machine has 3583 bytes free for BASIC;
 | 
									// An unexpanded machine has 3583 bytes free for BASIC;
 | 
				
			||||||
				// a 3kb expanded machine has 6655 bytes free.
 | 
									// a 3kb expanded machine has 6655 bytes free.
 | 
				
			||||||
				if(file_size > 6655)
 | 
									if(file_size > 6655)
 | 
				
			||||||
					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
										target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
				
			||||||
				else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
 | 
									else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
 | 
				
			||||||
					target.vic20.memory_model = Vic20MemoryModel::EightKB;
 | 
										target->vic20.memory_model = Vic20MemoryModel::EightKB;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
			{*/
 | 
								{*/
 | 
				
			||||||
@@ -124,18 +129,26 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target>
 | 
				
			|||||||
				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
 | 
									// 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 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.
 | 
									// A 32kb expanded Vic has memory in the entire low 32kb.
 | 
				
			||||||
				uint16_t starting_address = file.starting_address;
 | 
					//				uint16_t starting_address = file.starting_address;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
 | 
									// 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.
 | 
									// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
 | 
				
			||||||
				if(starting_address + file_size > 0x2000)
 | 
					//				if(starting_address + file_size > 0x2000)
 | 
				
			||||||
					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
					//					target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
				
			||||||
				else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
 | 
					//				else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
 | 
				
			||||||
					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
					//					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;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!target.media.tapes.empty() || !target.media.cartridges.empty() || !target.media.disks.empty())
 | 
							destination.push_back(std::move(target));
 | 
				
			||||||
		destination.push_back(target);
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -10,12 +10,15 @@
 | 
				
			|||||||
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
 | 
					#define StaticAnalyser_Commodore_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Commodore {
 | 
					namespace Commodore {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AddTargets(const Media &media, std::list<Target> &destination);
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, const std::string &file_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,13 +8,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "Tape.hpp"
 | 
					#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;
 | 
						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);
 | 
						std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,15 +9,16 @@
 | 
				
			|||||||
#ifndef StaticAnalyser_Commodore_Tape_hpp
 | 
					#ifndef StaticAnalyser_Commodore_Tape_hpp
 | 
				
			||||||
#define StaticAnalyser_Commodore_Tape_hpp
 | 
					#define StaticAnalyser_Commodore_Tape_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Storage/Tape/Tape.hpp"
 | 
					#include "../../../Storage/Tape/Tape.hpp"
 | 
				
			||||||
#include "File.hpp"
 | 
					#include "File.hpp"
 | 
				
			||||||
#include <list>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Commodore {
 | 
					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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										42
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_Commodore_Target_h */
 | 
				
			||||||
@@ -6,24 +6,25 @@
 | 
				
			|||||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
					//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Disassembler6502.hpp"
 | 
					#include "6502.hpp"
 | 
				
			||||||
#include <map>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace StaticAnalyser::MOS6502;
 | 
					#include "Kernel.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct PartialDisassembly {
 | 
					using namespace Analyser::Static::MOS6502;
 | 
				
			||||||
	Disassembly disassembly;
 | 
					namespace  {
 | 
				
			||||||
	std::vector<uint16_t> remaining_entry_points;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
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);
 | 
						disassembly.disassembly.internal_calls.insert(entry_point);
 | 
				
			||||||
	uint16_t address = entry_point;
 | 
						uint16_t address = entry_point;
 | 
				
			||||||
	while(1) {
 | 
						while(true) {
 | 
				
			||||||
		size_t local_address = address_mapper(address);
 | 
							std::size_t local_address = address_mapper(address);
 | 
				
			||||||
		if(local_address >= memory.size()) return;
 | 
							if(local_address >= memory.size()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct Instruction instruction;
 | 
							Instruction instruction;
 | 
				
			||||||
		instruction.address = address;
 | 
							instruction.address = 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::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
 | 
				
			||||||
			case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
 | 
								case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
 | 
				
			||||||
			case Instruction::Relative: {
 | 
								case Instruction::Relative: {
 | 
				
			||||||
				size_t operand_address = address_mapper(address);
 | 
									std::size_t operand_address = address_mapper(address);
 | 
				
			||||||
				if(operand_address >= memory.size()) return;
 | 
									if(operand_address >= memory.size()) return;
 | 
				
			||||||
				address++;
 | 
									address++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -244,8 +245,8 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
			// two-byte operands
 | 
								// two-byte operands
 | 
				
			||||||
			case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
 | 
								case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
 | 
				
			||||||
			case Instruction::Indirect: {
 | 
								case Instruction::Indirect: {
 | 
				
			||||||
				size_t low_operand_address = address_mapper(address);
 | 
									std::size_t low_operand_address = address_mapper(address);
 | 
				
			||||||
				size_t high_operand_address = address_mapper(address + 1);
 | 
									std::size_t high_operand_address = address_mapper(address + 1);
 | 
				
			||||||
				if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
 | 
									if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
 | 
				
			||||||
				address += 2;
 | 
									address += 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -259,7 +260,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// TODO: something wider-ranging than this
 | 
							// TODO: something wider-ranging than this
 | 
				
			||||||
		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
 | 
							if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
 | 
				
			||||||
			size_t mapped_address = address_mapper(instruction.operand);
 | 
								std::size_t mapped_address = address_mapper(instruction.operand);
 | 
				
			||||||
			bool is_external = mapped_address >= memory.size();
 | 
								bool is_external = mapped_address >= memory.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch(instruction.operation) {
 | 
								switch(instruction.operation) {
 | 
				
			||||||
@@ -307,31 +308,13 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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()) {
 | 
					}	// end of anonymous namespace
 | 
				
			||||||
		// 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();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// if that address has already bene visited, forget about it
 | 
					Disassembly Analyser::Static::MOS6502::Disassemble(
 | 
				
			||||||
		if(partialDisassembly.disassembly.instructions_by_address.find(next_entry_point) != partialDisassembly.disassembly.instructions_by_address.end()) continue;
 | 
						const std::vector<uint8_t> &memory,
 | 
				
			||||||
 | 
						const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
				
			||||||
		// if it's outgoing, log it as such and forget about it; otherwise disassemble
 | 
						std::vector<uint16_t> entry_points) {
 | 
				
			||||||
		size_t mapped_entry_point = address_mapper(next_entry_point);
 | 
						return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
 | 
				
			||||||
		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 static_cast<size_t>(argument - start_address);
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										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 */
 | 
				
			||||||
							
								
								
									
										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 */
 | 
				
			||||||
							
								
								
									
										295
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,295 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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 std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom(
 | 
				
			||||||
 | 
						const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
				
			||||||
 | 
						// No cartridges implies no targets.
 | 
				
			||||||
 | 
						if(cartridges.empty()) {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::vector<std::unique_ptr<Analyser::Static::Target>> 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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &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(const 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));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								Analyser/Static/MSX/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Analyser/Static/MSX/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace MSX {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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 */
 | 
				
			||||||
							
								
								
									
										26
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace MSX {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Target: public ::Analyser::Static::Target {
 | 
				
			||||||
 | 
						bool has_disk_drive = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_MSX_Target_h */
 | 
				
			||||||
@@ -9,11 +9,18 @@
 | 
				
			|||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Tape.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;
 | 
						int score = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
						for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
				
			||||||
@@ -23,7 +30,7 @@ static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const
 | 
				
			|||||||
	return score;
 | 
						return score;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
 | 
					static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
				
			||||||
	std::set<uint16_t> rom_functions = {
 | 
						std::set<uint16_t> rom_functions = {
 | 
				
			||||||
		0x0228,	0x022b,
 | 
							0x0228,	0x022b,
 | 
				
			||||||
		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
							0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
				
			||||||
@@ -47,7 +54,7 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
 | 
				
			|||||||
	return Score(disassembly, rom_functions, variable_locations);
 | 
						return Score(disassembly, rom_functions, variable_locations);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
 | 
					static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
				
			||||||
	std::set<uint16_t> rom_functions = {
 | 
						std::set<uint16_t> rom_functions = {
 | 
				
			||||||
		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247,
 | 
							0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247,
 | 
				
			||||||
		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
							0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
				
			||||||
@@ -72,23 +79,44 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
 | 
				
			|||||||
	return Score(disassembly, rom_functions, variable_locations);
 | 
						return Score(disassembly, rom_functions, variable_locations);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &destination) {
 | 
					static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
 | 
				
			||||||
	Target target;
 | 
						/*
 | 
				
			||||||
	target.machine = Target::Oric;
 | 
							The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
 | 
				
			||||||
	target.probability = 1.0;
 | 
						*/
 | 
				
			||||||
 | 
						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));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
 | 
				
			||||||
 | 
						std::unique_ptr<Target> target(new Target);
 | 
				
			||||||
 | 
						target->machine = Machine::Oric;
 | 
				
			||||||
 | 
						target->confidence = 0.5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int basic10_votes = 0;
 | 
						int basic10_votes = 0;
 | 
				
			||||||
	int basic11_votes = 0;
 | 
						int basic11_votes = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for(auto &tape : media.tapes) {
 | 
						for(auto &tape : media.tapes) {
 | 
				
			||||||
		std::list<File> tape_files = GetFiles(tape);
 | 
							std::vector<File> tape_files = GetFiles(tape);
 | 
				
			||||||
		tape->reset();
 | 
							tape->reset();
 | 
				
			||||||
		if(tape_files.size()) {
 | 
							if(tape_files.size()) {
 | 
				
			||||||
			for(auto file : tape_files) {
 | 
								for(auto file : tape_files) {
 | 
				
			||||||
				if(file.data_type == File::MachineCode) {
 | 
									if(file.data_type == File::MachineCode) {
 | 
				
			||||||
					std::vector<uint16_t> entry_points = {file.starting_address};
 | 
										std::vector<uint16_t> entry_points = {file.starting_address};
 | 
				
			||||||
					StaticAnalyser::MOS6502::Disassembly disassembly =
 | 
										Analyser::Static::MOS6502::Disassembly disassembly =
 | 
				
			||||||
						StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::MOS6502::OffsetMapper(file.starting_address), entry_points);
 | 
											Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					int basic10_score = Basic10Score(disassembly);
 | 
										int basic10_score = Basic10Score(disassembly);
 | 
				
			||||||
					int basic11_score = Basic11Score(disassembly);
 | 
										int basic11_score = Basic11Score(disassembly);
 | 
				
			||||||
@@ -96,23 +124,28 @@ void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &des
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			target.media.tapes.push_back(tape);
 | 
								target->media.tapes.push_back(tape);
 | 
				
			||||||
			target.loadingCommand = "CLOAD\"\"\n";
 | 
								target->loading_command = "CLOAD\"\"\n";
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// trust that any disk supplied can be handled by the Microdisc. TODO: check.
 | 
					 | 
				
			||||||
	if(!media.disks.empty()) {
 | 
						if(!media.disks.empty()) {
 | 
				
			||||||
		target.oric.has_microdisc = true;
 | 
							// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
 | 
				
			||||||
		target.media.disks = media.disks;
 | 
							for(const auto &disk: media.disks) {
 | 
				
			||||||
 | 
								Storage::Encodings::MFM::Parser parser(true, disk);
 | 
				
			||||||
 | 
								if(IsMicrodisc(parser)) {
 | 
				
			||||||
 | 
									target->has_microdrive = true;
 | 
				
			||||||
 | 
									target->media.disks.push_back(disk);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		target.oric.has_microdisc = false;
 | 
							target->has_microdrive = false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: really this should add two targets if not all votes agree
 | 
						// TODO: really this should add two targets if not all votes agree
 | 
				
			||||||
	target.oric.use_atmos_rom = basic11_votes >= basic10_votes;
 | 
						target->use_atmos_rom = basic11_votes >= basic10_votes;
 | 
				
			||||||
	if(target.oric.has_microdisc) target.oric.use_atmos_rom = true;
 | 
						if(target->has_microdrive) target->use_atmos_rom = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
 | 
						if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
 | 
				
			||||||
		destination.push_back(target);
 | 
							destination.push_back(std::move(target));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -11,13 +11,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Oric {
 | 
					namespace Oric {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AddTargets(const Media &media, std::list<Target> &destination);
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* StaticAnalyser_hpp */
 | 
					#endif /* StaticAnalyser_hpp */
 | 
				
			||||||
@@ -7,12 +7,12 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Tape.hpp"
 | 
					#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::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
				
			||||||
	std::list<File> files;
 | 
						std::vector<File> files;
 | 
				
			||||||
	Storage::Tape::Oric::Parser parser;
 | 
						Storage::Tape::Oric::Parser parser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while(!tape->is_at_end()) {
 | 
						while(!tape->is_at_end()) {
 | 
				
			||||||
@@ -69,9 +69,9 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
 | 
				
			|||||||
		new_file.name = file_name;
 | 
							new_file.name = file_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// read body
 | 
							// 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);
 | 
							new_file.data.reserve(body_length);
 | 
				
			||||||
		for(size_t c = 0; c < body_length; c++) {
 | 
							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)));
 | 
								new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,12 +9,13 @@
 | 
				
			|||||||
#ifndef StaticAnalyser_Oric_Tape_hpp
 | 
					#ifndef StaticAnalyser_Oric_Tape_hpp
 | 
				
			||||||
#define StaticAnalyser_Oric_Tape_hpp
 | 
					#define StaticAnalyser_Oric_Tape_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Storage/Tape/Tape.hpp"
 | 
					#include "../../../Storage/Tape/Tape.hpp"
 | 
				
			||||||
#include <list>
 | 
					
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace Oric {
 | 
					namespace Oric {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct File {
 | 
					struct File {
 | 
				
			||||||
@@ -30,8 +31,9 @@ struct File {
 | 
				
			|||||||
	std::vector<uint8_t> data;
 | 
						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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										25
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace Oric {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Target: public ::Analyser::Static::Target {
 | 
				
			||||||
 | 
						bool use_atmos_rom = false;
 | 
				
			||||||
 | 
						bool has_microdrive = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_Oric_Target_h */
 | 
				
			||||||
							
								
								
									
										175
									
								
								Analyser/Static/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								Analyser/Static/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Analysers
 | 
				
			||||||
 | 
					#include "Acorn/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "AmstradCPC/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "Atari/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "Coleco/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "Commodore/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/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/OricMFMDSK.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/DiskImage/Formats/SSD.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("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::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("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// O
 | 
				
			||||||
 | 
						Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::string &file_name) {
 | 
				
			||||||
 | 
						std::vector<std::unique_ptr<Target>> 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.
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::Acorn)			Acorn::AddTargets(media, targets);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::AmstradCPC)	AmstradCPC::AddTargets(media, targets);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::Atari2600)		Atari::AddTargets(media, targets);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::ColecoVision)	Coleco::AddTargets(media, targets);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::Commodore)		Commodore::AddTargets(media, targets, file_name);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::MSX)			MSX::AddTargets(media, targets);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::Oric)			Oric::AddTargets(media, targets);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::ZX8081)		ZX8081::AddTargets(media, targets, potential_platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reset any tapes to their initial position
 | 
				
			||||||
 | 
						for(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 <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;
 | 
				
			||||||
 | 
						std::string loading_command;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						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.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Target>> 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 */
 | 
				
			||||||
@@ -11,7 +11,8 @@
 | 
				
			|||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <vector>
 | 
					#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) {
 | 
					static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
				
			||||||
	std::vector<Storage::Data::ZX8081::File> files;
 | 
						std::vector<Storage::Data::ZX8081::File> files;
 | 
				
			||||||
@@ -27,41 +28,41 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
 | 
				
			|||||||
	return files;
 | 
						return files;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void StaticAnalyser::ZX8081::AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms) {
 | 
					void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	if(!media.tapes.empty()) {
 | 
						if(!media.tapes.empty()) {
 | 
				
			||||||
		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
 | 
							std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
 | 
				
			||||||
		media.tapes.front()->reset();
 | 
							media.tapes.front()->reset();
 | 
				
			||||||
		if(!files.empty()) {
 | 
							if(!files.empty()) {
 | 
				
			||||||
			StaticAnalyser::Target target;
 | 
								Target *target = new Target;
 | 
				
			||||||
			target.machine = Target::ZX8081;
 | 
								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.
 | 
								// Guess the machine type from the file only if it isn't already known.
 | 
				
			||||||
			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
 | 
								switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
					target.zx8081.isZX81 = files.front().isZX81;
 | 
										target->is_ZX81 = files.front().isZX81;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case TargetPlatform::ZX80:	target.zx8081.isZX81 = false;	break;
 | 
									case TargetPlatform::ZX80:	target->is_ZX81 = false;	break;
 | 
				
			||||||
				case TargetPlatform::ZX81:	target.zx8081.isZX81 = true;	break;
 | 
									case TargetPlatform::ZX81:	target->is_ZX81 = true;		break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/*if(files.front().data.size() > 16384) {
 | 
								/*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) {
 | 
								} else*/ if(files.front().data.size() > 1024) {
 | 
				
			||||||
				target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
 | 
									target->memory_model = Target::MemoryModel::SixteenKB;
 | 
				
			||||||
			} else {
 | 
								} 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.
 | 
								// TODO: how to run software once loaded? Might require a BASIC detokeniser.
 | 
				
			||||||
			if(target.zx8081.isZX81) {
 | 
								if(target->is_ZX81) {
 | 
				
			||||||
				target.loadingCommand = "J\"\"\n";
 | 
									target->loading_command = "J\"\"\n";
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				target.loadingCommand = "W\n";
 | 
									target->loading_command = "W\n";
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			destination.push_back(target);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -10,13 +10,15 @@
 | 
				
			|||||||
#define StaticAnalyser_ZX8081_StaticAnalyser_hpp
 | 
					#define StaticAnalyser_ZX8081_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
#include "../../Storage/TargetPlatforms.hpp"
 | 
					#include "../../../Storage/TargetPlatforms.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace StaticAnalyser {
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
namespace ZX8081 {
 | 
					namespace ZX8081 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms);
 | 
					void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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.
 | 
				
			||||||
@@ -169,13 +169,20 @@ class HalfCycles: public WrappedInt<HalfCycles> {
 | 
				
			|||||||
			return Cycles(length_ >> 1);
 | 
								return Cycles(length_ >> 1);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		///Flushes the whole cycles in @c this, subtracting that many from the total stored here.
 | 
							/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
 | 
				
			||||||
		inline Cycles flush_cycles() {
 | 
							inline Cycles flush_cycles() {
 | 
				
			||||||
			Cycles result(length_ >> 1);
 | 
								Cycles result(length_ >> 1);
 | 
				
			||||||
			length_ &= 1;
 | 
								length_ &= 1;
 | 
				
			||||||
			return result;
 | 
								return result;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
 | 
				
			||||||
 | 
							inline 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.
 | 
								the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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 */
 | 
				
			||||||
@@ -11,28 +11,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace WD;
 | 
					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) :
 | 
					WD1770::WD1770(Personality p) :
 | 
				
			||||||
		Storage::Disk::MFMController(8000000),
 | 
							Storage::Disk::MFMController(8000000),
 | 
				
			||||||
		interesting_event_mask_(static_cast<int>(Event1770::Command)),
 | 
					 | 
				
			||||||
		resume_point_(0),
 | 
					 | 
				
			||||||
		delay_time_(0),
 | 
					 | 
				
			||||||
		index_hole_count_target_(-1),
 | 
					 | 
				
			||||||
		delegate_(nullptr),
 | 
					 | 
				
			||||||
		personality_(p),
 | 
							personality_(p),
 | 
				
			||||||
		head_is_loaded_(false) {
 | 
							interesting_event_mask_(static_cast<int>(Event1770::Command)) {
 | 
				
			||||||
	set_is_double_density(false);
 | 
						set_is_double_density(false);
 | 
				
			||||||
	posit_event(static_cast<int>(Event1770::Command));
 | 
						posit_event(static_cast<int>(Event1770::Command));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -41,10 +23,16 @@ void WD1770::set_register(int address, uint8_t value) {
 | 
				
			|||||||
	switch(address&3) {
 | 
						switch(address&3) {
 | 
				
			||||||
		case 0: {
 | 
							case 0: {
 | 
				
			||||||
			if((value&0xf0) == 0xd0) {
 | 
								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");
 | 
										printf("!!!TODO: force interrupt!!!\n");
 | 
				
			||||||
					update_status([] (Status &status) {
 | 
										update_status([] (Status &status) {
 | 
				
			||||||
						status.type = Status::One;
 | 
											status.type = Status::One;
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				command_ = value;
 | 
									command_ = value;
 | 
				
			||||||
				posit_event(static_cast<int>(Event1770::Command));
 | 
									posit_event(static_cast<int>(Event1770::Command));
 | 
				
			||||||
@@ -129,7 +117,7 @@ void WD1770::run_for(const Cycles cycles) {
 | 
				
			|||||||
#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
 | 
					#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_ = static_cast<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 BEGIN_SECTION()	switch(resume_point_) { default:
 | 
				
			||||||
#define END_SECTION()	0; }
 | 
					#define END_SECTION()	(void)0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define READ_ID()	\
 | 
					#define READ_ID()	\
 | 
				
			||||||
		if(new_event_type == static_cast<int>(Event::Token)) {	\
 | 
							if(new_event_type == static_cast<int>(Event::Token)) {	\
 | 
				
			||||||
@@ -187,13 +175,23 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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;
 | 
							if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
 | 
				
			||||||
		interesting_event_mask_ &= ~new_event_type;
 | 
							interesting_event_mask_ &= ~new_event_type;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Status new_status;
 | 
						Status new_status;
 | 
				
			||||||
	BEGIN_SECTION()
 | 
						BEGIN_SECTION()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Wait for a new command, branch to the appropriate handler.
 | 
						// Wait for a new command, branch to the appropriate handler.
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
	wait_for_command:
 | 
						wait_for_command:
 | 
				
			||||||
		printf("Idle...\n");
 | 
							printf("Idle...\n");
 | 
				
			||||||
		set_data_mode(DataMode::Scanning);
 | 
							set_data_mode(DataMode::Scanning);
 | 
				
			||||||
@@ -478,7 +476,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
				sector_++;
 | 
									sector_++;
 | 
				
			||||||
				goto test_type2_write_protection;
 | 
									goto test_type2_write_protection;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			printf("Read sector %d\n", sector_);
 | 
								printf("Finished reading sector %d\n", sector_);
 | 
				
			||||||
			goto wait_for_command;
 | 
								goto wait_for_command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		goto type2_check_crc;
 | 
							goto type2_check_crc;
 | 
				
			||||||
@@ -674,7 +672,6 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			status.lost_data = false;
 | 
								status.lost_data = false;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	write_track_test_write_protect:
 | 
					 | 
				
			||||||
		if(get_drive().get_is_read_only()) {
 | 
							if(get_drive().get_is_read_only()) {
 | 
				
			||||||
			update_status([] (Status &status) {
 | 
								update_status([] (Status &status) {
 | 
				
			||||||
				status.write_protect = true;
 | 
									status.write_protect = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -85,20 +85,19 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
		inline bool has_head_load_line() { return (personality_ == P1793 ); }
 | 
							inline bool has_head_load_line() { return (personality_ == P1793 ); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct Status {
 | 
							struct Status {
 | 
				
			||||||
			Status();
 | 
								bool write_protect = false;
 | 
				
			||||||
			bool write_protect;
 | 
								bool record_type = false;
 | 
				
			||||||
			bool record_type;
 | 
								bool spin_up = false;
 | 
				
			||||||
			bool spin_up;
 | 
								bool record_not_found = false;
 | 
				
			||||||
			bool record_not_found;
 | 
								bool crc_error = false;
 | 
				
			||||||
			bool crc_error;
 | 
								bool seek_error = false;
 | 
				
			||||||
			bool seek_error;
 | 
								bool lost_data = false;
 | 
				
			||||||
			bool lost_data;
 | 
								bool data_request = false;
 | 
				
			||||||
			bool data_request;
 | 
								bool interrupt_request = false;
 | 
				
			||||||
			bool interrupt_request;
 | 
								bool busy = false;
 | 
				
			||||||
			bool busy;
 | 
					 | 
				
			||||||
			enum {
 | 
								enum {
 | 
				
			||||||
				One, Two, Three
 | 
									One, Two, Three
 | 
				
			||||||
			} type;
 | 
								} type = One;
 | 
				
			||||||
		} status_;
 | 
							} status_;
 | 
				
			||||||
		uint8_t track_;
 | 
							uint8_t track_;
 | 
				
			||||||
		uint8_t sector_;
 | 
							uint8_t sector_;
 | 
				
			||||||
@@ -106,7 +105,7 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
		uint8_t command_;
 | 
							uint8_t command_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int index_hole_count_;
 | 
							int index_hole_count_;
 | 
				
			||||||
		int index_hole_count_target_;
 | 
							int index_hole_count_target_ = -1;
 | 
				
			||||||
		int distance_into_section_;
 | 
							int distance_into_section_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int step_direction_;
 | 
							int step_direction_;
 | 
				
			||||||
@@ -117,21 +116,22 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
			Command			= (1 << 3),	// Indicates receipt of a new command.
 | 
								Command			= (1 << 3),	// Indicates receipt of a new command.
 | 
				
			||||||
			HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only).
 | 
								HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only).
 | 
				
			||||||
			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out.
 | 
								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);
 | 
							void posit_event(int type);
 | 
				
			||||||
		int interesting_event_mask_;
 | 
							int interesting_event_mask_;
 | 
				
			||||||
		int resume_point_;
 | 
							int resume_point_ = 0;
 | 
				
			||||||
		unsigned int delay_time_;
 | 
							unsigned int delay_time_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ID buffer
 | 
							// ID buffer
 | 
				
			||||||
		uint8_t header_[6];
 | 
							uint8_t header_[6];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 1793 head-loading logic
 | 
							// 1793 head-loading logic
 | 
				
			||||||
		bool head_is_loaded_;
 | 
							bool head_is_loaded_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// delegate
 | 
							// delegate
 | 
				
			||||||
		Delegate *delegate_;
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,12 +121,9 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		MOS6532() :
 | 
							MOS6532() {
 | 
				
			||||||
			interrupt_status_(0),
 | 
								timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
 | 
				
			||||||
			port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
 | 
							}
 | 
				
			||||||
			a7_interrupt_({.last_port_value = 0, .enabled = false}),
 | 
					 | 
				
			||||||
			interrupt_line_(false),
 | 
					 | 
				
			||||||
			timer_{.value = static_cast<unsigned int>((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline void set_port_did_change(int port) {
 | 
							inline void set_port_did_change(int port) {
 | 
				
			||||||
			if(!port) {
 | 
								if(!port) {
 | 
				
			||||||
@@ -154,26 +151,26 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		struct {
 | 
							struct {
 | 
				
			||||||
			unsigned int value;
 | 
								unsigned int value;
 | 
				
			||||||
			unsigned int activeShift, writtenShift;
 | 
								unsigned int activeShift = 10, writtenShift = 10;
 | 
				
			||||||
			bool interrupt_enabled;
 | 
								bool interrupt_enabled = false;
 | 
				
			||||||
		} timer_;
 | 
							} timer_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct {
 | 
							struct {
 | 
				
			||||||
			bool enabled;
 | 
								bool enabled = false;
 | 
				
			||||||
			bool active_on_positive;
 | 
								bool active_on_positive = false;
 | 
				
			||||||
			uint8_t last_port_value;
 | 
								uint8_t last_port_value = 0;
 | 
				
			||||||
		} a7_interrupt_;
 | 
							} a7_interrupt_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct {
 | 
							struct {
 | 
				
			||||||
			uint8_t output_mask, output;
 | 
								uint8_t output_mask = 0, output = 0;
 | 
				
			||||||
		} port_[2];
 | 
							} port_[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint8_t interrupt_status_;
 | 
							uint8_t interrupt_status_ = 0;
 | 
				
			||||||
		enum InterruptFlag: uint8_t {
 | 
							enum InterruptFlag: uint8_t {
 | 
				
			||||||
			Timer = 0x80,
 | 
								Timer = 0x80,
 | 
				
			||||||
			PA7 = 0x40
 | 
								PA7 = 0x40
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		bool interrupt_line_;
 | 
							bool interrupt_line_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// expected to be overridden
 | 
							// expected to be overridden
 | 
				
			||||||
		uint8_t get_port_input(int port)										{	return 0xff;	}
 | 
							uint8_t get_port_input(int port)										{	return 0xff;	}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,22 +8,22 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "6560.hpp"
 | 
					#include "6560.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace MOS;
 | 
					using namespace MOS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Speaker::Speaker() :
 | 
					AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
 | 
				
			||||||
	volume_(0),
 | 
						audio_queue_(audio_queue) {}
 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Speaker::set_volume(uint8_t volume) {
 | 
					
 | 
				
			||||||
	enqueue([=]() {
 | 
					void AudioGenerator::set_volume(uint8_t volume) {
 | 
				
			||||||
		volume_ = volume;
 | 
						audio_queue_.defer([=]() {
 | 
				
			||||||
 | 
							volume_ = static_cast<int16_t>(volume) * range_multiplier_;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Speaker::set_control(int channel, uint8_t value) {
 | 
					void AudioGenerator::set_control(int channel, uint8_t value) {
 | 
				
			||||||
	enqueue([=]() {
 | 
						audio_queue_.defer([=]() {
 | 
				
			||||||
		control_registers_[channel] = value;
 | 
							control_registers_[channel] = value;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -105,7 +105,7 @@ static uint8_t noise_pattern[] = {
 | 
				
			|||||||
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
 | 
					// 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.
 | 
					// means every second cycle, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
 | 
					void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			||||||
	for(unsigned int c = 0; c < number_of_samples; c++) {
 | 
						for(unsigned int c = 0; c < number_of_samples; c++) {
 | 
				
			||||||
		update(0, 2, shift);
 | 
							update(0, 2, shift);
 | 
				
			||||||
		update(1, 1, shift);
 | 
							update(1, 1, shift);
 | 
				
			||||||
@@ -114,16 +114,16 @@ 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;
 | 
							// this sums the output of all three sounds channels plus a DC offset for volume;
 | 
				
			||||||
		// TODO: what's the real ratio of this stuff?
 | 
							// TODO: what's the real ratio of this stuff?
 | 
				
			||||||
		target[c] = (
 | 
							target[c] = static_cast<int16_t>(
 | 
				
			||||||
			(shift_registers_[0]&1) +
 | 
								(shift_registers_[0]&1) +
 | 
				
			||||||
			(shift_registers_[1]&1) +
 | 
								(shift_registers_[1]&1) +
 | 
				
			||||||
			(shift_registers_[2]&1) +
 | 
								(shift_registers_[2]&1) +
 | 
				
			||||||
			((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&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) {
 | 
					void AudioGenerator::skip_samples(std::size_t number_of_samples) {
 | 
				
			||||||
	for(unsigned int c = 0; c < number_of_samples; c++) {
 | 
						for(unsigned int c = 0; c < number_of_samples; c++) {
 | 
				
			||||||
		update(0, 2, shift);
 | 
							update(0, 2, shift);
 | 
				
			||||||
		update(1, 1, shift);
 | 
							update(1, 1, 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 shift
 | 
				
			||||||
#undef increment
 | 
					#undef increment
 | 
				
			||||||
#undef update
 | 
					#undef update
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,28 +9,35 @@
 | 
				
			|||||||
#ifndef _560_hpp
 | 
					#ifndef _560_hpp
 | 
				
			||||||
#define _560_hpp
 | 
					#define _560_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Outputs/CRT/CRT.hpp"
 | 
					 | 
				
			||||||
#include "../../Outputs/Speaker.hpp"
 | 
					 | 
				
			||||||
#include "../../ClockReceiver/ClockReceiver.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 MOS {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// audio state
 | 
					// audio state
 | 
				
			||||||
class Speaker: public ::Outputs::Filter<Speaker> {
 | 
					class AudioGenerator: public ::Outputs::Speaker::SampleSource {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		Speaker();
 | 
							AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_volume(uint8_t volume);
 | 
							void set_volume(uint8_t volume);
 | 
				
			||||||
		void set_control(int channel, uint8_t value);
 | 
							void set_control(int channel, uint8_t value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void get_samples(unsigned int number_of_samples, int16_t *target);
 | 
							// For ::SampleSource.
 | 
				
			||||||
		void skip_samples(unsigned int number_of_samples);
 | 
							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:
 | 
						private:
 | 
				
			||||||
		unsigned int counters_[4];
 | 
							Concurrency::DeferringAsyncTaskQueue &audio_queue_;
 | 
				
			||||||
		unsigned int shift_registers_[4];
 | 
					
 | 
				
			||||||
		uint8_t control_registers_[4];
 | 
							unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels
 | 
				
			||||||
		uint8_t volume_;
 | 
							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;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
@@ -44,33 +51,42 @@ class Speaker: public ::Outputs::Filter<Speaker> {
 | 
				
			|||||||
template <class T> class MOS6560 {
 | 
					template <class T> class MOS6560 {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		MOS6560() :
 | 
							MOS6560() :
 | 
				
			||||||
				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)),
 | 
									crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
 | 
				
			||||||
				speaker_(new Speaker),
 | 
									audio_generator_(audio_queue_),
 | 
				
			||||||
				horizontal_counter_(0),
 | 
									speaker_(audio_generator_)
 | 
				
			||||||
				vertical_counter_(0),
 | 
							{
 | 
				
			||||||
				cycles_since_speaker_update_(0),
 | 
								crt_->set_svideo_sampling_function(
 | 
				
			||||||
				is_odd_frame_(false),
 | 
									"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
 | 
				
			||||||
				is_odd_line_(false) {
 | 
					 | 
				
			||||||
			crt_->set_composite_sampling_function(
 | 
					 | 
				
			||||||
				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
 | 
					 | 
				
			||||||
				"{"
 | 
									"{"
 | 
				
			||||||
					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
 | 
										"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
 | 
				
			||||||
					"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
					"float chroma = cos(phase + phaseOffset);"
 | 
										"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
 | 
				
			||||||
					"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
 | 
										"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
 | 
								// default to NTSC
 | 
				
			||||||
			set_output_mode(OutputMode::NTSC);
 | 
								set_output_mode(OutputMode::NTSC);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_clock_rate(double clock_rate) {
 | 
							~MOS6560() {
 | 
				
			||||||
			speaker_->set_input_rate(static_cast<float>(clock_rate / 4.0));
 | 
								audio_queue_.flush();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
 | 
							void set_clock_rate(double clock_rate) {
 | 
				
			||||||
		std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
 | 
								speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		enum OutputMode {
 | 
							enum OutputMode {
 | 
				
			||||||
			PAL, NTSC
 | 
								PAL, NTSC
 | 
				
			||||||
@@ -82,36 +98,36 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
		void set_output_mode(OutputMode output_mode) {
 | 
							void set_output_mode(OutputMode output_mode) {
 | 
				
			||||||
			output_mode_ = 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] = {
 | 
								const uint8_t luminances[16] = {
 | 
				
			||||||
				0,		255,	109,	189,
 | 
									0,		255,	60,		189,
 | 
				
			||||||
				199,	144,	159,	161,
 | 
									80,		144,	40,		227,
 | 
				
			||||||
				126,	227,	227,	207,
 | 
									90,		161,	207,	227,
 | 
				
			||||||
				235,	173,	188,	196
 | 
									200,	196,	160,	196
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 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
 | 
								// anything above 191 disables the colour subcarrier. Phase is relative to the
 | 
				
			||||||
			// colour burst, so 0 is green.
 | 
								// colour burst, so 0 is green.
 | 
				
			||||||
			const uint8_t pal_chrominances[16] = {
 | 
								const uint8_t pal_chrominances[16] = {
 | 
				
			||||||
				255,	255,	40,		112,
 | 
									255,	255,	37,		101,
 | 
				
			||||||
				8,		88,		120,	56,
 | 
									19,		86,		123,	59,
 | 
				
			||||||
				40,		48,		40,		112,
 | 
									46,		53,		37,		101,
 | 
				
			||||||
				8,		88,		120,	56,
 | 
									19,		86,		123,	59,
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			const uint8_t ntsc_chrominances[16] = {
 | 
								const uint8_t ntsc_chrominances[16] = {
 | 
				
			||||||
				255,	255,	8,		72,
 | 
									255,	255,	7,		71,
 | 
				
			||||||
				32,		88,		48,		112,
 | 
									25,		86,		48,		112,
 | 
				
			||||||
				0,		0,		8,		72,
 | 
									0,		119,	7,		71,
 | 
				
			||||||
				32,		88,		48,		112,
 | 
									25,		86,		48,		112,
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			const uint8_t *chrominances;
 | 
								const uint8_t *chrominances;
 | 
				
			||||||
			Outputs::CRT::DisplayType display_type;
 | 
								Outputs::CRT::DisplayType display_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch(output_mode) {
 | 
								switch(output_mode) {
 | 
				
			||||||
				case OutputMode::PAL:
 | 
									default:
 | 
				
			||||||
					chrominances = pal_chrominances;
 | 
										chrominances = pal_chrominances;
 | 
				
			||||||
					display_type = Outputs::CRT::PAL50;
 | 
										display_type = Outputs::CRT::DisplayType::PAL50;
 | 
				
			||||||
					timing_.cycles_per_line = 71;
 | 
										timing_.cycles_per_line = 71;
 | 
				
			||||||
					timing_.line_counter_increment_offset = 0;
 | 
										timing_.line_counter_increment_offset = 0;
 | 
				
			||||||
					timing_.lines_per_progressive_field = 312;
 | 
										timing_.lines_per_progressive_field = 312;
 | 
				
			||||||
@@ -120,7 +136,7 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				case OutputMode::NTSC:
 | 
									case OutputMode::NTSC:
 | 
				
			||||||
					chrominances = ntsc_chrominances;
 | 
										chrominances = ntsc_chrominances;
 | 
				
			||||||
					display_type = Outputs::CRT::NTSC60;
 | 
										display_type = Outputs::CRT::DisplayType::NTSC60;
 | 
				
			||||||
					timing_.cycles_per_line = 65;
 | 
										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 = 65 - 33;	// TODO: this is a bit of a hack; separate vertical and horizontal counting
 | 
				
			||||||
					timing_.lines_per_progressive_field = 261;
 | 
										timing_.lines_per_progressive_field = 261;
 | 
				
			||||||
@@ -129,16 +145,15 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
 | 
								crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
 | 
				
			||||||
			crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
//			switch(output_mode) {
 | 
								switch(output_mode) {
 | 
				
			||||||
//				case OutputMode::PAL:
 | 
									case OutputMode::PAL:
 | 
				
			||||||
//					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
 | 
										crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.05f, 0.9f, 0.9f));
 | 
				
			||||||
//				break;
 | 
									break;
 | 
				
			||||||
//				case OutputMode::NTSC:
 | 
									case OutputMode::NTSC:
 | 
				
			||||||
//					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
 | 
										crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
 | 
				
			||||||
//				break;
 | 
									break;
 | 
				
			||||||
//			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for(int c = 0; c < 16; c++) {
 | 
								for(int c = 0; c < 16; c++) {
 | 
				
			||||||
				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
 | 
									uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
 | 
				
			||||||
@@ -325,7 +340,10 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Causes the 6560 to flush as much pending CRT and speaker communications as possible.
 | 
								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.
 | 
								Writes to a 6560 register.
 | 
				
			||||||
@@ -363,13 +381,13 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
				case 0xc:
 | 
									case 0xc:
 | 
				
			||||||
				case 0xd:
 | 
									case 0xd:
 | 
				
			||||||
					update_audio();
 | 
										update_audio();
 | 
				
			||||||
					speaker_->set_control(address - 0xa, value);
 | 
										audio_generator_.set_control(address - 0xa, value);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 0xe:
 | 
									case 0xe:
 | 
				
			||||||
					update_audio();
 | 
										update_audio();
 | 
				
			||||||
					registers_.auxiliary_colour = colours_[value >> 4];
 | 
										registers_.auxiliary_colour = colours_[value >> 4];
 | 
				
			||||||
					speaker_->set_volume(value & 0xf);
 | 
										audio_generator_.set_volume(value & 0xf);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 0xf: {
 | 
									case 0xf: {
 | 
				
			||||||
@@ -405,12 +423,15 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::shared_ptr<Outputs::CRT::CRT> crt_;
 | 
							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_;
 | 
							Cycles cycles_since_speaker_update_;
 | 
				
			||||||
		void update_audio() {
 | 
							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
 | 
							// register state
 | 
				
			||||||
@@ -432,7 +453,7 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
		unsigned int cycles_in_state_;
 | 
							unsigned int cycles_in_state_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// counters that cover an entire field
 | 
							// counters that cover an entire field
 | 
				
			||||||
		int horizontal_counter_, vertical_counter_, full_frame_counter_;
 | 
							int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// latches dictating start and length of drawing
 | 
							// latches dictating start and length of drawing
 | 
				
			||||||
		bool vertical_drawing_latch_, horizontal_drawing_latch_;
 | 
							bool vertical_drawing_latch_, horizontal_drawing_latch_;
 | 
				
			||||||
@@ -447,7 +468,7 @@ template <class T> class MOS6560 {
 | 
				
			|||||||
		// data latched from the bus
 | 
							// data latched from the bus
 | 
				
			||||||
		uint8_t character_code_, character_colour_, character_value_;
 | 
							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
 | 
							// lookup table from 6560 colour index to appropriate PAL/NTSC value
 | 
				
			||||||
		uint16_t colours_[16];
 | 
							uint16_t colours_[16];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,8 +76,8 @@ template <class T> class i8255 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		void update_outputs() {
 | 
							void update_outputs() {
 | 
				
			||||||
			port_handler_.set_value(0, outputs_[0]);
 | 
								if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
 | 
				
			||||||
			port_handler_.set_value(1, outputs_[1]);
 | 
								if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
 | 
				
			||||||
			port_handler_.set_value(2, outputs_[2]);
 | 
								port_handler_.set_value(2, outputs_[2]);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -210,7 +210,7 @@ uint8_t i8272::get_register(int address) {
 | 
				
			|||||||
	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\
 | 
						else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\
 | 
				
			||||||
	\
 | 
						\
 | 
				
			||||||
	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\
 | 
						if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\
 | 
				
			||||||
	CONCAT(header_found, __LINE__):	0;\
 | 
						CONCAT(header_found, __LINE__):	(void)0;\
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define FIND_DATA()	\
 | 
					#define FIND_DATA()	\
 | 
				
			||||||
	set_data_mode(DataMode::Scanning);	\
 | 
						set_data_mode(DataMode::Scanning);	\
 | 
				
			||||||
@@ -291,7 +291,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
			WAIT_FOR_EVENT(Event8272::CommandByte)
 | 
								WAIT_FOR_EVENT(Event8272::CommandByte)
 | 
				
			||||||
			SetBusy();
 | 
								SetBusy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			static const size_t required_lengths[32] = {
 | 
								static const std::size_t required_lengths[32] = {
 | 
				
			||||||
				0,	0,	9,	3,	2,	9,	9,	2,
 | 
									0,	0,	9,	3,	2,	9,	9,	2,
 | 
				
			||||||
				1,	9,	2,	0,	9,	6,	0,	3,
 | 
									1,	9,	2,	0,	9,	6,	0,	3,
 | 
				
			||||||
				0,	9,	0,	0,	0,	0,	0,	0,
 | 
									0,	9,	0,	0,	0,	0,	0,	0,
 | 
				
			||||||
@@ -563,7 +563,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.
 | 
							// 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.
 | 
							// 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;
 | 
								index_hole_limit_ = 2;
 | 
				
			||||||
		read_id_find_next_sector:
 | 
					 | 
				
			||||||
			FIND_HEADER();
 | 
								FIND_HEADER();
 | 
				
			||||||
			if(!index_hole_limit_) {
 | 
								if(!index_hole_limit_) {
 | 
				
			||||||
				SetMissingAddressMark();
 | 
									SetMissingAddressMark();
 | 
				
			||||||
@@ -833,7 +832,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
	// last thing in it will be returned first.
 | 
						// last thing in it will be returned first.
 | 
				
			||||||
	post_result:
 | 
						post_result:
 | 
				
			||||||
			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
 | 
								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("%02x ", result_stack_[result_stack_.size() - 1 - c]);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			printf("\n");
 | 
								printf("\n");
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										712
									
								
								Components/9918/9918.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										712
									
								
								Components/9918/9918.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,712 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  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_) {
 | 
				
			||||||
 | 
											crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4);
 | 
				
			||||||
 | 
											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 */
 | 
				
			||||||
@@ -8,18 +8,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "AY38910.hpp"
 | 
					#include "AY38910.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace GI::AY38910;
 | 
					using namespace GI::AY38910;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AY38910::AY38910() :
 | 
					AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
 | 
				
			||||||
		selected_register_(0),
 | 
					 | 
				
			||||||
		tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
 | 
					 | 
				
			||||||
		noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
 | 
					 | 
				
			||||||
		envelope_divider_(0), envelope_period_(0), envelope_position_(0),
 | 
					 | 
				
			||||||
		master_divider_(0),
 | 
					 | 
				
			||||||
		output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 | 
					 | 
				
			||||||
		port_handler_(nullptr) {
 | 
					 | 
				
			||||||
	output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// set up envelope lookup tables
 | 
						// set up envelope lookup tables
 | 
				
			||||||
	for(int c = 0; c < 16; c++) {
 | 
						for(int c = 0; c < 16; c++) {
 | 
				
			||||||
		for(int p = 0; p < 32; p++) {
 | 
							for(int p = 0; p < 32; p++) {
 | 
				
			||||||
@@ -63,21 +56,22 @@ AY38910::AY38910() :
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set_sample_volume_range(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AY38910::set_sample_volume_range(std::int16_t range) {
 | 
				
			||||||
	// set up volume lookup table
 | 
						// set up volume lookup table
 | 
				
			||||||
	float max_volume = 8192;
 | 
						const float max_volume = static_cast<float>(range) / 3.0f;	// As there are three channels.
 | 
				
			||||||
	float root_two = sqrtf(2.0f);
 | 
						const float root_two = sqrtf(2.0f);
 | 
				
			||||||
	for(int v = 0; v < 16; v++) {
 | 
						for(int v = 0; v < 16; v++) {
 | 
				
			||||||
		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
 | 
							volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	volumes_[0] = 0;
 | 
						volumes_[0] = 0;
 | 
				
			||||||
 | 
						evaluate_output_volume();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_clock_rate(double clock_rate) {
 | 
					void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			||||||
	set_input_rate(static_cast<float>(clock_rate));
 | 
						std::size_t c = 0;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
 | 
					 | 
				
			||||||
	unsigned int c = 0;
 | 
					 | 
				
			||||||
	while((master_divider_&7) && c < number_of_samples) {
 | 
						while((master_divider_&7) && c < number_of_samples) {
 | 
				
			||||||
		target[c] = output_volume_;
 | 
							target[c] = output_volume_;
 | 
				
			||||||
		master_divider_++;
 | 
							master_divider_++;
 | 
				
			||||||
@@ -167,7 +161,12 @@ void AY38910::evaluate_output_volume() {
 | 
				
			|||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#pragma mark - Register manipulation
 | 
					bool AY38910::is_zero_level() {
 | 
				
			||||||
 | 
						// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
 | 
				
			||||||
 | 
						return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Register manipulation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::select_register(uint8_t r) {
 | 
					void AY38910::select_register(uint8_t r) {
 | 
				
			||||||
	selected_register_ = r;
 | 
						selected_register_ = r;
 | 
				
			||||||
@@ -178,7 +177,7 @@ void AY38910::set_register_value(uint8_t value) {
 | 
				
			|||||||
	registers_[selected_register_] = value;
 | 
						registers_[selected_register_] = value;
 | 
				
			||||||
	if(selected_register_ < 14) {
 | 
						if(selected_register_ < 14) {
 | 
				
			||||||
		int selected_register = selected_register_;
 | 
							int selected_register = selected_register_;
 | 
				
			||||||
		enqueue([=] () {
 | 
							task_queue_.defer([=] () {
 | 
				
			||||||
			uint8_t masked_value = value;
 | 
								uint8_t masked_value = value;
 | 
				
			||||||
			switch(selected_register) {
 | 
								switch(selected_register) {
 | 
				
			||||||
				case 0: case 2: case 4:
 | 
									case 0: case 2: case 4:
 | 
				
			||||||
@@ -233,13 +232,13 @@ uint8_t AY38910::get_register_value() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#pragma mark - Port handling
 | 
					// MARK: - Port handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t AY38910::get_port_output(bool port_b) {
 | 
					uint8_t AY38910::get_port_output(bool port_b) {
 | 
				
			||||||
	return registers_[port_b ? 15 : 14];
 | 
						return registers_[port_b ? 15 : 14];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#pragma mark - Bus handling
 | 
					// MARK: - Bus handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_port_handler(PortHandler *handler) {
 | 
					void AY38910::set_port_handler(PortHandler *handler) {
 | 
				
			||||||
	port_handler_ = handler;
 | 
						port_handler_ = handler;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,8 @@
 | 
				
			|||||||
#ifndef AY_3_8910_hpp
 | 
					#ifndef AY_3_8910_hpp
 | 
				
			||||||
#define AY_3_8910_hpp
 | 
					#define AY_3_8910_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Outputs/Speaker.hpp"
 | 
					#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
 | 
				
			||||||
 | 
					#include "../../Concurrency/AsyncTaskQueue.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace GI {
 | 
					namespace GI {
 | 
				
			||||||
namespace AY38910 {
 | 
					namespace AY38910 {
 | 
				
			||||||
@@ -55,13 +56,10 @@ enum ControlLines {
 | 
				
			|||||||
	noise generator and a volume envelope generator, which also provides two bidirectional
 | 
						noise generator and a volume envelope generator, which also provides two bidirectional
 | 
				
			||||||
	interface ports.
 | 
						interface ports.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class AY38910: public ::Outputs::Filter<AY38910> {
 | 
					class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Creates a new AY38910.
 | 
							/// Creates a new AY38910.
 | 
				
			||||||
		AY38910();
 | 
							AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		/// Sets the clock rate at which this AY38910 will be run.
 | 
					 | 
				
			||||||
		void set_clock_rate(double clock_rate);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the value the AY would read from its data lines if it were not outputting.
 | 
							/// Sets the value the AY would read from its data lines if it were not outputting.
 | 
				
			||||||
		void set_data_input(uint8_t r);
 | 
							void set_data_input(uint8_t r);
 | 
				
			||||||
@@ -86,27 +84,32 @@ class AY38910: public ::Outputs::Filter<AY38910> {
 | 
				
			|||||||
		void set_port_handler(PortHandler *);
 | 
							void set_port_handler(PortHandler *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
 | 
							// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
 | 
				
			||||||
		void get_samples(unsigned int number_of_samples, int16_t *target);
 | 
							void get_samples(std::size_t number_of_samples, int16_t *target);
 | 
				
			||||||
 | 
							bool is_zero_level();
 | 
				
			||||||
 | 
							void set_sample_volume_range(std::int16_t range);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		int selected_register_;
 | 
							Concurrency::DeferringAsyncTaskQueue &task_queue_;
 | 
				
			||||||
		uint8_t registers_[16], output_registers_[16];
 | 
					
 | 
				
			||||||
 | 
							int selected_register_ = 0;
 | 
				
			||||||
 | 
							uint8_t registers_[16];
 | 
				
			||||||
 | 
							uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 | 
				
			||||||
		uint8_t port_inputs_[2];
 | 
							uint8_t port_inputs_[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int master_divider_;
 | 
							int master_divider_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int tone_periods_[3];
 | 
							int tone_periods_[3] = {0, 0, 0};
 | 
				
			||||||
		int tone_counters_[3];
 | 
							int tone_counters_[3] = {0, 0, 0};
 | 
				
			||||||
		int tone_outputs_[3];
 | 
							int tone_outputs_[3] = {0, 0, 0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int noise_period_;
 | 
							int noise_period_ = 0;
 | 
				
			||||||
		int noise_counter_;
 | 
							int noise_counter_ = 0;
 | 
				
			||||||
		int noise_shift_register_;
 | 
							int noise_shift_register_ = 0xffff;
 | 
				
			||||||
		int noise_output_;
 | 
							int noise_output_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int envelope_period_;
 | 
							int envelope_period_ = 0;
 | 
				
			||||||
		int envelope_divider_;
 | 
							int envelope_divider_ = 0;
 | 
				
			||||||
		int envelope_position_;
 | 
							int envelope_position_ = 0;
 | 
				
			||||||
		int envelope_shapes_[16][32];
 | 
							int envelope_shapes_[16][32];
 | 
				
			||||||
		int envelope_overflow_masks_[16];
 | 
							int envelope_overflow_masks_[16];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,7 +132,7 @@ class AY38910: public ::Outputs::Filter<AY38910> {
 | 
				
			|||||||
		inline void evaluate_output_volume();
 | 
							inline void evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline void update_bus();
 | 
							inline void update_bus();
 | 
				
			||||||
		PortHandler *port_handler_;
 | 
							PortHandler *port_handler_ = nullptr;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										115
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  KonamiSCC.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 06/01/2018.
 | 
				
			||||||
 | 
					//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "KonamiSCC.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Konami;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
 | 
				
			||||||
 | 
						task_queue_(task_queue) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SCC::is_zero_level() {
 | 
				
			||||||
 | 
						return !(channel_enable_ & 0x1f);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
 | 
				
			||||||
 | 
						if(is_zero_level()) {
 | 
				
			||||||
 | 
							std::memset(target, 0, sizeof(std::int16_t) * number_of_samples);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::size_t c = 0;
 | 
				
			||||||
 | 
						while((master_divider_&7) && c < number_of_samples) {
 | 
				
			||||||
 | 
							target[c] = transient_output_level_;
 | 
				
			||||||
 | 
							master_divider_++;
 | 
				
			||||||
 | 
							c++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while(c < number_of_samples) {
 | 
				
			||||||
 | 
							for(int channel = 0; channel < 5; ++channel) {
 | 
				
			||||||
 | 
								if(channels_[channel].tone_counter) channels_[channel].tone_counter--;
 | 
				
			||||||
 | 
								else {
 | 
				
			||||||
 | 
									channels_[channel].offset = (channels_[channel].offset + 1) & 0x1f;
 | 
				
			||||||
 | 
									channels_[channel].tone_counter = channels_[channel].period;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) {
 | 
				
			||||||
 | 
								target[c] = transient_output_level_;
 | 
				
			||||||
 | 
								c++;
 | 
				
			||||||
 | 
								master_divider_++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SCC::write(uint16_t address, uint8_t value) {
 | 
				
			||||||
 | 
						address &= 0xff;
 | 
				
			||||||
 | 
						if(address < 0x80) ram_[address] = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						task_queue_.defer([=] {
 | 
				
			||||||
 | 
							// Check for a write into waveform memory.
 | 
				
			||||||
 | 
							if(address < 0x80) {
 | 
				
			||||||
 | 
								waves_[address >> 5].samples[address & 0x1f] = value;
 | 
				
			||||||
 | 
							} else switch(address) {
 | 
				
			||||||
 | 
								default: break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x80: case 0x82: case 0x84: case 0x86: case 0x88: {
 | 
				
			||||||
 | 
									int channel = (address - 0x80) >> 1;
 | 
				
			||||||
 | 
									channels_[channel].period = (channels_[channel].period & ~0xff) | value;
 | 
				
			||||||
 | 
								} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x81: case 0x83: case 0x85: case 0x87: case 0x89: {
 | 
				
			||||||
 | 
									int channel = (address - 0x80) >> 1;
 | 
				
			||||||
 | 
									channels_[channel].period = (channels_[channel].period & 0xff) | ((value & 0xf) << 8);
 | 
				
			||||||
 | 
								} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e:
 | 
				
			||||||
 | 
									channels_[address - 0x8a].amplitude = value & 0xf;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x8f:
 | 
				
			||||||
 | 
									channel_enable_ = value;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							evaluate_output_volume();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SCC::evaluate_output_volume() {
 | 
				
			||||||
 | 
						transient_output_level_ =
 | 
				
			||||||
 | 
							static_cast<int16_t>(
 | 
				
			||||||
 | 
								((
 | 
				
			||||||
 | 
									(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
 | 
				
			||||||
 | 
									(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
 | 
				
			||||||
 | 
									(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
 | 
				
			||||||
 | 
									(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
 | 
				
			||||||
 | 
									(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
 | 
				
			||||||
 | 
								) * master_volume_) / (255*15*5)
 | 
				
			||||||
 | 
								// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5.
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SCC::set_sample_volume_range(std::int16_t range) {
 | 
				
			||||||
 | 
						master_volume_ = range;
 | 
				
			||||||
 | 
						evaluate_output_volume();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t SCC::read(uint16_t address) {
 | 
				
			||||||
 | 
						address &= 0xff;
 | 
				
			||||||
 | 
						if(address < 0x80) {
 | 
				
			||||||
 | 
							return ram_[address];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0xff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										74
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  KonamiSCC.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 06/01/2018.
 | 
				
			||||||
 | 
					//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef KonamiSCC_hpp
 | 
				
			||||||
 | 
					#define KonamiSCC_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
 | 
				
			||||||
 | 
					#include "../../Concurrency/AsyncTaskQueue.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Konami {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Provides an emulation of Konami's Sound Creative Chip ('SCC').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						The SCC is a primitive wavetable synthesis chip, offering 32-sample tables,
 | 
				
			||||||
 | 
						and five channels of output. The original SCC uses the same wave for channels
 | 
				
			||||||
 | 
						four and five, the SCC+ supports different waves for the two channels.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class SCC: public ::Outputs::Speaker::SampleSource {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/// Creates a new SCC.
 | 
				
			||||||
 | 
							SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// As per ::SampleSource; provides a broadphase test for silence.
 | 
				
			||||||
 | 
							bool is_zero_level();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// As per ::SampleSource; provides audio output.
 | 
				
			||||||
 | 
							void get_samples(std::size_t number_of_samples, std::int16_t *target);
 | 
				
			||||||
 | 
							void set_sample_volume_range(std::int16_t range);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Writes to the SCC.
 | 
				
			||||||
 | 
							void write(uint16_t address, uint8_t value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Reads from the SCC.
 | 
				
			||||||
 | 
							uint8_t read(uint16_t address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							Concurrency::DeferringAsyncTaskQueue &task_queue_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// State from here on down is accessed ony from the audio thread.
 | 
				
			||||||
 | 
							int master_divider_ = 0;
 | 
				
			||||||
 | 
							std::int16_t master_volume_ = 0;
 | 
				
			||||||
 | 
							int16_t transient_output_level_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct Channel {
 | 
				
			||||||
 | 
								int period = 0;
 | 
				
			||||||
 | 
								int amplitude = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								int tone_counter = 0;
 | 
				
			||||||
 | 
								int offset = 0;
 | 
				
			||||||
 | 
							} channels_[5];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct Wavetable {
 | 
				
			||||||
 | 
								std::uint8_t samples[32];
 | 
				
			||||||
 | 
							} waves_[4];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::uint8_t channel_enable_ = 0;
 | 
				
			||||||
 | 
							std::uint8_t test_register_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// This keeps a copy of wave memory that is accessed from the
 | 
				
			||||||
 | 
							// main emulation thread.
 | 
				
			||||||
 | 
							std::uint8_t ram_[128];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* KonamiSCC_hpp */
 | 
				
			||||||
							
								
								
									
										161
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  SN76489.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 26/02/2018.
 | 
				
			||||||
 | 
					//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "SN76489.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cassert>
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace TI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
 | 
				
			||||||
 | 
						set_sample_volume_range(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch(personality) {
 | 
				
			||||||
 | 
							case Personality::SN76494:
 | 
				
			||||||
 | 
								master_divider_period_ = 2;
 | 
				
			||||||
 | 
								shifter_is_16bit_ = false;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
							case Personality::SN76489:
 | 
				
			||||||
 | 
								master_divider_period_ = 16;
 | 
				
			||||||
 | 
								shifter_is_16bit_ = false;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
							case Personality::SMS:
 | 
				
			||||||
 | 
								master_divider_period_ = 16;
 | 
				
			||||||
 | 
								shifter_is_16bit_ = true;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert((master_divider_period_ % additional_divider) == 0);
 | 
				
			||||||
 | 
						assert(additional_divider < master_divider_period_);
 | 
				
			||||||
 | 
						master_divider_period_ /= additional_divider;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SN76489::set_sample_volume_range(std::int16_t range) {
 | 
				
			||||||
 | 
						// Build a volume table.
 | 
				
			||||||
 | 
						double multiplier = pow(10.0, -0.1);
 | 
				
			||||||
 | 
						double volume = static_cast<float>(range) / 4.0f;	// As there are four channels.
 | 
				
			||||||
 | 
						for(int c = 0; c < 16; ++c) {
 | 
				
			||||||
 | 
							volumes_[c] = (int)round(volume);
 | 
				
			||||||
 | 
							volume *= multiplier;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						volumes_[15] = 0;
 | 
				
			||||||
 | 
						evaluate_output_volume();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SN76489::set_register(uint8_t value) {
 | 
				
			||||||
 | 
						task_queue_.defer([value, this] () {
 | 
				
			||||||
 | 
							if(value & 0x80) {
 | 
				
			||||||
 | 
								active_register_ = value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const int channel = (active_register_ >> 5)&3;
 | 
				
			||||||
 | 
							if(active_register_ & 0x10) {
 | 
				
			||||||
 | 
								// latch for volume
 | 
				
			||||||
 | 
								channels_[channel].volume = value & 0xf;
 | 
				
			||||||
 | 
								evaluate_output_volume();
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// latch for tone/data
 | 
				
			||||||
 | 
								if(channel < 3) {
 | 
				
			||||||
 | 
									if(value & 0x80) {
 | 
				
			||||||
 | 
										channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// writes to the noise register always reset the shifter
 | 
				
			||||||
 | 
									noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if(value & 4) {
 | 
				
			||||||
 | 
										noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3));
 | 
				
			||||||
 | 
									// Special case: if these bits are both set, the noise channel should track channel 2,
 | 
				
			||||||
 | 
									// which is marked with a divider of 0xffff.
 | 
				
			||||||
 | 
									if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SN76489::is_zero_level() {
 | 
				
			||||||
 | 
						return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SN76489::evaluate_output_volume() {
 | 
				
			||||||
 | 
						output_volume_ = static_cast<int16_t>(
 | 
				
			||||||
 | 
							channels_[0].level * volumes_[channels_[0].volume] +
 | 
				
			||||||
 | 
							channels_[1].level * volumes_[channels_[1].volume] +
 | 
				
			||||||
 | 
							channels_[2].level * volumes_[channels_[2].volume] +
 | 
				
			||||||
 | 
							channels_[3].level * volumes_[channels_[3].volume]
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
 | 
				
			||||||
 | 
						std::size_t c = 0;
 | 
				
			||||||
 | 
						while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) {
 | 
				
			||||||
 | 
							target[c] = output_volume_;
 | 
				
			||||||
 | 
							master_divider_++;
 | 
				
			||||||
 | 
							c++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while(c < number_of_samples) {
 | 
				
			||||||
 | 
							bool did_flip = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define step_channel(x, s) \
 | 
				
			||||||
 | 
							if(channels_[x].counter) channels_[x].counter--;\
 | 
				
			||||||
 | 
							else {\
 | 
				
			||||||
 | 
								channels_[x].level ^= 1;\
 | 
				
			||||||
 | 
								channels_[x].counter = channels_[x].divider;\
 | 
				
			||||||
 | 
								s;\
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							step_channel(0, /**/);
 | 
				
			||||||
 | 
							step_channel(1, /**/);
 | 
				
			||||||
 | 
							step_channel(2, did_flip = true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#undef step_channel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(channels_[3].divider != 0xffff) {
 | 
				
			||||||
 | 
								if(channels_[3].counter) channels_[3].counter--;
 | 
				
			||||||
 | 
								else {
 | 
				
			||||||
 | 
									did_flip = true;
 | 
				
			||||||
 | 
									channels_[3].counter = channels_[3].divider;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(did_flip) {
 | 
				
			||||||
 | 
								channels_[3].level = noise_shifter_ & 1;
 | 
				
			||||||
 | 
								int new_bit = channels_[3].level;
 | 
				
			||||||
 | 
								switch(noise_mode_) {
 | 
				
			||||||
 | 
									default: break;
 | 
				
			||||||
 | 
									case Noise15:
 | 
				
			||||||
 | 
										new_bit ^= (noise_shifter_ >> 1);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case Noise16:
 | 
				
			||||||
 | 
										new_bit ^= (noise_shifter_ >> 3);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								noise_shifter_ >>= 1;
 | 
				
			||||||
 | 
								noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) {
 | 
				
			||||||
 | 
								target[c] = output_volume_;
 | 
				
			||||||
 | 
								c++;
 | 
				
			||||||
 | 
								master_divider_++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						master_divider_ &= (master_divider_period_ - 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										68
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  SN76489.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 26/02/2018.
 | 
				
			||||||
 | 
					//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef SN76489_hpp
 | 
				
			||||||
 | 
					#define SN76489_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
 | 
				
			||||||
 | 
					#include "../../Concurrency/AsyncTaskQueue.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace TI {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SN76489: public Outputs::Speaker::SampleSource {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							enum class Personality {
 | 
				
			||||||
 | 
								SN76489,
 | 
				
			||||||
 | 
								SN76494,
 | 
				
			||||||
 | 
								SMS
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Creates a new SN76489.
 | 
				
			||||||
 | 
							SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Writes a new value to the SN76489.
 | 
				
			||||||
 | 
							void set_register(uint8_t value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// As per SampleSource.
 | 
				
			||||||
 | 
							void get_samples(std::size_t number_of_samples, std::int16_t *target);
 | 
				
			||||||
 | 
							bool is_zero_level();
 | 
				
			||||||
 | 
							void set_sample_volume_range(std::int16_t range);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							int master_divider_ = 0;
 | 
				
			||||||
 | 
							int master_divider_period_ = 16;
 | 
				
			||||||
 | 
							int16_t output_volume_ = 0;
 | 
				
			||||||
 | 
							void evaluate_output_volume();
 | 
				
			||||||
 | 
							int volumes_[16];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Concurrency::DeferringAsyncTaskQueue &task_queue_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct ToneChannel {
 | 
				
			||||||
 | 
								// Programmatically-set state; updated by the processor.
 | 
				
			||||||
 | 
								uint16_t divider = 0;
 | 
				
			||||||
 | 
								uint8_t volume = 0xf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Active state; self-evolving as a function of time.
 | 
				
			||||||
 | 
								uint16_t counter = 0;
 | 
				
			||||||
 | 
								int level = 0;
 | 
				
			||||||
 | 
							} channels_[4];
 | 
				
			||||||
 | 
							enum {
 | 
				
			||||||
 | 
								Periodic15,
 | 
				
			||||||
 | 
								Periodic16,
 | 
				
			||||||
 | 
								Noise15,
 | 
				
			||||||
 | 
								Noise16
 | 
				
			||||||
 | 
							} noise_mode_ = Periodic15;
 | 
				
			||||||
 | 
							uint16_t noise_shifter_ = 0;
 | 
				
			||||||
 | 
							int active_register_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool shifter_is_16bit_ = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* SN76489_hpp */
 | 
				
			||||||
@@ -45,6 +45,7 @@ AsyncTaskQueue::AsyncTaskQueue()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
AsyncTaskQueue::~AsyncTaskQueue() {
 | 
					AsyncTaskQueue::~AsyncTaskQueue() {
 | 
				
			||||||
#ifdef __APPLE__
 | 
					#ifdef __APPLE__
 | 
				
			||||||
 | 
						flush();
 | 
				
			||||||
	dispatch_release(serial_dispatch_queue_);
 | 
						dispatch_release(serial_dispatch_queue_);
 | 
				
			||||||
	serial_dispatch_queue_ = nullptr;
 | 
						serial_dispatch_queue_ = nullptr;
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
@@ -79,3 +80,26 @@ void AsyncTaskQueue::flush() {
 | 
				
			|||||||
	flush_condition->wait(lock);
 | 
						flush_condition->wait(lock);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
 | 
				
			||||||
 | 
						perform();
 | 
				
			||||||
 | 
						flush();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
 | 
				
			||||||
 | 
						if(!deferred_tasks_) {
 | 
				
			||||||
 | 
							deferred_tasks_.reset(new std::list<std::function<void(void)>>);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						deferred_tasks_->push_back(function);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DeferringAsyncTaskQueue::perform() {
 | 
				
			||||||
 | 
						if(!deferred_tasks_) return;
 | 
				
			||||||
 | 
						std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
 | 
				
			||||||
 | 
						deferred_tasks_.reset();
 | 
				
			||||||
 | 
						enqueue([deferred_tasks] {
 | 
				
			||||||
 | 
							for(auto &function : *deferred_tasks) {
 | 
				
			||||||
 | 
								function();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,10 +9,12 @@
 | 
				
			|||||||
#ifndef AsyncTaskQueue_hpp
 | 
					#ifndef AsyncTaskQueue_hpp
 | 
				
			||||||
#define AsyncTaskQueue_hpp
 | 
					#define AsyncTaskQueue_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <condition_variable>
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <list>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <thread>
 | 
					#include <thread>
 | 
				
			||||||
#include <list>
 | 
					 | 
				
			||||||
#include <condition_variable>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef __APPLE__
 | 
					#ifdef __APPLE__
 | 
				
			||||||
#include <dispatch/dispatch.h>
 | 
					#include <dispatch/dispatch.h>
 | 
				
			||||||
@@ -28,7 +30,7 @@ namespace Concurrency {
 | 
				
			|||||||
class AsyncTaskQueue {
 | 
					class AsyncTaskQueue {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		AsyncTaskQueue();
 | 
							AsyncTaskQueue();
 | 
				
			||||||
		~AsyncTaskQueue();
 | 
							virtual ~AsyncTaskQueue();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Adds @c function to the queue.
 | 
								Adds @c function to the queue.
 | 
				
			||||||
@@ -57,6 +59,39 @@ class AsyncTaskQueue {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						A deferring async task queue is one that accepts a list of functions to be performed but defers
 | 
				
			||||||
 | 
						any action until told to perform. It performs them by enquing a single asynchronous task that will
 | 
				
			||||||
 | 
						perform the deferred tasks in order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It therefore offers similar semantics to an asynchronous task queue, but allows for management of
 | 
				
			||||||
 | 
						synchronisation costs, since neither defer nor perform make any effort to be thread safe.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class DeferringAsyncTaskQueue: public AsyncTaskQueue {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							~DeferringAsyncTaskQueue();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Adds a function to the deferral list.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								This is not thread safe; it should be serialised with other calls to itself and to perform.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void defer(std::function<void(void)> function);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Enqueues a function that will perform all currently deferred functions, in the
 | 
				
			||||||
 | 
								order that they were deferred.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								This is not thread safe; it should be serialised with other calls to itself and to defer.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void perform();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							// TODO: this is a shared_ptr because of the issues capturing moveables in C++11;
 | 
				
			||||||
 | 
							// switch to a unique_ptr if/when adapting to C++14
 | 
				
			||||||
 | 
							std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* Concurrency_hpp */
 | 
					#endif /* Concurrency_hpp */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,16 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace Concurrency;
 | 
					using namespace Concurrency;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BestEffortUpdater::BestEffortUpdater() {
 | 
				
			||||||
 | 
						// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means.
 | 
				
			||||||
 | 
						update_is_ongoing_.clear();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BestEffortUpdater::~BestEffortUpdater() {
 | 
				
			||||||
 | 
						// Don't allow further deconstruction until the task queue is stopped.
 | 
				
			||||||
 | 
						flush();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BestEffortUpdater::update() {
 | 
					void BestEffortUpdater::update() {
 | 
				
			||||||
	// Perform an update only if one is not currently ongoing.
 | 
						// Perform an update only if one is not currently ongoing.
 | 
				
			||||||
	if(!update_is_ongoing_.test_and_set()) {
 | 
						if(!update_is_ongoing_.test_and_set()) {
 | 
				
			||||||
@@ -26,13 +36,11 @@ void BestEffortUpdater::update() {
 | 
				
			|||||||
				// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
 | 
									// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
 | 
				
			||||||
				// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
 | 
									// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
 | 
				
			||||||
				// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
 | 
									// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
 | 
				
			||||||
				const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
 | 
									const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
 | 
				
			||||||
				if(duration > 0) {
 | 
									if(integer_duration > 0) {
 | 
				
			||||||
					double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
 | 
					 | 
				
			||||||
					error_ = fmod(cycles, 1.0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					if(delegate_) {
 | 
										if(delegate_) {
 | 
				
			||||||
						delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
 | 
											const double duration = static_cast<double>(integer_duration) / 1e9;
 | 
				
			||||||
 | 
											delegate_->update(this, duration, has_skipped_);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					has_skipped_ = false;
 | 
										has_skipped_ = false;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -60,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) {
 | 
				
			|||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
 | 
					 | 
				
			||||||
	async_task_queue_.enqueue([this, clock_rate]() {
 | 
					 | 
				
			||||||
		this->clock_rate_ = clock_rate;
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@
 | 
				
			|||||||
#include <chrono>
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "AsyncTaskQueue.hpp"
 | 
					#include "AsyncTaskQueue.hpp"
 | 
				
			||||||
 | 
					#include "../ClockReceiver/TimeTypes.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Concurrency {
 | 
					namespace Concurrency {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,17 +26,17 @@ namespace Concurrency {
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
class BestEffortUpdater {
 | 
					class BestEffortUpdater {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
 | 
							BestEffortUpdater();
 | 
				
			||||||
 | 
							~BestEffortUpdater();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// A delegate receives timing cues.
 | 
							/// A delegate receives timing cues.
 | 
				
			||||||
		struct Delegate {
 | 
							struct Delegate {
 | 
				
			||||||
			virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
 | 
								virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the current delegate.
 | 
							/// Sets the current delegate.
 | 
				
			||||||
		void set_delegate(Delegate *);
 | 
							void set_delegate(Delegate *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the clock rate of the delegate.
 | 
					 | 
				
			||||||
		void set_clock_rate(double clock_rate);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
 | 
								If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
 | 
				
			||||||
			The call is asynchronous; this method will return immediately.
 | 
								The call is asynchronous; this method will return immediately.
 | 
				
			||||||
@@ -51,11 +52,9 @@ class BestEffortUpdater {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
 | 
							std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
 | 
				
			||||||
		bool has_previous_time_point_ = false;
 | 
							bool has_previous_time_point_ = false;
 | 
				
			||||||
		double error_ = 0.0;
 | 
					 | 
				
			||||||
		bool has_skipped_ = false;
 | 
							bool has_skipped_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Delegate *delegate_ = nullptr;
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
		double clock_rate_ = 1.0;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								Configurable/Configurable.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Configurable/Configurable.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Configurable.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 18/11/2017.
 | 
				
			||||||
 | 
					//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Configurable.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Configurable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ListSelection *BooleanSelection::list_selection() {
 | 
				
			||||||
 | 
						return new ListSelection(value ? "yes" : "no");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ListSelection *ListSelection::list_selection() {
 | 
				
			||||||
 | 
						return new ListSelection(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BooleanSelection *ListSelection::boolean_selection() {
 | 
				
			||||||
 | 
						return new BooleanSelection(value != "no" && value != "n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BooleanSelection *BooleanSelection::boolean_selection() {
 | 
				
			||||||
 | 
						return new BooleanSelection(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								Configurable/Configurable.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								Configurable/Configurable.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Configurable.h
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 17/11/2017.
 | 
				
			||||||
 | 
					//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Configurable_h
 | 
				
			||||||
 | 
					#define Configurable_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Configurable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						The Option class hierarchy provides a way for components, machines, etc, to provide a named
 | 
				
			||||||
 | 
						list of typed options to which they can respond.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					struct Option {
 | 
				
			||||||
 | 
						std::string long_name;
 | 
				
			||||||
 | 
						std::string short_name;
 | 
				
			||||||
 | 
						virtual ~Option() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual bool operator==(const Option &rhs) {
 | 
				
			||||||
 | 
							return long_name == rhs.long_name && short_name == rhs.short_name;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct BooleanOption: public Option {
 | 
				
			||||||
 | 
						BooleanOption(const std::string &long_name, const std::string &short_name) : Option(long_name, short_name) {}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ListOption: public Option {
 | 
				
			||||||
 | 
						std::vector<std::string> options;
 | 
				
			||||||
 | 
						ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual bool operator==(const Option &rhs) {
 | 
				
			||||||
 | 
							const ListOption *list_rhs = dynamic_cast<const ListOption *>(&rhs);
 | 
				
			||||||
 | 
							if(!list_rhs) return false;
 | 
				
			||||||
 | 
							return long_name == rhs.long_name && short_name == rhs.short_name && options == list_rhs->options;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct BooleanSelection;
 | 
				
			||||||
 | 
					struct ListSelection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Selections are responses to Options.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					struct Selection {
 | 
				
			||||||
 | 
						virtual ~Selection() {}
 | 
				
			||||||
 | 
						virtual ListSelection *list_selection() = 0;
 | 
				
			||||||
 | 
						virtual BooleanSelection *boolean_selection() = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct BooleanSelection: public Selection {
 | 
				
			||||||
 | 
						bool value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ListSelection *list_selection();
 | 
				
			||||||
 | 
						BooleanSelection *boolean_selection();
 | 
				
			||||||
 | 
						BooleanSelection(bool value) : value(value) {}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ListSelection: public Selection {
 | 
				
			||||||
 | 
						std::string value;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						ListSelection *list_selection();
 | 
				
			||||||
 | 
						BooleanSelection *boolean_selection();
 | 
				
			||||||
 | 
						ListSelection(const std::string value) : value(value) {}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using SelectionSet = std::map<std::string, std::unique_ptr<Selection>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						A Configuratble provides the options that it responds to and allows selections to be set.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					struct Device {
 | 
				
			||||||
 | 
						virtual std::vector<std::unique_ptr<Option>> get_options() = 0;
 | 
				
			||||||
 | 
						virtual void set_selections(const SelectionSet &selection_by_option) = 0;
 | 
				
			||||||
 | 
						virtual SelectionSet get_accurate_selections() = 0;
 | 
				
			||||||
 | 
						virtual SelectionSet get_user_friendly_selections() = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> T *selection(const Configurable::SelectionSet &selections_by_option, const std::string &name) {
 | 
				
			||||||
 | 
						auto selection = selections_by_option.find(name);
 | 
				
			||||||
 | 
						if(selection == selections_by_option.end()) return nullptr;
 | 
				
			||||||
 | 
						return dynamic_cast<T *>(selection->second.get());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Configurable_h */
 | 
				
			||||||
							
								
								
									
										93
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StandardOptions.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 20/11/2017.
 | 
				
			||||||
 | 
					//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "StandardOptions.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Appends a Boolean selection of @c selection for option @c name to @c selection_set.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) {
 | 
				
			||||||
 | 
						selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) {
 | 
				
			||||||
 | 
						auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
 | 
				
			||||||
 | 
						if(!quickload) return false;
 | 
				
			||||||
 | 
						result = quickload->value;
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Standard option list builder
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
 | 
				
			||||||
 | 
						std::vector<std::unique_ptr<Configurable::Option>> options;
 | 
				
			||||||
 | 
						if(mask & QuickLoadTape)				options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
 | 
				
			||||||
 | 
						if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) {
 | 
				
			||||||
 | 
							std::vector<std::string> display_options;
 | 
				
			||||||
 | 
							if(mask & DisplayComposite)	display_options.emplace_back("composite");
 | 
				
			||||||
 | 
							if(mask & DisplaySVideo) 	display_options.emplace_back("svideo");
 | 
				
			||||||
 | 
							if(mask & DisplayRGB) 		display_options.emplace_back("rgb");
 | 
				
			||||||
 | 
							options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
 | 
				
			||||||
 | 
						return options;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Selection appenders
 | 
				
			||||||
 | 
					void Configurable::append_quick_load_tape_selection(Configurable::SelectionSet &selection_set, bool selection) {
 | 
				
			||||||
 | 
						append_bool(selection_set, "quickload", selection);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection) {
 | 
				
			||||||
 | 
						append_bool(selection_set, "autotapemotor", selection);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) {
 | 
				
			||||||
 | 
						std::string string_selection;
 | 
				
			||||||
 | 
						switch(selection) {
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							case Display::RGB:			string_selection = "rgb";		break;
 | 
				
			||||||
 | 
							case Display::SVideo:		string_selection = "svideo";	break;
 | 
				
			||||||
 | 
							case Display::Composite:	string_selection = "composite";	break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Selection parsers
 | 
				
			||||||
 | 
					bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) {
 | 
				
			||||||
 | 
						return get_bool(selections_by_option, "quickload", result);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Configurable::get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result) {
 | 
				
			||||||
 | 
						return get_bool(selections_by_option, "autotapemotor", result);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Configurable::get_display(const Configurable::SelectionSet &selections_by_option, Configurable::Display &result) {
 | 
				
			||||||
 | 
						auto display = Configurable::selection<Configurable::ListSelection>(selections_by_option, "display");
 | 
				
			||||||
 | 
						if(display) {
 | 
				
			||||||
 | 
							if(display->value == "rgb") {
 | 
				
			||||||
 | 
								result = Configurable::Display::RGB;
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if(display->value == "svideo") {
 | 
				
			||||||
 | 
								result = Configurable::Display::SVideo;
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if(display->value == "composite") {
 | 
				
			||||||
 | 
								result = Configurable::Display::Composite;
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										79
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StandardOptions.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 20/11/2017.
 | 
				
			||||||
 | 
					//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef StandardOptions_hpp
 | 
				
			||||||
 | 
					#define StandardOptions_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Configurable.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Configurable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum StandardOptions {
 | 
				
			||||||
 | 
						DisplayRGB					= (1 << 0),
 | 
				
			||||||
 | 
						DisplaySVideo				= (1 << 1),
 | 
				
			||||||
 | 
						DisplayComposite			= (1 << 2),
 | 
				
			||||||
 | 
						QuickLoadTape				= (1 << 3),
 | 
				
			||||||
 | 
						AutomaticTapeMotorControl	= (1 << 4)
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class Display {
 | 
				
			||||||
 | 
						RGB,
 | 
				
			||||||
 | 
						SVideo,
 | 
				
			||||||
 | 
						Composite
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						@returns An option list comprised of the standard names for all the options indicated by @c mask.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Option>> standard_options(StandardOptions mask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Appends to @c selection_set a selection of @c selection for QuickLoadTape.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void append_quick_load_tape_selection(SelectionSet &selection_set, bool selection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Appends to @c selection_set a selection of @c selection for AutomaticTapeMotorControl.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Appends to @c selection_set a selection of @c selection for DisplayRGBComposite.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void append_display_selection(SelectionSet &selection_set, Display selection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Attempts to discern a QuickLoadTape selection from @c selections_by_option.
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
						@param selections_by_option The user selections.
 | 
				
			||||||
 | 
						@param result The location to which the selection will be stored if found.
 | 
				
			||||||
 | 
						@returns @c true if a selection is found; @c false otherwise.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool get_quick_load_tape(const SelectionSet &selections_by_option, bool &result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Attempts to discern an AutomaticTapeMotorControl selection from @c selections_by_option.
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
						@param selections_by_option The user selections.
 | 
				
			||||||
 | 
						@param result The location to which the selection will be stored if found.
 | 
				
			||||||
 | 
						@returns @c true if a selection is found; @c false otherwise.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Attempts to discern a display RGB/composite selection from @c selections_by_option.
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
						@param selections_by_option The user selections.
 | 
				
			||||||
 | 
						@param result The location to which the selection will be stored if found.
 | 
				
			||||||
 | 
						@returns @c true if a selection is found; @c false otherwise.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool get_display(const SelectionSet &selections_by_option, Display &result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* StandardOptions_hpp */
 | 
				
			||||||
@@ -21,18 +21,45 @@ class Joystick {
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		virtual ~Joystick() {}
 | 
							virtual ~Joystick() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		enum class DigitalInput {
 | 
							struct DigitalInput {
 | 
				
			||||||
			Up, Down, Left, Right, Fire
 | 
								enum Type {
 | 
				
			||||||
 | 
									Up, Down, Left, Right, Fire,
 | 
				
			||||||
 | 
									Key
 | 
				
			||||||
 | 
								} type;
 | 
				
			||||||
 | 
								union {
 | 
				
			||||||
 | 
									struct {
 | 
				
			||||||
 | 
										int index;
 | 
				
			||||||
 | 
									} control;
 | 
				
			||||||
 | 
									struct {
 | 
				
			||||||
 | 
										wchar_t symbol;
 | 
				
			||||||
 | 
									} key;
 | 
				
			||||||
 | 
								} info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								DigitalInput(Type type, int index = 0) : type(type) {
 | 
				
			||||||
 | 
									info.control.index = index;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								DigitalInput(wchar_t symbol) : type(Key) {
 | 
				
			||||||
 | 
									info.key.symbol = symbol;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bool operator == (const DigitalInput &rhs) {
 | 
				
			||||||
 | 
									if(rhs.type != type) return false;
 | 
				
			||||||
 | 
									if(rhs.type == Key) {
 | 
				
			||||||
 | 
										return rhs.info.key.symbol == info.key.symbol;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										return rhs.info.control.index == info.control.index;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							virtual std::vector<DigitalInput> get_inputs() = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Host interface.
 | 
							// Host interface.
 | 
				
			||||||
		virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0;
 | 
							virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0;
 | 
				
			||||||
		virtual void reset_all_inputs() {
 | 
							virtual void reset_all_inputs() {
 | 
				
			||||||
			set_digital_input(DigitalInput::Up, false);
 | 
								for(const auto &input: get_inputs()) {
 | 
				
			||||||
			set_digital_input(DigitalInput::Down, false);
 | 
									set_digital_input(input, false);
 | 
				
			||||||
			set_digital_input(DigitalInput::Left, false);
 | 
								}
 | 
				
			||||||
			set_digital_input(DigitalInput::Right, false);
 | 
					 | 
				
			||||||
			set_digital_input(DigitalInput::Fire, false);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ using namespace Inputs;
 | 
				
			|||||||
Keyboard::Keyboard() {}
 | 
					Keyboard::Keyboard() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Keyboard::set_key_pressed(Key key, bool is_pressed) {
 | 
					void Keyboard::set_key_pressed(Key key, bool is_pressed) {
 | 
				
			||||||
	size_t key_offset = static_cast<size_t>(key);
 | 
						std::size_t key_offset = static_cast<std::size_t>(key);
 | 
				
			||||||
	if(key_offset >= key_states_.size()) {
 | 
						if(key_offset >= key_states_.size()) {
 | 
				
			||||||
		key_states_.resize(key_offset+1, false);
 | 
							key_states_.resize(key_offset+1, false);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -32,7 +32,7 @@ void Keyboard::set_delegate(Delegate *delegate) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Keyboard::get_key_state(Key key) {
 | 
					bool Keyboard::get_key_state(Key key) {
 | 
				
			||||||
	size_t key_offset = static_cast<size_t>(key);
 | 
						std::size_t key_offset = static_cast<std::size_t>(key);
 | 
				
			||||||
	if(key_offset >= key_states_.size()) return false;
 | 
						if(key_offset >= key_states_.size()) return false;
 | 
				
			||||||
	return key_states_[key_offset];
 | 
						return key_states_[key_offset];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,12 +20,29 @@
 | 
				
			|||||||
#include "../Utility/MemoryFuzzer.hpp"
 | 
					#include "../Utility/MemoryFuzzer.hpp"
 | 
				
			||||||
#include "../Utility/Typer.hpp"
 | 
					#include "../Utility/Typer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../ConfigurationTarget.hpp"
 | 
				
			||||||
 | 
					#include "../CRTMachine.hpp"
 | 
				
			||||||
 | 
					#include "../KeyboardMachine.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Storage/Tape/Tape.hpp"
 | 
					#include "../../Storage/Tape/Tape.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../ClockReceiver/ForceInline.hpp"
 | 
					#include "../../ClockReceiver/ForceInline.hpp"
 | 
				
			||||||
 | 
					#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Analyser/Static/AmstradCPC/Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace AmstradCPC {
 | 
					namespace AmstradCPC {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ROMType: int {
 | 
				
			||||||
 | 
						OS464 = 0,	BASIC464,
 | 
				
			||||||
 | 
						OS664,		BASIC664,
 | 
				
			||||||
 | 
						OS6128,		BASIC6128,
 | 
				
			||||||
 | 
						AMSDOS
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
 | 
						Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
 | 
				
			||||||
	is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period
 | 
						is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period
 | 
				
			||||||
@@ -105,14 +122,12 @@ class InterruptTimer {
 | 
				
			|||||||
class AYDeferrer {
 | 
					class AYDeferrer {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Constructs a new AY instance and sets its clock rate.
 | 
							/// Constructs a new AY instance and sets its clock rate.
 | 
				
			||||||
		inline void setup_output() {
 | 
							AYDeferrer() : ay_(audio_queue_), speaker_(ay_) {
 | 
				
			||||||
			ay_.reset(new GI::AY38910::AY38910);
 | 
								speaker_.set_input_rate(1000000);
 | 
				
			||||||
			ay_->set_input_rate(1000000);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Destructs the AY.
 | 
							~AYDeferrer() {
 | 
				
			||||||
		inline void close_output() {
 | 
								audio_queue_.flush();
 | 
				
			||||||
			ay_.reset();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Adds @c half_cycles half cycles to the amount of time that has passed.
 | 
							/// Adds @c half_cycles half cycles to the amount of time that has passed.
 | 
				
			||||||
@@ -122,26 +137,28 @@ class AYDeferrer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		/// Enqueues an update-to-now into the AY's deferred queue.
 | 
							/// Enqueues an update-to-now into the AY's deferred queue.
 | 
				
			||||||
		inline void update() {
 | 
							inline void update() {
 | 
				
			||||||
			ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4)));
 | 
								speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4)));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Issues a request to the AY to perform all processing up to the current time.
 | 
							/// Issues a request to the AY to perform all processing up to the current time.
 | 
				
			||||||
		inline void flush() {
 | 
							inline void flush() {
 | 
				
			||||||
			ay_->flush();
 | 
								audio_queue_.perform();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns the speaker the AY is using for output.
 | 
							/// @returns the speaker the AY is using for output.
 | 
				
			||||||
		std::shared_ptr<Outputs::Speaker> get_speaker() {
 | 
							Outputs::Speaker::Speaker *get_speaker() {
 | 
				
			||||||
			return ay_;
 | 
								return &speaker_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns the AY itself.
 | 
							/// @returns the AY itself.
 | 
				
			||||||
		GI::AY38910::AY38910 *ay() {
 | 
							GI::AY38910::AY38910 &ay() {
 | 
				
			||||||
			return ay_.get();
 | 
								return ay_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::shared_ptr<GI::AY38910::AY38910> ay_;
 | 
							Concurrency::DeferringAsyncTaskQueue audio_queue_;
 | 
				
			||||||
 | 
							GI::AY38910::AY38910 ay_;
 | 
				
			||||||
 | 
							Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
 | 
				
			||||||
		HalfCycles cycles_since_update_;
 | 
							HalfCycles cycles_since_update_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -300,7 +317,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
 | 
										"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
 | 
				
			||||||
				"}");
 | 
									"}");
 | 
				
			||||||
			crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
 | 
								crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
 | 
				
			||||||
			crt_->set_output_device(Outputs::CRT::Monitor);
 | 
								crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Destructs the CRT.
 | 
							/// Destructs the CRT.
 | 
				
			||||||
@@ -309,8 +326,8 @@ class CRTCBusHandler {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns the CRT.
 | 
							/// @returns the CRT.
 | 
				
			||||||
		std::shared_ptr<Outputs::CRT::CRT> get_crt() {
 | 
							Outputs::CRT::CRT *get_crt() {
 | 
				
			||||||
			return crt_;
 | 
								return crt_.get();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
@@ -493,7 +510,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
		bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
 | 
							bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
 | 
				
			||||||
		int cycles_into_hsync_ = 0;
 | 
							int cycles_into_hsync_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::shared_ptr<Outputs::CRT::CRT> crt_;
 | 
							std::unique_ptr<Outputs::CRT::CRT> crt_;
 | 
				
			||||||
		uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
 | 
							uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint8_t *ram_ = nullptr;
 | 
							uint8_t *ram_ = nullptr;
 | 
				
			||||||
@@ -602,9 +619,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
 | 
				
			|||||||
			const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc,
 | 
								const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc,
 | 
				
			||||||
			AYDeferrer &ay,
 | 
								AYDeferrer &ay,
 | 
				
			||||||
			Storage::Tape::BinaryTapePlayer &tape_player) :
 | 
								Storage::Tape::BinaryTapePlayer &tape_player) :
 | 
				
			||||||
				key_state_(key_state),
 | 
					 | 
				
			||||||
				crtc_(crtc),
 | 
					 | 
				
			||||||
				ay_(ay),
 | 
									ay_(ay),
 | 
				
			||||||
 | 
									crtc_(crtc),
 | 
				
			||||||
 | 
									key_state_(key_state),
 | 
				
			||||||
				tape_player_(tape_player) {}
 | 
									tape_player_(tape_player) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// The i8255 will call this to set a new output value of @c value for @c port.
 | 
							/// The i8255 will call this to set a new output value of @c value for @c port.
 | 
				
			||||||
@@ -613,7 +630,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
 | 
				
			|||||||
				case 0:
 | 
									case 0:
 | 
				
			||||||
					// Port A is connected to the AY's data bus.
 | 
										// Port A is connected to the AY's data bus.
 | 
				
			||||||
					ay_.update();
 | 
										ay_.update();
 | 
				
			||||||
					ay_.ay()->set_data_input(value);
 | 
										ay_.ay().set_data_input(value);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case 1:
 | 
									case 1:
 | 
				
			||||||
					// Port B is an input only. So output goes nowehere.
 | 
										// Port B is an input only. So output goes nowehere.
 | 
				
			||||||
@@ -629,7 +646,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
 | 
				
			|||||||
					tape_player_.set_tape_output((value & 0x20) ? true : false);
 | 
										tape_player_.set_tape_output((value & 0x20) ? true : false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Bits 6 and 7 set BDIR and BC1 for the AY.
 | 
										// Bits 6 and 7 set BDIR and BC1 for the AY.
 | 
				
			||||||
					ay_.ay()->set_control_lines(
 | 
										ay_.ay().set_control_lines(
 | 
				
			||||||
						(GI::AY38910::ControlLines)(
 | 
											(GI::AY38910::ControlLines)(
 | 
				
			||||||
							((value & 0x80) ? GI::AY38910::BDIR : 0) |
 | 
												((value & 0x80) ? GI::AY38910::BDIR : 0) |
 | 
				
			||||||
							((value & 0x40) ? GI::AY38910::BC1 : 0) |
 | 
												((value & 0x40) ? GI::AY38910::BC1 : 0) |
 | 
				
			||||||
@@ -642,7 +659,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
 | 
				
			|||||||
		/// The i8255 will call this to obtain a new input for @c port.
 | 
							/// The i8255 will call this to obtain a new input for @c port.
 | 
				
			||||||
		uint8_t get_value(int port) {
 | 
							uint8_t get_value(int port) {
 | 
				
			||||||
			switch(port) {
 | 
								switch(port) {
 | 
				
			||||||
				case 0: return ay_.ay()->get_data_output();	// Port A is wired to the AY
 | 
									case 0: return ay_.ay().get_data_output();	// Port A is wired to the AY
 | 
				
			||||||
				case 1:	return
 | 
									case 1:	return
 | 
				
			||||||
					(crtc_.get_bus_state().vsync ? 0x01 : 0x00) |	// Bit 0 returns CRTC vsync.
 | 
										(crtc_.get_bus_state().vsync ? 0x01 : 0x00) |	// Bit 0 returns CRTC vsync.
 | 
				
			||||||
					(tape_player_.get_input() ? 0x80 : 0x00) |		// Bit 7 returns cassette input.
 | 
										(tape_player_.get_input() ? 0x80 : 0x00) |		// Bit 7 returns cassette input.
 | 
				
			||||||
@@ -658,8 +675,8 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		AYDeferrer &ay_;
 | 
							AYDeferrer &ay_;
 | 
				
			||||||
		KeyboardState &key_state_;
 | 
					 | 
				
			||||||
		const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_;
 | 
							const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_;
 | 
				
			||||||
 | 
							KeyboardState &key_state_;
 | 
				
			||||||
		Storage::Tape::BinaryTapePlayer &tape_player_;
 | 
							Storage::Tape::BinaryTapePlayer &tape_player_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -667,6 +684,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
 | 
				
			|||||||
	The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
 | 
						The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class ConcreteMachine:
 | 
					class ConcreteMachine:
 | 
				
			||||||
 | 
						public CRTMachine::Machine,
 | 
				
			||||||
 | 
						public ConfigurationTarget::Machine,
 | 
				
			||||||
 | 
						public KeyboardMachine::Machine,
 | 
				
			||||||
	public Utility::TypeRecipient,
 | 
						public Utility::TypeRecipient,
 | 
				
			||||||
	public CPU::Z80::BusHandler,
 | 
						public CPU::Z80::BusHandler,
 | 
				
			||||||
	public Sleeper::SleepObserver,
 | 
						public Sleeper::SleepObserver,
 | 
				
			||||||
@@ -674,12 +694,13 @@ class ConcreteMachine:
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		ConcreteMachine() :
 | 
							ConcreteMachine() :
 | 
				
			||||||
			z80_(*this),
 | 
								z80_(*this),
 | 
				
			||||||
			crtc_counter_(HalfCycles(4)),	// This starts the CRTC exactly out of phase with the CPU's memory accesses
 | 
					 | 
				
			||||||
			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
 | 
					 | 
				
			||||||
			crtc_bus_handler_(ram_, interrupt_timer_),
 | 
								crtc_bus_handler_(ram_, interrupt_timer_),
 | 
				
			||||||
			i8255_(i8255_port_handler_),
 | 
								crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
 | 
				
			||||||
			i8255_port_handler_(key_state_, crtc_, ay_, tape_player_),
 | 
								i8255_port_handler_(key_state_, crtc_, ay_, tape_player_),
 | 
				
			||||||
			tape_player_(8000000) {
 | 
								i8255_(i8255_port_handler_),
 | 
				
			||||||
 | 
								tape_player_(8000000),
 | 
				
			||||||
 | 
								crtc_counter_(HalfCycles(4))	// This starts the CRTC exactly out of phase with the CPU's memory accesses
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
			// primary clock is 4Mhz
 | 
								// primary clock is 4Mhz
 | 
				
			||||||
			set_clock_rate(4000000);
 | 
								set_clock_rate(4000000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -692,6 +713,8 @@ class ConcreteMachine:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			tape_player_.set_sleep_observer(this);
 | 
								tape_player_.set_sleep_observer(this);
 | 
				
			||||||
			tape_player_is_sleeping_ = tape_player_.is_sleeping();
 | 
								tape_player_is_sleeping_ = tape_player_.is_sleeping();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ay_.ay().set_port_handler(&key_state_);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// The entry point for performing a partial Z80 machine cycle.
 | 
							/// The entry point for performing a partial Z80 machine cycle.
 | 
				
			||||||
@@ -834,23 +857,20 @@ class ConcreteMachine:
 | 
				
			|||||||
		/// A CRTMachine function; indicates that outputs should be created now.
 | 
							/// A CRTMachine function; indicates that outputs should be created now.
 | 
				
			||||||
		void setup_output(float aspect_ratio) override final {
 | 
							void setup_output(float aspect_ratio) override final {
 | 
				
			||||||
			crtc_bus_handler_.setup_output(aspect_ratio);
 | 
								crtc_bus_handler_.setup_output(aspect_ratio);
 | 
				
			||||||
			ay_.setup_output();
 | 
					 | 
				
			||||||
			ay_.ay()->set_port_handler(&key_state_);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// A CRTMachine function; indicates that outputs should be destroyed now.
 | 
							/// A CRTMachine function; indicates that outputs should be destroyed now.
 | 
				
			||||||
		void close_output() override final {
 | 
							void close_output() override final {
 | 
				
			||||||
			crtc_bus_handler_.close_output();
 | 
								crtc_bus_handler_.close_output();
 | 
				
			||||||
			ay_.close_output();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns the CRT in use.
 | 
							/// @returns the CRT in use.
 | 
				
			||||||
		std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
 | 
							Outputs::CRT::CRT *get_crt() override final {
 | 
				
			||||||
			return crtc_bus_handler_.get_crt();
 | 
								return crtc_bus_handler_.get_crt();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns the speaker in use.
 | 
							/// @returns the speaker in use.
 | 
				
			||||||
		std::shared_ptr<Outputs::Speaker> get_speaker() override final {
 | 
							Outputs::Speaker::Speaker *get_speaker() override final {
 | 
				
			||||||
			return ay_.get_speaker();
 | 
								return ay_.get_speaker();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -860,19 +880,21 @@ class ConcreteMachine:
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
 | 
							/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
 | 
				
			||||||
		void configure_as_target(const StaticAnalyser::Target &target) override final  {
 | 
							void configure_as_target(const Analyser::Static::Target *target) override final  {
 | 
				
			||||||
			switch(target.amstradcpc.model) {
 | 
								auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target);
 | 
				
			||||||
				case StaticAnalyser::AmstradCPCModel::CPC464:
 | 
					
 | 
				
			||||||
 | 
								switch(cpc_target->model) {
 | 
				
			||||||
 | 
									case Analyser::Static::AmstradCPC::Target::Model::CPC464:
 | 
				
			||||||
					rom_model_ = ROMType::OS464;
 | 
										rom_model_ = ROMType::OS464;
 | 
				
			||||||
					has_128k_ = false;
 | 
										has_128k_ = false;
 | 
				
			||||||
					has_fdc_ = false;
 | 
										has_fdc_ = false;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case StaticAnalyser::AmstradCPCModel::CPC664:
 | 
									case Analyser::Static::AmstradCPC::Target::Model::CPC664:
 | 
				
			||||||
					rom_model_ = ROMType::OS664;
 | 
										rom_model_ = ROMType::OS664;
 | 
				
			||||||
					has_128k_ = false;
 | 
										has_128k_ = false;
 | 
				
			||||||
					has_fdc_ = true;
 | 
										has_fdc_ = true;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case StaticAnalyser::AmstradCPCModel::CPC6128:
 | 
									case Analyser::Static::AmstradCPC::Target::Model::CPC6128:
 | 
				
			||||||
					rom_model_ = ROMType::OS6128;
 | 
										rom_model_ = ROMType::OS6128;
 | 
				
			||||||
					has_128k_ = true;
 | 
										has_128k_ = true;
 | 
				
			||||||
					has_fdc_ = true;
 | 
										has_fdc_ = true;
 | 
				
			||||||
@@ -894,14 +916,14 @@ class ConcreteMachine:
 | 
				
			|||||||
			read_pointers_[3] = roms_[upper_rom_].data();
 | 
								read_pointers_[3] = roms_[upper_rom_].data();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Type whatever is required.
 | 
								// Type whatever is required.
 | 
				
			||||||
			if(target.loadingCommand.length()) {
 | 
								if(target->loading_command.length()) {
 | 
				
			||||||
				set_typer_for_string(target.loadingCommand.c_str());
 | 
									type_string(target->loading_command);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			insert_media(target.media);
 | 
								insert_media(target->media);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool insert_media(const StaticAnalyser::Media &media) override final {
 | 
							bool insert_media(const Analyser::Static::Media &media) override final {
 | 
				
			||||||
			// If there are any tapes supplied, use the first of them.
 | 
								// If there are any tapes supplied, use the first of them.
 | 
				
			||||||
			if(!media.tapes.empty()) {
 | 
								if(!media.tapes.empty()) {
 | 
				
			||||||
				tape_player_.set_tape(media.tapes.front());
 | 
									tape_player_.set_tape(media.tapes.front());
 | 
				
			||||||
@@ -918,9 +940,25 @@ class ConcreteMachine:
 | 
				
			|||||||
			return !media.tapes.empty() || (!media.disks.empty() && has_fdc_);
 | 
								return !media.tapes.empty() || (!media.disks.empty() && has_fdc_);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// See header; provides the system ROMs.
 | 
							// Obtains the system ROMs.
 | 
				
			||||||
		void set_rom(ROMType type, std::vector<uint8_t> data) override final {
 | 
							bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
 | 
				
			||||||
			roms_[static_cast<int>(type)] = data;
 | 
								auto roms = roms_with_names(
 | 
				
			||||||
 | 
									"AmstradCPC",
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										"os464.rom",	"basic464.rom",
 | 
				
			||||||
 | 
										"os664.rom",	"basic664.rom",
 | 
				
			||||||
 | 
										"os6128.rom",	"basic6128.rom",
 | 
				
			||||||
 | 
										"amsdos.rom"
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for(std::size_t index = 0; index < roms.size(); ++index) {
 | 
				
			||||||
 | 
									auto &data = roms[index];
 | 
				
			||||||
 | 
									if(!data) return false;
 | 
				
			||||||
 | 
									roms_[static_cast<int>(index)] = std::move(*data);
 | 
				
			||||||
 | 
									roms_[static_cast<int>(index)].resize(16384);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_component_is_sleeping(void *component, bool is_sleeping) override final {
 | 
							void set_component_is_sleeping(void *component, bool is_sleeping) override final {
 | 
				
			||||||
@@ -928,11 +966,11 @@ class ConcreteMachine:
 | 
				
			|||||||
			tape_player_is_sleeping_ = tape_player_.is_sleeping();
 | 
								tape_player_is_sleeping_ = tape_player_.is_sleeping();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#pragma mark - Keyboard
 | 
					// MARK: - Keyboard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_typer_for_string(const char *string) override final {
 | 
							void type_string(const std::string &string) override final {
 | 
				
			||||||
			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
 | 
								std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
 | 
				
			||||||
			Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
 | 
								Utility::TypeRecipient::add_typer(string, std::move(mapper));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		HalfCycles get_typer_delay() override final {
 | 
							HalfCycles get_typer_delay() override final {
 | 
				
			||||||
@@ -953,8 +991,8 @@ class ConcreteMachine:
 | 
				
			|||||||
			key_state_.clear_all_keys();
 | 
								key_state_.clear_all_keys();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		KeyboardMapper &get_keyboard_mapper() override {
 | 
							KeyboardMapper *get_keyboard_mapper() override {
 | 
				
			||||||
			return keyboard_mapper_;
 | 
								return &keyboard_mapper_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user