mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	Compare commits
	
		
			1875 Commits
		
	
	
		
			2017-01-01
			...
			2018-03-07
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a942e1319b | ||
| 
						 | 
					9abc020818 | ||
| 
						 | 
					2dade8d353 | ||
| 
						 | 
					dfcc502a88 | ||
| 
						 | 
					1c6faaae88 | ||
| 
						 | 
					35c8a0dd8c | ||
| 
						 | 
					38feedaf6a | ||
| 
						 | 
					0a2f908af4 | ||
| 
						 | 
					705d53cc21 | ||
| 
						 | 
					35b18d58af | ||
| 
						 | 
					3c5a8d9ff3 | ||
| 
						 | 
					7ca02be578 | ||
| 
						 | 
					ea13c7dd32 | ||
| 
						 | 
					fdfd72a42c | ||
| 
						 | 
					da97bf95c0 | ||
| 
						 | 
					bdfc36427c | ||
| 
						 | 
					74dfe56d2b | ||
| 
						 | 
					6cce9aa54e | ||
| 
						 | 
					ba68b7247b | ||
| 
						 | 
					b02e4fbbf6 | ||
| 
						 | 
					59b4c7314d | ||
| 
						 | 
					d328589bd0 | ||
| 
						 | 
					b05d2b26bf | ||
| 
						 | 
					86239469e7 | ||
| 
						 | 
					7890506b16 | ||
| 
						 | 
					83f73c3f02 | ||
| 
						 | 
					87760297fc | ||
| 
						 | 
					5b854d51e7 | ||
| 
						 | 
					d4df101ab6 | ||
| 
						 | 
					0ad2676640 | ||
| 
						 | 
					a074ee2071 | ||
| 
						 | 
					204d5cc964 | ||
| 
						 | 
					23d15a4d6c | ||
| 
						 | 
					23c47e21de | ||
| 
						 | 
					5530b96446 | ||
| 
						 | 
					99d28a172b | ||
| 
						 | 
					d83178f29d | ||
| 
						 | 
					d9d5ffdaa2 | ||
| 
						 | 
					cabad6fc05 | ||
| 
						 | 
					a4dc9c0403 | ||
| 
						 | 
					270723ae72 | ||
| 
						 | 
					b215cf83d5 | ||
| 
						 | 
					f237dcf904 | ||
| 
						 | 
					fc81bfa59b | ||
| 
						 | 
					832ac173ae | ||
| 
						 | 
					3673cfe9be | ||
| 
						 | 
					6aaef97158 | ||
| 
						 | 
					b0ab617393 | ||
| 
						 | 
					6780b0bf11 | ||
| 
						 | 
					9c0a440c38 | ||
| 
						 | 
					2439f5aee5 | ||
| 
						 | 
					8265f289bd | ||
| 
						 | 
					9728bea0a7 | ||
| 
						 | 
					fc9e84c72e | ||
| 
						 | 
					7d75e864b1 | ||
| 
						 | 
					a005dabbe3 | ||
| 
						 | 
					c8a4432c63 | ||
| 
						 | 
					7b420d56e3 | ||
| 
						 | 
					ddf1bf3cbf | ||
| 
						 | 
					7ea4ca00dc | ||
| 
						 | 
					6b8c223804 | ||
| 
						 | 
					23105956d6 | ||
| 
						 | 
					d751b7e2cb | ||
| 
						 | 
					f02989649c | ||
| 
						 | 
					dcf313a833 | ||
| 
						 | 
					9960121b08 | ||
| 
						 | 
					8eea55b51c | ||
| 
						 | 
					e1cab52c84 | ||
| 
						 | 
					eb39617ad0 | ||
| 
						 | 
					43b682a5af | ||
| 
						 | 
					043fd5d404 | ||
| 
						 | 
					d63a95983d | ||
| 
						 | 
					4cf258f952 | ||
| 
						 | 
					4e720d57b2 | ||
| 
						 | 
					c12aaea747 | ||
| 
						 | 
					ca48497e87 | ||
| 
						 | 
					d493ea4bca | ||
| 
						 | 
					e025674eb2 | ||
| 
						 | 
					f2519f4fd7 | ||
| 
						 | 
					db914d8c56 | ||
| 
						 | 
					66faed4008 | ||
| 
						 | 
					11abc99ef8 | ||
| 
						 | 
					21efb32b6f | ||
| 
						 | 
					622a04aec8 | ||
| 
						 | 
					d360b2c62d | ||
| 
						 | 
					6a112edc18 | ||
| 
						 | 
					8fb4409ebb | ||
| 
						 | 
					d213341d9c | ||
| 
						 | 
					c2f1306d85 | ||
| 
						 | 
					2143ea6f12 | ||
| 
						 | 
					edb30b3c6c | ||
| 
						 | 
					234e4f6f66 | ||
| 
						 | 
					ce2d3c6e82 | ||
| 
						 | 
					46c76b9c07 | ||
| 
						 | 
					583c3cfe7d | ||
| 
						 | 
					e13312dcc5 | ||
| 
						 | 
					d9e49c0d5f | ||
| 
						 | 
					8a370cc1ac | ||
| 
						 | 
					cdae0fa593 | ||
| 
						 | 
					765c0d4ff8 | ||
| 
						 | 
					4cf2e16b5c | ||
| 
						 | 
					9cbd61e709 | ||
| 
						 | 
					0202c7afb2 | ||
| 
						 | 
					c187c5a637 | ||
| 
						 | 
					23c34a8c14 | ||
| 
						 | 
					93ece2aec7 | ||
| 
						 | 
					e12ab8fe2e | ||
| 
						 | 
					2fe0ceb52a | ||
| 
						 | 
					f354c12c81 | ||
| 
						 | 
					def82cba49 | ||
| 
						 | 
					e7bc7b94c9 | ||
| 
						 | 
					aafdff49be | ||
| 
						 | 
					4ef583813a | ||
| 
						 | 
					9f97fb738e | ||
| 
						 | 
					4e124047c6 | ||
| 
						 | 
					6eb56a1564 | ||
| 
						 | 
					35fc0a5c16 | ||
| 
						 | 
					b36c917810 | ||
| 
						 | 
					a5ac8c824e | ||
| 
						 | 
					0ccc104027 | ||
| 
						 | 
					8be6cb827b | ||
| 
						 | 
					2f59226300 | ||
| 
						 | 
					793ef68206 | ||
| 
						 | 
					513c067f94 | ||
| 
						 | 
					999a0c22d4 | ||
| 
						 | 
					5d0832613f | ||
| 
						 | 
					2ffde4c3c2 | ||
| 
						 | 
					57ddfcd645 | ||
| 
						 | 
					fc16e8eb8c | ||
| 
						 | 
					655b971976 | ||
| 
						 | 
					3e1d8ea082 | ||
| 
						 | 
					772c320d5a | ||
| 
						 | 
					bcc7ad0c30 | ||
| 
						 | 
					73b4e1722b | ||
| 
						 | 
					185cd3c123 | ||
| 
						 | 
					ed564cb810 | ||
| 
						 | 
					b78ece1f1e | ||
| 
						 | 
					c8367a017f | ||
| 
						 | 
					344a12566b | ||
| 
						 | 
					c07113ea95 | ||
| 
						 | 
					bc2879c412 | ||
| 
						 | 
					1d47b55729 | ||
| 
						 | 
					db25b4554b | ||
| 
						 | 
					05b95ea2e0 | ||
| 
						 | 
					250f7bf6b0 | ||
| 
						 | 
					34db35b500 | ||
| 
						 | 
					f75590253d | ||
| 
						 | 
					4f6abc9059 | ||
| 
						 | 
					c70dbc6a49 | ||
| 
						 | 
					1c255b9e7d | ||
| 
						 | 
					188bfa9c18 | ||
| 
						 | 
					c7f8f37822 | ||
| 
						 | 
					4a19dbb8cb | ||
| 
						 | 
					bf0601123b | ||
| 
						 | 
					9339f3413f | ||
| 
						 | 
					c18517be4b | ||
| 
						 | 
					eef34adcbd | ||
| 
						 | 
					769d9dfbb9 | ||
| 
						 | 
					6a0bb83716 | ||
| 
						 | 
					6da8a3e24b | ||
| 
						 | 
					e349161a53 | ||
| 
						 | 
					d5b1a9d918 | ||
| 
						 | 
					76af0228dd | ||
| 
						 | 
					2cc1a2684a | ||
| 
						 | 
					98a9d57c0b | ||
| 
						 | 
					c481293aca | ||
| 
						 | 
					5fd0a2b9ea | ||
| 
						 | 
					11b73a9c0b | ||
| 
						 | 
					c4950574ea | ||
| 
						 | 
					0b297f2972 | ||
| 
						 | 
					f9f870ad2d | ||
| 
						 | 
					cbba6a5595 | ||
| 
						 | 
					0a079b0f94 | ||
| 
						 | 
					9a7e974579 | ||
| 
						 | 
					f4d414d6e4 | ||
| 
						 | 
					b4bfcd4279 | ||
| 
						 | 
					e8ddff0ee0 | ||
| 
						 | 
					b61fab9df7 | ||
| 
						 | 
					28fb1ce2ae | ||
| 
						 | 
					b9b107ee85 | ||
| 
						 | 
					f17758e7f9 | ||
| 
						 | 
					0bb24075b6 | ||
| 
						 | 
					db6d9b59d0 | ||
| 
						 | 
					51e82c10c5 | ||
| 
						 | 
					2d892da225 | ||
| 
						 | 
					b99ba2bc02 | ||
| 
						 | 
					d36e9d0b0d | ||
| 
						 | 
					2dc1d4443e | ||
| 
						 | 
					f8a2459c91 | ||
| 
						 | 
					ac80d10cd8 | ||
| 
						 | 
					eb6b612052 | ||
| 
						 | 
					d66a33f249 | ||
| 
						 | 
					ec4c259695 | ||
| 
						 | 
					ad50b6b1fb | ||
| 
						 | 
					3da323c657 | ||
| 
						 | 
					aca7842ca4 | ||
| 
						 | 
					38c912b968 | ||
| 
						 | 
					7a52e7d6d2 | ||
| 
						 | 
					c36de4f640 | ||
| 
						 | 
					504772bcda | ||
| 
						 | 
					5d0c33d545 | ||
| 
						 | 
					7bc1bcd493 | ||
| 
						 | 
					b0616ee10c | ||
| 
						 | 
					da57df55e8 | ||
| 
						 | 
					4daea1121b | ||
| 
						 | 
					afcdd64d5e | ||
| 
						 | 
					798cdba979 | ||
| 
						 | 
					f957344ac4 | ||
| 
						 | 
					b3fbd0f352 | ||
| 
						 | 
					042edc72f7 | ||
| 
						 | 
					943418c434 | ||
| 
						 | 
					7d7e2538bd | ||
| 
						 | 
					7a544731e2 | ||
| 
						 | 
					e1914b4f16 | ||
| 
						 | 
					202958303e | ||
| 
						 | 
					57b060ac3c | ||
| 
						 | 
					8653eb8b55 | ||
| 
						 | 
					a4f0a260fd | ||
| 
						 | 
					d4a53e82bb | ||
| 
						 | 
					6eedc99286 | ||
| 
						 | 
					ec266d6c8e | ||
| 
						 | 
					e3a5218e78 | ||
| 
						 | 
					a473338abe | ||
| 
						 | 
					ae21782adc | ||
| 
						 | 
					ee44d671e7 | ||
| 
						 | 
					3766bef962 | ||
| 
						 | 
					ad3df36c20 | ||
| 
						 | 
					38b11893e8 | ||
| 
						 | 
					e4534775b0 | ||
| 
						 | 
					fe7fc6b22e | ||
| 
						 | 
					fe0cdc8d69 | ||
| 
						 | 
					7f8a13a409 | ||
| 
						 | 
					ca26ce8400 | ||
| 
						 | 
					d3dd8f3f2a | ||
| 
						 | 
					3c8d2d579d | ||
| 
						 | 
					edcbb3dfed | ||
| 
						 | 
					9c8158753e | ||
| 
						 | 
					5da9cb2957 | ||
| 
						 | 
					54c845b6e2 | ||
| 
						 | 
					ee84f33ab5 | ||
| 
						 | 
					f0f149c018 | ||
| 
						 | 
					7dfbe4bb93 | ||
| 
						 | 
					aa4eef41d8 | ||
| 
						 | 
					69ec8a362e | ||
| 
						 | 
					ecd7d4731b | ||
| 
						 | 
					563aa051e4 | ||
| 
						 | 
					642bb8333f | ||
| 
						 | 
					c558e86e03 | ||
| 
						 | 
					dbb14ea2e2 | ||
| 
						 | 
					173e16b107 | ||
| 
						 | 
					7d2adad67e | ||
| 
						 | 
					d33612def5 | ||
| 
						 | 
					9cb6ca3440 | ||
| 
						 | 
					e957e40b14 | ||
| 
						 | 
					7a8a43a96a | ||
| 
						 | 
					0eb5dd9688 | ||
| 
						 | 
					a14b53a9ab | ||
| 
						 | 
					576d554a2c | ||
| 
						 | 
					68a2895753 | ||
| 
						 | 
					f90b3f06aa | ||
| 
						 | 
					f067fa9923 | ||
| 
						 | 
					ee9f89ccb5 | ||
| 
						 | 
					573a9c6fb2 | ||
| 
						 | 
					a46a37fba9 | ||
| 
						 | 
					324b57c054 | ||
| 
						 | 
					ae50ca9ab2 | ||
| 
						 | 
					6e4bde00d3 | ||
| 
						 | 
					d4d0dd87c9 | ||
| 
						 | 
					221c05ca76 | ||
| 
						 | 
					ff21ff90eb | ||
| 
						 | 
					fcf295fd68 | ||
| 
						 | 
					2008dec1ed | ||
| 
						 | 
					b4f3c41aae | ||
| 
						 | 
					90c4e3726f | ||
| 
						 | 
					c83b3cefbc | ||
| 
						 | 
					a8ac51da73 | ||
| 
						 | 
					bc65ba3f9b | ||
| 
						 | 
					79674fdbd3 | ||
| 
						 | 
					adea4711f1 | ||
| 
						 | 
					bded406caa | ||
| 
						 | 
					85085a6375 | ||
| 
						 | 
					d122d598d3 | ||
| 
						 | 
					d6192b8c58 | ||
| 
						 | 
					f02d4dbb59 | ||
| 
						 | 
					f3818991f6 | ||
| 
						 | 
					c7dd6247f0 | ||
| 
						 | 
					99e17600d7 | ||
| 
						 | 
					1d821ad459 | ||
| 
						 | 
					c60a9ee3c3 | ||
| 
						 | 
					ffcbd1e94d | ||
| 
						 | 
					6c8b503402 | ||
| 
						 | 
					55e1d25966 | ||
| 
						 | 
					0bdd776114 | ||
| 
						 | 
					c1b7bceec8 | ||
| 
						 | 
					dc4f58e40c | ||
| 
						 | 
					3b8cdd620c | ||
| 
						 | 
					3365ff0200 | ||
| 
						 | 
					89c3e2ba5a | ||
| 
						 | 
					c6306db47c | ||
| 
						 | 
					8ddc64c82a | ||
| 
						 | 
					b887cb7255 | ||
| 
						 | 
					d54ee2af82 | ||
| 
						 | 
					723c113186 | ||
| 
						 | 
					c368c4443e | ||
| 
						 | 
					7b25b03cd5 | ||
| 
						 | 
					9961d13e2d | ||
| 
						 | 
					29b5ccc767 | ||
| 
						 | 
					90af395df2 | ||
| 
						 | 
					6f8d4d6c5c | ||
| 
						 | 
					63381ff505 | ||
| 
						 | 
					2ea050556b | ||
| 
						 | 
					8dcac6561e | ||
| 
						 | 
					90d33949f9 | ||
| 
						 | 
					d3e68914dd | ||
| 
						 | 
					82ad0354c4 | ||
| 
						 | 
					073e439518 | ||
| 
						 | 
					27b123549b | ||
| 
						 | 
					de9db724a7 | ||
| 
						 | 
					532ea35ee9 | ||
| 
						 | 
					e9ddec35d6 | ||
| 
						 | 
					7647f8089b | ||
| 
						 | 
					f00f0353a6 | ||
| 
						 | 
					e19ae5d43d | ||
| 
						 | 
					0a9622435c | ||
| 
						 | 
					f704932475 | ||
| 
						 | 
					d0f096a20b | ||
| 
						 | 
					949d0f3928 | ||
| 
						 | 
					a2d48223c3 | ||
| 
						 | 
					fc080c773f | ||
| 
						 | 
					adb3811847 | ||
| 
						 | 
					dbbea78b76 | ||
| 
						 | 
					fd96e3e657 | ||
| 
						 | 
					06d81b3a97 | ||
| 
						 | 
					88551607a6 | ||
| 
						 | 
					2a9dccff26 | ||
| 
						 | 
					1027f85683 | ||
| 
						 | 
					9bb9cb4a65 | ||
| 
						 | 
					2de80646ec | ||
| 
						 | 
					bf4ed57f68 | ||
| 
						 | 
					9578f3dc44 | ||
| 
						 | 
					a97c478a34 | ||
| 
						 | 
					e0113d5dce | ||
| 
						 | 
					980cf541d2 | ||
| 
						 | 
					69c983f9ee | ||
| 
						 | 
					70039d22f1 | ||
| 
						 | 
					ebdb80c908 | ||
| 
						 | 
					0eaac99d74 | ||
| 
						 | 
					792061a82b | ||
| 
						 | 
					d2ba7d7430 | ||
| 
						 | 
					8713cfa613 | ||
| 
						 | 
					aa77be1c10 | ||
| 
						 | 
					e6aa2321cd | ||
| 
						 | 
					c827d14d97 | ||
| 
						 | 
					2979d19621 | ||
| 
						 | 
					282e5c9d3e | ||
| 
						 | 
					ede47d4ba7 | ||
| 
						 | 
					5408efe9b5 | ||
| 
						 | 
					d6141cb020 | ||
| 
						 | 
					198d0fd1de | ||
| 
						 | 
					6d80856f02 | ||
| 
						 | 
					4778616fd7 | ||
| 
						 | 
					2e025d85eb | ||
| 
						 | 
					61f2191c86 | ||
| 
						 | 
					c1eab8d5f3 | ||
| 
						 | 
					91d2d59ae5 | ||
| 
						 | 
					5aef81cf24 | ||
| 
						 | 
					3550196bed | ||
| 
						 | 
					bce58683fa | ||
| 
						 | 
					c91a5875b2 | ||
| 
						 | 
					2e15fab651 | ||
| 
						 | 
					6a176082a0 | ||
| 
						 | 
					fd346bac3e | ||
| 
						 | 
					25e9dcc800 | ||
| 
						 | 
					792cbb1536 | ||
| 
						 | 
					2e12370251 | ||
| 
						 | 
					7adc25694a | ||
| 
						 | 
					ca80da7fbe | ||
| 
						 | 
					f853d87884 | ||
| 
						 | 
					524087805f | ||
| 
						 | 
					916eb96b47 | ||
| 
						 | 
					4add2c1051 | ||
| 
						 | 
					cb0f58ab7a | ||
| 
						 | 
					d9e56711ce | ||
| 
						 | 
					d60692b6fd | ||
| 
						 | 
					5b6ea35d96 | ||
| 
						 | 
					4cbc87a17d | ||
| 
						 | 
					46e7c199b2 | ||
| 
						 | 
					ff7ba526fb | ||
| 
						 | 
					a825da3715 | ||
| 
						 | 
					fabaf4e607 | ||
| 
						 | 
					153067c018 | ||
| 
						 | 
					f7f2736d4d | ||
| 
						 | 
					a16ca65825 | ||
| 
						 | 
					cb015c83e1 | ||
| 
						 | 
					2203499215 | ||
| 
						 | 
					c0055a5a5f | ||
| 
						 | 
					62218e81bf | ||
| 
						 | 
					c45d4831ec | ||
| 
						 | 
					9fd33bdfde | ||
| 
						 | 
					6e1d69581c | ||
| 
						 | 
					f95515ae81 | ||
| 
						 | 
					09c855a659 | ||
| 
						 | 
					16c96b605a | ||
| 
						 | 
					e10d369e53 | ||
| 
						 | 
					0d1b63a8c5 | ||
| 
						 | 
					ddcdd07dd0 | ||
| 
						 | 
					35da3edf60 | ||
| 
						 | 
					d605022ea3 | ||
| 
						 | 
					0da78065ce | ||
| 
						 | 
					4b68c372c6 | ||
| 
						 | 
					13406fedd8 | ||
| 
						 | 
					a209ae76ca | ||
| 
						 | 
					0116d7f071 | ||
| 
						 | 
					512e877d06 | ||
| 
						 | 
					1e1efcdcb8 | ||
| 
						 | 
					bc2f58e9de | ||
| 
						 | 
					fd10c42433 | ||
| 
						 | 
					794437f20f | ||
| 
						 | 
					23d5849cda | ||
| 
						 | 
					5070a8414f | ||
| 
						 | 
					5a3ca0e447 | ||
| 
						 | 
					e384c50580 | ||
| 
						 | 
					b9734278f6 | ||
| 
						 | 
					f807a6b608 | ||
| 
						 | 
					833f8c02a4 | ||
| 
						 | 
					0248c6a282 | ||
| 
						 | 
					218b976dbc | ||
| 
						 | 
					513903890e | ||
| 
						 | 
					1157bde453 | ||
| 
						 | 
					46345c6a3e | ||
| 
						 | 
					c13f8e5390 | ||
| 
						 | 
					ad9df4bb90 | ||
| 
						 | 
					e983854e71 | ||
| 
						 | 
					ec999446e8 | ||
| 
						 | 
					5e3e91373a | ||
| 
						 | 
					c52348d8d7 | ||
| 
						 | 
					9e0907ee76 | ||
| 
						 | 
					9ad4025138 | ||
| 
						 | 
					405f58d6a3 | ||
| 
						 | 
					afbd1c425c | ||
| 
						 | 
					b2c1b83fcd | ||
| 
						 | 
					8d2b9a581a | ||
| 
						 | 
					1825af0dd3 | ||
| 
						 | 
					c2f6799f0c | ||
| 
						 | 
					b5b6219cb7 | ||
| 
						 | 
					185a699279 | ||
| 
						 | 
					96b8f9ae9f | ||
| 
						 | 
					88e2350b8f | ||
| 
						 | 
					5c141af734 | ||
| 
						 | 
					da580e4186 | ||
| 
						 | 
					57ee09dffb | ||
| 
						 | 
					7c8e830b90 | ||
| 
						 | 
					ba5f668338 | ||
| 
						 | 
					2c1e99858b | ||
| 
						 | 
					7f2febeec9 | ||
| 
						 | 
					2d7a4fe5f0 | ||
| 
						 | 
					91b867a7b3 | ||
| 
						 | 
					3944e734d3 | ||
| 
						 | 
					ce78d9d12c | ||
| 
						 | 
					edbc60a3fb | ||
| 
						 | 
					6ea3ff62df | ||
| 
						 | 
					88959571f1 | ||
| 
						 | 
					b4583e976e | ||
| 
						 | 
					92d9805f09 | ||
| 
						 | 
					0c2dd62328 | ||
| 
						 | 
					3f4d90d775 | ||
| 
						 | 
					542ec4312f | ||
| 
						 | 
					18798c9886 | ||
| 
						 | 
					7aaf27389c | ||
| 
						 | 
					ee179aa7bd | ||
| 
						 | 
					3a05ce36de | ||
| 
						 | 
					4f289ab10b | ||
| 
						 | 
					78ee46270b | ||
| 
						 | 
					edb632af52 | ||
| 
						 | 
					19c03a08a6 | ||
| 
						 | 
					44cdc124af | ||
| 
						 | 
					b37787a414 | ||
| 
						 | 
					53b99ea248 | ||
| 
						 | 
					97a2be71e3 | ||
| 
						 | 
					f623bff5c3 | ||
| 
						 | 
					2511fc8401 | ||
| 
						 | 
					d37ec9e5b0 | ||
| 
						 | 
					95c82f5b36 | ||
| 
						 | 
					ec202ed8be | ||
| 
						 | 
					7190225603 | ||
| 
						 | 
					52e7cabd4e | ||
| 
						 | 
					064f1dfdbc | ||
| 
						 | 
					f40e1fd840 | ||
| 
						 | 
					e194a2a015 | ||
| 
						 | 
					c39759333a | ||
| 
						 | 
					edb9fd301c | ||
| 
						 | 
					ea5023ac26 | ||
| 
						 | 
					0fb363ea0e | ||
| 
						 | 
					1cc85615d5 | ||
| 
						 | 
					7b01c1bee6 | ||
| 
						 | 
					35705c5345 | ||
| 
						 | 
					f41da83d97 | ||
| 
						 | 
					cd1e5dea4d | ||
| 
						 | 
					ef605eda51 | ||
| 
						 | 
					2f48ee59fa | ||
| 
						 | 
					f86729c4ac | ||
| 
						 | 
					5f99f4442c | ||
| 
						 | 
					326857a84d | ||
| 
						 | 
					5dd3945695 | ||
| 
						 | 
					19eb975c73 | ||
| 
						 | 
					698ffca51b | ||
| 
						 | 
					fe3cc5c57c | ||
| 
						 | 
					f488854720 | ||
| 
						 | 
					51c0c45e04 | ||
| 
						 | 
					c3e1489a8e | ||
| 
						 | 
					e3420f62c6 | ||
| 
						 | 
					970c80f2e3 | ||
| 
						 | 
					9f4a407f94 | ||
| 
						 | 
					5dda897334 | ||
| 
						 | 
					3982e375e3 | ||
| 
						 | 
					a8524daecb | ||
| 
						 | 
					d1ce764201 | ||
| 
						 | 
					8875982e1f | ||
| 
						 | 
					3319a4f589 | ||
| 
						 | 
					c7f27b2db4 | ||
| 
						 | 
					631f630549 | ||
| 
						 | 
					2a08bd9ecc | ||
| 
						 | 
					f789ee4ff0 | ||
| 
						 | 
					a295b42497 | ||
| 
						 | 
					d8337492cc | ||
| 
						 | 
					15c8debc16 | ||
| 
						 | 
					67af153c16 | ||
| 
						 | 
					d72dad2d1a | ||
| 
						 | 
					698e4fe550 | ||
| 
						 | 
					b5406b90cd | ||
| 
						 | 
					05a93ba237 | ||
| 
						 | 
					77548d14db | ||
| 
						 | 
					b85dd608e7 | ||
| 
						 | 
					231f13d810 | ||
| 
						 | 
					704bfa114c | ||
| 
						 | 
					44a56724cb | ||
| 
						 | 
					5fbea625ae | ||
| 
						 | 
					ac57b37e96 | ||
| 
						 | 
					e3e9baeaa4 | ||
| 
						 | 
					e071123f90 | ||
| 
						 | 
					98adb01721 | ||
| 
						 | 
					d6a5f9a29e | ||
| 
						 | 
					0d84b4b9dd | ||
| 
						 | 
					a85909198f | ||
| 
						 | 
					98751e6ac8 | ||
| 
						 | 
					da082673d7 | ||
| 
						 | 
					35fe4d50d4 | ||
| 
						 | 
					b835cb73e2 | ||
| 
						 | 
					662d031e3c | ||
| 
						 | 
					bf20c717fb | ||
| 
						 | 
					4d4a0cf1d2 | ||
| 
						 | 
					b62f3e726a | ||
| 
						 | 
					82b13e98f2 | ||
| 
						 | 
					9ac831b09c | ||
| 
						 | 
					42616da7ff | ||
| 
						 | 
					2f13517f38 | ||
| 
						 | 
					fb9fd26af7 | ||
| 
						 | 
					d3c385b471 | ||
| 
						 | 
					96bf133924 | ||
| 
						 | 
					6d6cac429d | ||
| 
						 | 
					dc0b65f9c9 | ||
| 
						 | 
					8882aa496f | ||
| 
						 | 
					0622187ddf | ||
| 
						 | 
					523e1288fa | ||
| 
						 | 
					1a96cce26f | ||
| 
						 | 
					a4e275e1fc | ||
| 
						 | 
					6075064400 | ||
| 
						 | 
					ff6e65cca9 | ||
| 
						 | 
					90d2347c90 | ||
| 
						 | 
					90c7056d12 | ||
| 
						 | 
					fed2bc9fc9 | ||
| 
						 | 
					ff510f3b84 | ||
| 
						 | 
					3b12fca417 | ||
| 
						 | 
					8eeb7e73cd | ||
| 
						 | 
					7fd6699e0b | ||
| 
						 | 
					ed70b15fc9 | ||
| 
						 | 
					ff24e1de31 | ||
| 
						 | 
					6547102511 | ||
| 
						 | 
					d538ff5039 | ||
| 
						 | 
					a49594c6a3 | ||
| 
						 | 
					3544c0f014 | ||
| 
						 | 
					f26fe3756c | ||
| 
						 | 
					a42ca290cb | ||
| 
						 | 
					da09098e49 | ||
| 
						 | 
					450712f39c | ||
| 
						 | 
					24b3faa427 | ||
| 
						 | 
					40d11ea0e3 | ||
| 
						 | 
					ab2bcb939f | ||
| 
						 | 
					45499050b6 | ||
| 
						 | 
					0c9197df30 | ||
| 
						 | 
					a1e200cc65 | ||
| 
						 | 
					8a612bb6ab | ||
| 
						 | 
					e6ac939ae0 | ||
| 
						 | 
					b034d4e6f8 | ||
| 
						 | 
					de218611e4 | ||
| 
						 | 
					615f7ce176 | ||
| 
						 | 
					b306776ba9 | ||
| 
						 | 
					0f85cffc78 | ||
| 
						 | 
					96648df5fe | ||
| 
						 | 
					2c99a2d6ec | ||
| 
						 | 
					4af333d5ec | ||
| 
						 | 
					a5f9869769 | ||
| 
						 | 
					f10be2a18a | ||
| 
						 | 
					c88d627b4e | ||
| 
						 | 
					b30bb2a234 | ||
| 
						 | 
					d498080eb4 | ||
| 
						 | 
					334afbc710 | ||
| 
						 | 
					17c13624e5 | ||
| 
						 | 
					113349d272 | ||
| 
						 | 
					0ced7866fc | ||
| 
						 | 
					d06031dfcb | ||
| 
						 | 
					3f22a71276 | ||
| 
						 | 
					53a88a7e12 | ||
| 
						 | 
					4a66dd9e82 | ||
| 
						 | 
					522839143f | ||
| 
						 | 
					b4c532c0d5 | ||
| 
						 | 
					a3e2d142e3 | ||
| 
						 | 
					63ee8c9d58 | ||
| 
						 | 
					437023bff6 | ||
| 
						 | 
					4465098157 | ||
| 
						 | 
					56dd677e9c | ||
| 
						 | 
					9aa150c338 | ||
| 
						 | 
					fab6908129 | ||
| 
						 | 
					e34d4ce903 | ||
| 
						 | 
					d411827733 | ||
| 
						 | 
					f1ba7755dd | ||
| 
						 | 
					57bfec285f | ||
| 
						 | 
					bdda701207 | ||
| 
						 | 
					487fe83dca | ||
| 
						 | 
					6c5a03187b | ||
| 
						 | 
					97f57a3948 | ||
| 
						 | 
					7d7aa2f5d5 | ||
| 
						 | 
					e7ad79c79a | ||
| 
						 | 
					28550c0227 | ||
| 
						 | 
					6e99169348 | ||
| 
						 | 
					1017bb9f6b | ||
| 
						 | 
					3caa4705ca | ||
| 
						 | 
					039aed1bd1 | ||
| 
						 | 
					d77d7fdd78 | ||
| 
						 | 
					c6e6c3fcfb | ||
| 
						 | 
					ecd3350a6f | ||
| 
						 | 
					fa19e2d9c2 | ||
| 
						 | 
					95d360251d | ||
| 
						 | 
					7af3de010e | ||
| 
						 | 
					cefd421992 | ||
| 
						 | 
					a914eadc85 | ||
| 
						 | 
					131b340d75 | ||
| 
						 | 
					e956740c56 | ||
| 
						 | 
					8afd83b91f | ||
| 
						 | 
					40d7a603db | ||
| 
						 | 
					ee71be0e7e | ||
| 
						 | 
					cde29c4bf4 | ||
| 
						 | 
					e1aded0d95 | ||
| 
						 | 
					1237f174fe | ||
| 
						 | 
					0cbc1753b9 | ||
| 
						 | 
					5cf0395936 | ||
| 
						 | 
					6315c22b80 | ||
| 
						 | 
					4614a56843 | ||
| 
						 | 
					8f5ae4a326 | ||
| 
						 | 
					8fdc5012e4 | ||
| 
						 | 
					e88a51e75e | ||
| 
						 | 
					49285e9caa | ||
| 
						 | 
					e3f2118757 | ||
| 
						 | 
					daeaa4752f | ||
| 
						 | 
					5344e3098b | ||
| 
						 | 
					cedb809c21 | ||
| 
						 | 
					2d9efccc98 | ||
| 
						 | 
					8ce46b6e49 | ||
| 
						 | 
					f2699a3f2b | ||
| 
						 | 
					85253a5876 | ||
| 
						 | 
					911ee5a0d3 | ||
| 
						 | 
					57c5b38a6d | ||
| 
						 | 
					669e0caff5 | ||
| 
						 | 
					b24d04fc09 | ||
| 
						 | 
					ef07c33741 | ||
| 
						 | 
					e559a65ede | ||
| 
						 | 
					5bdd24d93f | ||
| 
						 | 
					af61a7fa28 | ||
| 
						 | 
					c8c1792c3f | ||
| 
						 | 
					e6683e7f2d | ||
| 
						 | 
					0c1714b695 | ||
| 
						 | 
					dc0ca83003 | ||
| 
						 | 
					2c2dd8073c | ||
| 
						 | 
					4f8b89772e | ||
| 
						 | 
					733ee5a5c3 | ||
| 
						 | 
					fedf5a44a6 | ||
| 
						 | 
					6a09022896 | ||
| 
						 | 
					5b3c707959 | ||
| 
						 | 
					9b21ef1507 | ||
| 
						 | 
					da3e8655e9 | ||
| 
						 | 
					41e4386164 | ||
| 
						 | 
					b0a98bd239 | ||
| 
						 | 
					42ad670ec8 | ||
| 
						 | 
					58063b69a6 | ||
| 
						 | 
					378f231499 | ||
| 
						 | 
					f68565a33f | ||
| 
						 | 
					175faebdc9 | ||
| 
						 | 
					76c6b715a2 | ||
| 
						 | 
					b476f06524 | ||
| 
						 | 
					48290a8bbe | ||
| 
						 | 
					9d9a1c341d | ||
| 
						 | 
					952da1e581 | ||
| 
						 | 
					a988255558 | ||
| 
						 | 
					cca66ab450 | ||
| 
						 | 
					bcd7a312a4 | ||
| 
						 | 
					925e774015 | ||
| 
						 | 
					4c15e46fd1 | ||
| 
						 | 
					3c50903a2b | ||
| 
						 | 
					75208b0762 | ||
| 
						 | 
					f1e64169cd | ||
| 
						 | 
					903a17ae11 | ||
| 
						 | 
					de1c526789 | ||
| 
						 | 
					b7e0f64892 | ||
| 
						 | 
					148591b7f2 | ||
| 
						 | 
					2105597910 | ||
| 
						 | 
					3c148f5721 | ||
| 
						 | 
					360c8a99a3 | ||
| 
						 | 
					06e31f5102 | ||
| 
						 | 
					42b5b66305 | ||
| 
						 | 
					27018ba64e | ||
| 
						 | 
					e208f03636 | ||
| 
						 | 
					cc9d23f23b | ||
| 
						 | 
					1a831bcf9b | ||
| 
						 | 
					82367a2246 | ||
| 
						 | 
					f18206767f | ||
| 
						 | 
					3947347d88 | ||
| 
						 | 
					8a37a0ff2e | ||
| 
						 | 
					468770b382 | ||
| 
						 | 
					6cfc3daacb | ||
| 
						 | 
					aefbafa18d | ||
| 
						 | 
					d12c834f9c | ||
| 
						 | 
					56de5cb2b3 | ||
| 
						 | 
					709257a0c5 | ||
| 
						 | 
					75a9d2bb33 | ||
| 
						 | 
					7b92b235e1 | ||
| 
						 | 
					c196f0018f | ||
| 
						 | 
					73080d6c36 | ||
| 
						 | 
					9541a2a5f0 | ||
| 
						 | 
					944222eba4 | ||
| 
						 | 
					9d77f33611 | ||
| 
						 | 
					7d132f81f7 | ||
| 
						 | 
					0972f19fc5 | ||
| 
						 | 
					6553bf05b4 | ||
| 
						 | 
					0816d3f5a9 | ||
| 
						 | 
					55055c7847 | ||
| 
						 | 
					113da93796 | ||
| 
						 | 
					cddcd0fb79 | ||
| 
						 | 
					a366298022 | ||
| 
						 | 
					4df9307d25 | ||
| 
						 | 
					d7bed958b3 | ||
| 
						 | 
					9038ba622e | ||
| 
						 | 
					7b8bb0297a | ||
| 
						 | 
					0da02d3902 | ||
| 
						 | 
					334872d374 | ||
| 
						 | 
					2e5ad19fe1 | ||
| 
						 | 
					a10389a22c | ||
| 
						 | 
					cefec7a19f | ||
| 
						 | 
					7264fbb3d2 | ||
| 
						 | 
					0e083e9dc6 | ||
| 
						 | 
					8a7b23dc9e | ||
| 
						 | 
					b7065575f3 | ||
| 
						 | 
					7ea703f150 | ||
| 
						 | 
					ea64125124 | ||
| 
						 | 
					1011143dbe | ||
| 
						 | 
					9ace6e1f71 | ||
| 
						 | 
					750f2cb883 | ||
| 
						 | 
					5221837be8 | ||
| 
						 | 
					1576b4500b | ||
| 
						 | 
					e1e9a06712 | ||
| 
						 | 
					6e36f8ffa4 | ||
| 
						 | 
					b0a7208cc7 | ||
| 
						 | 
					eec42aa7ae | ||
| 
						 | 
					6d2e969e7d | ||
| 
						 | 
					5f42022c1d | ||
| 
						 | 
					11d0c37506 | ||
| 
						 | 
					58bad1e2a3 | ||
| 
						 | 
					27d1dc5c37 | ||
| 
						 | 
					e7345c7a20 | ||
| 
						 | 
					186048a88e | ||
| 
						 | 
					7135259cc1 | ||
| 
						 | 
					4909325e79 | ||
| 
						 | 
					a4ee697ed1 | ||
| 
						 | 
					0f15a2f97f | ||
| 
						 | 
					89ace671a4 | ||
| 
						 | 
					e7db2a2f6d | ||
| 
						 | 
					8c33ac71ee | ||
| 
						 | 
					69914faf02 | ||
| 
						 | 
					daafebe7ac | ||
| 
						 | 
					2d81acb82e | ||
| 
						 | 
					82ca49c840 | ||
| 
						 | 
					bfe297052d | ||
| 
						 | 
					ffb1a14ace | ||
| 
						 | 
					7e35e44934 | ||
| 
						 | 
					0c8769e335 | ||
| 
						 | 
					83c7d34df2 | ||
| 
						 | 
					ad3c9842d7 | ||
| 
						 | 
					44dace2eef | ||
| 
						 | 
					a12671010a | ||
| 
						 | 
					23c149368b | ||
| 
						 | 
					09716d4716 | ||
| 
						 | 
					4b7c504d22 | ||
| 
						 | 
					1e4f9d4eda | ||
| 
						 | 
					e4f04d0977 | ||
| 
						 | 
					0f75525640 | ||
| 
						 | 
					edb088526f | ||
| 
						 | 
					80ebc63101 | ||
| 
						 | 
					cf1403bc79 | ||
| 
						 | 
					fcf63a7547 | ||
| 
						 | 
					9c0b75faba | ||
| 
						 | 
					1f2bfc9581 | ||
| 
						 | 
					14ab03d1e0 | ||
| 
						 | 
					3831fbaca2 | ||
| 
						 | 
					1d8edf58dd | ||
| 
						 | 
					4785e316ff | ||
| 
						 | 
					44da9de5b0 | ||
| 
						 | 
					4ecd093891 | ||
| 
						 | 
					dd4bc87d52 | ||
| 
						 | 
					570d25214e | ||
| 
						 | 
					f0b7e58968 | ||
| 
						 | 
					0411b51582 | ||
| 
						 | 
					dea782cff9 | ||
| 
						 | 
					388dd99762 | ||
| 
						 | 
					026101a268 | ||
| 
						 | 
					734099a956 | ||
| 
						 | 
					6be5851484 | ||
| 
						 | 
					994179f188 | ||
| 
						 | 
					6a65c7a52a | ||
| 
						 | 
					0d2d3ea17c | ||
| 
						 | 
					5d374ebb18 | ||
| 
						 | 
					62eadbb51a | ||
| 
						 | 
					ad8c8166bc | ||
| 
						 | 
					a5593bec79 | ||
| 
						 | 
					a1e2646301 | ||
| 
						 | 
					cf810d8357 | ||
| 
						 | 
					f258d6fbb2 | ||
| 
						 | 
					4961fda2a9 | ||
| 
						 | 
					6a6e5ae79c | ||
| 
						 | 
					02d792c003 | ||
| 
						 | 
					be8e7a4144 | ||
| 
						 | 
					b1dbd7833a | ||
| 
						 | 
					84ab05c5ef | ||
| 
						 | 
					78138261c2 | ||
| 
						 | 
					a4c910f1de | ||
| 
						 | 
					2eed24e859 | ||
| 
						 | 
					7d1023ea98 | ||
| 
						 | 
					b11d142cff | ||
| 
						 | 
					021ff8674e | ||
| 
						 | 
					1b86bc21ab | ||
| 
						 | 
					e3d1f4fe1e | ||
| 
						 | 
					a7452aebff | ||
| 
						 | 
					484524d781 | ||
| 
						 | 
					dbf9927caf | ||
| 
						 | 
					3bdedfd749 | ||
| 
						 | 
					005084af3d | ||
| 
						 | 
					46278ff297 | ||
| 
						 | 
					d73dcbb268 | ||
| 
						 | 
					390ecec3d9 | ||
| 
						 | 
					41a30c147d | ||
| 
						 | 
					4709ae80cb | ||
| 
						 | 
					7fbb455836 | ||
| 
						 | 
					745afd217f | ||
| 
						 | 
					4427e9a254 | ||
| 
						 | 
					2b0dcf8573 | ||
| 
						 | 
					47732ffb98 | ||
| 
						 | 
					d07f3216ab | ||
| 
						 | 
					56d65ba6f3 | ||
| 
						 | 
					895a3cbf24 | ||
| 
						 | 
					d951c8c1c2 | ||
| 
						 | 
					a294963e98 | ||
| 
						 | 
					68c73184b1 | ||
| 
						 | 
					7f824d6494 | ||
| 
						 | 
					3219212f03 | ||
| 
						 | 
					d90e35e5bd | ||
| 
						 | 
					73f8488150 | ||
| 
						 | 
					3853966a1e | ||
| 
						 | 
					d63893a437 | ||
| 
						 | 
					90c74043f5 | ||
| 
						 | 
					600445d90a | ||
| 
						 | 
					e4b405fd3d | ||
| 
						 | 
					3b7ecbdf0d | ||
| 
						 | 
					01efb645cb | ||
| 
						 | 
					b5ec1f42d5 | ||
| 
						 | 
					c839556a27 | ||
| 
						 | 
					e9972aa0dd | ||
| 
						 | 
					1c9a744b01 | ||
| 
						 | 
					e6d4bb29d8 | ||
| 
						 | 
					6c5b562d97 | ||
| 
						 | 
					a7103f9333 | ||
| 
						 | 
					c12425e141 | ||
| 
						 | 
					89f6de1383 | ||
| 
						 | 
					77da582e88 | ||
| 
						 | 
					34eaf75352 | ||
| 
						 | 
					ffadb04761 | ||
| 
						 | 
					29288b690e | ||
| 
						 | 
					25fd3f7e50 | ||
| 
						 | 
					4d60b8801c | ||
| 
						 | 
					3e984e75b6 | ||
| 
						 | 
					9e8645ca7a | ||
| 
						 | 
					caf3ac0645 | ||
| 
						 | 
					0f4343cd84 | ||
| 
						 | 
					192f232d3f | ||
| 
						 | 
					6e4d3b8a77 | ||
| 
						 | 
					8eda24261c | ||
| 
						 | 
					75c59fefab | ||
| 
						 | 
					4b19cf60df | ||
| 
						 | 
					2b476554e7 | ||
| 
						 | 
					b3788fed41 | ||
| 
						 | 
					b999c1d8aa | ||
| 
						 | 
					a63aa80dc9 | ||
| 
						 | 
					63f57c8c4f | ||
| 
						 | 
					7e6a6365c9 | ||
| 
						 | 
					3dbf26ac49 | ||
| 
						 | 
					f075fea78c | ||
| 
						 | 
					cc8380c286 | ||
| 
						 | 
					c0f0c68f4f | ||
| 
						 | 
					c2bb019381 | ||
| 
						 | 
					26ce6cdab2 | ||
| 
						 | 
					d9097facf1 | ||
| 
						 | 
					b927500487 | ||
| 
						 | 
					e71eabedf9 | ||
| 
						 | 
					33ed27c3ad | ||
| 
						 | 
					45cbab6751 | ||
| 
						 | 
					a7b74d6164 | ||
| 
						 | 
					575b1dba75 | ||
| 
						 | 
					a3b16b6dfa | ||
| 
						 | 
					c8f4de6f11 | ||
| 
						 | 
					bbb17acf3a | ||
| 
						 | 
					ad3a98387f | ||
| 
						 | 
					985fbf59c2 | ||
| 
						 | 
					2f2071be8a | ||
| 
						 | 
					6d510e4e70 | ||
| 
						 | 
					8e0736fbe6 | ||
| 
						 | 
					681d1e2f8d | ||
| 
						 | 
					42e70ef993 | ||
| 
						 | 
					039811ce6a | ||
| 
						 | 
					a54ccd1969 | ||
| 
						 | 
					707821ca55 | ||
| 
						 | 
					d3bf8fa53b | ||
| 
						 | 
					f5e2dd410e | ||
| 
						 | 
					3ca9c38777 | ||
| 
						 | 
					2d2cefb0b0 | ||
| 
						 | 
					2fd071e45d | ||
| 
						 | 
					d7a5c3f49a | ||
| 
						 | 
					819761f9fb | ||
| 
						 | 
					e50adf1cc8 | ||
| 
						 | 
					dcab10f53e | ||
| 
						 | 
					633d8965e2 | ||
| 
						 | 
					f602f9b6ec | ||
| 
						 | 
					f7e66dea61 | ||
| 
						 | 
					bda9441620 | ||
| 
						 | 
					4d5d5041df | ||
| 
						 | 
					587eb3a67c | ||
| 
						 | 
					6ca07f1e28 | ||
| 
						 | 
					8d39a20088 | ||
| 
						 | 
					4b6370eb86 | ||
| 
						 | 
					c6e340a8a2 | ||
| 
						 | 
					31c7153301 | ||
| 
						 | 
					7e04d00cc1 | ||
| 
						 | 
					9d43784c65 | ||
| 
						 | 
					eca9586a0f | ||
| 
						 | 
					0267bc237f | ||
| 
						 | 
					2e4577f741 | ||
| 
						 | 
					f5b278d683 | ||
| 
						 | 
					e6854ff8db | ||
| 
						 | 
					3b292273c7 | ||
| 
						 | 
					cb732e5d5f | ||
| 
						 | 
					2d4e202be3 | ||
| 
						 | 
					64da8e17d1 | ||
| 
						 | 
					08ad35efd9 | ||
| 
						 | 
					58b98267fc | ||
| 
						 | 
					ace71280a0 | ||
| 
						 | 
					a27946102a | ||
| 
						 | 
					1d99c116e7 | ||
| 
						 | 
					ee27e16fb1 | ||
| 
						 | 
					6ac7132799 | ||
| 
						 | 
					ca42abab70 | ||
| 
						 | 
					933d69a256 | ||
| 
						 | 
					3b1db14817 | ||
| 
						 | 
					10a5581aea | ||
| 
						 | 
					3ae699964f | ||
| 
						 | 
					9d953421d8 | ||
| 
						 | 
					763e3b65d1 | ||
| 
						 | 
					42dd27c9b1 | ||
| 
						 | 
					2b168f7383 | ||
| 
						 | 
					0536f089e1 | ||
| 
						 | 
					3df13cddd4 | ||
| 
						 | 
					e3f677fa37 | ||
| 
						 | 
					c2253c1e0f | ||
| 
						 | 
					5c68b6cc21 | ||
| 
						 | 
					ffaa627820 | ||
| 
						 | 
					f742fd5d4a | ||
| 
						 | 
					69b99fe127 | ||
| 
						 | 
					5a396f6787 | ||
| 
						 | 
					cb0dc7b434 | ||
| 
						 | 
					e28829bd1b | ||
| 
						 | 
					68ceeab610 | ||
| 
						 | 
					68dca9d047 | ||
| 
						 | 
					d88ca151f4 | ||
| 
						 | 
					3c90218c3d | ||
| 
						 | 
					afd409c883 | ||
| 
						 | 
					26b6c03a2a | ||
| 
						 | 
					9c04d851e4 | ||
| 
						 | 
					1d6fe11906 | ||
| 
						 | 
					c0f1313830 | ||
| 
						 | 
					fb51fadf00 | ||
| 
						 | 
					55fd9122d0 | ||
| 
						 | 
					5b5720fac0 | ||
| 
						 | 
					d25d7d7d40 | ||
| 
						 | 
					ba4f2d8917 | ||
| 
						 | 
					a2aec39633 | ||
| 
						 | 
					0bf4fdc9af | ||
| 
						 | 
					ed8c73eb14 | ||
| 
						 | 
					3528a7f78b | ||
| 
						 | 
					54bcc40192 | ||
| 
						 | 
					4b5e9ffb83 | ||
| 
						 | 
					a7f5f035a6 | ||
| 
						 | 
					4abd62e62b | ||
| 
						 | 
					1fb158b297 | ||
| 
						 | 
					968d2bb8ba | ||
| 
						 | 
					92a3dfe44a | ||
| 
						 | 
					9ef232157b | ||
| 
						 | 
					b9f4f7a530 | ||
| 
						 | 
					761afad118 | ||
| 
						 | 
					8848ebbd4f | ||
| 
						 | 
					37950143fc | ||
| 
						 | 
					25fd95044c | ||
| 
						 | 
					1da24d10fd | ||
| 
						 | 
					60e374dca3 | ||
| 
						 | 
					7a65f91575 | ||
| 
						 | 
					6f8b558724 | ||
| 
						 | 
					1a88b62bf7 | ||
| 
						 | 
					8361756dc4 | ||
| 
						 | 
					273299028e | ||
| 
						 | 
					847e49ccdf | ||
| 
						 | 
					81a3899381 | ||
| 
						 | 
					9257a3f6d7 | ||
| 
						 | 
					728143247d | ||
| 
						 | 
					6ec4e4e3d7 | ||
| 
						 | 
					7922a12f02 | ||
| 
						 | 
					37ccb9d3b6 | ||
| 
						 | 
					3c254360ba | ||
| 
						 | 
					d2a0beaa67 | ||
| 
						 | 
					cda223ffc0 | ||
| 
						 | 
					3ca51bedc6 | ||
| 
						 | 
					36076b7ea5 | ||
| 
						 | 
					e90d128a26 | ||
| 
						 | 
					966b5e6372 | ||
| 
						 | 
					279c369a1f | ||
| 
						 | 
					d9c6b3bcf7 | ||
| 
						 | 
					1c2f68f129 | ||
| 
						 | 
					296c7cec05 | ||
| 
						 | 
					75d67ee770 | ||
| 
						 | 
					a1e9a54765 | ||
| 
						 | 
					8d1dacd951 | ||
| 
						 | 
					545683df6f | ||
| 
						 | 
					cfbd62a5dc | ||
| 
						 | 
					40339a12e1 | ||
| 
						 | 
					90bf6565d0 | ||
| 
						 | 
					9be9bd9106 | ||
| 
						 | 
					c1527cc9e2 | ||
| 
						 | 
					a1a3aab115 | ||
| 
						 | 
					c77a83d86f | ||
| 
						 | 
					a6e377aa57 | ||
| 
						 | 
					df4732be2e | ||
| 
						 | 
					9435c1e12a | ||
| 
						 | 
					efdac2ce8c | ||
| 
						 | 
					2912d7055b | ||
| 
						 | 
					d056f2e246 | ||
| 
						 | 
					55ecb0c022 | ||
| 
						 | 
					13f7aa4063 | ||
| 
						 | 
					915f587ef1 | ||
| 
						 | 
					b7f88e8f61 | ||
| 
						 | 
					677ed463f0 | ||
| 
						 | 
					8a2bdb8d22 | ||
| 
						 | 
					9bff787ee1 | ||
| 
						 | 
					b3ae920746 | ||
| 
						 | 
					b82bef95f3 | ||
| 
						 | 
					e6578defcd | ||
| 
						 | 
					ace8e30818 | ||
| 
						 | 
					ec3aa06caf | ||
| 
						 | 
					ba088e5545 | ||
| 
						 | 
					8a0b0cb3d7 | ||
| 
						 | 
					6369138bd1 | ||
| 
						 | 
					c2a7dffa7d | ||
| 
						 | 
					b0c2325adc | ||
| 
						 | 
					2ff157cf7a | ||
| 
						 | 
					83628b285b | ||
| 
						 | 
					1ba3f262a2 | ||
| 
						 | 
					97334e10af | ||
| 
						 | 
					31b4f8fa31 | ||
| 
						 | 
					1bbb4cb478 | ||
| 
						 | 
					d46da6ac9d | ||
| 
						 | 
					825b38850e | ||
| 
						 | 
					8755824c64 | ||
| 
						 | 
					4ea835e50b | ||
| 
						 | 
					5fddbec132 | ||
| 
						 | 
					b3d3fd70aa | ||
| 
						 | 
					6633537fb8 | ||
| 
						 | 
					4dec9716c4 | ||
| 
						 | 
					313b36944f | ||
| 
						 | 
					456fdda6c2 | ||
| 
						 | 
					6437c43147 | ||
| 
						 | 
					5928a24803 | ||
| 
						 | 
					20a6bcc676 | ||
| 
						 | 
					eaf313b0f6 | ||
| 
						 | 
					d51b66c204 | ||
| 
						 | 
					660f0e4c40 | ||
| 
						 | 
					540a03f75c | ||
| 
						 | 
					a6b239698c | ||
| 
						 | 
					92d1dd9a4a | ||
| 
						 | 
					45ec5f8eab | ||
| 
						 | 
					1d01acce06 | ||
| 
						 | 
					be750ee427 | ||
| 
						 | 
					dddb30477b | ||
| 
						 | 
					37459a3ebc | ||
| 
						 | 
					449c33ee8b | ||
| 
						 | 
					2b5d0877a8 | ||
| 
						 | 
					6d5807ec4b | ||
| 
						 | 
					64865b3f41 | ||
| 
						 | 
					53f0e1896b | ||
| 
						 | 
					aaa60dab12 | ||
| 
						 | 
					9b72c445a7 | ||
| 
						 | 
					3f609e17b3 | ||
| 
						 | 
					ef03c84b21 | ||
| 
						 | 
					163c0f1b44 | ||
| 
						 | 
					807e1d36d5 | ||
| 
						 | 
					5545cc8bab | ||
| 
						 | 
					d6e60c8e3a | ||
| 
						 | 
					2d8e7e9f8b | ||
| 
						 | 
					5b4c5b0cbf | ||
| 
						 | 
					2471ef805b | ||
| 
						 | 
					2a7fc86b15 | ||
| 
						 | 
					3c8bf52fb8 | ||
| 
						 | 
					a026998682 | ||
| 
						 | 
					06ea81fdb2 | ||
| 
						 | 
					d69b1e2d11 | ||
| 
						 | 
					a3e0024980 | ||
| 
						 | 
					d9a2c32aca | ||
| 
						 | 
					e152ed2e61 | ||
| 
						 | 
					9e975a46a2 | ||
| 
						 | 
					70af075012 | ||
| 
						 | 
					44e5a03cf2 | ||
| 
						 | 
					c8cee88e33 | ||
| 
						 | 
					35296017b5 | ||
| 
						 | 
					130d598ec9 | ||
| 
						 | 
					0350925c1e | ||
| 
						 | 
					94e3dd0d4f | ||
| 
						 | 
					5d44422230 | ||
| 
						 | 
					eafdd7dbd7 | ||
| 
						 | 
					127b584e79 | ||
| 
						 | 
					2179edc7fe | ||
| 
						 | 
					9108495586 | ||
| 
						 | 
					fa617eac6b | ||
| 
						 | 
					b63971caf7 | ||
| 
						 | 
					7327da6350 | ||
| 
						 | 
					8f72fc4a44 | ||
| 
						 | 
					238348c885 | ||
| 
						 | 
					7b5f93510b | ||
| 
						 | 
					b3861ff755 | ||
| 
						 | 
					5a6b7219b9 | ||
| 
						 | 
					c2bc34fd87 | ||
| 
						 | 
					1d3ae31755 | ||
| 
						 | 
					8ddd686049 | ||
| 
						 | 
					e5188a60dc | ||
| 
						 | 
					e71d13c090 | ||
| 
						 | 
					ba83dfd454 | ||
| 
						 | 
					2fb0aea990 | ||
| 
						 | 
					51177e4e1f | ||
| 
						 | 
					004d6da9fb | ||
| 
						 | 
					561373e793 | ||
| 
						 | 
					279f4760d7 | ||
| 
						 | 
					f931cd582d | ||
| 
						 | 
					ab51bc443b | ||
| 
						 | 
					c8575fe6e0 | ||
| 
						 | 
					4489f120f9 | ||
| 
						 | 
					253f9603ed | ||
| 
						 | 
					6ca712b498 | ||
| 
						 | 
					b743566339 | ||
| 
						 | 
					481487f084 | ||
| 
						 | 
					fc8313430a | ||
| 
						 | 
					648618d280 | ||
| 
						 | 
					ae1a130843 | ||
| 
						 | 
					33d16ae0cd | ||
| 
						 | 
					f09fe30af5 | ||
| 
						 | 
					33eadb5549 | ||
| 
						 | 
					368bff1a82 | ||
| 
						 | 
					d853841dd5 | ||
| 
						 | 
					eacaafeb48 | ||
| 
						 | 
					ac59dd8b1d | ||
| 
						 | 
					353c854734 | ||
| 
						 | 
					3e5c209039 | ||
| 
						 | 
					f56d96267e | ||
| 
						 | 
					ed28260aaf | ||
| 
						 | 
					8ccec37a4b | ||
| 
						 | 
					646622b99e | ||
| 
						 | 
					a25c2fd6b5 | ||
| 
						 | 
					ee1a9a4781 | ||
| 
						 | 
					a0367d6669 | ||
| 
						 | 
					87658e83c1 | ||
| 
						 | 
					4509c3ce34 | ||
| 
						 | 
					30e93979d2 | ||
| 
						 | 
					d6b87053bf | ||
| 
						 | 
					22389a5d2d | ||
| 
						 | 
					37696532c2 | ||
| 
						 | 
					54efcb7e2f | ||
| 
						 | 
					bcb7c27cc4 | ||
| 
						 | 
					e2575d6de4 | ||
| 
						 | 
					23e989e170 | ||
| 
						 | 
					28412150e6 | ||
| 
						 | 
					6d941b0c1f | ||
| 
						 | 
					2f90f35478 | ||
| 
						 | 
					12f7e1b804 | ||
| 
						 | 
					c7fa2ed11a | ||
| 
						 | 
					bfbe12b94b | ||
| 
						 | 
					7476c64a66 | ||
| 
						 | 
					46fff8e8a2 | ||
| 
						 | 
					cd646aab9e | ||
| 
						 | 
					a3684545b5 | ||
| 
						 | 
					2f42874fd3 | ||
| 
						 | 
					84d0e9b4cd | ||
| 
						 | 
					a53011f778 | ||
| 
						 | 
					b842c5b8bb | ||
| 
						 | 
					ab1374f801 | ||
| 
						 | 
					a5359027f0 | ||
| 
						 | 
					43951a36eb | ||
| 
						 | 
					55df96491c | ||
| 
						 | 
					0c037627fc | ||
| 
						 | 
					344d267fd2 | ||
| 
						 | 
					4211389ac7 | ||
| 
						 | 
					c6d00ec7d1 | ||
| 
						 | 
					212ae60622 | ||
| 
						 | 
					a72a2e0a1a | ||
| 
						 | 
					50375fb373 | ||
| 
						 | 
					2d02c23574 | ||
| 
						 | 
					cb105fdeb4 | ||
| 
						 | 
					acfd4dde36 | ||
| 
						 | 
					919fc48cc5 | ||
| 
						 | 
					aec4fd066b | ||
| 
						 | 
					73c7b18900 | ||
| 
						 | 
					3dfe45d225 | ||
| 
						 | 
					95a6b0f85c | ||
| 
						 | 
					87ee8450fe | ||
| 
						 | 
					f2a6bcf2a8 | ||
| 
						 | 
					644ef13acd | ||
| 
						 | 
					342574761f | ||
| 
						 | 
					b7c978e078 | ||
| 
						 | 
					f0398a6db8 | ||
| 
						 | 
					f3b1ef99cc | ||
| 
						 | 
					52d9ddf9e5 | ||
| 
						 | 
					93f251dbcd | ||
| 
						 | 
					a6810fc3ef | ||
| 
						 | 
					15f6c51062 | ||
| 
						 | 
					5e21c706f3 | ||
| 
						 | 
					e1355d4b62 | ||
| 
						 | 
					7eeac3b586 | ||
| 
						 | 
					4bf13610ce | ||
| 
						 | 
					0e0ce379b4 | ||
| 
						 | 
					36e8a11505 | ||
| 
						 | 
					45f442ea63 | ||
| 
						 | 
					db743c90d8 | ||
| 
						 | 
					10cc94f581 | ||
| 
						 | 
					108da64562 | ||
| 
						 | 
					f85b46286e | ||
| 
						 | 
					184b371649 | ||
| 
						 | 
					b0375bb037 | ||
| 
						 | 
					48942848e7 | ||
| 
						 | 
					27ac342928 | ||
| 
						 | 
					25aba16ef8 | ||
| 
						 | 
					a0d0f383c8 | ||
| 
						 | 
					6752f165db | ||
| 
						 | 
					e05076b258 | ||
| 
						 | 
					fadbfdf801 | ||
| 
						 | 
					cb277b8d1e | ||
| 
						 | 
					234f14dbbe | ||
| 
						 | 
					99ede3a9ef | ||
| 
						 | 
					378233f53d | ||
| 
						 | 
					f903408980 | ||
| 
						 | 
					cc8f316941 | ||
| 
						 | 
					b684254908 | ||
| 
						 | 
					351d90ca55 | ||
| 
						 | 
					23177df26a | ||
| 
						 | 
					ba15371948 | ||
| 
						 | 
					73dbaebbc1 | ||
| 
						 | 
					8d60734737 | ||
| 
						 | 
					002098d496 | ||
| 
						 | 
					e3244eb68e | ||
| 
						 | 
					85c6fb1430 | ||
| 
						 | 
					54e4643396 | ||
| 
						 | 
					85c5c4405a | ||
| 
						 | 
					d668879ba6 | ||
| 
						 | 
					cb140aa06e | ||
| 
						 | 
					6a769d3953 | ||
| 
						 | 
					3be8ffd826 | ||
| 
						 | 
					bb910e14a4 | ||
| 
						 | 
					69ebbe019a | ||
| 
						 | 
					0d39672d32 | ||
| 
						 | 
					0d1231980a | ||
| 
						 | 
					82a015892b | ||
| 
						 | 
					194b7f60c5 | ||
| 
						 | 
					ebc7356db5 | ||
| 
						 | 
					e1a2580b2a | ||
| 
						 | 
					b6f51474ff | ||
| 
						 | 
					0f18768091 | ||
| 
						 | 
					efc7f9df37 | ||
| 
						 | 
					50cd617bd9 | ||
| 
						 | 
					838b818cd3 | ||
| 
						 | 
					cf795562bf | ||
| 
						 | 
					ac37424878 | ||
| 
						 | 
					a336048c98 | ||
| 
						 | 
					87496f9978 | ||
| 
						 | 
					08a542a324 | ||
| 
						 | 
					9b3d05e05f | ||
| 
						 | 
					d8e3103a2b | ||
| 
						 | 
					76a64d13a0 | ||
| 
						 | 
					1e975859c2 | ||
| 
						 | 
					4c5261bfa0 | ||
| 
						 | 
					aed2827e7b | ||
| 
						 | 
					e6e6e4e62b | ||
| 
						 | 
					8b09b4180b | ||
| 
						 | 
					626737b9fa | ||
| 
						 | 
					22de481557 | ||
| 
						 | 
					b9dbb6bcf8 | ||
| 
						 | 
					a48616a138 | ||
| 
						 | 
					8222aac9e3 | ||
| 
						 | 
					77aa3c187e | ||
| 
						 | 
					ee0283c985 | ||
| 
						 | 
					302c2e94de | ||
| 
						 | 
					06fe07932a | ||
| 
						 | 
					6913c7a018 | ||
| 
						 | 
					6b602c74b7 | ||
| 
						 | 
					8116f85479 | ||
| 
						 | 
					e40d553045 | ||
| 
						 | 
					2c6414ce11 | ||
| 
						 | 
					e5aea632ee | ||
| 
						 | 
					e5b30cdfbb | ||
| 
						 | 
					ba5f34f827 | ||
| 
						 | 
					84d2feb2e6 | ||
| 
						 | 
					d12e50eb02 | ||
| 
						 | 
					c2bc9a8c62 | ||
| 
						 | 
					d910a4fd38 | ||
| 
						 | 
					db30f53ab0 | ||
| 
						 | 
					50be3a24fe | ||
| 
						 | 
					256ba4028b | ||
| 
						 | 
					b07af2660d | ||
| 
						 | 
					bc0d70b2f7 | ||
| 
						 | 
					c6e48dfd56 | ||
| 
						 | 
					c775db50ef | ||
| 
						 | 
					ee4c8b5ad2 | ||
| 
						 | 
					d8b76e31c3 | ||
| 
						 | 
					7e10c7f9d8 | ||
| 
						 | 
					c47128f433 | ||
| 
						 | 
					8aab9acc10 | ||
| 
						 | 
					350b36df25 | ||
| 
						 | 
					dbd2944c13 | ||
| 
						 | 
					4603fa6f24 | ||
| 
						 | 
					60300851ea | ||
| 
						 | 
					58312ea2b7 | ||
| 
						 | 
					cb534d8b85 | ||
| 
						 | 
					5626d35bc4 | ||
| 
						 | 
					4677cebf40 | ||
| 
						 | 
					7399f3d798 | ||
| 
						 | 
					faeecf7665 | ||
| 
						 | 
					63e0802f4e | ||
| 
						 | 
					e3ee9604a5 | ||
| 
						 | 
					8c66e1d99d | ||
| 
						 | 
					b55579c348 | ||
| 
						 | 
					ca9e8aecd6 | ||
| 
						 | 
					cc4cb45e9d | ||
| 
						 | 
					ebbf6e6133 | ||
| 
						 | 
					cba07dec7e | ||
| 
						 | 
					6f7037b2b1 | ||
| 
						 | 
					ef4b2f963d | ||
| 
						 | 
					97f3ff03b6 | ||
| 
						 | 
					2fbc7a2869 | ||
| 
						 | 
					4983718df7 | ||
| 
						 | 
					23ca00fd9a | ||
| 
						 | 
					3df6eba237 | ||
| 
						 | 
					893f61b490 | ||
| 
						 | 
					e940e02126 | ||
| 
						 | 
					7e3a46c33e | ||
| 
						 | 
					7f743c6fb0 | ||
| 
						 | 
					73654d51dd | ||
| 
						 | 
					096551ab3e | ||
| 
						 | 
					c485c460f7 | ||
| 
						 | 
					b0a7c58287 | ||
| 
						 | 
					d2637123c4 | ||
| 
						 | 
					02b7c3d1b0 | ||
| 
						 | 
					8c1769f157 | ||
| 
						 | 
					655809517c | ||
| 
						 | 
					2190f60a89 | ||
| 
						 | 
					18faebc93c | ||
| 
						 | 
					0eebfdb4cc | ||
| 
						 | 
					7811374b0f | ||
| 
						 | 
					a2f01b4a46 | ||
| 
						 | 
					f5c910beb7 | ||
| 
						 | 
					4e014ca748 | ||
| 
						 | 
					87095b0578 | ||
| 
						 | 
					fba6ac2b4c | ||
| 
						 | 
					1a811b1ab1 | ||
| 
						 | 
					c26349624c | ||
| 
						 | 
					b642d9f712 | ||
| 
						 | 
					fd6623b5a5 | ||
| 
						 | 
					0b2a3f18bc | ||
| 
						 | 
					b304c3a4b9 | ||
| 
						 | 
					3ceef2005b | ||
| 
						 | 
					0f438f524b | ||
| 
						 | 
					24c84ca6f5 | ||
| 
						 | 
					7898f643ac | ||
| 
						 | 
					7bd45d308a | ||
| 
						 | 
					b3da16911f | ||
| 
						 | 
					e52892f75b | ||
| 
						 | 
					8c41a0f0ed | ||
| 
						 | 
					3e9212aaff | ||
| 
						 | 
					a2ec902773 | ||
| 
						 | 
					1c0130fd02 | ||
| 
						 | 
					3e3d6f97f4 | ||
| 
						 | 
					9c3bda0111 | ||
| 
						 | 
					d14902700a | ||
| 
						 | 
					c95c32a9fe | ||
| 
						 | 
					35e045d7a7 | ||
| 
						 | 
					084e1f3d51 | ||
| 
						 | 
					5b43cefb85 | ||
| 
						 | 
					aab637c9e7 | ||
| 
						 | 
					7d9b197383 | ||
| 
						 | 
					c9dd267ec1 | ||
| 
						 | 
					a5254989f8 | ||
| 
						 | 
					494ce073b5 | ||
| 
						 | 
					b99e4210ba | ||
| 
						 | 
					d3b74cbc91 | ||
| 
						 | 
					5ff73faf48 | ||
| 
						 | 
					2f7f11e2e5 | ||
| 
						 | 
					5119997122 | ||
| 
						 | 
					b5c1773d59 | ||
| 
						 | 
					dfb5057342 | ||
| 
						 | 
					7bddd294c9 | ||
| 
						 | 
					01f7394f7f | ||
| 
						 | 
					5aa8b03349 | ||
| 
						 | 
					b5ad910b81 | ||
| 
						 | 
					da65bae86e | ||
| 
						 | 
					a0189a6fe1 | ||
| 
						 | 
					244b5ba3c2 | ||
| 
						 | 
					960de7bd7b | ||
| 
						 | 
					c6185baa99 | ||
| 
						 | 
					4d4695032c | ||
| 
						 | 
					9d29cefe75 | ||
| 
						 | 
					35f535b9a3 | ||
| 
						 | 
					6d22f6fcd5 | ||
| 
						 | 
					8bfaa487ce | ||
| 
						 | 
					0d067d2f01 | ||
| 
						 | 
					d66755fd1e | ||
| 
						 | 
					267b2add9a | ||
| 
						 | 
					d290e3d99e | ||
| 
						 | 
					a6a4c5a936 | ||
| 
						 | 
					8a8f0cef20 | ||
| 
						 | 
					91dc0d5f4a | ||
| 
						 | 
					ed7b07c8b1 | ||
| 
						 | 
					3f880fa769 | ||
| 
						 | 
					d83dd17738 | ||
| 
						 | 
					9ade0dcae3 | ||
| 
						 | 
					a329d85697 | ||
| 
						 | 
					c322410783 | ||
| 
						 | 
					b67331e018 | ||
| 
						 | 
					a47b339668 | ||
| 
						 | 
					ad56a9215c | ||
| 
						 | 
					c56a5344b9 | ||
| 
						 | 
					1f62cbe21a | ||
| 
						 | 
					47845f8c19 | ||
| 
						 | 
					409c82ce73 | ||
| 
						 | 
					dc3f5b6211 | ||
| 
						 | 
					fb02b77e63 | ||
| 
						 | 
					f974d54c7a | ||
| 
						 | 
					68978c6e25 | ||
| 
						 | 
					6e83b7d6df | ||
| 
						 | 
					5a4d448cc1 | ||
| 
						 | 
					743eac8c55 | ||
| 
						 | 
					6b66c8f304 | ||
| 
						 | 
					c976fbfcd5 | ||
| 
						 | 
					ed3e38ac31 | ||
| 
						 | 
					76f03900d2 | ||
| 
						 | 
					035df316aa | ||
| 
						 | 
					9759a04c7d | ||
| 
						 | 
					c7cb47a1d8 | ||
| 
						 | 
					0d2d04e17b | ||
| 
						 | 
					98423c6e41 | ||
| 
						 | 
					33c3fa21e3 | ||
| 
						 | 
					2141d52794 | ||
| 
						 | 
					16b8021401 | ||
| 
						 | 
					151b09b5ca | ||
| 
						 | 
					9bc2b48d9b | ||
| 
						 | 
					ab8a98f1df | ||
| 
						 | 
					efe354a7b1 | ||
| 
						 | 
					d50d3fc837 | ||
| 
						 | 
					83ee92af1a | ||
| 
						 | 
					ea0ad9fd87 | ||
| 
						 | 
					ff3c60c0e1 | ||
| 
						 | 
					399703a471 | ||
| 
						 | 
					82017c4aea | ||
| 
						 | 
					bdf07c3dc9 | ||
| 
						 | 
					598be24644 | ||
| 
						 | 
					e4e71a1e5f | ||
| 
						 | 
					fba5af280e | ||
| 
						 | 
					c668ff9472 | ||
| 
						 | 
					2cadc706e2 | ||
| 
						 | 
					3c6f63abcc | ||
| 
						 | 
					00cd7e7e9c | ||
| 
						 | 
					055c860b43 | ||
| 
						 | 
					454c8628c3 | ||
| 
						 | 
					a23a6db4d6 | ||
| 
						 | 
					6575091a78 | ||
| 
						 | 
					9e25d014d2 | ||
| 
						 | 
					41d5dd8679 | ||
| 
						 | 
					c3ea6dc1f5 | ||
| 
						 | 
					22afa509ca | ||
| 
						 | 
					3fb3cc8269 | ||
| 
						 | 
					e3e461d7cb | ||
| 
						 | 
					c16fccb317 | ||
| 
						 | 
					b9cffdf2bd | ||
| 
						 | 
					f2aae72cc2 | ||
| 
						 | 
					fe8db1873c | ||
| 
						 | 
					c66c715ac9 | ||
| 
						 | 
					5dcfd85642 | ||
| 
						 | 
					c70dfe1b09 | ||
| 
						 | 
					232c591655 | ||
| 
						 | 
					790614b544 | ||
| 
						 | 
					32c032cd97 | ||
| 
						 | 
					e48ee16366 | ||
| 
						 | 
					e92d936ce8 | ||
| 
						 | 
					4e210c5396 | ||
| 
						 | 
					3d3e60b1fc | ||
| 
						 | 
					f3f0e2f1a9 | ||
| 
						 | 
					08206eea56 | ||
| 
						 | 
					78296246e8 | ||
| 
						 | 
					85b5dd35b1 | ||
| 
						 | 
					11cfaa3e3d | ||
| 
						 | 
					103c863534 | ||
| 
						 | 
					6688f83226 | ||
| 
						 | 
					01a064dd63 | ||
| 
						 | 
					7b234078ae | ||
| 
						 | 
					add02a7897 | ||
| 
						 | 
					19167df692 | ||
| 
						 | 
					6766845e21 | ||
| 
						 | 
					bc3b5f3e35 | ||
| 
						 | 
					5fe23113ec | ||
| 
						 | 
					c55e1c1d17 | ||
| 
						 | 
					d910405648 | ||
| 
						 | 
					62b432c046 | ||
| 
						 | 
					eae1f78221 | ||
| 
						 | 
					11d05fb3b8 | ||
| 
						 | 
					58efca835f | ||
| 
						 | 
					da6e520b91 | ||
| 
						 | 
					a5099f69d8 | ||
| 
						 | 
					9398b6c2c8 | ||
| 
						 | 
					99f2060fc1 | ||
| 
						 | 
					5d3ebcb35a | ||
| 
						 | 
					509d011fbe | ||
| 
						 | 
					17ffd604bf | ||
| 
						 | 
					a3dafa9056 | ||
| 
						 | 
					21d0602305 | ||
| 
						 | 
					64d6ee1be5 | ||
| 
						 | 
					1378ab7278 | ||
| 
						 | 
					87a021ec2d | ||
| 
						 | 
					189317b80c | ||
| 
						 | 
					4f0775cc7c | ||
| 
						 | 
					7190f927b7 | ||
| 
						 | 
					d559d8b901 | ||
| 
						 | 
					2562306802 | ||
| 
						 | 
					15394358df | ||
| 
						 | 
					df4d4467b3 | ||
| 
						 | 
					67ec0b9e6c | ||
| 
						 | 
					2ee8a7056e | ||
| 
						 | 
					a5075d9eb5 | ||
| 
						 | 
					50bb4f0142 | ||
| 
						 | 
					df80c37adb | ||
| 
						 | 
					7da51602d5 | ||
| 
						 | 
					5152517887 | ||
| 
						 | 
					eb8a2de5d6 | ||
| 
						 | 
					f2a1a906ff | ||
| 
						 | 
					0808e9b6fb | ||
| 
						 | 
					b81a2cc273 | ||
| 
						 | 
					abeaedf16f | ||
| 
						 | 
					8e35e913bb | ||
| 
						 | 
					81c5f4ab19 | ||
| 
						 | 
					e270b726b3 | ||
| 
						 | 
					c2b5a9bb1f | ||
| 
						 | 
					44ce7fa54c | ||
| 
						 | 
					b0142cf050 | ||
| 
						 | 
					a340331229 | ||
| 
						 | 
					b14c892740 | ||
| 
						 | 
					15d17c12d5 | ||
| 
						 | 
					99800d9840 | ||
| 
						 | 
					5d91a2600d | ||
| 
						 | 
					cb66c7e2dc | ||
| 
						 | 
					58488c93be | ||
| 
						 | 
					61f8f2f18c | ||
| 
						 | 
					7b43ae0a92 | ||
| 
						 | 
					2807e3134f | ||
| 
						 | 
					0771363f3b | ||
| 
						 | 
					1f56e85f6d | ||
| 
						 | 
					2edf73908c | ||
| 
						 | 
					6a37a02eee | ||
| 
						 | 
					5998123868 | ||
| 
						 | 
					26cb903b08 | ||
| 
						 | 
					92a8b68859 | ||
| 
						 | 
					4127350abe | ||
| 
						 | 
					ed6b135015 | ||
| 
						 | 
					f95015c7f6 | ||
| 
						 | 
					defec2c9b0 | ||
| 
						 | 
					04921e64de | ||
| 
						 | 
					bdd432fe1d | ||
| 
						 | 
					6e9ab9f330 | ||
| 
						 | 
					814c0ada13 | ||
| 
						 | 
					dfc468f220 | ||
| 
						 | 
					6817b38322 | ||
| 
						 | 
					e01f3f06c8 | ||
| 
						 | 
					3229502fa1 | ||
| 
						 | 
					a4c5eebd1e | ||
| 
						 | 
					a3c22d5abb | ||
| 
						 | 
					a26b87f348 | ||
| 
						 | 
					4c3cc42c91 | ||
| 
						 | 
					f3f4e1a541 | ||
| 
						 | 
					4722f6b5c4 | ||
| 
						 | 
					7d8d1c7828 | ||
| 
						 | 
					4bb70e7d31 | ||
| 
						 | 
					321030bb44 | ||
| 
						 | 
					6c161b1150 | ||
| 
						 | 
					d5c37c8619 | ||
| 
						 | 
					c445eaec3e | ||
| 
						 | 
					7c66c36d3f | ||
| 
						 | 
					031a68000a | ||
| 
						 | 
					7d7b665be8 | ||
| 
						 | 
					c3d82f88a5 | ||
| 
						 | 
					c033bad0b9 | ||
| 
						 | 
					c31d85f820 | ||
| 
						 | 
					217fbf257e | ||
| 
						 | 
					0b611a14b9 | ||
| 
						 | 
					df6861c9dc | ||
| 
						 | 
					a4cd12394e | ||
| 
						 | 
					e0bca1e37b | ||
| 
						 | 
					55ce851bb2 | ||
| 
						 | 
					e8d34f2eb4 | ||
| 
						 | 
					bb3daaa99b | ||
| 
						 | 
					36b58d03b7 | ||
| 
						 | 
					7958459db9 | ||
| 
						 | 
					14a76af0d3 | ||
| 
						 | 
					a04a58e01f | ||
| 
						 | 
					afbd9fd41b | ||
| 
						 | 
					3d53d4e55e | ||
| 
						 | 
					7302703039 | ||
| 
						 | 
					97a8a96593 | ||
| 
						 | 
					be2e99077e | ||
| 
						 | 
					3b29276228 | ||
| 
						 | 
					4a528b9ecb | ||
| 
						 | 
					b3632a4e86 | ||
| 
						 | 
					8a659e3117 | ||
| 
						 | 
					a6897ebde0 | ||
| 
						 | 
					582da14a14 | ||
| 
						 | 
					b81bf6b547 | ||
| 
						 | 
					8e147444d5 | ||
| 
						 | 
					a9964ee0c8 | ||
| 
						 | 
					b671df9906 | ||
| 
						 | 
					0bcf9c30de | ||
| 
						 | 
					2c07cce282 | ||
| 
						 | 
					8c5e39c0c5 | ||
| 
						 | 
					cae48aaa95 | ||
| 
						 | 
					37f4f6ba14 | ||
| 
						 | 
					597bd97b01 | ||
| 
						 | 
					38de5300e5 | ||
| 
						 | 
					62b3c9dda8 | ||
| 
						 | 
					146f3ea0f5 | ||
| 
						 | 
					af9b7fbc30 | ||
| 
						 | 
					78213f1e95 | ||
| 
						 | 
					de347ad7c8 | ||
| 
						 | 
					a4bba8a92e | ||
| 
						 | 
					fcacfc2726 | ||
| 
						 | 
					bab464e765 | ||
| 
						 | 
					2879763c34 | ||
| 
						 | 
					ea2ea30193 | ||
| 
						 | 
					608569cc48 | ||
| 
						 | 
					c7e973aab4 | ||
| 
						 | 
					443d57bc32 | ||
| 
						 | 
					57ec756f5b | ||
| 
						 | 
					9286a5ba73 | ||
| 
						 | 
					1c9dffe41f | ||
| 
						 | 
					8c7f724ce4 | ||
| 
						 | 
					b193248056 | ||
| 
						 | 
					f0d944847b | ||
| 
						 | 
					a72d70e707 | ||
| 
						 | 
					add14fb43a | ||
| 
						 | 
					38ce4dc56c | ||
| 
						 | 
					bce5abd33b | ||
| 
						 | 
					3f36eeb071 | ||
| 
						 | 
					33bda2d40c | ||
| 
						 | 
					2b5e3a600e | ||
| 
						 | 
					8dbf9fd302 | ||
| 
						 | 
					9c72ce5bd2 | ||
| 
						 | 
					ec2762509b | ||
| 
						 | 
					e63229a5e5 | ||
| 
						 | 
					ad73379d1c | ||
| 
						 | 
					abd4d2c42a | ||
| 
						 | 
					79784a8e57 | ||
| 
						 | 
					61b8fc1e2f | ||
| 
						 | 
					4751615623 | ||
| 
						 | 
					cccdc558e7 | ||
| 
						 | 
					d3257c345a | ||
| 
						 | 
					e09b76bf32 | ||
| 
						 | 
					837cccdf83 | ||
| 
						 | 
					a3fcd15980 | ||
| 
						 | 
					93d1573481 | ||
| 
						 | 
					893a5dd007 | ||
| 
						 | 
					026b418b4a | ||
| 
						 | 
					06dd98b23c | ||
| 
						 | 
					2a81ae1dec | ||
| 
						 | 
					1625b9c7f9 | ||
| 
						 | 
					184c8ae707 | ||
| 
						 | 
					8f8b103224 | ||
| 
						 | 
					1af415a88e | ||
| 
						 | 
					fe07cd0248 | ||
| 
						 | 
					a3d339092e | ||
| 
						 | 
					837216ee9a | ||
| 
						 | 
					dcd0c90283 | ||
| 
						 | 
					b24cd00a39 | ||
| 
						 | 
					0273860018 | ||
| 
						 | 
					82c089cde4 | ||
| 
						 | 
					997707a45b | ||
| 
						 | 
					9d7985c1e1 | ||
| 
						 | 
					8b1ec827e0 | ||
| 
						 | 
					153525f23d | ||
| 
						 | 
					3101dc94a7 | ||
| 
						 | 
					e6a84fd26b | ||
| 
						 | 
					440467ea3e | ||
| 
						 | 
					98376de9ad | ||
| 
						 | 
					e61e355251 | ||
| 
						 | 
					c898c8a99e | ||
| 
						 | 
					8c9062857c | ||
| 
						 | 
					77ed4ddc05 | ||
| 
						 | 
					82f392fada | ||
| 
						 | 
					2f0c923c29 | ||
| 
						 | 
					8291a63d5f | ||
| 
						 | 
					4c947ad553 | ||
| 
						 | 
					6120dae61a | ||
| 
						 | 
					1d03793f22 | ||
| 
						 | 
					4f5f191cd6 | ||
| 
						 | 
					21abf4e9fc | ||
| 
						 | 
					144d6b70d9 | ||
| 
						 | 
					b769f22ca0 | ||
| 
						 | 
					7019d396d0 | ||
| 
						 | 
					f4447fd9cd | ||
| 
						 | 
					36396b3d62 | ||
| 
						 | 
					d1dbf8c21f | ||
| 
						 | 
					1bde0fed6f | ||
| 
						 | 
					7ab2358bba | ||
| 
						 | 
					99547181f1 | ||
| 
						 | 
					2bf784535c | ||
| 
						 | 
					57f434c199 | ||
| 
						 | 
					87afa9140e | ||
| 
						 | 
					d19f26887d | ||
| 
						 | 
					6cb95b4fc5 | ||
| 
						 | 
					d979a822ac | ||
| 
						 | 
					fccdce65b9 | ||
| 
						 | 
					99a35266e1 | ||
| 
						 | 
					51bcaea60c | ||
| 
						 | 
					e00339ef0a | ||
| 
						 | 
					53cd125712 | ||
| 
						 | 
					04693b067c | ||
| 
						 | 
					cd7876a746 | ||
| 
						 | 
					ed5ff49ef5 | ||
| 
						 | 
					8d502a0b03 | ||
| 
						 | 
					5ea232310f | ||
| 
						 | 
					09309aa74f | ||
| 
						 | 
					b5357860b9 | ||
| 
						 | 
					dd17459687 | ||
| 
						 | 
					cd90118a0f | ||
| 
						 | 
					25776de59d | ||
| 
						 | 
					600bdc9af7 | ||
| 
						 | 
					0c9be2b09e | ||
| 
						 | 
					df8a5cbe6d | ||
| 
						 | 
					9ce68c38ae | ||
| 
						 | 
					40954d6a2a | ||
| 
						 | 
					ac444a3f34 | ||
| 
						 | 
					b8abeced6d | ||
| 
						 | 
					aeff59addc | ||
| 
						 | 
					7ab6023a0c | ||
| 
						 | 
					97cdfea9e9 | ||
| 
						 | 
					aff69dbc34 | ||
| 
						 | 
					6381e4e1b0 | ||
| 
						 | 
					c8e595d9aa | ||
| 
						 | 
					8c88fd4261 | ||
| 
						 | 
					a86a6367b5 | ||
| 
						 | 
					905ed1f87b | ||
| 
						 | 
					8de6caf6ff | ||
| 
						 | 
					327c19a222 | ||
| 
						 | 
					40d3f5f7f6 | ||
| 
						 | 
					64d5712d1d | ||
| 
						 | 
					3b20d862f0 | ||
| 
						 | 
					2e9ef2b0ef | ||
| 
						 | 
					70745286a5 | ||
| 
						 | 
					dcb7584060 | ||
| 
						 | 
					a477499724 | ||
| 
						 | 
					944d835eea | ||
| 
						 | 
					8f5039130c | ||
| 
						 | 
					ba165bb70a | ||
| 
						 | 
					474e2e8d2c | ||
| 
						 | 
					8b8eb787df | ||
| 
						 | 
					66bcdd36f3 | ||
| 
						 | 
					fcf8cafb5d | ||
| 
						 | 
					6bcf95042c | ||
| 
						 | 
					23f3ccd77a | ||
| 
						 | 
					f2437cb257 | ||
| 
						 | 
					abe04334c2 | ||
| 
						 | 
					8545707b54 | ||
| 
						 | 
					2b08758b2b | ||
| 
						 | 
					764b528891 | ||
| 
						 | 
					92754ace7a | ||
| 
						 | 
					1cc13b2799 | ||
| 
						 | 
					38f944bc34 | ||
| 
						 | 
					427175b9c0 | ||
| 
						 | 
					ebde955356 | ||
| 
						 | 
					7fd02e7f4c | ||
| 
						 | 
					d51f185dc7 | ||
| 
						 | 
					2390358c24 | ||
| 
						 | 
					2432a3b4d7 | ||
| 
						 | 
					9c3597c7e3 | ||
| 
						 | 
					fba6baaa9c | ||
| 
						 | 
					a246530953 | ||
| 
						 | 
					0ffded72a6 | ||
| 
						 | 
					acadfbabec | ||
| 
						 | 
					9001cc3fc2 | ||
| 
						 | 
					015b2b49f9 | ||
| 
						 | 
					92f928ca42 | ||
| 
						 | 
					6d087ca054 | ||
| 
						 | 
					c2d7e36c8f | ||
| 
						 | 
					4d6e78e641 | ||
| 
						 | 
					5761c8267b | ||
| 
						 | 
					a66a8c31b2 | ||
| 
						 | 
					19e4ee12e1 | ||
| 
						 | 
					4871572a33 | ||
| 
						 | 
					2e744a95e4 | ||
| 
						 | 
					ff87f1390d | ||
| 
						 | 
					76ca30c26d | ||
| 
						 | 
					7c2685cb34 | ||
| 
						 | 
					8cf25a2d70 | ||
| 
						 | 
					8d69dd30f3 | ||
| 
						 | 
					ae8068b86f | ||
| 
						 | 
					baeb0ee89f | ||
| 
						 | 
					c07993bb0a | ||
| 
						 | 
					7680cbf9c3 | ||
| 
						 | 
					4920fe6701 | ||
| 
						 | 
					55fe0176bd | ||
| 
						 | 
					99fcbb55d1 | ||
| 
						 | 
					6f78ecd12b | ||
| 
						 | 
					ced644b103 | ||
| 
						 | 
					be1cb2a551 | ||
| 
						 | 
					b4159295f6 | ||
| 
						 | 
					d0a93409e6 | ||
| 
						 | 
					4c3669f210 | ||
| 
						 | 
					eeb646868b | ||
| 
						 | 
					3d789732a2 | ||
| 
						 | 
					d2a7d39749 | ||
| 
						 | 
					9521718120 | ||
| 
						 | 
					28909e33ca | ||
| 
						 | 
					79632b1d34 | ||
| 
						 | 
					cf6d03e35c | ||
| 
						 | 
					4a4b31a15c | ||
| 
						 | 
					f3d9aec8fc | ||
| 
						 | 
					7ad64ff16b | ||
| 
						 | 
					6153ada33b | ||
| 
						 | 
					be48c950b4 | ||
| 
						 | 
					0487b8c178 | ||
| 
						 | 
					5740015f56 | ||
| 
						 | 
					c84004bfa3 | ||
| 
						 | 
					c746a3711f | ||
| 
						 | 
					aa7774a9a6 | ||
| 
						 | 
					a836120945 | ||
| 
						 | 
					7d60df9075 | ||
| 
						 | 
					f2b8b26bc4 | 
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -18,8 +18,14 @@ DerivedData
 | 
			
		||||
*.xcuserstate
 | 
			
		||||
.DS_Store
 | 
			
		||||
 | 
			
		||||
# Exclude system ROMs
 | 
			
		||||
# Exclude system ROMs and unit test ROMs
 | 
			
		||||
ROMImages/*
 | 
			
		||||
OSBindings/Mac/Clock SignalTests/Atari ROMs
 | 
			
		||||
OSBindings/Mac/Clock SignalTests/MSX ROMs
 | 
			
		||||
 | 
			
		||||
# Exclude intermediate build products
 | 
			
		||||
*.o
 | 
			
		||||
.sconsign.dblite
 | 
			
		||||
 | 
			
		||||
# CocoaPods
 | 
			
		||||
#
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 */
 | 
			
		||||
							
								
								
									
										115
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
//
 | 
			
		||||
//  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(const Cycles cycles) {
 | 
			
		||||
	perform_parallel([=](::CRTMachine::Machine *machine) {
 | 
			
		||||
		if(machine->get_confidence() >= 0.01f) machine->run_for(cycles);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if(delegate_) delegate_->multi_crt_did_run_machines();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double MultiCRTMachine::get_clock_rate() {
 | 
			
		||||
	// TODO: something smarter than this? Not all clock rates will necessarily be the same.
 | 
			
		||||
	std::lock_guard<std::mutex> machines_lock(machines_mutex_);
 | 
			
		||||
	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
 | 
			
		||||
	return crt_machine ? crt_machine->get_clock_rate() : 0.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MultiCRTMachine::get_clock_is_unlimited() {
 | 
			
		||||
	std::lock_guard<std::mutex> machines_lock(machines_mutex_);
 | 
			
		||||
	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
 | 
			
		||||
	return crt_machine ? crt_machine->get_clock_is_unlimited() : false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiCRTMachine::did_change_machine_order() {
 | 
			
		||||
	if(speaker_) {
 | 
			
		||||
		speaker_->set_new_front_machine(machines_.front().get());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) {
 | 
			
		||||
	// TODO: 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) {
 | 
			
		||||
	// TODO: consider passing along.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) {
 | 
			
		||||
	// TODO: consider passing along.
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
//
 | 
			
		||||
//  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 CRTMachine::Machine::Delegate {
 | 
			
		||||
	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(const Cycles cycles) override;
 | 
			
		||||
		double get_clock_rate() override;
 | 
			
		||||
		bool get_clock_is_unlimited() override;
 | 
			
		||||
		void set_delegate(::CRTMachine::Machine::Delegate *delegate) override;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		// CRTMachine::Machine::Delegate
 | 
			
		||||
		void machine_did_change_clock_rate(Machine *machine) override;
 | 
			
		||||
		void machine_did_change_clock_is_unlimited(Machine *machine) 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,22 @@
 | 
			
		||||
//
 | 
			
		||||
//  MultiJoystickMachine.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 09/02/2018.
 | 
			
		||||
//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "MultiJoystickMachine.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
			
		||||
	for(const auto &machine: machines) {
 | 
			
		||||
		JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
 | 
			
		||||
		if(joystick_machine) machines_.push_back(joystick_machine);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
 | 
			
		||||
	return joysticks_;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
//
 | 
			
		||||
//  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<JoystickMachine::Machine *> machines_;
 | 
			
		||||
		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,60 @@
 | 
			
		||||
//
 | 
			
		||||
//  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) {
 | 
			
		||||
	std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
 | 
			
		||||
	if(delegate_ && speaker == front_speaker_) {
 | 
			
		||||
		delegate_->speaker_did_complete_samples(this, buffer);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,58 @@
 | 
			
		||||
//
 | 
			
		||||
//  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);
 | 
			
		||||
		void set_output_rate(float cycles_per_second, int buffer_size);
 | 
			
		||||
		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate);
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer);
 | 
			
		||||
		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 */
 | 
			
		||||
							
								
								
									
										104
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
//
 | 
			
		||||
//  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_;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiMachine::multi_crt_did_run_machines() {
 | 
			
		||||
	std::lock_guard<std::mutex> machines_lock(machines_mutex_);
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		CRTMachine::Machine *crt = machine->crt_machine();
 | 
			
		||||
		printf("%0.2f ", crt->get_confidence());
 | 
			
		||||
		crt->print_type();
 | 
			
		||||
		printf("; ");
 | 
			
		||||
	}
 | 
			
		||||
	printf("\n");
 | 
			
		||||
 | 
			
		||||
	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(
 | 
			
		||||
		(machines_.front()->crt_machine()->get_confidence() > 0.9f) ||
 | 
			
		||||
		(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence())
 | 
			
		||||
	) {
 | 
			
		||||
		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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
//
 | 
			
		||||
//  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:
 | 
			
		||||
		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 */
 | 
			
		||||
							
								
								
									
										105
									
								
								Analyser/Static/Acorn/Disk.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								Analyser/Static/Acorn/Disk.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
//
 | 
			
		||||
//  Disk.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 18/09/2016.
 | 
			
		||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../NumberTheory/CRC.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Acorn;
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | 
			
		||||
	std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(false, disk);
 | 
			
		||||
 | 
			
		||||
	Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
 | 
			
		||||
	Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
 | 
			
		||||
 | 
			
		||||
	if(!names || !details) return nullptr;
 | 
			
		||||
	if(names->samples.empty() || details->samples.empty()) return nullptr;
 | 
			
		||||
	if(names->samples[0].size() != 256 || details->samples[0].size() != 256) return nullptr;
 | 
			
		||||
 | 
			
		||||
	uint8_t final_file_offset = details->samples[0][5];
 | 
			
		||||
	if(final_file_offset&7) return nullptr;
 | 
			
		||||
	if(final_file_offset < 8) return nullptr;
 | 
			
		||||
 | 
			
		||||
	char disk_name[13];
 | 
			
		||||
	snprintf(disk_name, 13, "%.8s%.4s", &names->samples[0][0], &details->samples[0][0]);
 | 
			
		||||
	catalogue->name = disk_name;
 | 
			
		||||
 | 
			
		||||
	switch((details->samples[0][6] >> 4)&3) {
 | 
			
		||||
		case 0: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
			
		||||
		case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
			
		||||
		case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
			
		||||
		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
 | 
			
		||||
		File new_file;
 | 
			
		||||
		char name[10];
 | 
			
		||||
		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
 | 
			
		||||
		new_file.name = name;
 | 
			
		||||
		new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
 | 
			
		||||
		new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
 | 
			
		||||
		new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
 | 
			
		||||
 | 
			
		||||
		long data_length = static_cast<long>(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
 | 
			
		||||
		int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
 | 
			
		||||
		new_file.data.reserve(static_cast<std::size_t>(data_length));
 | 
			
		||||
 | 
			
		||||
		if(start_sector < 2) continue;
 | 
			
		||||
		while(data_length > 0) {
 | 
			
		||||
			uint8_t sector = static_cast<uint8_t>(start_sector % 10);
 | 
			
		||||
			uint8_t track = static_cast<uint8_t>(start_sector / 10);
 | 
			
		||||
			start_sector++;
 | 
			
		||||
 | 
			
		||||
			Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
 | 
			
		||||
			if(!next_sector) break;
 | 
			
		||||
 | 
			
		||||
			long length_from_sector = std::min(data_length, 256l);
 | 
			
		||||
			new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
 | 
			
		||||
			data_length -= length_from_sector;
 | 
			
		||||
		}
 | 
			
		||||
		if(!data_length) catalogue->files.push_back(new_file);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return catalogue;
 | 
			
		||||
}
 | 
			
		||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
	std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
			
		||||
 | 
			
		||||
	Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
 | 
			
		||||
	if(!free_space_map_second_half) return nullptr;
 | 
			
		||||
 | 
			
		||||
	std::vector<uint8_t> root_directory;
 | 
			
		||||
	root_directory.reserve(5 * 256);
 | 
			
		||||
	for(uint8_t c = 2; c < 7; c++) {
 | 
			
		||||
		Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
 | 
			
		||||
		if(!sector) return nullptr;
 | 
			
		||||
		root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Quick sanity checks.
 | 
			
		||||
	if(root_directory[0x4cb]) return nullptr;
 | 
			
		||||
	if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
 | 
			
		||||
	if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
 | 
			
		||||
 | 
			
		||||
	switch(free_space_map_second_half->samples[0][0xfd]) {
 | 
			
		||||
		default: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
			
		||||
		case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
			
		||||
		case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
			
		||||
		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return catalogue;
 | 
			
		||||
}
 | 
			
		||||
@@ -10,14 +10,16 @@
 | 
			
		||||
#define StaticAnalyser_Acorn_Disk_hpp
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Disk.hpp"
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
/// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
 | 
			
		||||
struct Catalogue {
 | 
			
		||||
	std::string name;
 | 
			
		||||
	std::list<File> files;
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	enum class BootOption {
 | 
			
		||||
		None,
 | 
			
		||||
		LoadBOOT,
 | 
			
		||||
@@ -29,6 +31,7 @@ struct Catalogue {
 | 
			
		||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -6,15 +6,15 @@
 | 
			
		||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef File_hpp
 | 
			
		||||
#define File_hpp
 | 
			
		||||
#ifndef StaticAnalyser_Acorn_File_hpp
 | 
			
		||||
#define StaticAnalyser_Acorn_File_hpp
 | 
			
		||||
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
struct File {
 | 
			
		||||
@@ -38,9 +38,10 @@ struct File {
 | 
			
		||||
		std::vector<uint8_t> data;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::list<Chunk> chunks;
 | 
			
		||||
	std::vector<Chunk> chunks;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										125
									
								
								Analyser/Static/Acorn/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								Analyser/Static/Acorn/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
//
 | 
			
		||||
//  AcornAnalyser.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 29/08/2016.
 | 
			
		||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Acorn;
 | 
			
		||||
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
		AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
		const auto &segments = cartridge->get_segments();
 | 
			
		||||
 | 
			
		||||
		// only one mapped item is allowed
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// which must be 8 or 16 kb in size
 | 
			
		||||
		const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
			
		||||
		if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
 | 
			
		||||
 | 
			
		||||
		// is a copyright string present?
 | 
			
		||||
		uint8_t copyright_offset = segment.data[7];
 | 
			
		||||
		if(
 | 
			
		||||
			segment.data[copyright_offset] != 0x00 ||
 | 
			
		||||
			segment.data[copyright_offset+1] != 0x28 ||
 | 
			
		||||
			segment.data[copyright_offset+2] != 0x43 ||
 | 
			
		||||
			segment.data[copyright_offset+3] != 0x29
 | 
			
		||||
		) continue;
 | 
			
		||||
 | 
			
		||||
		// is the language entry point valid?
 | 
			
		||||
		if(!(
 | 
			
		||||
			(segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) ||
 | 
			
		||||
			(segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0)
 | 
			
		||||
			)) continue;
 | 
			
		||||
 | 
			
		||||
		// is the service entry point valid?
 | 
			
		||||
		if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue;
 | 
			
		||||
 | 
			
		||||
		// probability of a random binary blob that isn't an Acorn ROM proceeding to here:
 | 
			
		||||
		//		1/(2^32) *
 | 
			
		||||
		//		( ((2^24)-1)/(2^24)*(1/4)		+		1/(2^24)	) *
 | 
			
		||||
		//		1/4
 | 
			
		||||
		//	= something very improbable — around 1/16th of 1 in 2^32, but not exactly.
 | 
			
		||||
		acorn_cartridges.push_back(cartridge);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return acorn_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
 | 
			
		||||
	std::unique_ptr<Target> target(new Target);
 | 
			
		||||
	target->machine = Machine::Electron;
 | 
			
		||||
	target->confidence = 1.0; // TODO: a proper estimation
 | 
			
		||||
	target->acorn.has_dfs = false;
 | 
			
		||||
	target->acorn.has_adfs = false;
 | 
			
		||||
	target->acorn.should_shift_restart = false;
 | 
			
		||||
 | 
			
		||||
	// strip out inappropriate cartridges
 | 
			
		||||
	target->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
			
		||||
 | 
			
		||||
	// if there are any tapes, attempt to get data from the first
 | 
			
		||||
	if(media.tapes.size() > 0) {
 | 
			
		||||
		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
 | 
			
		||||
		std::vector<File> files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
 | 
			
		||||
		// continue if there are any files
 | 
			
		||||
		if(files.size()) {
 | 
			
		||||
			bool is_basic = true;
 | 
			
		||||
 | 
			
		||||
			// protected files are always for *RUNning only
 | 
			
		||||
			if(files.front().is_protected) is_basic = false;
 | 
			
		||||
 | 
			
		||||
			// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
 | 
			
		||||
			// so that's also justification to *RUN
 | 
			
		||||
			std::size_t pointer = 0;
 | 
			
		||||
			uint8_t *data = &files.front().data[0];
 | 
			
		||||
			std::size_t data_size = files.front().data.size();
 | 
			
		||||
			while(1) {
 | 
			
		||||
				if(pointer >= data_size-1 || data[pointer] != 13) {
 | 
			
		||||
					is_basic = false;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
				if((data[pointer+1]&0x7f) == 0x7f) break;
 | 
			
		||||
				pointer += data[pointer+3];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Inspect first file. If it's protected or doesn't look like BASIC
 | 
			
		||||
			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | 
			
		||||
			target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
 | 
			
		||||
 | 
			
		||||
			target->media.tapes = media.tapes;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(media.disks.size() > 0) {
 | 
			
		||||
		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
 | 
			
		||||
		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
 | 
			
		||||
		dfs_catalogue = GetDFSCatalogue(disk);
 | 
			
		||||
		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
 | 
			
		||||
		if(dfs_catalogue || adfs_catalogue) {
 | 
			
		||||
			target->media.disks = media.disks;
 | 
			
		||||
			target->acorn.has_dfs = !!dfs_catalogue;
 | 
			
		||||
			target->acorn.has_adfs = !!adfs_catalogue;
 | 
			
		||||
 | 
			
		||||
			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
			
		||||
			if(bootOption != Catalogue::BootOption::None)
 | 
			
		||||
				target->acorn.should_shift_restart = true;
 | 
			
		||||
			else
 | 
			
		||||
				target->loading_command = "*CAT\n";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
 | 
			
		||||
		destination.push_back(std::move(target));
 | 
			
		||||
}
 | 
			
		||||
@@ -11,16 +11,13 @@
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
void AddTargets(
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
 | 
			
		||||
	std::list<Target> &destination
 | 
			
		||||
);
 | 
			
		||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -9,13 +9,13 @@
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <deque>
 | 
			
		||||
#include "../../NumberTheory/CRC.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Parsers/Acorn.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace StaticAnalyser::Acorn;
 | 
			
		||||
#include "../../../NumberTheory/CRC.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
 | 
			
		||||
 | 
			
		||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser)
 | 
			
		||||
{
 | 
			
		||||
using namespace Analyser::Static::Acorn;
 | 
			
		||||
 | 
			
		||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
 | 
			
		||||
	std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
 | 
			
		||||
	int shift_register = 0;
 | 
			
		||||
 | 
			
		||||
@@ -23,14 +23,12 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
 | 
			
		||||
#define shift()	shift_register = (shift_register >> 1) |  (parser.get_next_bit(tape) << 9)
 | 
			
		||||
 | 
			
		||||
	// find next area of high tone
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x3ff))
 | 
			
		||||
	{
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x3ff)) {
 | 
			
		||||
		shift();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// find next 0x2a (swallowing stop bit)
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x254))
 | 
			
		||||
	{
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x254)) {
 | 
			
		||||
		shift();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -41,9 +39,8 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
 | 
			
		||||
 | 
			
		||||
	// read out name
 | 
			
		||||
	char name[11];
 | 
			
		||||
	int name_ptr = 0;
 | 
			
		||||
	while(!tape->is_at_end() && name_ptr < sizeof(name))
 | 
			
		||||
	{
 | 
			
		||||
	std::size_t name_ptr = 0;
 | 
			
		||||
	while(!tape->is_at_end() && name_ptr < sizeof(name)) {
 | 
			
		||||
		name[name_ptr] = (char)parser.get_next_byte(tape);
 | 
			
		||||
		if(!name[name_ptr]) break;
 | 
			
		||||
		name_ptr++;
 | 
			
		||||
@@ -54,43 +51,39 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
 | 
			
		||||
	// addresses
 | 
			
		||||
	new_chunk->load_address = (uint32_t)parser.get_next_word(tape);
 | 
			
		||||
	new_chunk->execution_address = (uint32_t)parser.get_next_word(tape);
 | 
			
		||||
	new_chunk->block_number = (uint16_t)parser.get_next_short(tape);
 | 
			
		||||
	new_chunk->block_length = (uint16_t)parser.get_next_short(tape);
 | 
			
		||||
	new_chunk->block_flag = (uint8_t)parser.get_next_byte(tape);
 | 
			
		||||
	new_chunk->block_number = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
			
		||||
	new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
			
		||||
	new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape));
 | 
			
		||||
	new_chunk->next_address = (uint32_t)parser.get_next_word(tape);
 | 
			
		||||
 | 
			
		||||
	uint16_t calculated_header_crc = parser.get_crc();
 | 
			
		||||
	uint16_t stored_header_crc = (uint16_t)parser.get_next_short(tape);
 | 
			
		||||
	stored_header_crc = (uint16_t)((stored_header_crc >> 8) | (stored_header_crc << 8));
 | 
			
		||||
	uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
			
		||||
	stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8));
 | 
			
		||||
	new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
 | 
			
		||||
 | 
			
		||||
	if(!new_chunk->header_crc_matched) return nullptr;
 | 
			
		||||
 | 
			
		||||
	parser.reset_crc();
 | 
			
		||||
	new_chunk->data.reserve(new_chunk->block_length);
 | 
			
		||||
	for(int c = 0; c < new_chunk->block_length; c++)
 | 
			
		||||
	{
 | 
			
		||||
		new_chunk->data.push_back((uint8_t)parser.get_next_byte(tape));
 | 
			
		||||
	for(int c = 0; c < new_chunk->block_length; c++) {
 | 
			
		||||
		new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(new_chunk->block_length && !(new_chunk->block_flag&0x40))
 | 
			
		||||
	{
 | 
			
		||||
	if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
 | 
			
		||||
		uint16_t calculated_data_crc = parser.get_crc();
 | 
			
		||||
		uint16_t stored_data_crc = (uint16_t)parser.get_next_short(tape);
 | 
			
		||||
		stored_data_crc = (uint16_t)((stored_data_crc >> 8) | (stored_data_crc << 8));
 | 
			
		||||
		uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
			
		||||
		stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8));
 | 
			
		||||
		new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
	} else {
 | 
			
		||||
		new_chunk->data_crc_matched = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return parser.get_error_flag() ? nullptr : std::move(new_chunk);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
 | 
			
		||||
{
 | 
			
		||||
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
 | 
			
		||||
	// find next chunk with a block number of 0
 | 
			
		||||
	while(chunks.size() && chunks.front().block_number)
 | 
			
		||||
	{
 | 
			
		||||
	while(chunks.size() && chunks.front().block_number) {
 | 
			
		||||
		chunks.pop_front();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -101,8 +94,7 @@ std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
 | 
			
		||||
 | 
			
		||||
	uint16_t block_number = 0;
 | 
			
		||||
 | 
			
		||||
	while(chunks.size())
 | 
			
		||||
	{
 | 
			
		||||
	while(chunks.size()) {
 | 
			
		||||
		if(chunks.front().block_number != block_number) return nullptr;
 | 
			
		||||
 | 
			
		||||
		bool was_last = chunks.front().block_flag & 0x80;
 | 
			
		||||
@@ -120,37 +112,31 @@ std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
 | 
			
		||||
	file->is_protected = !!(file->chunks.back().block_flag & 0x01);	// I think the last flags are the ones that count; TODO: check.
 | 
			
		||||
 | 
			
		||||
	// copy all data into a single big block
 | 
			
		||||
	for(File::Chunk chunk : file->chunks)
 | 
			
		||||
	{
 | 
			
		||||
	for(File::Chunk chunk : file->chunks) {
 | 
			
		||||
		file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return file;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
 | 
			
		||||
{
 | 
			
		||||
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	Storage::Tape::Acorn::Parser parser;
 | 
			
		||||
 | 
			
		||||
	// populate chunk list
 | 
			
		||||
	std::deque<File::Chunk> chunk_list;
 | 
			
		||||
	while(!tape->is_at_end())
 | 
			
		||||
	{
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
 | 
			
		||||
		if(chunk)
 | 
			
		||||
		{
 | 
			
		||||
		if(chunk) {
 | 
			
		||||
			chunk_list.push_back(*chunk);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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);
 | 
			
		||||
		if(next_file)
 | 
			
		||||
		{
 | 
			
		||||
		if(next_file) {
 | 
			
		||||
			file_list.push_back(*next_file);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -12,13 +12,15 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										228
									
								
								Analyser/Static/AmstradCPC/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								Analyser/Static/AmstradCPC/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
			
		||||
//
 | 
			
		||||
//  AmstradCPC.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 30/07/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
 | 
			
		||||
static bool strcmp_insensitive(const char *a, const char *b) {
 | 
			
		||||
	if(std::strlen(a) != std::strlen(b)) return false;
 | 
			
		||||
	while(*a) {
 | 
			
		||||
		if(std::tolower(*a) != std::tolower(*b)) return false;
 | 
			
		||||
		a++;
 | 
			
		||||
		b++;
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool is_implied_extension(const std::string &extension) {
 | 
			
		||||
	return
 | 
			
		||||
		extension == "   " ||
 | 
			
		||||
		strcmp_insensitive(extension.c_str(), "BAS") ||
 | 
			
		||||
		strcmp_insensitive(extension.c_str(), "BIN");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void right_trim(std::string &string) {
 | 
			
		||||
	string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
 | 
			
		||||
        return !std::isspace(ch);
 | 
			
		||||
    }).base(), string.end());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
 | 
			
		||||
	// Trim spaces from the name.
 | 
			
		||||
	std::string name = file.name;
 | 
			
		||||
	right_trim(name);
 | 
			
		||||
 | 
			
		||||
	// Form the basic command.
 | 
			
		||||
	std::string command = "run\"" + name;
 | 
			
		||||
 | 
			
		||||
	// Consider whether the extension is required.
 | 
			
		||||
	if(!is_implied_extension(file.type)) {
 | 
			
		||||
		std::string type = file.type;
 | 
			
		||||
		right_trim(type);
 | 
			
		||||
		command += "." + type;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add a newline and return.
 | 
			
		||||
	return command + "\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void InspectCatalogue(
 | 
			
		||||
	const Storage::Disk::CPM::Catalogue &catalogue,
 | 
			
		||||
	const std::unique_ptr<Analyser::Static::Target> &target) {
 | 
			
		||||
 | 
			
		||||
	std::vector<const Storage::Disk::CPM::File *> candidate_files;
 | 
			
		||||
	candidate_files.reserve(catalogue.files.size());
 | 
			
		||||
	for(auto &file : catalogue.files) {
 | 
			
		||||
		candidate_files.push_back(&file);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Remove all files with untypable characters.
 | 
			
		||||
	candidate_files.erase(
 | 
			
		||||
		std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
 | 
			
		||||
			for(auto c : file->name + file->type) {
 | 
			
		||||
				if(c < 32) return true;
 | 
			
		||||
			}
 | 
			
		||||
			return false;
 | 
			
		||||
		}),
 | 
			
		||||
		candidate_files.end());
 | 
			
		||||
 | 
			
		||||
	// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files.
 | 
			
		||||
	bool are_all_system = true;
 | 
			
		||||
	for(auto file : candidate_files) {
 | 
			
		||||
		if(!file->system) {
 | 
			
		||||
			are_all_system = false;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!are_all_system) {
 | 
			
		||||
		candidate_files.erase(
 | 
			
		||||
			std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
 | 
			
		||||
				return file->system;
 | 
			
		||||
			}),
 | 
			
		||||
			candidate_files.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If there's just one file, run that.
 | 
			
		||||
	if(candidate_files.size() == 1) {
 | 
			
		||||
		target->loading_command = RunCommandFor(*candidate_files[0]);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
 | 
			
		||||
	// that AMSDOS allows to be omitted, pick that one.
 | 
			
		||||
	int basic_files = 0;
 | 
			
		||||
	int implicit_suffixed_files = 0;
 | 
			
		||||
 | 
			
		||||
	std::size_t last_basic_file = 0;
 | 
			
		||||
	std::size_t last_implicit_suffixed_file = 0;
 | 
			
		||||
 | 
			
		||||
	for(std::size_t c = 0; c < candidate_files.size(); c++) {
 | 
			
		||||
		// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
 | 
			
		||||
		if(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ")
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		// Check for whether this is [potentially] BASIC.
 | 
			
		||||
		if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
 | 
			
		||||
			basic_files++;
 | 
			
		||||
			last_basic_file = c;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check suffix for emptiness.
 | 
			
		||||
		if(is_implied_extension(candidate_files[c]->type)) {
 | 
			
		||||
			implicit_suffixed_files++;
 | 
			
		||||
			last_implicit_suffixed_file = c;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if(basic_files == 1 || implicit_suffixed_files == 1) {
 | 
			
		||||
		std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
 | 
			
		||||
		target->loading_command = RunCommandFor(*candidate_files[selected_file]);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// One more guess: if only one remaining candidate file has a different name than the others,
 | 
			
		||||
	// assume it is intended to stand out.
 | 
			
		||||
	std::map<std::string, int> name_counts;
 | 
			
		||||
	std::map<std::string, std::size_t> indices_by_name;
 | 
			
		||||
	std::size_t index = 0;
 | 
			
		||||
	for(auto file : candidate_files) {
 | 
			
		||||
		name_counts[file->name]++;
 | 
			
		||||
		indices_by_name[file->name] = index;
 | 
			
		||||
		index++;
 | 
			
		||||
	}
 | 
			
		||||
	if(name_counts.size() == 2) {
 | 
			
		||||
		for(auto &pair : name_counts) {
 | 
			
		||||
			if(pair.second == 1) {
 | 
			
		||||
				target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Desperation.
 | 
			
		||||
	target->loading_command = "cat\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::Target> &target) {
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
			
		||||
	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
 | 
			
		||||
	if(boot_sector != nullptr && !boot_sector->samples.empty()) {
 | 
			
		||||
		// Check that the first 64 bytes of the sector aren't identical; if they are then probably
 | 
			
		||||
		// this disk was formatted and the filler byte never replaced.
 | 
			
		||||
		bool matched = true;
 | 
			
		||||
		for(std::size_t c = 1; c < 64; c++) {
 | 
			
		||||
			if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) {
 | 
			
		||||
				matched = false;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// This is a system disk, then launch it as though it were CP/M.
 | 
			
		||||
		if(!matched) {
 | 
			
		||||
			target->loading_command = "|cpm\n";
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
 | 
			
		||||
	std::unique_ptr<Target> target(new Target);
 | 
			
		||||
	target->machine = Machine::AmstradCPC;
 | 
			
		||||
	target->confidence = 1.0;
 | 
			
		||||
	target->media.disks = media.disks;
 | 
			
		||||
	target->media.tapes = media.tapes;
 | 
			
		||||
	target->media.cartridges = media.cartridges;
 | 
			
		||||
 | 
			
		||||
	target->amstradcpc.model = AmstradCPCModel::CPC6128;
 | 
			
		||||
 | 
			
		||||
	if(!target->media.tapes.empty()) {
 | 
			
		||||
		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | 
			
		||||
		// enter and responding to the follow-on prompt to press a key, so just type for
 | 
			
		||||
		// a while. Yuck!
 | 
			
		||||
		target->loading_command = "|tape\nrun\"\n1234567890";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!target->media.disks.empty()) {
 | 
			
		||||
		Storage::Disk::CPM::ParameterBlock data_format;
 | 
			
		||||
		data_format.sectors_per_track = 9;
 | 
			
		||||
		data_format.tracks = 40;
 | 
			
		||||
		data_format.block_size = 1024;
 | 
			
		||||
		data_format.first_sector = 0xc1;
 | 
			
		||||
		data_format.catalogue_allocation_bitmap = 0xc000;
 | 
			
		||||
		data_format.reserved_tracks = 0;
 | 
			
		||||
 | 
			
		||||
		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;
 | 
			
		||||
				system_format.sectors_per_track = 9;
 | 
			
		||||
				system_format.tracks = 40;
 | 
			
		||||
				system_format.block_size = 1024;
 | 
			
		||||
				system_format.first_sector = 0x41;
 | 
			
		||||
				system_format.catalogue_allocation_bitmap = 0xc000;
 | 
			
		||||
				system_format.reserved_tracks = 2;
 | 
			
		||||
 | 
			
		||||
				std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), system_format);
 | 
			
		||||
				if(system_catalogue) {
 | 
			
		||||
					InspectCatalogue(*system_catalogue, target);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	destination.push_back(std::move(target));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								Analyser/Static/AmstradCPC/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Analyser/Static/AmstradCPC/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 30/07/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AmstradCPC {
 | 
			
		||||
 | 
			
		||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_AmstradCPC_StaticAnalyser_hpp */
 | 
			
		||||
							
								
								
									
										201
									
								
								Analyser/Static/Atari/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								Analyser/Static/Atari/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 15/09/2016.
 | 
			
		||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Atari;
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
			
		||||
	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | 
			
		||||
	uint16_t entry_address, break_address;
 | 
			
		||||
 | 
			
		||||
	entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
 | 
			
		||||
	break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
 | 
			
		||||
 | 
			
		||||
	// a CommaVid start address needs to be outside of its RAM
 | 
			
		||||
	if(entry_address < 0x1800 || break_address < 0x1800) return;
 | 
			
		||||
 | 
			
		||||
	std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
 | 
			
		||||
		address &= 0x1fff;
 | 
			
		||||
		return static_cast<std::size_t>(address - 0x1800);
 | 
			
		||||
	};
 | 
			
		||||
	Analyser::Static::MOS6502::Disassembly high_location_disassembly =
 | 
			
		||||
		Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
 | 
			
		||||
 | 
			
		||||
	// assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 
			
		||||
	// large amounts of memory
 | 
			
		||||
	bool has_wide_area_store = false;
 | 
			
		||||
	for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
 | 
			
		||||
		if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
 | 
			
		||||
 | 
			
		||||
			if(has_wide_area_store) break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
 | 
			
		||||
	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | 
			
		||||
	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | 
			
		||||
	// attempts to modify itself but it probably doesn't
 | 
			
		||||
	if(has_wide_area_store) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CommaVid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | 
			
		||||
	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | 
			
		||||
	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | 
			
		||||
	if(
 | 
			
		||||
		segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
 | 
			
		||||
		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
 | 
			
		||||
		segment.data[0] == 0x78
 | 
			
		||||
	) {
 | 
			
		||||
		target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make an assumption that this is the Atari paging model
 | 
			
		||||
	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari8k;
 | 
			
		||||
 | 
			
		||||
	std::set<uint16_t> internal_accesses;
 | 
			
		||||
	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
			
		||||
	internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
 | 
			
		||||
	internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
 | 
			
		||||
 | 
			
		||||
	int atari_access_count = 0;
 | 
			
		||||
	int parker_access_count = 0;
 | 
			
		||||
	int tigervision_access_count = 0;
 | 
			
		||||
	for(uint16_t address : internal_accesses) {
 | 
			
		||||
		uint16_t masked_address = address & 0x1fff;
 | 
			
		||||
		atari_access_count += masked_address >= 0x1ff8 && masked_address < 0x1ffa;
 | 
			
		||||
		parker_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ff8;
 | 
			
		||||
	}
 | 
			
		||||
	for(uint16_t address: disassembly.external_stores) {
 | 
			
		||||
		uint16_t masked_address = address & 0x1fff;
 | 
			
		||||
		tigervision_access_count += masked_address == 0x3f;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros;
 | 
			
		||||
	else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	// make an assumption that this is the Atari paging model
 | 
			
		||||
	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k;
 | 
			
		||||
 | 
			
		||||
	std::set<uint16_t> internal_accesses;
 | 
			
		||||
	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
			
		||||
	internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
 | 
			
		||||
	internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
 | 
			
		||||
 | 
			
		||||
	int atari_access_count = 0;
 | 
			
		||||
	int mnetwork_access_count = 0;
 | 
			
		||||
	for(uint16_t address : internal_accesses) {
 | 
			
		||||
		uint16_t masked_address = address & 0x1fff;
 | 
			
		||||
		atari_access_count += masked_address >= 0x1ff6 && masked_address < 0x1ffa;
 | 
			
		||||
		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(mnetwork_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::MNetwork;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor64kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	// make an assumption that this is a Tigervision if there is a write to 3F
 | 
			
		||||
	target.atari.paging_model =
 | 
			
		||||
		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
 | 
			
		||||
			Analyser::Static::Atari2600PagingModel::Tigervision : Analyser::Static::Atari2600PagingModel::MegaBoy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingForCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
			
		||||
	if(segment.data.size() == 2048) {
 | 
			
		||||
		DeterminePagingFor2kCartridge(target, segment);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint16_t entry_address, break_address;
 | 
			
		||||
 | 
			
		||||
	entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
 | 
			
		||||
	break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
 | 
			
		||||
 | 
			
		||||
	std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
 | 
			
		||||
		if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
 | 
			
		||||
		return static_cast<std::size_t>(address & 0xfff);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
 | 
			
		||||
	Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
			
		||||
 | 
			
		||||
	switch(segment.data.size()) {
 | 
			
		||||
		case 8192:
 | 
			
		||||
			DeterminePagingFor8kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		case 10495:
 | 
			
		||||
			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2;
 | 
			
		||||
		break;
 | 
			
		||||
		case 12288:
 | 
			
		||||
			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus;
 | 
			
		||||
		break;
 | 
			
		||||
		case 16384:
 | 
			
		||||
			DeterminePagingFor16kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		case 32768:
 | 
			
		||||
			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k;
 | 
			
		||||
		break;
 | 
			
		||||
		case 65536:
 | 
			
		||||
			DeterminePagingFor64kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | 
			
		||||
	// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | 
			
		||||
	// next 128 bytes. So check for that.
 | 
			
		||||
	if(	target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus &&
 | 
			
		||||
		target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) {
 | 
			
		||||
		bool has_superchip = true;
 | 
			
		||||
		for(std::size_t address = 0; address < 128; address++) {
 | 
			
		||||
			if(segment.data[address] != segment.data[address+128]) {
 | 
			
		||||
				has_superchip = false;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		target.atari.uses_superchip = has_superchip;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check for a Tigervision or Tigervision-esque scheme
 | 
			
		||||
	if(target.atari.paging_model == Analyser::Static::Atari2600PagingModel::None && segment.data.size() > 4096) {
 | 
			
		||||
		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
 | 
			
		||||
		if(looks_like_tigervision) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
 | 
			
		||||
	// TODO: sanity checking; is this image really for an Atari 2600.
 | 
			
		||||
	std::unique_ptr<Target> target(new Target);
 | 
			
		||||
	target->machine = Machine::Atari2600;
 | 
			
		||||
	target->confidence = 1.0;
 | 
			
		||||
	target->media.cartridges = media.cartridges;
 | 
			
		||||
	target->atari.paging_model = Atari2600PagingModel::None;
 | 
			
		||||
	target->atari.uses_superchip = false;
 | 
			
		||||
 | 
			
		||||
	// try to figure out the paging scheme
 | 
			
		||||
	if(!media.cartridges.empty()) {
 | 
			
		||||
		const auto &segments = media.cartridges.front()->get_segments();
 | 
			
		||||
 | 
			
		||||
		if(segments.size() == 1) {
 | 
			
		||||
			const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
			
		||||
			DeterminePagingForCartridge(*target, segment);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	destination.push_back(std::move(target));
 | 
			
		||||
}
 | 
			
		||||
@@ -11,16 +11,13 @@
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Atari {
 | 
			
		||||
 | 
			
		||||
void AddTargets(
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
 | 
			
		||||
	std::list<Target> &destination
 | 
			
		||||
);
 | 
			
		||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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 = 0.5;
 | 
			
		||||
	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,28 +7,27 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskController.hpp"
 | 
			
		||||
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
			
		||||
#include "../../Storage/Data/Commodore.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
			
		||||
#include "../../../Storage/Data/Commodore.hpp"
 | 
			
		||||
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <array>
 | 
			
		||||
 | 
			
		||||
using namespace StaticAnalyser::Commodore;
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
			
		||||
	public:
 | 
			
		||||
		std::shared_ptr<Storage::Disk::Drive> drive;
 | 
			
		||||
 | 
			
		||||
		CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1)
 | 
			
		||||
		{
 | 
			
		||||
			drive.reset(new Storage::Disk::Drive);
 | 
			
		||||
		CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
 | 
			
		||||
			drive.reset(new Storage::Disk::Drive(4000000, 300, 2));
 | 
			
		||||
			set_drive(drive);
 | 
			
		||||
			drive->set_motor_on(true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		struct Sector
 | 
			
		||||
		{
 | 
			
		||||
		struct Sector {
 | 
			
		||||
			uint8_t sector, track;
 | 
			
		||||
			std::array<uint8_t, 256> data;
 | 
			
		||||
			bool header_checksum_matched;
 | 
			
		||||
@@ -40,17 +39,15 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
			
		||||
 | 
			
		||||
			@returns a sector if one was found; @c nullptr otherwise.
 | 
			
		||||
		*/
 | 
			
		||||
		std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector)
 | 
			
		||||
		{
 | 
			
		||||
			int difference = (int)track - (int)track_;
 | 
			
		||||
		std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
 | 
			
		||||
			int difference = static_cast<int>(track) - static_cast<int>(track_);
 | 
			
		||||
			track_ = track;
 | 
			
		||||
 | 
			
		||||
			if(difference)
 | 
			
		||||
			{
 | 
			
		||||
			if(difference) {
 | 
			
		||||
				int direction = difference < 0 ? -1 : 1;
 | 
			
		||||
				difference *= 2 * direction;
 | 
			
		||||
 | 
			
		||||
				for(int c = 0; c < difference; c++) step(direction);
 | 
			
		||||
				for(int c = 0; c < difference; c++) get_drive().step(direction);
 | 
			
		||||
 | 
			
		||||
				unsigned int zone = 3;
 | 
			
		||||
				if(track >= 18) zone = 2;
 | 
			
		||||
@@ -69,112 +66,96 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
			
		||||
		uint8_t track_;
 | 
			
		||||
		std::shared_ptr<Sector> sector_cache_[65536];
 | 
			
		||||
 | 
			
		||||
		void process_input_bit(int value, unsigned int cycles_since_index_hole)
 | 
			
		||||
		{
 | 
			
		||||
			shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff;
 | 
			
		||||
		void process_input_bit(int value) {
 | 
			
		||||
			shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff;
 | 
			
		||||
			bit_count_++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		unsigned int proceed_to_next_block()
 | 
			
		||||
		{
 | 
			
		||||
		unsigned int proceed_to_next_block() {
 | 
			
		||||
			// find GCR lead-in
 | 
			
		||||
			proceed_to_shift_value(0x3ff);
 | 
			
		||||
			if(shift_register_ != 0x3ff) return 0xff;
 | 
			
		||||
 | 
			
		||||
			// find end of lead-in
 | 
			
		||||
			while(shift_register_ == 0x3ff && index_count_ < 2)
 | 
			
		||||
			{
 | 
			
		||||
				run_for_cycles(1);
 | 
			
		||||
			while(shift_register_ == 0x3ff && index_count_ < 2) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// continue for a further nine bits
 | 
			
		||||
			bit_count_ = 0;
 | 
			
		||||
			while(bit_count_ < 9 && index_count_ < 2)
 | 
			
		||||
			{
 | 
			
		||||
				run_for_cycles(1);
 | 
			
		||||
			while(bit_count_ < 9 && index_count_ < 2) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		unsigned int get_next_byte()
 | 
			
		||||
		{
 | 
			
		||||
		unsigned int get_next_byte() {
 | 
			
		||||
			bit_count_ = 0;
 | 
			
		||||
			while(bit_count_ < 10) run_for_cycles(1);
 | 
			
		||||
			while(bit_count_ < 10) run_for(Cycles(1));
 | 
			
		||||
			return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void proceed_to_shift_value(unsigned int shift_value)
 | 
			
		||||
		{
 | 
			
		||||
		void proceed_to_shift_value(unsigned int shift_value) {
 | 
			
		||||
			index_count_ = 0;
 | 
			
		||||
			while(shift_register_ != shift_value && index_count_ < 2)
 | 
			
		||||
			{
 | 
			
		||||
				run_for_cycles(1);
 | 
			
		||||
			while(shift_register_ != shift_value && index_count_ < 2) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void process_index_hole()
 | 
			
		||||
		{
 | 
			
		||||
		void process_index_hole() {
 | 
			
		||||
			index_count_++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::shared_ptr<Sector> get_sector(uint8_t sector)
 | 
			
		||||
		{
 | 
			
		||||
			uint16_t sector_address = (uint16_t)((track_ << 8) | sector);
 | 
			
		||||
		std::shared_ptr<Sector> get_sector(uint8_t sector) {
 | 
			
		||||
			uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector);
 | 
			
		||||
			if(sector_cache_[sector_address]) return sector_cache_[sector_address];
 | 
			
		||||
 | 
			
		||||
			std::shared_ptr<Sector> first_sector = get_next_sector();
 | 
			
		||||
			if(!first_sector) return first_sector;
 | 
			
		||||
			if(first_sector->sector == sector) return first_sector;
 | 
			
		||||
 | 
			
		||||
			while(1)
 | 
			
		||||
			{
 | 
			
		||||
			while(1) {
 | 
			
		||||
				std::shared_ptr<Sector> next_sector = get_next_sector();
 | 
			
		||||
				if(next_sector->sector == first_sector->sector) return nullptr;
 | 
			
		||||
				if(next_sector->sector == sector) return next_sector;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::shared_ptr<Sector> get_next_sector()
 | 
			
		||||
		{
 | 
			
		||||
		std::shared_ptr<Sector> get_next_sector() {
 | 
			
		||||
			std::shared_ptr<Sector> sector(new Sector);
 | 
			
		||||
			index_count_ = 0;
 | 
			
		||||
 | 
			
		||||
			while(index_count_ < 2)
 | 
			
		||||
			{
 | 
			
		||||
			while(index_count_ < 2) {
 | 
			
		||||
				// look for a sector header
 | 
			
		||||
				while(1)
 | 
			
		||||
				{
 | 
			
		||||
				while(1) {
 | 
			
		||||
					if(proceed_to_next_block() == 0x08) break;
 | 
			
		||||
					if(index_count_ >= 2) return nullptr;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// get sector details, skip if this looks malformed
 | 
			
		||||
				uint8_t checksum = (uint8_t)get_next_byte();
 | 
			
		||||
				sector->sector = (uint8_t)get_next_byte();
 | 
			
		||||
				sector->track = (uint8_t)get_next_byte();
 | 
			
		||||
				uint8_t checksum = static_cast<uint8_t>(get_next_byte());
 | 
			
		||||
				sector->sector = static_cast<uint8_t>(get_next_byte());
 | 
			
		||||
				sector->track = static_cast<uint8_t>(get_next_byte());
 | 
			
		||||
				uint8_t disk_id[2];
 | 
			
		||||
				disk_id[0] = (uint8_t)get_next_byte();
 | 
			
		||||
				disk_id[1] = (uint8_t)get_next_byte();
 | 
			
		||||
				disk_id[0] = static_cast<uint8_t>(get_next_byte());
 | 
			
		||||
				disk_id[1] = static_cast<uint8_t>(get_next_byte());
 | 
			
		||||
				if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
 | 
			
		||||
 | 
			
		||||
				// look for the following data
 | 
			
		||||
				while(1)
 | 
			
		||||
				{
 | 
			
		||||
				while(1) {
 | 
			
		||||
					if(proceed_to_next_block() == 0x07) break;
 | 
			
		||||
					if(index_count_ >= 2) return nullptr;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				checksum = 0;
 | 
			
		||||
				for(size_t c = 0; c < 256; c++)
 | 
			
		||||
				{
 | 
			
		||||
					sector->data[c] = (uint8_t)get_next_byte();
 | 
			
		||||
				for(std::size_t c = 0; c < 256; c++) {
 | 
			
		||||
					sector->data[c] = static_cast<uint8_t>(get_next_byte());
 | 
			
		||||
					checksum ^= sector->data[c];
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(checksum == get_next_byte())
 | 
			
		||||
				{
 | 
			
		||||
					uint16_t sector_address = (uint16_t)((sector->track << 8) | sector->sector);
 | 
			
		||||
				if(checksum == get_next_byte()) {
 | 
			
		||||
					uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector);
 | 
			
		||||
					sector_cache_[sector_address] = sector;
 | 
			
		||||
					return sector;
 | 
			
		||||
				}
 | 
			
		||||
@@ -184,9 +165,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
			
		||||
		}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk)
 | 
			
		||||
{
 | 
			
		||||
	std::list<File> files;
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	CommodoreGCRParser parser;
 | 
			
		||||
	parser.drive->set_disk(disk);
 | 
			
		||||
 | 
			
		||||
@@ -197,8 +177,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
 | 
			
		||||
	std::vector<uint8_t> directory;
 | 
			
		||||
	uint8_t next_track = 18;
 | 
			
		||||
	uint8_t next_sector = 1;
 | 
			
		||||
	while(1)
 | 
			
		||||
	{
 | 
			
		||||
	while(1) {
 | 
			
		||||
		sector = parser.get_sector(next_track, next_sector);
 | 
			
		||||
		if(!sector) break;
 | 
			
		||||
		directory.insert(directory.end(), sector->data.begin(), sector->data.end());
 | 
			
		||||
@@ -209,14 +188,12 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// parse directory
 | 
			
		||||
	size_t header_pointer = (size_t)-32;
 | 
			
		||||
	while(header_pointer+32+31 < directory.size())
 | 
			
		||||
	{
 | 
			
		||||
	std::size_t header_pointer = static_cast<std::size_t>(-32);
 | 
			
		||||
	while(header_pointer+32+31 < directory.size()) {
 | 
			
		||||
		header_pointer += 32;
 | 
			
		||||
 | 
			
		||||
		File new_file;
 | 
			
		||||
		switch(directory[header_pointer + 2] & 7)
 | 
			
		||||
		{
 | 
			
		||||
		switch(directory[header_pointer + 2] & 7) {
 | 
			
		||||
			case 0:				// DEL files
 | 
			
		||||
			default: continue;	// Unknown file types
 | 
			
		||||
 | 
			
		||||
@@ -230,25 +207,23 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
 | 
			
		||||
		next_sector = directory[header_pointer + 4];
 | 
			
		||||
 | 
			
		||||
		new_file.raw_name.reserve(16);
 | 
			
		||||
		for(size_t c = 0; c < 16; c++)
 | 
			
		||||
		{
 | 
			
		||||
		for(std::size_t c = 0; c < 16; c++) {
 | 
			
		||||
			new_file.raw_name.push_back(directory[header_pointer + 5 + c]);
 | 
			
		||||
		}
 | 
			
		||||
		new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
 | 
			
		||||
 | 
			
		||||
		size_t number_of_sectors = (size_t)directory[header_pointer + 0x1e] + ((size_t)directory[header_pointer + 0x1f] << 8);
 | 
			
		||||
		std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8);
 | 
			
		||||
		new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
			
		||||
 | 
			
		||||
		bool is_first_sector = true;
 | 
			
		||||
		while(next_track)
 | 
			
		||||
		{
 | 
			
		||||
		while(next_track) {
 | 
			
		||||
			sector = parser.get_sector(next_track, next_sector);
 | 
			
		||||
			if(!sector) break;
 | 
			
		||||
 | 
			
		||||
			next_track = sector->data[0];
 | 
			
		||||
			next_sector = sector->data[1];
 | 
			
		||||
 | 
			
		||||
			if(is_first_sector) new_file.starting_address = (uint16_t)sector->data[2] | (uint16_t)(sector->data[3] << 8);
 | 
			
		||||
			if(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<uint16_t>(sector->data[3] << 8);
 | 
			
		||||
			if(next_track)
 | 
			
		||||
				new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
 | 
			
		||||
			else
 | 
			
		||||
@@ -9,17 +9,19 @@
 | 
			
		||||
#ifndef StaticAnalyser_Commodore_Disk_hpp
 | 
			
		||||
#define StaticAnalyser_Commodore_Disk_hpp
 | 
			
		||||
 | 
			
		||||
#include "../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include <list>
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Disk_hpp */
 | 
			
		||||
@@ -8,8 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
 | 
			
		||||
bool StaticAnalyser::Commodore::File::is_basic()
 | 
			
		||||
{
 | 
			
		||||
bool Analyser::Static::Commodore::File::is_basic() {
 | 
			
		||||
	// BASIC files are always relocatable (?)
 | 
			
		||||
	if(type != File::RelocatableProgram) return false;
 | 
			
		||||
 | 
			
		||||
@@ -23,26 +22,24 @@ bool StaticAnalyser::Commodore::File::is_basic()
 | 
			
		||||
	//		[4 bytes: this line number]
 | 
			
		||||
	//		... null-terminated code ...
 | 
			
		||||
	//	(with a next line address of 0000 indicating end of program)ß
 | 
			
		||||
	while(1)
 | 
			
		||||
	{
 | 
			
		||||
		if(line_address - starting_address >= data.size() + 2) break;
 | 
			
		||||
	while(1) {
 | 
			
		||||
		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
 | 
			
		||||
 | 
			
		||||
		uint16_t next_line_address = data[line_address - starting_address];
 | 
			
		||||
		next_line_address |= data[line_address - starting_address + 1] << 8;
 | 
			
		||||
 | 
			
		||||
		if(!next_line_address)
 | 
			
		||||
		{
 | 
			
		||||
		if(!next_line_address) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		if(next_line_address < line_address + 5) break;
 | 
			
		||||
 | 
			
		||||
		if(line_address - starting_address >= data.size() + 5) break;
 | 
			
		||||
		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break;
 | 
			
		||||
		uint16_t next_line_number = data[line_address - starting_address + 2];
 | 
			
		||||
		next_line_number |= data[line_address - starting_address + 3] << 8;
 | 
			
		||||
 | 
			
		||||
		if(next_line_number <= line_number) break;
 | 
			
		||||
 | 
			
		||||
		line_number = (uint16_t)next_line_number;
 | 
			
		||||
		line_number = static_cast<uint16_t>(next_line_number);
 | 
			
		||||
		line_address = next_line_address;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -12,18 +12,17 @@
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
struct File {
 | 
			
		||||
	File() : is_closed(false), is_locked(false) {}
 | 
			
		||||
 | 
			
		||||
	std::wstring name;
 | 
			
		||||
	std::vector<uint8_t> raw_name;
 | 
			
		||||
	uint16_t starting_address;
 | 
			
		||||
	uint16_t ending_address;
 | 
			
		||||
	bool is_locked;
 | 
			
		||||
	bool is_closed;
 | 
			
		||||
	bool is_locked = false;
 | 
			
		||||
	bool is_closed = false;
 | 
			
		||||
	enum {
 | 
			
		||||
		RelocatableProgram,
 | 
			
		||||
		NonRelocatableProgram,
 | 
			
		||||
@@ -36,6 +35,7 @@ struct File {
 | 
			
		||||
	bool is_basic();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										141
									
								
								Analyser/Static/Commodore/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								Analyser/Static/Commodore/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
//
 | 
			
		||||
//  CommodoreAnalyser.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 06/09/2016.
 | 
			
		||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
			
		||||
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
		Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
		const auto &segments = cartridge->get_segments();
 | 
			
		||||
 | 
			
		||||
		// only one mapped item is allowed
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// which must be 16 kb in size
 | 
			
		||||
		Storage::Cartridge::Cartridge::Segment segment = segments.front();
 | 
			
		||||
		if(segment.start_address != 0xa000) continue;
 | 
			
		||||
		if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
 | 
			
		||||
 | 
			
		||||
		vic20_cartridges.push_back(cartridge);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return vic20_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
 | 
			
		||||
	std::unique_ptr<Target> target(new Target);
 | 
			
		||||
	target->machine = Machine::Vic20;	// TODO: machine estimation
 | 
			
		||||
	target->confidence = 1.0; // TODO: a proper estimation
 | 
			
		||||
 | 
			
		||||
	int device = 0;
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	bool is_disk = false;
 | 
			
		||||
 | 
			
		||||
	// strip out inappropriate cartridges
 | 
			
		||||
	target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
 | 
			
		||||
 | 
			
		||||
	// check disks
 | 
			
		||||
	for(auto &disk : media.disks) {
 | 
			
		||||
		std::vector<File> disk_files = GetFiles(disk);
 | 
			
		||||
		if(!disk_files.empty()) {
 | 
			
		||||
			is_disk = true;
 | 
			
		||||
			files.insert(files.end(), disk_files.begin(), disk_files.end());
 | 
			
		||||
			target->media.disks.push_back(disk);
 | 
			
		||||
			if(!device) device = 8;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check tapes
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
		if(!tape_files.empty()) {
 | 
			
		||||
			files.insert(files.end(), tape_files.begin(), tape_files.end());
 | 
			
		||||
			target->media.tapes.push_back(tape);
 | 
			
		||||
			if(!device) device = 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!files.empty()) {
 | 
			
		||||
		target->vic20.memory_model = Vic20MemoryModel::Unexpanded;
 | 
			
		||||
		std::ostringstream string_stream;
 | 
			
		||||
		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
 | 
			
		||||
  		if(files.front().is_basic()) {
 | 
			
		||||
			string_stream << "0";
 | 
			
		||||
		} else {
 | 
			
		||||
			string_stream << "1";
 | 
			
		||||
		}
 | 
			
		||||
		string_stream << "\nRUN\n";
 | 
			
		||||
		target->loading_command = string_stream.str();
 | 
			
		||||
 | 
			
		||||
		// make a first guess based on loading address
 | 
			
		||||
		switch(files.front().starting_address) {
 | 
			
		||||
			case 0x1001:
 | 
			
		||||
			default: break;
 | 
			
		||||
			case 0x1201:
 | 
			
		||||
				target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
			
		||||
			break;
 | 
			
		||||
			case 0x0401:
 | 
			
		||||
				target->vic20.memory_model = Vic20MemoryModel::EightKB;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// General approach: increase memory size conservatively such that the largest file found will fit.
 | 
			
		||||
		for(File &file : files) {
 | 
			
		||||
			std::size_t file_size = file.data.size();
 | 
			
		||||
//			bool is_basic = file.is_basic();
 | 
			
		||||
 | 
			
		||||
			/*if(is_basic)
 | 
			
		||||
			{
 | 
			
		||||
				// BASIC files may be relocated, so the only limit is size.
 | 
			
		||||
				//
 | 
			
		||||
				// An unexpanded machine has 3583 bytes free for BASIC;
 | 
			
		||||
				// a 3kb expanded machine has 6655 bytes free.
 | 
			
		||||
				if(file_size > 6655)
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
			
		||||
				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::EightKB;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{*/
 | 
			
		||||
//			if(!file.type == File::NonRelocatableProgram)
 | 
			
		||||
//			{
 | 
			
		||||
				// Non-BASIC files may be relocatable but, if so, by what logic?
 | 
			
		||||
				// Given that this is unknown, take starting address as literal
 | 
			
		||||
				// and check against memory windows.
 | 
			
		||||
				//
 | 
			
		||||
				// (ignoring colour memory...)
 | 
			
		||||
				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
 | 
			
		||||
				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
 | 
			
		||||
				// A 32kb expanded Vic has memory in the entire low 32kb.
 | 
			
		||||
				uint16_t starting_address = file.starting_address;
 | 
			
		||||
 | 
			
		||||
				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
 | 
			
		||||
				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
 | 
			
		||||
				if(starting_address + file_size > 0x2000)
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
			
		||||
				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
			
		||||
//			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!target->media.empty())
 | 
			
		||||
		destination.push_back(std::move(target));
 | 
			
		||||
}
 | 
			
		||||
@@ -11,16 +11,13 @@
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
void AddTargets(
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
 | 
			
		||||
	std::list<Target> &destination
 | 
			
		||||
);
 | 
			
		||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -8,29 +8,24 @@
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../Storage/Tape/Parsers/Commodore.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace StaticAnalyser::Commodore;
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
 | 
			
		||||
{
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	Storage::Tape::Commodore::Parser parser;
 | 
			
		||||
	std::list<File> file_list;
 | 
			
		||||
	std::vector<File> file_list;
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
 | 
			
		||||
 | 
			
		||||
	while(!tape->is_at_end())
 | 
			
		||||
	{
 | 
			
		||||
		if(!header)
 | 
			
		||||
		{
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		if(!header) {
 | 
			
		||||
			header = parser.get_next_header(tape);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch(header->type)
 | 
			
		||||
		{
 | 
			
		||||
			case Storage::Tape::Commodore::Header::DataSequenceHeader:
 | 
			
		||||
			{
 | 
			
		||||
		switch(header->type) {
 | 
			
		||||
			case Storage::Tape::Commodore::Header::DataSequenceHeader: {
 | 
			
		||||
				File new_file;
 | 
			
		||||
				new_file.name = header->name;
 | 
			
		||||
				new_file.raw_name = header->raw_name;
 | 
			
		||||
@@ -39,8 +34,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
 | 
			
		||||
				new_file.type = File::DataSequence;
 | 
			
		||||
 | 
			
		||||
				new_file.data.swap(header->data);
 | 
			
		||||
				while(!tape->is_at_end())
 | 
			
		||||
				{
 | 
			
		||||
				while(!tape->is_at_end()) {
 | 
			
		||||
					header = parser.get_next_header(tape);
 | 
			
		||||
					if(!header) continue;
 | 
			
		||||
					if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
 | 
			
		||||
@@ -52,11 +46,9 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			case Storage::Tape::Commodore::Header::RelocatableProgram:
 | 
			
		||||
			case Storage::Tape::Commodore::Header::NonRelocatableProgram:
 | 
			
		||||
			{
 | 
			
		||||
			case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
 | 
			
		||||
				std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
 | 
			
		||||
				if(data)
 | 
			
		||||
				{
 | 
			
		||||
				if(data) {
 | 
			
		||||
					File new_file;
 | 
			
		||||
					new_file.name = header->name;
 | 
			
		||||
					new_file.raw_name = header->raw_name;
 | 
			
		||||
@@ -9,15 +9,16 @@
 | 
			
		||||
#ifndef StaticAnalyser_Commodore_Tape_hpp
 | 
			
		||||
#define StaticAnalyser_Commodore_Tape_hpp
 | 
			
		||||
 | 
			
		||||
#include "../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include <list>
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -6,25 +6,25 @@
 | 
			
		||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Disassembler6502.hpp"
 | 
			
		||||
#include <map>
 | 
			
		||||
#include "6502.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace StaticAnalyser::MOS6502;
 | 
			
		||||
#include "Kernel.hpp"
 | 
			
		||||
 | 
			
		||||
struct PartialDisassembly {
 | 
			
		||||
	Disassembly disassembly;
 | 
			
		||||
	std::vector<uint16_t> remaining_entry_points;
 | 
			
		||||
};
 | 
			
		||||
using namespace Analyser::Static::MOS6502;
 | 
			
		||||
namespace  {
 | 
			
		||||
 | 
			
		||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, uint16_t start_address, uint16_t entry_point)
 | 
			
		||||
{
 | 
			
		||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
 | 
			
		||||
 | 
			
		||||
struct MOS6502Disassembler {
 | 
			
		||||
 | 
			
		||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
 | 
			
		||||
	disassembly.disassembly.internal_calls.insert(entry_point);
 | 
			
		||||
	uint16_t address = entry_point;
 | 
			
		||||
	while(1)
 | 
			
		||||
	{
 | 
			
		||||
		uint16_t local_address = address - start_address;
 | 
			
		||||
	while(true) {
 | 
			
		||||
		std::size_t local_address = address_mapper(address);
 | 
			
		||||
		if(local_address >= memory.size()) return;
 | 
			
		||||
 | 
			
		||||
		struct Instruction instruction;
 | 
			
		||||
		Instruction instruction;
 | 
			
		||||
		instruction.address = address;
 | 
			
		||||
		address++;
 | 
			
		||||
 | 
			
		||||
@@ -32,8 +32,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
		uint8_t operation = memory[local_address];
 | 
			
		||||
 | 
			
		||||
		// decode addressing mode
 | 
			
		||||
		switch(operation&0x1f)
 | 
			
		||||
		{
 | 
			
		||||
		switch(operation&0x1f) {
 | 
			
		||||
			case 0x00:
 | 
			
		||||
				if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
 | 
			
		||||
				else if(operation == 0x20) instruction.addressing_mode = Instruction::Absolute;
 | 
			
		||||
@@ -93,8 +92,11 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
 | 
			
		||||
#define IM_INSTRUCTION(base, op)	\
 | 
			
		||||
	case base:	instruction.operation = op; break;
 | 
			
		||||
		switch(operation)
 | 
			
		||||
		{
 | 
			
		||||
		switch(operation) {
 | 
			
		||||
			default:
 | 
			
		||||
				instruction.operation = Instruction::KIL;
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			IM_INSTRUCTION(0x00, Instruction::BRK)
 | 
			
		||||
			IM_INSTRUCTION(0x20, Instruction::JSR)
 | 
			
		||||
			IM_INSTRUCTION(0x40, Instruction::RTI)
 | 
			
		||||
@@ -189,7 +191,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			case 0x87: case 0x97: case 0x83: case 0x8f:
 | 
			
		||||
				instruction.operation = Instruction::SAX;
 | 
			
		||||
				instruction.operation = Instruction::AXS;
 | 
			
		||||
			break;
 | 
			
		||||
			case 0xa7: case 0xb7: case 0xa3: case 0xb3: case 0xaf: case 0xbf:
 | 
			
		||||
				instruction.operation = Instruction::LAX;
 | 
			
		||||
@@ -205,7 +207,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
			IM_INSTRUCTION(0x6b, Instruction::ARR)
 | 
			
		||||
			IM_INSTRUCTION(0x8b, Instruction::XAA)
 | 
			
		||||
			IM_INSTRUCTION(0xab, Instruction::LAX)
 | 
			
		||||
			IM_INSTRUCTION(0xcb, Instruction::AXS)
 | 
			
		||||
			IM_INSTRUCTION(0xcb, Instruction::SAX)
 | 
			
		||||
			IM_INSTRUCTION(0xeb, Instruction::SBC)
 | 
			
		||||
			case 0x93: case 0x9f:
 | 
			
		||||
				instruction.operation = Instruction::AHX;
 | 
			
		||||
@@ -221,8 +223,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
#undef IM_INSTRUCTION
 | 
			
		||||
 | 
			
		||||
		// get operand
 | 
			
		||||
		switch(instruction.addressing_mode)
 | 
			
		||||
		{
 | 
			
		||||
		switch(instruction.addressing_mode) {
 | 
			
		||||
			// zero-byte operands
 | 
			
		||||
			case Instruction::Implied:
 | 
			
		||||
				instruction.operand = 0;
 | 
			
		||||
@@ -232,9 +233,8 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
			case Instruction::Immediate:
 | 
			
		||||
			case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
 | 
			
		||||
			case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
 | 
			
		||||
			case Instruction::Relative:
 | 
			
		||||
			{
 | 
			
		||||
				uint16_t operand_address = address - start_address;
 | 
			
		||||
			case Instruction::Relative: {
 | 
			
		||||
				std::size_t operand_address = address_mapper(address);
 | 
			
		||||
				if(operand_address >= memory.size()) return;
 | 
			
		||||
				address++;
 | 
			
		||||
 | 
			
		||||
@@ -244,13 +244,13 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
 | 
			
		||||
			// two-byte operands
 | 
			
		||||
			case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
 | 
			
		||||
			case Instruction::Indirect:
 | 
			
		||||
			{
 | 
			
		||||
				uint16_t operand_address = address - start_address;
 | 
			
		||||
				if(operand_address >= memory.size()-1) return;
 | 
			
		||||
			case Instruction::Indirect: {
 | 
			
		||||
				std::size_t low_operand_address = address_mapper(address);
 | 
			
		||||
				std::size_t high_operand_address = address_mapper(address + 1);
 | 
			
		||||
				if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
 | 
			
		||||
				address += 2;
 | 
			
		||||
 | 
			
		||||
				instruction.operand = memory[operand_address] | (uint16_t)(memory[operand_address+1] << 8);
 | 
			
		||||
				instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
@@ -259,59 +259,62 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
		disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
 | 
			
		||||
 | 
			
		||||
		// TODO: something wider-ranging than this
 | 
			
		||||
		if(instruction.addressing_mode == Instruction::Absolute && (instruction.operand < start_address || instruction.operand >= start_address + memory.size()))
 | 
			
		||||
		{
 | 
			
		||||
			if(	instruction.operation == Instruction::STY ||
 | 
			
		||||
				instruction.operation == Instruction::STX ||
 | 
			
		||||
				instruction.operation == Instruction::STA)
 | 
			
		||||
					disassembly.disassembly.external_stores.insert(instruction.operand);
 | 
			
		||||
			if(	instruction.operation == Instruction::LDY ||
 | 
			
		||||
				instruction.operation == Instruction::LDX ||
 | 
			
		||||
				instruction.operation == Instruction::LDA)
 | 
			
		||||
					disassembly.disassembly.external_loads.insert(instruction.operand);
 | 
			
		||||
		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
 | 
			
		||||
			std::size_t mapped_address = address_mapper(instruction.operand);
 | 
			
		||||
			bool is_external = mapped_address >= memory.size();
 | 
			
		||||
 | 
			
		||||
			switch(instruction.operation) {
 | 
			
		||||
				default: break;
 | 
			
		||||
 | 
			
		||||
				case Instruction::LDY: case Instruction::LDX: case Instruction::LDA:
 | 
			
		||||
				case Instruction::LAX:
 | 
			
		||||
				case Instruction::AND: case Instruction::EOR: case Instruction::ORA: case Instruction::BIT:
 | 
			
		||||
				case Instruction::ADC: case Instruction::SBC:
 | 
			
		||||
				case Instruction::LAS:
 | 
			
		||||
				case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case Instruction::STY: case Instruction::STX: case Instruction::STA:
 | 
			
		||||
				case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
 | 
			
		||||
				case Instruction::TAS:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
 | 
			
		||||
				case Instruction::DCP: case Instruction::ISC:
 | 
			
		||||
				case Instruction::INC: case Instruction::DEC:
 | 
			
		||||
				case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// decide on overall flow control
 | 
			
		||||
		if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
 | 
			
		||||
		if(instruction.operation == Instruction::BRK) return;	// TODO: check whether IRQ vector is within memory range
 | 
			
		||||
		if(instruction.operation == Instruction::JSR)
 | 
			
		||||
		{
 | 
			
		||||
		if(instruction.operation == Instruction::JSR) {
 | 
			
		||||
			disassembly.remaining_entry_points.push_back(instruction.operand);
 | 
			
		||||
		}
 | 
			
		||||
		if(instruction.operation == Instruction::JMP)
 | 
			
		||||
		{
 | 
			
		||||
		if(instruction.operation == Instruction::JMP) {
 | 
			
		||||
			if(instruction.addressing_mode == Instruction::Absolute)
 | 
			
		||||
				disassembly.remaining_entry_points.push_back(instruction.operand);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if(instruction.addressing_mode == Instruction::Relative)
 | 
			
		||||
		{
 | 
			
		||||
			uint16_t destination = (uint16_t)(address + (int8_t)instruction.operand);
 | 
			
		||||
		if(instruction.addressing_mode == Instruction::Relative) {
 | 
			
		||||
			uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand);
 | 
			
		||||
			disassembly.remaining_entry_points.push_back(destination);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, uint16_t start_address, std::vector<uint16_t> entry_points)
 | 
			
		||||
{
 | 
			
		||||
	PartialDisassembly partialDisassembly;
 | 
			
		||||
	partialDisassembly.remaining_entry_points = entry_points;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
	while(!partialDisassembly.remaining_entry_points.empty())
 | 
			
		||||
	{
 | 
			
		||||
		// pull the next entry point from the back of the vector
 | 
			
		||||
		uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back();
 | 
			
		||||
		partialDisassembly.remaining_entry_points.pop_back();
 | 
			
		||||
}	// end of anonymous namespace
 | 
			
		||||
 | 
			
		||||
		// if that address has already bene visited, forget about it
 | 
			
		||||
		if(partialDisassembly.disassembly.instructions_by_address.find(next_entry_point) != partialDisassembly.disassembly.instructions_by_address.end()) continue;
 | 
			
		||||
 | 
			
		||||
		// if it's outgoing, log it as such and forget about it; otherwise disassemble
 | 
			
		||||
		if(next_entry_point < start_address || next_entry_point >= start_address + memory.size())
 | 
			
		||||
			partialDisassembly.disassembly.outward_calls.insert(next_entry_point);
 | 
			
		||||
		else
 | 
			
		||||
			AddToDisassembly(partialDisassembly, memory, start_address, next_entry_point);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return std::move(partialDisassembly.disassembly);
 | 
			
		||||
Disassembly Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	std::vector<uint16_t> entry_points) {
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								Analyser/Static/Disassembler/6502.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								Analyser/Static/Disassembler/6502.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
//
 | 
			
		||||
//  6502.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 10/11/2016.
 | 
			
		||||
//  Copyright © 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef StaticAnalyser_Disassembler_6502_hpp
 | 
			
		||||
#define StaticAnalyser_Disassembler_6502_hpp
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <set>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace MOS6502 {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Describes a 6502 instruciton — its address, the operation it performs, its addressing mode
 | 
			
		||||
	and its operand, if any.
 | 
			
		||||
*/
 | 
			
		||||
struct Instruction {
 | 
			
		||||
	/*! The address this instruction starts at. This is a mapped address. */
 | 
			
		||||
	uint16_t address = 0;
 | 
			
		||||
	/*! The operation this instruction performs. */
 | 
			
		||||
	enum {
 | 
			
		||||
		BRK, JSR, RTI, RTS, JMP,
 | 
			
		||||
		CLC, SEC, CLD, SED, CLI, SEI, CLV,
 | 
			
		||||
		NOP,
 | 
			
		||||
 | 
			
		||||
		SLO, RLA, SRE, RRA, ALR, ARR,
 | 
			
		||||
		SAX, LAX, DCP, ISC,
 | 
			
		||||
		ANC, XAA, AXS,
 | 
			
		||||
		AND, EOR, ORA, BIT,
 | 
			
		||||
		ADC, SBC,
 | 
			
		||||
		AHX, SHY, SHX, TAS, LAS,
 | 
			
		||||
 | 
			
		||||
		LDA, STA, LDX, STX, LDY, STY,
 | 
			
		||||
 | 
			
		||||
		BPL, BMI, BVC, BVS, BCC, BCS, BNE, BEQ,
 | 
			
		||||
 | 
			
		||||
		CMP, CPX, CPY,
 | 
			
		||||
		INC, DEC, DEX, DEY, INX, INY,
 | 
			
		||||
		ASL, ROL, LSR, ROR,
 | 
			
		||||
		TAX, TXA, TAY, TYA, TSX, TXS,
 | 
			
		||||
		PLA, PHA, PLP, PHP,
 | 
			
		||||
 | 
			
		||||
		KIL
 | 
			
		||||
	} operation = NOP;
 | 
			
		||||
	/*! The addressing mode used by the instruction. */
 | 
			
		||||
	enum {
 | 
			
		||||
		Absolute,
 | 
			
		||||
		AbsoluteX,
 | 
			
		||||
		AbsoluteY,
 | 
			
		||||
		Immediate,
 | 
			
		||||
		Implied,
 | 
			
		||||
		ZeroPage,
 | 
			
		||||
		ZeroPageX,
 | 
			
		||||
		ZeroPageY,
 | 
			
		||||
		Indirect,
 | 
			
		||||
		IndexedIndirectX,
 | 
			
		||||
		IndirectIndexedY,
 | 
			
		||||
		Relative,
 | 
			
		||||
	} addressing_mode = Implied;
 | 
			
		||||
	/*! The instruction's operand, if any. */
 | 
			
		||||
	uint16_t operand = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Represents the disassembled form of a program. */
 | 
			
		||||
struct Disassembly {
 | 
			
		||||
	/*! All instructions found, mapped by address. */
 | 
			
		||||
	std::map<uint16_t, Instruction> instructions_by_address;
 | 
			
		||||
	/*! The set of all calls or jumps that land outside of the area covered by the data provided for disassembly. */
 | 
			
		||||
	std::set<uint16_t> outward_calls;
 | 
			
		||||
	/*! The set of all calls or jumps that land inside of the area covered by the data provided for disassembly. */
 | 
			
		||||
	std::set<uint16_t> internal_calls;
 | 
			
		||||
	/*! The sets of all stores, loads and modifies that occur to data outside of the area covered by the data provided for disassembly. */
 | 
			
		||||
	std::set<uint16_t> external_stores, external_loads, external_modifies;
 | 
			
		||||
	/*! The sets of all stores, loads and modifies that occur to data inside of the area covered by the data provided for disassembly. */
 | 
			
		||||
	std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Disassembles the data provided as @c memory, mapping it into the 6502's full address range via the @c address_mapper,
 | 
			
		||||
	starting disassembly from each of the @c entry_points.
 | 
			
		||||
*/
 | 
			
		||||
Disassembly Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	std::vector<uint16_t> entry_points);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Disassembler6502_hpp */
 | 
			
		||||
							
								
								
									
										9
									
								
								Analyser/Static/Disassembler/AddressMapper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Analyser/Static/Disassembler/AddressMapper.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
//
 | 
			
		||||
//  AddressMapper.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 30/12/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AddressMapper.hpp"
 | 
			
		||||
							
								
								
									
										32
									
								
								Analyser/Static/Disassembler/AddressMapper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Analyser/Static/Disassembler/AddressMapper.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
//
 | 
			
		||||
//  AddressMapper.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 30/12/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef AddressMapper_hpp
 | 
			
		||||
#define AddressMapper_hpp
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Disassembler {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides an address mapper that relocates a chunk of memory so that it starts at
 | 
			
		||||
	address @c start_address.
 | 
			
		||||
*/
 | 
			
		||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
 | 
			
		||||
	return [start_address](T argument) {
 | 
			
		||||
		return static_cast<std::size_t>(argument - start_address);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* AddressMapper_hpp */
 | 
			
		||||
							
								
								
									
										52
									
								
								Analyser/Static/Disassembler/Kernel.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Analyser/Static/Disassembler/Kernel.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
//
 | 
			
		||||
//  Kernel.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 31/12/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef Kernel_hpp
 | 
			
		||||
#define Kernel_hpp
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Disassembly {
 | 
			
		||||
 | 
			
		||||
template <typename D, typename S> struct PartialDisassembly {
 | 
			
		||||
	D disassembly;
 | 
			
		||||
	std::vector<S> remaining_entry_points;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename D, typename S, typename Disassembler> D Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(S)> &address_mapper,
 | 
			
		||||
	std::vector<S> entry_points) {
 | 
			
		||||
	PartialDisassembly<D, S> partial_disassembly;
 | 
			
		||||
	partial_disassembly.remaining_entry_points = entry_points;
 | 
			
		||||
 | 
			
		||||
	while(!partial_disassembly.remaining_entry_points.empty()) {
 | 
			
		||||
		// pull the next entry point from the back of the vector
 | 
			
		||||
		S next_entry_point = partial_disassembly.remaining_entry_points.back();
 | 
			
		||||
		partial_disassembly.remaining_entry_points.pop_back();
 | 
			
		||||
 | 
			
		||||
		// if that address has already been visited, forget about it
 | 
			
		||||
		if(	partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
 | 
			
		||||
			!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
 | 
			
		||||
 | 
			
		||||
		// if it's outgoing, log it as such and forget about it; otherwise disassemble
 | 
			
		||||
		std::size_t mapped_entry_point = address_mapper(next_entry_point);
 | 
			
		||||
		if(mapped_entry_point >= memory.size())
 | 
			
		||||
			partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
 | 
			
		||||
		else
 | 
			
		||||
			Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return partial_disassembly.disassembly;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Kernel_hpp */
 | 
			
		||||
							
								
								
									
										619
									
								
								Analyser/Static/Disassembler/Z80.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										619
									
								
								Analyser/Static/Disassembler/Z80.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,619 @@
 | 
			
		||||
//
 | 
			
		||||
//  Z80.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 30/12/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Z80.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Kernel.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Z80;
 | 
			
		||||
namespace  {
 | 
			
		||||
 | 
			
		||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
 | 
			
		||||
 | 
			
		||||
class Accessor {
 | 
			
		||||
	public:
 | 
			
		||||
		Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
 | 
			
		||||
			memory_(memory), address_mapper_(address_mapper), address_(address) {}
 | 
			
		||||
 | 
			
		||||
		uint8_t byte() {
 | 
			
		||||
			std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
			address_++;
 | 
			
		||||
			if(mapped_address >= memory_.size()) {
 | 
			
		||||
				overrun_ = true;
 | 
			
		||||
				return 0xff;
 | 
			
		||||
			}
 | 
			
		||||
			return memory_[mapped_address];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uint16_t word() {
 | 
			
		||||
			uint8_t low = byte();
 | 
			
		||||
			uint8_t high = byte();
 | 
			
		||||
			return static_cast<uint16_t>(low | (high << 8));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bool overrun() {
 | 
			
		||||
			return overrun_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bool at_end() {
 | 
			
		||||
			std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
			return mapped_address >= memory_.size();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uint16_t address() {
 | 
			
		||||
			return address_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<uint8_t> &memory_;
 | 
			
		||||
		const std::function<std::size_t(uint16_t)> &address_mapper_;
 | 
			
		||||
		uint16_t address_;
 | 
			
		||||
		bool overrun_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define x(v) (v >> 6)
 | 
			
		||||
#define y(v) ((v >> 3) & 7)
 | 
			
		||||
#define q(v) ((v >> 3) & 1)
 | 
			
		||||
#define p(v) ((v >> 4) & 3)
 | 
			
		||||
#define z(v) (v & 7)
 | 
			
		||||
 | 
			
		||||
Instruction::Condition condition_table[] = {
 | 
			
		||||
	Instruction::Condition::NZ, 	Instruction::Condition::Z,
 | 
			
		||||
	Instruction::Condition::NC, 	Instruction::Condition::C,
 | 
			
		||||
	Instruction::Condition::PO, 	Instruction::Condition::PE,
 | 
			
		||||
	Instruction::Condition::P,		Instruction::Condition::M
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Location register_pair_table[] = {
 | 
			
		||||
	Instruction::Location::BC,
 | 
			
		||||
	Instruction::Location::DE,
 | 
			
		||||
	Instruction::Location::HL,
 | 
			
		||||
	Instruction::Location::SP
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Location register_pair_table2[] = {
 | 
			
		||||
	Instruction::Location::BC,
 | 
			
		||||
	Instruction::Location::DE,
 | 
			
		||||
	Instruction::Location::HL,
 | 
			
		||||
	Instruction::Location::AF
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
	Instruction::Location register_table[] = {
 | 
			
		||||
		Instruction::Location::B,	Instruction::Location::C,
 | 
			
		||||
		Instruction::Location::D,	Instruction::Location::E,
 | 
			
		||||
		Instruction::Location::H,	Instruction::Location::L,
 | 
			
		||||
		Instruction::Location::HL_Indirect,
 | 
			
		||||
		Instruction::Location::A
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Instruction::Location location = register_table[offset];
 | 
			
		||||
	if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
 | 
			
		||||
		instruction.offset = accessor.byte() - 128;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return location;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Instruction::Operation alu_table[] = {
 | 
			
		||||
	Instruction::Operation::ADD,
 | 
			
		||||
	Instruction::Operation::ADC,
 | 
			
		||||
	Instruction::Operation::SUB,
 | 
			
		||||
	Instruction::Operation::SBC,
 | 
			
		||||
	Instruction::Operation::AND,
 | 
			
		||||
	Instruction::Operation::XOR,
 | 
			
		||||
	Instruction::Operation::OR,
 | 
			
		||||
	Instruction::Operation::CP
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Operation rotation_table[] = {
 | 
			
		||||
	Instruction::Operation::RLC,
 | 
			
		||||
	Instruction::Operation::RRC,
 | 
			
		||||
	Instruction::Operation::RL,
 | 
			
		||||
	Instruction::Operation::RR,
 | 
			
		||||
	Instruction::Operation::SLA,
 | 
			
		||||
	Instruction::Operation::SRA,
 | 
			
		||||
	Instruction::Operation::SLL,
 | 
			
		||||
	Instruction::Operation::SRL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Operation block_table[][4] = {
 | 
			
		||||
	{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
 | 
			
		||||
	{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
 | 
			
		||||
	{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
 | 
			
		||||
	{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
	const uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
	if(!x(operation)) {
 | 
			
		||||
		instruction.operation = rotation_table[y(operation)];
 | 
			
		||||
		instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
	} else {
 | 
			
		||||
		instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
		instruction.source = Instruction::Location::Operand;
 | 
			
		||||
		instruction.operand = y(operation);
 | 
			
		||||
 | 
			
		||||
		switch(x(operation)) {
 | 
			
		||||
			case 1:	instruction.operation = Instruction::Operation::BIT;	break;
 | 
			
		||||
			case 2:	instruction.operation = Instruction::Operation::RES;	break;
 | 
			
		||||
			case 3:	instruction.operation = Instruction::Operation::SET;	break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
	const uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
	switch(x(operation)) {
 | 
			
		||||
		default:
 | 
			
		||||
			instruction.operation = Instruction::Operation::Invalid;
 | 
			
		||||
		break;
 | 
			
		||||
		case 2:
 | 
			
		||||
			if(z(operation) < 4 && y(operation) >= 4) {
 | 
			
		||||
				instruction.operation = block_table[y(operation)-4][z(operation)];
 | 
			
		||||
			} else {
 | 
			
		||||
				instruction.operation = Instruction::Operation::Invalid;
 | 
			
		||||
			}
 | 
			
		||||
		break;
 | 
			
		||||
		case 3:
 | 
			
		||||
			switch(z(operation)) {
 | 
			
		||||
				case 0:
 | 
			
		||||
					instruction.operation = Instruction::Operation::IN;
 | 
			
		||||
					instruction.source = Instruction::Location::BC_Indirect;
 | 
			
		||||
					if(y(operation) == 6) {
 | 
			
		||||
						instruction.destination = Instruction::Location::None;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 1:
 | 
			
		||||
					instruction.operation = Instruction::Operation::OUT;
 | 
			
		||||
					instruction.destination = Instruction::Location::BC_Indirect;
 | 
			
		||||
					if(y(operation) == 6) {
 | 
			
		||||
						instruction.source = Instruction::Location::None;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 2:
 | 
			
		||||
					instruction.operation = (y(operation)&1) ? Instruction::Operation::ADC : Instruction::Operation::SBC;
 | 
			
		||||
					instruction.destination = Instruction::Location::HL;
 | 
			
		||||
					instruction.source = register_pair_table[y(operation) >> 1];
 | 
			
		||||
				break;
 | 
			
		||||
				case 3:
 | 
			
		||||
					instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
					if(q(operation)) {
 | 
			
		||||
						instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = Instruction::Location::Operand_Indirect;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.destination = Instruction::Location::Operand_Indirect;
 | 
			
		||||
						instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
					instruction.operand = accessor.word();
 | 
			
		||||
				break;
 | 
			
		||||
				case 4:
 | 
			
		||||
					instruction.operation = Instruction::Operation::NEG;
 | 
			
		||||
				break;
 | 
			
		||||
				case 5:
 | 
			
		||||
					instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
 | 
			
		||||
				break;
 | 
			
		||||
				case 6:
 | 
			
		||||
					instruction.operation = Instruction::Operation::IM;
 | 
			
		||||
					instruction.source = Instruction::Location::Operand;
 | 
			
		||||
					switch(y(operation)&3) {
 | 
			
		||||
						case 0:	instruction.operand = 0;	break;
 | 
			
		||||
						case 1:	instruction.operand = 0;	break;
 | 
			
		||||
						case 2:	instruction.operand = 1;	break;
 | 
			
		||||
						case 3:	instruction.operand = 2;	break;
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 7:
 | 
			
		||||
					switch(y(operation)) {
 | 
			
		||||
						case 0:
 | 
			
		||||
							instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
							instruction.destination = Instruction::Location::I;
 | 
			
		||||
							instruction.source = Instruction::Location::A;
 | 
			
		||||
						break;
 | 
			
		||||
						case 1:
 | 
			
		||||
							instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
							instruction.destination = Instruction::Location::R;
 | 
			
		||||
							instruction.source = Instruction::Location::A;
 | 
			
		||||
						break;
 | 
			
		||||
						case 2:
 | 
			
		||||
							instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
							instruction.destination = Instruction::Location::A;
 | 
			
		||||
							instruction.source = Instruction::Location::I;
 | 
			
		||||
						break;
 | 
			
		||||
						case 3:
 | 
			
		||||
							instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
							instruction.destination = Instruction::Location::A;
 | 
			
		||||
							instruction.source = Instruction::Location::R;
 | 
			
		||||
						break;
 | 
			
		||||
						case 4:		instruction.operation = Instruction::Operation::RRD;	break;
 | 
			
		||||
						case 5:		instruction.operation = Instruction::Operation::RLD;	break;
 | 
			
		||||
						default:	instruction.operation = Instruction::Operation::NOP;	break;
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
	bool needs_indirect_offset = false;
 | 
			
		||||
	enum HLSubstitution {
 | 
			
		||||
		None, IX, IY
 | 
			
		||||
	} hl_substitution = None;
 | 
			
		||||
 | 
			
		||||
	while(true) {
 | 
			
		||||
		uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
		switch(x(operation)) {
 | 
			
		||||
			case 0:
 | 
			
		||||
				switch(z(operation)) {
 | 
			
		||||
					case 0:
 | 
			
		||||
						switch(y(operation)) {
 | 
			
		||||
							case 0: instruction.operation = Instruction::Operation::NOP;		break;
 | 
			
		||||
							case 1: instruction.operation = Instruction::Operation::EXAFAFd;	break;
 | 
			
		||||
							case 2:
 | 
			
		||||
								instruction.operation = Instruction::Operation::DJNZ;
 | 
			
		||||
								instruction.operand = accessor.byte() - 128;
 | 
			
		||||
							break;
 | 
			
		||||
							default:
 | 
			
		||||
								instruction.operation = Instruction::Operation::JR;
 | 
			
		||||
								instruction.operand = accessor.byte() - 128;
 | 
			
		||||
								if(y(operation) >= 4) instruction.condition = condition_table[y(operation) - 4];
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case 1:
 | 
			
		||||
						if(y(operation)&1) {
 | 
			
		||||
							instruction.operation = Instruction::Operation::ADD;
 | 
			
		||||
							instruction.destination = Instruction::Location::HL;
 | 
			
		||||
							instruction.source = register_pair_table[y(operation) >> 1];
 | 
			
		||||
						} else {
 | 
			
		||||
							instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
							instruction.destination = register_pair_table[y(operation) >> 1];
 | 
			
		||||
							instruction.source = Instruction::Location::Operand;
 | 
			
		||||
							instruction.operand = accessor.word();
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case 2:
 | 
			
		||||
						switch(y(operation)) {
 | 
			
		||||
							case 0:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::BC_Indirect;
 | 
			
		||||
								instruction.source = Instruction::Location::A;
 | 
			
		||||
							break;
 | 
			
		||||
							case 1:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::A;
 | 
			
		||||
								instruction.source = Instruction::Location::BC_Indirect;
 | 
			
		||||
							break;
 | 
			
		||||
							case 2:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::DE_Indirect;
 | 
			
		||||
								instruction.source = Instruction::Location::A;
 | 
			
		||||
							break;
 | 
			
		||||
							case 3:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::A;
 | 
			
		||||
								instruction.source = Instruction::Location::DE_Indirect;
 | 
			
		||||
							break;
 | 
			
		||||
							case 4:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::Operand_Indirect;
 | 
			
		||||
								instruction.source = Instruction::Location::HL;
 | 
			
		||||
							break;
 | 
			
		||||
							case 5:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::HL;
 | 
			
		||||
								instruction.source = Instruction::Location::Operand_Indirect;
 | 
			
		||||
							break;
 | 
			
		||||
							case 6:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::Operand_Indirect;
 | 
			
		||||
								instruction.source = Instruction::Location::A;
 | 
			
		||||
							break;
 | 
			
		||||
							case 7:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::A;
 | 
			
		||||
								instruction.source = Instruction::Location::Operand_Indirect;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if(y(operation) > 3) {
 | 
			
		||||
							instruction.operand = accessor.word();
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case 3:
 | 
			
		||||
						if(y(operation)&1) {
 | 
			
		||||
							instruction.operation = Instruction::Operation::DEC;
 | 
			
		||||
						} else {
 | 
			
		||||
							instruction.operation = Instruction::Operation::INC;
 | 
			
		||||
						}
 | 
			
		||||
						instruction.source = instruction.destination = register_pair_table[y(operation) >> 1];
 | 
			
		||||
					break;
 | 
			
		||||
					case 4:
 | 
			
		||||
						instruction.operation = Instruction::Operation::INC;
 | 
			
		||||
						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					break;
 | 
			
		||||
					case 5:
 | 
			
		||||
						instruction.operation = Instruction::Operation::DEC;
 | 
			
		||||
						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					break;
 | 
			
		||||
					case 6:
 | 
			
		||||
						instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = Instruction::Location::Operand;
 | 
			
		||||
						instruction.operand = accessor.byte();
 | 
			
		||||
					break;
 | 
			
		||||
					case 7:
 | 
			
		||||
						switch(y(operation)) {
 | 
			
		||||
							case 0:	instruction.operation = Instruction::Operation::RLCA;	break;
 | 
			
		||||
							case 1:	instruction.operation = Instruction::Operation::RRCA;	break;
 | 
			
		||||
							case 2:	instruction.operation = Instruction::Operation::RLA;	break;
 | 
			
		||||
							case 3:	instruction.operation = Instruction::Operation::RRA;	break;
 | 
			
		||||
							case 4:	instruction.operation = Instruction::Operation::DAA;	break;
 | 
			
		||||
							case 5:	instruction.operation = Instruction::Operation::CPL;	break;
 | 
			
		||||
							case 6:	instruction.operation = Instruction::Operation::SCF;	break;
 | 
			
		||||
							case 7:	instruction.operation = Instruction::Operation::CCF;	break;
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			break;
 | 
			
		||||
			case 1:
 | 
			
		||||
				if(y(operation) == 6 && z(operation) == 6) {
 | 
			
		||||
					instruction.operation = Instruction::Operation::HALT;
 | 
			
		||||
				} else {
 | 
			
		||||
					instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
					instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
				}
 | 
			
		||||
			break;
 | 
			
		||||
			case 2:
 | 
			
		||||
				instruction.operation = alu_table[y(operation)];
 | 
			
		||||
				instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
				instruction.destination = Instruction::Location::A;
 | 
			
		||||
			break;
 | 
			
		||||
			case 3:
 | 
			
		||||
				switch(z(operation)) {
 | 
			
		||||
					case 0:
 | 
			
		||||
						instruction.operation = Instruction::Operation::RET;
 | 
			
		||||
						instruction.condition = condition_table[y(operation)];
 | 
			
		||||
					break;
 | 
			
		||||
					case 1:
 | 
			
		||||
						switch(y(operation)) {
 | 
			
		||||
							default:
 | 
			
		||||
								instruction.operation = Instruction::Operation::POP;
 | 
			
		||||
								instruction.source = register_pair_table2[y(operation) >> 1];
 | 
			
		||||
							break;
 | 
			
		||||
							case 1:
 | 
			
		||||
								instruction.operation = Instruction::Operation::RET;
 | 
			
		||||
							break;
 | 
			
		||||
							case 3:
 | 
			
		||||
								instruction.operation = Instruction::Operation::EXX;
 | 
			
		||||
							break;
 | 
			
		||||
							case 5:
 | 
			
		||||
								instruction.operation = Instruction::Operation::JP;
 | 
			
		||||
								instruction.source = Instruction::Location::HL;
 | 
			
		||||
							break;
 | 
			
		||||
							case 7:
 | 
			
		||||
								instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
								instruction.destination = Instruction::Location::SP;
 | 
			
		||||
								instruction.source = Instruction::Location::HL;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case 2:
 | 
			
		||||
						instruction.operation = Instruction::Operation::JP;
 | 
			
		||||
						instruction.condition = condition_table[y(operation)];
 | 
			
		||||
						instruction.operand = accessor.word();
 | 
			
		||||
					break;
 | 
			
		||||
					case 3:
 | 
			
		||||
						switch(y(operation)) {
 | 
			
		||||
							case 0:
 | 
			
		||||
								instruction.operation = Instruction::Operation::JP;
 | 
			
		||||
								instruction.source = Instruction::Location::Operand;
 | 
			
		||||
								instruction.operand = accessor.word();
 | 
			
		||||
							break;
 | 
			
		||||
							case 1:
 | 
			
		||||
								DisassembleCBPage(accessor, instruction, needs_indirect_offset);
 | 
			
		||||
							break;
 | 
			
		||||
							case 2:
 | 
			
		||||
								instruction.operation = Instruction::Operation::OUT;
 | 
			
		||||
								instruction.source = Instruction::Location::A;
 | 
			
		||||
								instruction.destination = Instruction::Location::Operand_Indirect;
 | 
			
		||||
								instruction.operand = accessor.byte();
 | 
			
		||||
							break;
 | 
			
		||||
							case 3:
 | 
			
		||||
								instruction.operation = Instruction::Operation::IN;
 | 
			
		||||
								instruction.destination = Instruction::Location::A;
 | 
			
		||||
								instruction.source = Instruction::Location::Operand_Indirect;
 | 
			
		||||
								instruction.operand = accessor.byte();
 | 
			
		||||
							break;
 | 
			
		||||
							case 4:
 | 
			
		||||
								instruction.operation = Instruction::Operation::EX;
 | 
			
		||||
								instruction.destination = Instruction::Location::SP_Indirect;
 | 
			
		||||
								instruction.source = Instruction::Location::HL;
 | 
			
		||||
							break;
 | 
			
		||||
							case 5:
 | 
			
		||||
								instruction.operation = Instruction::Operation::EX;
 | 
			
		||||
								instruction.destination = Instruction::Location::DE;
 | 
			
		||||
								instruction.source = Instruction::Location::HL;
 | 
			
		||||
							break;
 | 
			
		||||
							case 6:
 | 
			
		||||
								instruction.operation = Instruction::Operation::DI;
 | 
			
		||||
							break;
 | 
			
		||||
							case 7:
 | 
			
		||||
								instruction.operation = Instruction::Operation::EI;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case 4:
 | 
			
		||||
						instruction.operation = Instruction::Operation::CALL;
 | 
			
		||||
						instruction.source = Instruction::Location::Operand_Indirect;
 | 
			
		||||
						instruction.operand = accessor.word();
 | 
			
		||||
						instruction.condition = condition_table[y(operation)];
 | 
			
		||||
					break;
 | 
			
		||||
					case 5:
 | 
			
		||||
						switch(y(operation)) {
 | 
			
		||||
							default:
 | 
			
		||||
								instruction.operation = Instruction::Operation::PUSH;
 | 
			
		||||
								instruction.source = register_pair_table2[y(operation) >> 1];
 | 
			
		||||
							break;
 | 
			
		||||
							case 1:
 | 
			
		||||
								instruction.operation = Instruction::Operation::CALL;
 | 
			
		||||
								instruction.source = Instruction::Location::Operand;
 | 
			
		||||
								instruction.operand = accessor.word();
 | 
			
		||||
							break;
 | 
			
		||||
							case 3:
 | 
			
		||||
								needs_indirect_offset = true;
 | 
			
		||||
								hl_substitution = IX;
 | 
			
		||||
							continue;	// i.e. repeat loop.
 | 
			
		||||
							case 5:
 | 
			
		||||
								DisassembleEDPage(accessor, instruction, needs_indirect_offset);
 | 
			
		||||
							break;
 | 
			
		||||
							case 7:
 | 
			
		||||
								needs_indirect_offset = true;
 | 
			
		||||
								hl_substitution = IY;
 | 
			
		||||
							continue;	// i.e. repeat loop.
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case 6:
 | 
			
		||||
						instruction.operation = alu_table[y(operation)];
 | 
			
		||||
						instruction.source = Instruction::Location::Operand;
 | 
			
		||||
						instruction.destination = Instruction::Location::A;
 | 
			
		||||
						instruction.operand = accessor.byte();
 | 
			
		||||
					break;
 | 
			
		||||
					case 7:
 | 
			
		||||
						instruction.operation = Instruction::Operation::RST;
 | 
			
		||||
						instruction.source = Instruction::Location::Operand;
 | 
			
		||||
						instruction.operand = y(operation) << 3;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// This while(true) isn't an infinite loop for everything except those paths that opt in
 | 
			
		||||
		// via continue.
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Perform IX/IY substitution for HL, if applicable.
 | 
			
		||||
	if(hl_substitution != None) {
 | 
			
		||||
		// EX DE, HL is not affected.
 | 
			
		||||
		if(instruction.operation == Instruction::Operation::EX) return;
 | 
			
		||||
 | 
			
		||||
		// If an (HL) is involved, switch it for IX+d or IY+d.
 | 
			
		||||
		if(	instruction.source == Instruction::Location::HL_Indirect ||
 | 
			
		||||
			instruction.destination == Instruction::Location::HL_Indirect) {
 | 
			
		||||
 | 
			
		||||
			if(instruction.source == Instruction::Location::HL_Indirect) {
 | 
			
		||||
				instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
			}
 | 
			
		||||
			if(instruction.destination == Instruction::Location::HL_Indirect) {
 | 
			
		||||
				instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
			}
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Otherwise, switch either of H or L for I[X/Y]h and I[X/Y]l.
 | 
			
		||||
		if(instruction.source == Instruction::Location::H) {
 | 
			
		||||
			instruction.source = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
 | 
			
		||||
		}
 | 
			
		||||
		if(instruction.source == Instruction::Location::L) {
 | 
			
		||||
			instruction.source = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
 | 
			
		||||
		}
 | 
			
		||||
		if(instruction.destination == Instruction::Location::H) {
 | 
			
		||||
			instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
 | 
			
		||||
		}
 | 
			
		||||
		if(instruction.destination == Instruction::Location::L) {
 | 
			
		||||
			instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Z80Disassembler {
 | 
			
		||||
	static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
 | 
			
		||||
		disassembly.disassembly.internal_calls.insert(entry_point);
 | 
			
		||||
		Accessor accessor(memory, address_mapper, entry_point);
 | 
			
		||||
 | 
			
		||||
		while(!accessor.at_end()) {
 | 
			
		||||
			Instruction instruction;
 | 
			
		||||
			instruction.address = accessor.address();
 | 
			
		||||
 | 
			
		||||
			DisassembleMainPage(accessor, instruction);
 | 
			
		||||
 | 
			
		||||
			// If any memory access was invalid, end disassembly.
 | 
			
		||||
			if(accessor.overrun()) return;
 | 
			
		||||
 | 
			
		||||
			// Store the instruction away.
 | 
			
		||||
			disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
 | 
			
		||||
 | 
			
		||||
			// Update access tables.
 | 
			
		||||
			int access_type =
 | 
			
		||||
				((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
 | 
			
		||||
				((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
 | 
			
		||||
			uint16_t address = static_cast<uint16_t>(instruction.operand);
 | 
			
		||||
			bool is_internal = address_mapper(address) < memory.size();
 | 
			
		||||
			switch(access_type) {
 | 
			
		||||
				default: break;
 | 
			
		||||
				case 1:
 | 
			
		||||
					if(is_internal) {
 | 
			
		||||
						disassembly.disassembly.internal_loads.insert(address);
 | 
			
		||||
					} else {
 | 
			
		||||
						disassembly.disassembly.external_loads.insert(address);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 2:
 | 
			
		||||
					if(is_internal) {
 | 
			
		||||
						disassembly.disassembly.internal_stores.insert(address);
 | 
			
		||||
					} else {
 | 
			
		||||
						disassembly.disassembly.external_stores.insert(address);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 3:
 | 
			
		||||
					if(is_internal) {
 | 
			
		||||
						disassembly.disassembly.internal_modifies.insert(address);
 | 
			
		||||
					} else {
 | 
			
		||||
						disassembly.disassembly.internal_modifies.insert(address);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Add any (potentially) newly discovered entry point.
 | 
			
		||||
			if(	instruction.operation == Instruction::Operation::JP ||
 | 
			
		||||
				instruction.operation == Instruction::Operation::JR ||
 | 
			
		||||
				instruction.operation == Instruction::Operation::CALL ||
 | 
			
		||||
				instruction.operation == Instruction::Operation::RST) {
 | 
			
		||||
				disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
 | 
			
		||||
			if(instruction.condition != Instruction::Condition::None)	continue;
 | 
			
		||||
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::RET)	return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::RETI)	return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::RETN)	return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::JP)		return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::JR)		return;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}	// end of anonymous namespace
 | 
			
		||||
 | 
			
		||||
Disassembly Analyser::Static::Z80::Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	std::vector<uint16_t> entry_points) {
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								Analyser/Static/Disassembler/Z80.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								Analyser/Static/Disassembler/Z80.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
//
 | 
			
		||||
//  Z80.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 30/12/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef StaticAnalyser_Disassembler_Z80_hpp
 | 
			
		||||
#define StaticAnalyser_Disassembler_Z80_hpp
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <set>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Z80 {
 | 
			
		||||
 | 
			
		||||
struct Instruction {
 | 
			
		||||
	/*! The address this instruction starts at. This is a mapped address. */
 | 
			
		||||
	uint16_t address = 0;
 | 
			
		||||
 | 
			
		||||
	/*! The operation this instruction performs. */
 | 
			
		||||
	enum class Operation {
 | 
			
		||||
		NOP,
 | 
			
		||||
		EXAFAFd, EXX, EX,
 | 
			
		||||
		LD, HALT,
 | 
			
		||||
		ADD, ADC, SUB, SBC, AND, XOR, OR, CP,
 | 
			
		||||
		INC, DEC,
 | 
			
		||||
		RLCA, RRCA, RLA, RRA, DAA, CPL, SCF, CCF,
 | 
			
		||||
		RLD, RRD,
 | 
			
		||||
		DJNZ, JR, JP, CALL, RST, RET, RETI, RETN,
 | 
			
		||||
		PUSH, POP,
 | 
			
		||||
		IN, OUT,
 | 
			
		||||
		EI, DI,
 | 
			
		||||
		RLC, RRC, RL, RR, SLA, SRA, SLL, SRL,
 | 
			
		||||
		BIT, RES, SET,
 | 
			
		||||
		LDI, CPI, INI, OUTI,
 | 
			
		||||
		LDD, CPD, IND, OUTD,
 | 
			
		||||
		LDIR, CPIR, INIR, OTIR,
 | 
			
		||||
		LDDR, CPDR, INDR, OTDR,
 | 
			
		||||
		NEG,
 | 
			
		||||
		IM,
 | 
			
		||||
		Invalid
 | 
			
		||||
	} operation = Operation::NOP;
 | 
			
		||||
 | 
			
		||||
	/*! The condition required for this instruction to take effect. */
 | 
			
		||||
	enum class Condition {
 | 
			
		||||
		None, NZ, Z, NC, C, PO, PE, P, M
 | 
			
		||||
	} condition = Condition::None;
 | 
			
		||||
 | 
			
		||||
	enum class Location {
 | 
			
		||||
		B, C, D, E, H, L, HL_Indirect, A, I, R,
 | 
			
		||||
		BC, DE, HL, SP, AF, Operand,
 | 
			
		||||
		IX_Indirect_Offset, IY_Indirect_Offset, IXh, IXl, IYh, IYl,
 | 
			
		||||
		Operand_Indirect,
 | 
			
		||||
		BC_Indirect, DE_Indirect, SP_Indirect,
 | 
			
		||||
		None
 | 
			
		||||
	};
 | 
			
		||||
	/*! The locations of source data for this instruction. */
 | 
			
		||||
	Location source = Location::None;
 | 
			
		||||
	/*! The locations of destination data from this instruction. */
 | 
			
		||||
	Location destination = Location::None;
 | 
			
		||||
	/*! The operand, if any; if this is used then it'll be referenced by either the source or destination location. */
 | 
			
		||||
	int operand = 0;
 | 
			
		||||
	/*! The offset to apply, if any; applies to IX_Indirect_Offset and IY_Indirect_Offset locations. */
 | 
			
		||||
	int offset = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Disassembly {
 | 
			
		||||
	std::map<uint16_t, Instruction> instructions_by_address;
 | 
			
		||||
	std::set<uint16_t> outward_calls;
 | 
			
		||||
	std::set<uint16_t> internal_calls;
 | 
			
		||||
	std::set<uint16_t> external_stores, external_loads, external_modifies;
 | 
			
		||||
	std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Disassembly Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	std::vector<uint16_t> entry_points);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_Disassembler_Z80_hpp */
 | 
			
		||||
							
								
								
									
										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 */
 | 
			
		||||
							
								
								
									
										292
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,292 @@
 | 
			
		||||
//
 | 
			
		||||
//  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 "../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::Target> target(new Analyser::Static::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<Target>> &destination) {
 | 
			
		||||
	// Append targets for any cartridges that look correct.
 | 
			
		||||
	std::vector<std::unique_ptr<Target>> 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;
 | 
			
		||||
 | 
			
		||||
	if(!target->media.empty()) {
 | 
			
		||||
		target->machine = Machine::MSX;
 | 
			
		||||
		target->confidence = 1.0;
 | 
			
		||||
		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 */
 | 
			
		||||
@@ -9,12 +9,12 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "../Disassembler/Disassembler6502.hpp"
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
#include "../Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace StaticAnalyser::Oric;
 | 
			
		||||
using namespace Analyser::Static::Oric;
 | 
			
		||||
 | 
			
		||||
static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations)
 | 
			
		||||
{
 | 
			
		||||
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
 | 
			
		||||
	int score = 0;
 | 
			
		||||
 | 
			
		||||
	for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
			
		||||
@@ -24,8 +24,7 @@ static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const
 | 
			
		||||
	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 = {
 | 
			
		||||
		0x0228,	0x022b,
 | 
			
		||||
		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
			
		||||
@@ -49,8 +48,7 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
 | 
			
		||||
	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 = {
 | 
			
		||||
		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247,
 | 
			
		||||
		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
			
		||||
@@ -75,31 +73,23 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
 | 
			
		||||
	return Score(disassembly, rom_functions, variable_locations);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StaticAnalyser::Oric::AddTargets(
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
 | 
			
		||||
	std::list<StaticAnalyser::Target> &destination)
 | 
			
		||||
{
 | 
			
		||||
	Target target;
 | 
			
		||||
	target.machine = Target::Oric;
 | 
			
		||||
	target.probability = 1.0;
 | 
			
		||||
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
 | 
			
		||||
	std::unique_ptr<Target> target(new Target);
 | 
			
		||||
	target->machine = Machine::Oric;
 | 
			
		||||
	target->confidence = 1.0;
 | 
			
		||||
 | 
			
		||||
	int basic10_votes = 0;
 | 
			
		||||
	int basic11_votes = 0;
 | 
			
		||||
 | 
			
		||||
	for(auto tape : tapes)
 | 
			
		||||
	{
 | 
			
		||||
		std::list<File> tape_files = GetFiles(tape);
 | 
			
		||||
		if(tape_files.size())
 | 
			
		||||
		{
 | 
			
		||||
			for(auto file : tape_files)
 | 
			
		||||
			{
 | 
			
		||||
				if(file.data_type == File::MachineCode)
 | 
			
		||||
				{
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
		if(tape_files.size()) {
 | 
			
		||||
			for(auto file : tape_files) {
 | 
			
		||||
				if(file.data_type == File::MachineCode) {
 | 
			
		||||
					std::vector<uint16_t> entry_points = {file.starting_address};
 | 
			
		||||
					StaticAnalyser::MOS6502::Disassembly disassembly =
 | 
			
		||||
						StaticAnalyser::MOS6502::Disassemble(file.data, file.starting_address, entry_points);
 | 
			
		||||
					Analyser::Static::MOS6502::Disassembly disassembly =
 | 
			
		||||
						Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
 | 
			
		||||
 | 
			
		||||
					int basic10_score = Basic10Score(disassembly);
 | 
			
		||||
					int basic11_score = Basic11Score(disassembly);
 | 
			
		||||
@@ -107,26 +97,23 @@ void StaticAnalyser::Oric::AddTargets(
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			target.tapes.push_back(tape);
 | 
			
		||||
			target.loadingCommand = "CLOAD\"\"\n";
 | 
			
		||||
			target->media.tapes.push_back(tape);
 | 
			
		||||
			target->loading_command = "CLOAD\"\"\n";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// trust that any disk supplied can be handled by the Microdisc. TODO: check.
 | 
			
		||||
	if(!disks.empty())
 | 
			
		||||
	{
 | 
			
		||||
		target.oric.has_microdisc = true;
 | 
			
		||||
		target.disks = disks;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		target.oric.has_microdisc = false;
 | 
			
		||||
	if(!media.disks.empty()) {
 | 
			
		||||
		target->oric.has_microdisc = true;
 | 
			
		||||
		target->media.disks = media.disks;
 | 
			
		||||
	} else {
 | 
			
		||||
		target->oric.has_microdisc = false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: really this should add two targets if not all votes agree
 | 
			
		||||
	target.oric.use_atmos_rom = basic11_votes >= basic10_votes;
 | 
			
		||||
	if(target.oric.has_microdisc) target.oric.use_atmos_rom = true;
 | 
			
		||||
	target->oric.use_atmos_rom = basic11_votes >= basic10_votes;
 | 
			
		||||
	if(target->oric.has_microdisc) target->oric.use_atmos_rom = true;
 | 
			
		||||
 | 
			
		||||
	if(target.tapes.size() || target.disks.size() || target.cartridges.size())
 | 
			
		||||
		destination.push_back(target);
 | 
			
		||||
	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
 | 
			
		||||
		destination.push_back(std::move(target));
 | 
			
		||||
}
 | 
			
		||||
@@ -11,18 +11,14 @@
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Oric {
 | 
			
		||||
 | 
			
		||||
void AddTargets(
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
 | 
			
		||||
	const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
 | 
			
		||||
	std::list<Target> &destination
 | 
			
		||||
);
 | 
			
		||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
@@ -7,18 +7,15 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Parsers/Oric.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Oric.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace StaticAnalyser::Oric;
 | 
			
		||||
using namespace Analyser::Static::Oric;
 | 
			
		||||
 | 
			
		||||
std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
 | 
			
		||||
{
 | 
			
		||||
	std::list<File> files;
 | 
			
		||||
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	Storage::Tape::Oric::Parser parser;
 | 
			
		||||
 | 
			
		||||
	tape->reset();
 | 
			
		||||
	while(!tape->is_at_end())
 | 
			
		||||
	{
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		// sync to next lead-in, check that it's one of three 0x16s
 | 
			
		||||
		bool is_fast = parser.sync_and_get_encoding_speed(tape);
 | 
			
		||||
		int next_bytes[2];
 | 
			
		||||
@@ -29,8 +26,7 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
 | 
			
		||||
 | 
			
		||||
		// get the first byte that isn't a 0x16, check it was a 0x24
 | 
			
		||||
		int byte = 0x16;
 | 
			
		||||
		while(!tape->is_at_end() && byte == 0x16)
 | 
			
		||||
		{
 | 
			
		||||
		while(!tape->is_at_end() && byte == 0x16) {
 | 
			
		||||
			byte = parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		}
 | 
			
		||||
		if(byte != 0x24) continue;
 | 
			
		||||
@@ -41,24 +37,22 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
 | 
			
		||||
 | 
			
		||||
		// get data and launch types
 | 
			
		||||
		File new_file;
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast))
 | 
			
		||||
		{
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast)) {
 | 
			
		||||
			case 0x00:	new_file.data_type = File::ProgramType::BASIC;			break;
 | 
			
		||||
			case 0x80:	new_file.data_type = File::ProgramType::MachineCode;	break;
 | 
			
		||||
			default:	new_file.data_type = File::ProgramType::None;			break;
 | 
			
		||||
		}
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast))
 | 
			
		||||
		{
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast)) {
 | 
			
		||||
			case 0x80:	new_file.launch_type = File::ProgramType::BASIC;		break;
 | 
			
		||||
			case 0xc7:	new_file.launch_type = File::ProgramType::MachineCode;	break;
 | 
			
		||||
			default:	new_file.launch_type = File::ProgramType::None;			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// read end and start addresses
 | 
			
		||||
		new_file.ending_address = (uint16_t)(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.ending_address |= (uint16_t)parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		new_file.starting_address = (uint16_t)(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.starting_address |= (uint16_t)parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		new_file.ending_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
		new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
 | 
			
		||||
		// skip an empty byte
 | 
			
		||||
		parser.get_next_byte(tape, is_fast);
 | 
			
		||||
@@ -66,8 +60,7 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
 | 
			
		||||
		// read file name, up to 16 characters and null terminated
 | 
			
		||||
		char file_name[17];
 | 
			
		||||
		int name_pos = 0;
 | 
			
		||||
		while(name_pos < 16)
 | 
			
		||||
		{
 | 
			
		||||
		while(name_pos < 16) {
 | 
			
		||||
			file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast);
 | 
			
		||||
			if(!file_name[name_pos]) break;
 | 
			
		||||
			name_pos++;
 | 
			
		||||
@@ -76,20 +69,17 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
 | 
			
		||||
		new_file.name = file_name;
 | 
			
		||||
 | 
			
		||||
		// read body
 | 
			
		||||
		size_t body_length = new_file.ending_address - new_file.starting_address + 1;
 | 
			
		||||
		std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
 | 
			
		||||
		new_file.data.reserve(body_length);
 | 
			
		||||
		for(size_t c = 0; c < body_length; c++)
 | 
			
		||||
		{
 | 
			
		||||
			new_file.data.push_back((uint8_t)parser.get_next_byte(tape, is_fast));
 | 
			
		||||
		for(std::size_t c = 0; c < body_length; c++) {
 | 
			
		||||
			new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// only one validation check: was there enough tape?
 | 
			
		||||
		if(!tape->is_at_end())
 | 
			
		||||
		{
 | 
			
		||||
		if(!tape->is_at_end()) {
 | 
			
		||||
			files.push_back(new_file);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	tape->reset();
 | 
			
		||||
 | 
			
		||||
	return files;
 | 
			
		||||
}
 | 
			
		||||
@@ -9,12 +9,13 @@
 | 
			
		||||
#ifndef StaticAnalyser_Oric_Tape_hpp
 | 
			
		||||
#define StaticAnalyser_Oric_Tape_hpp
 | 
			
		||||
 | 
			
		||||
#include "../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include <list>
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace StaticAnalyser {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Oric {
 | 
			
		||||
 | 
			
		||||
struct File {
 | 
			
		||||
@@ -30,8 +31,9 @@ struct File {
 | 
			
		||||
	std::vector<uint8_t> data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										182
									
								
								Analyser/Static/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								Analyser/Static/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
//
 | 
			
		||||
//  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 char *file_name, TargetPlatform::IntType &potential_platforms) {
 | 
			
		||||
	// 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.
 | 
			
		||||
	const char *mixed_case_extension = strrchr(file_name, '.');
 | 
			
		||||
	char *lowercase_extension = nullptr;
 | 
			
		||||
	if(mixed_case_extension) {
 | 
			
		||||
		lowercase_extension = strdup(mixed_case_extension+1);
 | 
			
		||||
		char *parser = lowercase_extension;
 | 
			
		||||
		while(*parser) {
 | 
			
		||||
			*parser = (char)tolower(*parser);
 | 
			
		||||
			parser++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Media result;
 | 
			
		||||
#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(extension, list, class, platforms) \
 | 
			
		||||
	if(!std::strcmp(lowercase_extension, extension))	{	\
 | 
			
		||||
		TryInsert(list, class, platforms)	\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(lowercase_extension) {
 | 
			
		||||
		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::Atari2600)						// 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::AmstradCPC)		// HFE (TODO: plus other target platforms)
 | 
			
		||||
		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(!std::strcmp(lowercase_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::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision)						// 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
 | 
			
		||||
 | 
			
		||||
		// Filter potential platforms as per file preferences, if any.
 | 
			
		||||
 | 
			
		||||
		free(lowercase_extension);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Media Analyser::Static::GetMedia(const char *file_name) {
 | 
			
		||||
	TargetPlatform::IntType throwaway;
 | 
			
		||||
	return GetMediaAndPlatforms(file_name, throwaway);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *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);
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								Analyser/Static/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								Analyser/Static/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
//
 | 
			
		||||
//  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 {
 | 
			
		||||
 | 
			
		||||
enum class Vic20MemoryModel {
 | 
			
		||||
	Unexpanded,
 | 
			
		||||
	EightKB,
 | 
			
		||||
	ThirtyTwoKB
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class Atari2600PagingModel {
 | 
			
		||||
	None,
 | 
			
		||||
	CommaVid,
 | 
			
		||||
	Atari8k,
 | 
			
		||||
	Atari16k,
 | 
			
		||||
	Atari32k,
 | 
			
		||||
	ActivisionStack,
 | 
			
		||||
	ParkerBros,
 | 
			
		||||
	Tigervision,
 | 
			
		||||
	CBSRamPlus,
 | 
			
		||||
	MNetwork,
 | 
			
		||||
	MegaBoy,
 | 
			
		||||
	Pitfall2
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class ZX8081MemoryModel {
 | 
			
		||||
	Unexpanded,
 | 
			
		||||
	SixteenKB,
 | 
			
		||||
	SixtyFourKB
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class AmstradCPCModel {
 | 
			
		||||
	CPC464,
 | 
			
		||||
	CPC664,
 | 
			
		||||
	CPC6128
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	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 {
 | 
			
		||||
	Machine machine;
 | 
			
		||||
	Media media;
 | 
			
		||||
 | 
			
		||||
	float confidence;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	// TODO: this is too C-like a solution; make Target a base class and
 | 
			
		||||
	// turn the following into information held by more specific subclasses.
 | 
			
		||||
	union {
 | 
			
		||||
		struct {
 | 
			
		||||
			bool has_adfs;
 | 
			
		||||
			bool has_dfs;
 | 
			
		||||
			bool should_shift_restart;
 | 
			
		||||
		} acorn;
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			Atari2600PagingModel paging_model;
 | 
			
		||||
			bool uses_superchip;
 | 
			
		||||
		} atari;
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			bool use_atmos_rom;
 | 
			
		||||
			bool has_microdisc;
 | 
			
		||||
		} oric;
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			Vic20MemoryModel memory_model;
 | 
			
		||||
			bool has_c1540;
 | 
			
		||||
		} vic20;
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			ZX8081MemoryModel memory_model;
 | 
			
		||||
			bool isZX81;
 | 
			
		||||
		} zx8081;
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			AmstradCPCModel model;
 | 
			
		||||
		} amstradcpc;
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	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 char *file_name);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Inspects the supplied file and determines the media included.
 | 
			
		||||
*/
 | 
			
		||||
Media GetMedia(const char *file_name);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
							
								
								
									
										67
									
								
								Analyser/Static/ZX8081/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Analyser/Static/ZX8081/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/06/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
 | 
			
		||||
 | 
			
		||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	std::vector<Storage::Data::ZX8081::File> files;
 | 
			
		||||
	Storage::Tape::ZX8081::Parser parser;
 | 
			
		||||
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
 | 
			
		||||
		if(next_file != nullptr) {
 | 
			
		||||
			files.push_back(*next_file);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return files;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms) {
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
 | 
			
		||||
		media.tapes.front()->reset();
 | 
			
		||||
		if(!files.empty()) {
 | 
			
		||||
			std::unique_ptr<Target> target(new Target);
 | 
			
		||||
			target->machine = Machine::ZX8081;
 | 
			
		||||
 | 
			
		||||
			// Guess the machine type from the file only if it isn't already known.
 | 
			
		||||
			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
 | 
			
		||||
				default:
 | 
			
		||||
					target->zx8081.isZX81 = files.front().isZX81;
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case TargetPlatform::ZX80:	target->zx8081.isZX81 = false;	break;
 | 
			
		||||
				case TargetPlatform::ZX81:	target->zx8081.isZX81 = true;	break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/*if(files.front().data.size() > 16384) {
 | 
			
		||||
				target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
 | 
			
		||||
			} else*/ if(files.front().data.size() > 1024) {
 | 
			
		||||
				target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
 | 
			
		||||
			} else {
 | 
			
		||||
				target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
 | 
			
		||||
			}
 | 
			
		||||
			target->media.tapes = media.tapes;
 | 
			
		||||
 | 
			
		||||
			// TODO: how to run software once loaded? Might require a BASIC detokeniser.
 | 
			
		||||
			if(target->zx8081.isZX81) {
 | 
			
		||||
				target->loading_command = "J\"\"\n";
 | 
			
		||||
			} else {
 | 
			
		||||
				target->loading_command = "W\n";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			destination.push_back(std::move(target));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								Analyser/Static/ZX8081/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Analyser/Static/ZX8081/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/06/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef StaticAnalyser_ZX8081_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_ZX8081_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace ZX8081 {
 | 
			
		||||
 | 
			
		||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
							
								
								
									
										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.
 | 
			
		||||
							
								
								
									
										216
									
								
								ClockReceiver/ClockReceiver.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								ClockReceiver/ClockReceiver.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
//
 | 
			
		||||
//  ClockReceiver.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 22/07/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ClockReceiver_hpp
 | 
			
		||||
#define ClockReceiver_hpp
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Informal pattern for all classes that run from a clock cycle:
 | 
			
		||||
 | 
			
		||||
		Each will implement either or both of run_for(Cycles) and run_for(HalfCycles), as
 | 
			
		||||
		is appropriate.
 | 
			
		||||
 | 
			
		||||
		Callers that are accumulating HalfCycles but want to talk to receivers that implement
 | 
			
		||||
		only run_for(Cycles) can use HalfCycle.flush_cycles if they have appropriate storage, or
 | 
			
		||||
		can wrap the receiver in HalfClockReceiver in order automatically to bind half-cycle
 | 
			
		||||
		storage to it.
 | 
			
		||||
 | 
			
		||||
	Alignment rule:
 | 
			
		||||
 | 
			
		||||
		run_for(Cycles) may be called only after an even number of half cycles. E.g. the following
 | 
			
		||||
		sequence will have undefined results:
 | 
			
		||||
 | 
			
		||||
			run_for(HalfCycles(1))
 | 
			
		||||
			run_for(Cycles(1))
 | 
			
		||||
 | 
			
		||||
		An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and
 | 
			
		||||
		run_for(HalfCycles) to use.
 | 
			
		||||
 | 
			
		||||
	Reasoning:
 | 
			
		||||
 | 
			
		||||
		Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles)
 | 
			
		||||
		where there is a need to implement at half-cycle precision but a faster execution
 | 
			
		||||
		path can be offered for full-cycle precision. Those users are permitted to assume
 | 
			
		||||
		phase in run_for(Cycles) and should do so to be compatible with callers that use
 | 
			
		||||
		only run_for(Cycles).
 | 
			
		||||
 | 
			
		||||
	Corollary:
 | 
			
		||||
 | 
			
		||||
		Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half
 | 
			
		||||
		of a full cycle. The second will do the second half. Etc.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides a class that wraps a plain int, providing most of the basic arithmetic and
 | 
			
		||||
	Boolean operators, but forcing callers and receivers to be explicit as to usage.
 | 
			
		||||
*/
 | 
			
		||||
template <class T> class WrappedInt {
 | 
			
		||||
	public:
 | 
			
		||||
		inline WrappedInt(int l) : length_(l) {}
 | 
			
		||||
		inline WrappedInt() : length_(0) {}
 | 
			
		||||
 | 
			
		||||
		inline T &operator =(const T &rhs) {
 | 
			
		||||
			length_ = rhs.length_;
 | 
			
		||||
			return *this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator +=(const T &rhs) {
 | 
			
		||||
			length_ += rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator -=(const T &rhs) {
 | 
			
		||||
			length_ -= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator ++() {
 | 
			
		||||
			++ length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator ++(int) {
 | 
			
		||||
			length_ ++;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator --() {
 | 
			
		||||
			-- length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator --(int) {
 | 
			
		||||
			length_ --;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator %=(const T &rhs) {
 | 
			
		||||
			length_ %= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T &operator &=(const T &rhs) {
 | 
			
		||||
			length_ &= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	}
 | 
			
		||||
		inline T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
		inline T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	}
 | 
			
		||||
		inline T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
		inline T operator -() const						{	return T(- length_);				}
 | 
			
		||||
 | 
			
		||||
		inline bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		}
 | 
			
		||||
		inline bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		}
 | 
			
		||||
		inline bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		}
 | 
			
		||||
		inline bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		}
 | 
			
		||||
		inline bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		}
 | 
			
		||||
		inline bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		}
 | 
			
		||||
 | 
			
		||||
		inline bool operator !() const					{	return !length_;					}
 | 
			
		||||
		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
 | 
			
		||||
 | 
			
		||||
		inline int as_int() const { return length_; }
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Severs from @c this the effect of dividing by @c divisor — @c this will end up with
 | 
			
		||||
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
			
		||||
		*/
 | 
			
		||||
		inline T divide(const T &divisor) {
 | 
			
		||||
			T result(length_ / divisor.length_);
 | 
			
		||||
			length_ %= divisor.length_;
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Flushes the value in @c this. The current value is returned, and the internal value
 | 
			
		||||
			is reset to zero.
 | 
			
		||||
		*/
 | 
			
		||||
		inline T flush() {
 | 
			
		||||
			T result(length_);
 | 
			
		||||
			length_ = 0;
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// operator int() is deliberately not provided, to avoid accidental subtitution of
 | 
			
		||||
		// classes that use this template.
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		int length_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Describes an integer number of whole cycles — pairs of clock signal transitions.
 | 
			
		||||
class Cycles: public WrappedInt<Cycles> {
 | 
			
		||||
	public:
 | 
			
		||||
		inline Cycles(int l) : WrappedInt<Cycles>(l) {}
 | 
			
		||||
		inline Cycles() : WrappedInt<Cycles>() {}
 | 
			
		||||
		inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Describes an integer number of half cycles — single clock signal transitions.
 | 
			
		||||
class HalfCycles: public WrappedInt<HalfCycles> {
 | 
			
		||||
	public:
 | 
			
		||||
		inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
 | 
			
		||||
		inline HalfCycles() : WrappedInt<HalfCycles>() {}
 | 
			
		||||
 | 
			
		||||
		inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
 | 
			
		||||
		inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
 | 
			
		||||
 | 
			
		||||
		/// @returns The number of whole cycles completely covered by this span of half cycles.
 | 
			
		||||
		inline Cycles cycles() {
 | 
			
		||||
			return Cycles(length_ >> 1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
 | 
			
		||||
		inline Cycles flush_cycles() {
 | 
			
		||||
			Cycles result(length_ >> 1);
 | 
			
		||||
			length_ &= 1;
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
 | 
			
		||||
		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
 | 
			
		||||
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
			
		||||
		*/
 | 
			
		||||
		inline Cycles divide_cycles(const Cycles &divisor) {
 | 
			
		||||
			HalfCycles half_divisor = HalfCycles(divisor);
 | 
			
		||||
			Cycles result(length_ / half_divisor.length_);
 | 
			
		||||
			length_ %= half_divisor.length_;
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
 | 
			
		||||
	automatically to gain run_for(HalfCycles).
 | 
			
		||||
*/
 | 
			
		||||
template <class T> class HalfClockReceiver: public T {
 | 
			
		||||
	public:
 | 
			
		||||
		using T::T;
 | 
			
		||||
 | 
			
		||||
		using T::run_for;
 | 
			
		||||
		inline void run_for(const HalfCycles half_cycles) {
 | 
			
		||||
			half_cycles_ += half_cycles;
 | 
			
		||||
			T::run_for(half_cycles_.flush_cycles());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		HalfCycles half_cycles_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif /* ClockReceiver_hpp */
 | 
			
		||||
							
								
								
									
										26
									
								
								ClockReceiver/ForceInline.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								ClockReceiver/ForceInline.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
//
 | 
			
		||||
//  ForceInline.h
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 01/08/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ForceInline_hpp
 | 
			
		||||
#define ForceInline_hpp
 | 
			
		||||
 | 
			
		||||
#ifdef DEBUG
 | 
			
		||||
 | 
			
		||||
#define forceinline
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
#ifdef __GNUC__
 | 
			
		||||
#define forceinline __attribute__((always_inline)) inline
 | 
			
		||||
#elif _MSC_VER
 | 
			
		||||
#define forceinline __forceinline
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif /* ForceInline_h */
 | 
			
		||||
							
								
								
									
										60
									
								
								ClockReceiver/Sleeper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ClockReceiver/Sleeper.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
//
 | 
			
		||||
//  Sleeper.h
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 20/08/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef Sleeper_hpp
 | 
			
		||||
#define Sleeper_hpp
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing
 | 
			
		||||
	any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
 | 
			
		||||
 | 
			
		||||
	A sleeper will signal sleeps and wakes to an observer.
 | 
			
		||||
 | 
			
		||||
	This is intended to allow for performance improvements to machines with components that can sleep. The observer
 | 
			
		||||
	callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
 | 
			
		||||
	into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
 | 
			
		||||
	the saved ::run_fors add up to a substantial amount.
 | 
			
		||||
 | 
			
		||||
	By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
 | 
			
		||||
	not a command.
 | 
			
		||||
*/
 | 
			
		||||
class Sleeper {
 | 
			
		||||
	public:
 | 
			
		||||
		Sleeper() : sleep_observer_(nullptr) {}
 | 
			
		||||
 | 
			
		||||
		class SleepObserver {
 | 
			
		||||
			public:
 | 
			
		||||
				/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
 | 
			
		||||
				virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/// Registers @c observer as the new sleep observer;
 | 
			
		||||
		void set_sleep_observer(SleepObserver *observer) {
 | 
			
		||||
			sleep_observer_ = observer;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// @returns @c true if the component is currently sleeping; @c false otherwise.
 | 
			
		||||
		virtual bool is_sleeping() = 0;
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		/// Provided for subclasses; send sleep announcements to the sleep_observer_.
 | 
			
		||||
		SleepObserver *sleep_observer_;
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
 | 
			
		||||
			if one exists.
 | 
			
		||||
 | 
			
		||||
			@c is_sleeping will be called only if there is an observer.
 | 
			
		||||
		*/
 | 
			
		||||
		void update_sleep_observer() {
 | 
			
		||||
			if(!sleep_observer_) return;
 | 
			
		||||
			sleep_observer_->set_component_is_sleeping(this, is_sleeping());
 | 
			
		||||
		}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif /* Sleeper_h */
 | 
			
		||||
@@ -7,69 +7,35 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "1770.hpp"
 | 
			
		||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
 | 
			
		||||
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace WD;
 | 
			
		||||
 | 
			
		||||
WD1770::Status::Status() :
 | 
			
		||||
	type(Status::One),
 | 
			
		||||
	write_protect(false),
 | 
			
		||||
	record_type(false),
 | 
			
		||||
	spin_up(false),
 | 
			
		||||
	record_not_found(false),
 | 
			
		||||
	crc_error(false),
 | 
			
		||||
	seek_error(false),
 | 
			
		||||
	lost_data(false),
 | 
			
		||||
	data_request(false),
 | 
			
		||||
	interrupt_request(false),
 | 
			
		||||
	busy(false)
 | 
			
		||||
{}
 | 
			
		||||
 | 
			
		||||
WD1770::WD1770(Personality p) :
 | 
			
		||||
	Storage::Disk::Controller(8000000, 16, 300),
 | 
			
		||||
	crc_generator_(0x1021, 0xffff),
 | 
			
		||||
	interesting_event_mask_(Event::Command),
 | 
			
		||||
	resume_point_(0),
 | 
			
		||||
	delay_time_(0),
 | 
			
		||||
	index_hole_count_target_(-1),
 | 
			
		||||
	is_awaiting_marker_value_(false),
 | 
			
		||||
	data_mode_(DataMode::Scanning),
 | 
			
		||||
	delegate_(nullptr),
 | 
			
		||||
	personality_(p),
 | 
			
		||||
	head_is_loaded_(false)
 | 
			
		||||
{
 | 
			
		||||
		Storage::Disk::MFMController(8000000),
 | 
			
		||||
		personality_(p),
 | 
			
		||||
		interesting_event_mask_(static_cast<int>(Event1770::Command)) {
 | 
			
		||||
	set_is_double_density(false);
 | 
			
		||||
	posit_event(Event::Command);
 | 
			
		||||
	posit_event(static_cast<int>(Event1770::Command));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::set_is_double_density(bool is_double_density)
 | 
			
		||||
{
 | 
			
		||||
	is_double_density_ = is_double_density;
 | 
			
		||||
	Storage::Time bit_length;
 | 
			
		||||
	bit_length.length = 1;
 | 
			
		||||
	bit_length.clock_rate = is_double_density ? 500000 : 250000;
 | 
			
		||||
	set_expected_bit_length(bit_length);
 | 
			
		||||
 | 
			
		||||
	if(!is_double_density) is_awaiting_marker_value_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::set_register(int address, uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
	switch(address&3)
 | 
			
		||||
	{
 | 
			
		||||
		case 0:
 | 
			
		||||
		{
 | 
			
		||||
			if((value&0xf0) == 0xd0)
 | 
			
		||||
			{
 | 
			
		||||
				printf("!!!TODO: force interrupt!!!\n");
 | 
			
		||||
				update_status([] (Status &status) {
 | 
			
		||||
					status.type = Status::One;
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
void WD1770::set_register(int address, uint8_t value) {
 | 
			
		||||
	switch(address&3) {
 | 
			
		||||
		case 0: {
 | 
			
		||||
			if((value&0xf0) == 0xd0) {
 | 
			
		||||
				if(value == 0xd0) {
 | 
			
		||||
					// Force interrupt **immediately**.
 | 
			
		||||
					printf("Force interrupt immediately\n");
 | 
			
		||||
					posit_event(static_cast<int>(Event1770::ForceInterrupt));
 | 
			
		||||
				} else {
 | 
			
		||||
					printf("!!!TODO: force interrupt!!!\n");
 | 
			
		||||
					update_status([] (Status &status) {
 | 
			
		||||
						status.type = Status::One;
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				command_ = value;
 | 
			
		||||
				posit_event(Event::Command);
 | 
			
		||||
				posit_event(static_cast<int>(Event1770::Command));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
@@ -84,12 +50,9 @@ void WD1770::set_register(int address, uint8_t value)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t WD1770::get_register(int address)
 | 
			
		||||
{
 | 
			
		||||
	switch(address&3)
 | 
			
		||||
	{
 | 
			
		||||
		default:
 | 
			
		||||
		{
 | 
			
		||||
uint8_t WD1770::get_register(int address) {
 | 
			
		||||
	switch(address&3) {
 | 
			
		||||
		default: {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.interrupt_request = false;
 | 
			
		||||
			});
 | 
			
		||||
@@ -97,11 +60,10 @@ uint8_t WD1770::get_register(int address)
 | 
			
		||||
					(status_.write_protect ? Flag::WriteProtect : 0) |
 | 
			
		||||
					(status_.crc_error ? Flag::CRCError : 0) |
 | 
			
		||||
					(status_.busy ? Flag::Busy : 0);
 | 
			
		||||
			switch(status_.type)
 | 
			
		||||
			{
 | 
			
		||||
			switch(status_.type) {
 | 
			
		||||
				case Status::One:
 | 
			
		||||
					status |=
 | 
			
		||||
						(get_is_track_zero() ? Flag::TrackZero : 0) |
 | 
			
		||||
						(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
 | 
			
		||||
						(status_.seek_error ? Flag::SeekError : 0);
 | 
			
		||||
						// TODO: index hole
 | 
			
		||||
				break;
 | 
			
		||||
@@ -116,15 +78,12 @@ uint8_t WD1770::get_register(int address)
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(!has_motor_on_line())
 | 
			
		||||
			{
 | 
			
		||||
				status |= get_drive_is_ready() ? 0 : Flag::NotReady;
 | 
			
		||||
			if(!has_motor_on_line()) {
 | 
			
		||||
				status |= get_drive().get_is_ready() ? 0 : Flag::NotReady;
 | 
			
		||||
				if(status_.type == Status::One)
 | 
			
		||||
					status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				status |= (get_motor_on() ? Flag::MotorOn : 0);
 | 
			
		||||
			} else {
 | 
			
		||||
				status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0);
 | 
			
		||||
				if(status_.type == Status::One)
 | 
			
		||||
					status |= (status_.spin_up ? Flag::SpinUp : 0);
 | 
			
		||||
			}
 | 
			
		||||
@@ -140,177 +99,31 @@ uint8_t WD1770::get_register(int address)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::run_for_cycles(unsigned int number_of_cycles)
 | 
			
		||||
{
 | 
			
		||||
	Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
 | 
			
		||||
void WD1770::run_for(const Cycles cycles) {
 | 
			
		||||
	Storage::Disk::Controller::run_for(cycles);
 | 
			
		||||
 | 
			
		||||
	if(delay_time_)
 | 
			
		||||
	{
 | 
			
		||||
		if(delay_time_ <= number_of_cycles)
 | 
			
		||||
		{
 | 
			
		||||
	if(delay_time_) {
 | 
			
		||||
		unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
 | 
			
		||||
		if(delay_time_ <= number_of_cycles) {
 | 
			
		||||
			delay_time_ = 0;
 | 
			
		||||
			posit_event(Event::Timer);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			posit_event(static_cast<int>(Event1770::Timer));
 | 
			
		||||
		} else {
 | 
			
		||||
			delay_time_ -= number_of_cycles;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
 | 
			
		||||
{
 | 
			
		||||
	if(data_mode_ == DataMode::Writing) return;
 | 
			
		||||
 | 
			
		||||
	shift_register_ = (shift_register_ << 1) | value;
 | 
			
		||||
	bits_since_token_++;
 | 
			
		||||
 | 
			
		||||
	if(data_mode_ == DataMode::Scanning)
 | 
			
		||||
	{
 | 
			
		||||
		Token::Type token_type = Token::Byte;
 | 
			
		||||
		if(!is_double_density_)
 | 
			
		||||
		{
 | 
			
		||||
			switch(shift_register_ & 0xffff)
 | 
			
		||||
			{
 | 
			
		||||
				case Storage::Encodings::MFM::FMIndexAddressMark:
 | 
			
		||||
					token_type = Token::Index;
 | 
			
		||||
					crc_generator_.reset();
 | 
			
		||||
					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
 | 
			
		||||
				break;
 | 
			
		||||
				case Storage::Encodings::MFM::FMIDAddressMark:
 | 
			
		||||
					token_type = Token::ID;
 | 
			
		||||
					crc_generator_.reset();
 | 
			
		||||
					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
 | 
			
		||||
				break;
 | 
			
		||||
				case Storage::Encodings::MFM::FMDataAddressMark:
 | 
			
		||||
					token_type = Token::Data;
 | 
			
		||||
					crc_generator_.reset();
 | 
			
		||||
					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
 | 
			
		||||
				break;
 | 
			
		||||
				case Storage::Encodings::MFM::FMDeletedDataAddressMark:
 | 
			
		||||
					token_type = Token::DeletedData;
 | 
			
		||||
					crc_generator_.reset();
 | 
			
		||||
					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
 | 
			
		||||
				break;
 | 
			
		||||
				default:
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			switch(shift_register_ & 0xffff)
 | 
			
		||||
			{
 | 
			
		||||
				case Storage::Encodings::MFM::MFMIndexSync:
 | 
			
		||||
					bits_since_token_ = 0;
 | 
			
		||||
					is_awaiting_marker_value_ = true;
 | 
			
		||||
 | 
			
		||||
					token_type = Token::Sync;
 | 
			
		||||
					latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
 | 
			
		||||
				break;
 | 
			
		||||
				case Storage::Encodings::MFM::MFMSync:
 | 
			
		||||
					bits_since_token_ = 0;
 | 
			
		||||
					is_awaiting_marker_value_ = true;
 | 
			
		||||
					crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
 | 
			
		||||
 | 
			
		||||
					token_type = Token::Sync;
 | 
			
		||||
					latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
 | 
			
		||||
				break;
 | 
			
		||||
				default:
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(token_type != Token::Byte)
 | 
			
		||||
		{
 | 
			
		||||
			latest_token_.type = token_type;
 | 
			
		||||
			bits_since_token_ = 0;
 | 
			
		||||
			posit_event(Event::Token);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(bits_since_token_ == 16)
 | 
			
		||||
	{
 | 
			
		||||
		latest_token_.type = Token::Byte;
 | 
			
		||||
		latest_token_.byte_value = (uint8_t)(
 | 
			
		||||
			((shift_register_ & 0x0001) >> 0) |
 | 
			
		||||
			((shift_register_ & 0x0004) >> 1) |
 | 
			
		||||
			((shift_register_ & 0x0010) >> 2) |
 | 
			
		||||
			((shift_register_ & 0x0040) >> 3) |
 | 
			
		||||
			((shift_register_ & 0x0100) >> 4) |
 | 
			
		||||
			((shift_register_ & 0x0400) >> 5) |
 | 
			
		||||
			((shift_register_ & 0x1000) >> 6) |
 | 
			
		||||
			((shift_register_ & 0x4000) >> 7));
 | 
			
		||||
		bits_since_token_ = 0;
 | 
			
		||||
 | 
			
		||||
		if(is_awaiting_marker_value_ && is_double_density_)
 | 
			
		||||
		{
 | 
			
		||||
			is_awaiting_marker_value_ = false;
 | 
			
		||||
			switch(latest_token_.byte_value)
 | 
			
		||||
			{
 | 
			
		||||
				case Storage::Encodings::MFM::IndexAddressByte:
 | 
			
		||||
					latest_token_.type = Token::Index;
 | 
			
		||||
				break;
 | 
			
		||||
				case Storage::Encodings::MFM::IDAddressByte:
 | 
			
		||||
					latest_token_.type = Token::ID;
 | 
			
		||||
				break;
 | 
			
		||||
				case Storage::Encodings::MFM::DataAddressByte:
 | 
			
		||||
					latest_token_.type = Token::Data;
 | 
			
		||||
				break;
 | 
			
		||||
				case Storage::Encodings::MFM::DeletedDataAddressByte:
 | 
			
		||||
					latest_token_.type = Token::DeletedData;
 | 
			
		||||
				break;
 | 
			
		||||
				default: break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		crc_generator_.add(latest_token_.byte_value);
 | 
			
		||||
		posit_event(Event::Token);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::process_index_hole()
 | 
			
		||||
{
 | 
			
		||||
	index_hole_count_++;
 | 
			
		||||
	posit_event(Event::IndexHole);
 | 
			
		||||
	if(index_hole_count_target_ == index_hole_count_)
 | 
			
		||||
	{
 | 
			
		||||
		posit_event(Event::IndexHoleTarget);
 | 
			
		||||
		index_hole_count_target_ = -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// motor power-down
 | 
			
		||||
	if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line())
 | 
			
		||||
	{
 | 
			
		||||
		set_motor_on(false);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// head unload
 | 
			
		||||
	if(index_hole_count_ == 15 && !status_.busy && has_head_load_line())
 | 
			
		||||
	{
 | 
			
		||||
		set_head_load_request(false);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::process_write_completed()
 | 
			
		||||
{
 | 
			
		||||
	posit_event(Event::DataWritten);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
 | 
			
		||||
#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
 | 
			
		||||
#define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
 | 
			
		||||
#define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
 | 
			
		||||
#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; 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 BEGIN_SECTION()	switch(resume_point_) { default:
 | 
			
		||||
#define END_SECTION()	0; }
 | 
			
		||||
#define END_SECTION()	(void)0; }
 | 
			
		||||
 | 
			
		||||
#define READ_ID()	\
 | 
			
		||||
		if(new_event_type == Event::Token)	\
 | 
			
		||||
		{	\
 | 
			
		||||
			if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }	\
 | 
			
		||||
			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte)	\
 | 
			
		||||
			{	\
 | 
			
		||||
				header_[distance_into_section_ - 1] = latest_token_.byte_value;	\
 | 
			
		||||
		if(new_event_type == static_cast<int>(Event::Token)) {	\
 | 
			
		||||
			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }	\
 | 
			
		||||
			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {	\
 | 
			
		||||
				header_[distance_into_section_ - 1] = get_latest_token().byte_value;	\
 | 
			
		||||
				distance_into_section_++;	\
 | 
			
		||||
			}	\
 | 
			
		||||
		}
 | 
			
		||||
@@ -323,7 +136,7 @@ void WD1770::process_write_completed()
 | 
			
		||||
		set_motor_on(true);	\
 | 
			
		||||
		index_hole_count_ = 0;	\
 | 
			
		||||
		index_hole_count_target_ = 6;	\
 | 
			
		||||
		WAIT_FOR_EVENT(Event::IndexHoleTarget);	\
 | 
			
		||||
		WAIT_FOR_EVENT(Event1770::IndexHoleTarget);	\
 | 
			
		||||
		status_.spin_up = true;
 | 
			
		||||
 | 
			
		||||
//     +--------+----------+-------------------------+
 | 
			
		||||
@@ -343,18 +156,45 @@ void WD1770::process_write_completed()
 | 
			
		||||
//     !	 4  ! Forc int !  1  1	0  1 i3 i2 i1 i0 !
 | 
			
		||||
//     +--------+----------+-------------------------+
 | 
			
		||||
 | 
			
		||||
void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
{
 | 
			
		||||
	if(!(interesting_event_mask_ & (int)new_event_type)) return;
 | 
			
		||||
	interesting_event_mask_ &= ~new_event_type;
 | 
			
		||||
void WD1770::posit_event(int new_event_type) {
 | 
			
		||||
	if(new_event_type == static_cast<int>(Event::IndexHole)) {
 | 
			
		||||
		index_hole_count_++;
 | 
			
		||||
		if(index_hole_count_target_ == index_hole_count_) {
 | 
			
		||||
			posit_event(static_cast<int>(Event1770::IndexHoleTarget));
 | 
			
		||||
			index_hole_count_target_ = -1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// motor power-down
 | 
			
		||||
		if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
 | 
			
		||||
			set_motor_on(false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// head unload
 | 
			
		||||
		if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
 | 
			
		||||
			set_head_load_request(false);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
 | 
			
		||||
		interesting_event_mask_ = 0;
 | 
			
		||||
		resume_point_ = 0;
 | 
			
		||||
		update_status([] (Status &status) {
 | 
			
		||||
			status.type = Status::One;
 | 
			
		||||
			status.data_request = false;
 | 
			
		||||
		});
 | 
			
		||||
	} else {
 | 
			
		||||
		if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
 | 
			
		||||
		interesting_event_mask_ &= ~new_event_type;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Status new_status;
 | 
			
		||||
	BEGIN_SECTION()
 | 
			
		||||
 | 
			
		||||
	// Wait for a new command, branch to the appropriate handler.
 | 
			
		||||
	case 0:
 | 
			
		||||
	wait_for_command:
 | 
			
		||||
		printf("Idle...\n");
 | 
			
		||||
		data_mode_ = DataMode::Scanning;
 | 
			
		||||
		set_data_mode(DataMode::Scanning);
 | 
			
		||||
		index_hole_count_ = 0;
 | 
			
		||||
 | 
			
		||||
		update_status([] (Status &status) {
 | 
			
		||||
@@ -362,7 +202,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
			status.interrupt_request = true;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		WAIT_FOR_EVENT(Event::Command);
 | 
			
		||||
		WAIT_FOR_EVENT(Event1770::Command);
 | 
			
		||||
 | 
			
		||||
		update_status([] (Status &status) {
 | 
			
		||||
			status.busy = true;
 | 
			
		||||
@@ -405,18 +245,17 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		goto begin_type1_load_head;
 | 
			
		||||
 | 
			
		||||
	begin_type1_load_head:
 | 
			
		||||
		if(!(command_&0x08))
 | 
			
		||||
		{
 | 
			
		||||
		if(!(command_&0x08)) {
 | 
			
		||||
			set_head_load_request(false);
 | 
			
		||||
			goto test_type1_type;
 | 
			
		||||
		}
 | 
			
		||||
		set_head_load_request(true);
 | 
			
		||||
		if(head_is_loaded_) goto test_type1_type;
 | 
			
		||||
		WAIT_FOR_EVENT(Event::HeadLoad);
 | 
			
		||||
		WAIT_FOR_EVENT(Event1770::HeadLoad);
 | 
			
		||||
		goto test_type1_type;
 | 
			
		||||
 | 
			
		||||
	begin_type1_spin_up:
 | 
			
		||||
		if((command_&0x08) || get_motor_on()) goto test_type1_type;
 | 
			
		||||
		if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
 | 
			
		||||
		SPIN_UP();
 | 
			
		||||
 | 
			
		||||
	test_type1_type:
 | 
			
		||||
@@ -426,8 +265,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		if((command_ >> 5) != 0) goto perform_step_command;
 | 
			
		||||
 | 
			
		||||
		// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00.
 | 
			
		||||
		if(!(command_ & 0x10))
 | 
			
		||||
		{
 | 
			
		||||
		if(!(command_ & 0x10)) {
 | 
			
		||||
			track_ = 0xff;
 | 
			
		||||
			data_ = 0;
 | 
			
		||||
		}
 | 
			
		||||
@@ -440,15 +278,13 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		if(step_direction_) track_++; else track_--;
 | 
			
		||||
 | 
			
		||||
	perform_step:
 | 
			
		||||
		if(!step_direction_ && get_is_track_zero())
 | 
			
		||||
		{
 | 
			
		||||
		if(!step_direction_ && get_drive().get_is_track_zero()) {
 | 
			
		||||
			track_ = 0;
 | 
			
		||||
			goto verify;
 | 
			
		||||
		}
 | 
			
		||||
		step(step_direction_ ? 1 : -1);
 | 
			
		||||
		int time_to_wait;
 | 
			
		||||
		switch(command_ & 3)
 | 
			
		||||
		{
 | 
			
		||||
		get_drive().step(step_direction_ ? 1 : -1);
 | 
			
		||||
		unsigned int time_to_wait;
 | 
			
		||||
		switch(command_ & 3) {
 | 
			
		||||
			default:
 | 
			
		||||
			case 0: time_to_wait = 6;	break;
 | 
			
		||||
			case 1: time_to_wait = 12;	break;
 | 
			
		||||
@@ -464,8 +300,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		goto perform_step;
 | 
			
		||||
 | 
			
		||||
	verify:
 | 
			
		||||
		if(!(command_ & 0x04))
 | 
			
		||||
		{
 | 
			
		||||
		if(!(command_ & 0x04)) {
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -473,29 +308,25 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		distance_into_section_ = 0;
 | 
			
		||||
 | 
			
		||||
	verify_read_data:
 | 
			
		||||
		WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
 | 
			
		||||
		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
 | 
			
		||||
		READ_ID();
 | 
			
		||||
 | 
			
		||||
		if(index_hole_count_ == 6)
 | 
			
		||||
		{
 | 
			
		||||
		if(index_hole_count_ == 6) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.seek_error = true;
 | 
			
		||||
			});
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
		if(distance_into_section_ == 7)
 | 
			
		||||
		{
 | 
			
		||||
			data_mode_ = DataMode::Scanning;
 | 
			
		||||
			if(crc_generator_.get_value())
 | 
			
		||||
			{
 | 
			
		||||
		if(distance_into_section_ == 7) {
 | 
			
		||||
			set_data_mode(DataMode::Scanning);
 | 
			
		||||
			if(get_crc_generator().get_value()) {
 | 
			
		||||
				update_status([] (Status &status) {
 | 
			
		||||
					status.crc_error = true;
 | 
			
		||||
				});
 | 
			
		||||
				goto verify_read_data;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(header_[0] == track_)
 | 
			
		||||
			{
 | 
			
		||||
			if(header_[0] == track_) {
 | 
			
		||||
				printf("Reached track %d\n", track_);
 | 
			
		||||
				update_status([] (Status &status) {
 | 
			
		||||
					status.crc_error = false;
 | 
			
		||||
@@ -539,11 +370,11 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
	begin_type2_load_head:
 | 
			
		||||
		set_head_load_request(true);
 | 
			
		||||
		if(head_is_loaded_) goto test_type2_delay;
 | 
			
		||||
		WAIT_FOR_EVENT(Event::HeadLoad);
 | 
			
		||||
		WAIT_FOR_EVENT(Event1770::HeadLoad);
 | 
			
		||||
		goto test_type2_delay;
 | 
			
		||||
 | 
			
		||||
	begin_type2_spin_up:
 | 
			
		||||
		if(get_motor_on()) goto test_type2_delay;
 | 
			
		||||
		if(get_drive().get_motor_on()) goto test_type2_delay;
 | 
			
		||||
		// Perform spin up.
 | 
			
		||||
		SPIN_UP();
 | 
			
		||||
 | 
			
		||||
@@ -553,8 +384,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		WAIT_FOR_TIME(30);
 | 
			
		||||
 | 
			
		||||
	test_type2_write_protection:
 | 
			
		||||
		if(command_&0x20 && get_drive_is_read_only())
 | 
			
		||||
		{
 | 
			
		||||
		if(command_&0x20 && get_drive().get_is_read_only()) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.write_protect = true;
 | 
			
		||||
			});
 | 
			
		||||
@@ -562,27 +392,23 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	type2_get_header:
 | 
			
		||||
		WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
 | 
			
		||||
		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
 | 
			
		||||
		READ_ID();
 | 
			
		||||
 | 
			
		||||
		if(index_hole_count_ == 5)
 | 
			
		||||
		{
 | 
			
		||||
		if(index_hole_count_ == 5) {
 | 
			
		||||
			printf("Failed to find sector %d\n", sector_);
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.record_not_found = true;
 | 
			
		||||
			});
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
		if(distance_into_section_ == 7)
 | 
			
		||||
		{
 | 
			
		||||
		if(distance_into_section_ == 7) {
 | 
			
		||||
			printf("Considering %d/%d\n", header_[0], header_[2]);
 | 
			
		||||
			data_mode_ = DataMode::Scanning;
 | 
			
		||||
			if(header_[0] == track_ && header_[2] == sector_ &&
 | 
			
		||||
				(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1]))
 | 
			
		||||
			{
 | 
			
		||||
			set_data_mode(DataMode::Scanning);
 | 
			
		||||
			if(		header_[0] == track_ && header_[2] == sector_ &&
 | 
			
		||||
					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
 | 
			
		||||
				printf("Found %d/%d\n", header_[0], header_[2]);
 | 
			
		||||
				if(crc_generator_.get_value())
 | 
			
		||||
				{
 | 
			
		||||
				if(get_crc_generator().get_value()) {
 | 
			
		||||
					printf("CRC error; back to searching\n");
 | 
			
		||||
					update_status([] (Status &status) {
 | 
			
		||||
						status.crc_error = true;
 | 
			
		||||
@@ -607,28 +433,26 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
	type2_read_data:
 | 
			
		||||
		WAIT_FOR_EVENT(Event::Token);
 | 
			
		||||
		// TODO: timeout
 | 
			
		||||
		if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData)
 | 
			
		||||
		{
 | 
			
		||||
		if(get_latest_token().type == Token::Data || get_latest_token().type == Token::DeletedData) {
 | 
			
		||||
			update_status([this] (Status &status) {
 | 
			
		||||
				status.record_type = (latest_token_.type == Token::DeletedData);
 | 
			
		||||
				status.record_type = (get_latest_token().type == Token::DeletedData);
 | 
			
		||||
			});
 | 
			
		||||
			distance_into_section_ = 0;
 | 
			
		||||
			data_mode_ = DataMode::Reading;
 | 
			
		||||
			set_data_mode(DataMode::Reading);
 | 
			
		||||
			goto type2_read_byte;
 | 
			
		||||
		}
 | 
			
		||||
		goto type2_read_data;
 | 
			
		||||
 | 
			
		||||
	type2_read_byte:
 | 
			
		||||
		WAIT_FOR_EVENT(Event::Token);
 | 
			
		||||
		if(latest_token_.type != Token::Byte) goto type2_read_byte;
 | 
			
		||||
		data_ = latest_token_.byte_value;
 | 
			
		||||
		if(get_latest_token().type != Token::Byte) goto type2_read_byte;
 | 
			
		||||
		data_ = get_latest_token().byte_value;
 | 
			
		||||
		update_status([] (Status &status) {
 | 
			
		||||
			status.lost_data |= status.data_request;
 | 
			
		||||
			status.data_request = true;
 | 
			
		||||
		});
 | 
			
		||||
		distance_into_section_++;
 | 
			
		||||
		if(distance_into_section_ == 128 << header_[3])
 | 
			
		||||
		{
 | 
			
		||||
		if(distance_into_section_ == 128 << header_[3]) {
 | 
			
		||||
			distance_into_section_ = 0;
 | 
			
		||||
			goto type2_check_crc;
 | 
			
		||||
		}
 | 
			
		||||
@@ -636,13 +460,11 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
 | 
			
		||||
	type2_check_crc:
 | 
			
		||||
		WAIT_FOR_EVENT(Event::Token);
 | 
			
		||||
		if(latest_token_.type != Token::Byte) goto type2_read_byte;
 | 
			
		||||
		header_[distance_into_section_] = latest_token_.byte_value;
 | 
			
		||||
		if(get_latest_token().type != Token::Byte) goto type2_read_byte;
 | 
			
		||||
		header_[distance_into_section_] = get_latest_token().byte_value;
 | 
			
		||||
		distance_into_section_++;
 | 
			
		||||
		if(distance_into_section_ == 2)
 | 
			
		||||
		{
 | 
			
		||||
			if(crc_generator_.get_value())
 | 
			
		||||
			{
 | 
			
		||||
		if(distance_into_section_ == 2) {
 | 
			
		||||
			if(get_crc_generator().get_value()) {
 | 
			
		||||
				printf("CRC error; terminating\n");
 | 
			
		||||
				update_status([this] (Status &status) {
 | 
			
		||||
					status.crc_error = true;
 | 
			
		||||
@@ -650,12 +472,11 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
				goto wait_for_command;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(command_ & 0x10)
 | 
			
		||||
			{
 | 
			
		||||
			if(command_ & 0x10) {
 | 
			
		||||
				sector_++;
 | 
			
		||||
				goto test_type2_write_protection;
 | 
			
		||||
			}
 | 
			
		||||
			printf("Read sector %d\n", sector_);
 | 
			
		||||
			printf("Finished reading sector %d\n", sector_);
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
		goto type2_check_crc;
 | 
			
		||||
@@ -667,37 +488,31 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
			status.data_request = true;
 | 
			
		||||
		});
 | 
			
		||||
		WAIT_FOR_BYTES(9);
 | 
			
		||||
		if(status_.data_request)
 | 
			
		||||
		{
 | 
			
		||||
		if(status_.data_request) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.lost_data = true;
 | 
			
		||||
			});
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
		WAIT_FOR_BYTES(1);
 | 
			
		||||
		if(is_double_density_)
 | 
			
		||||
		{
 | 
			
		||||
		if(get_is_double_density()) {
 | 
			
		||||
			WAIT_FOR_BYTES(11);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data_mode_ = DataMode::Writing;
 | 
			
		||||
		begin_writing();
 | 
			
		||||
		for(int c = 0; c < (is_double_density_ ? 12 : 6); c++)
 | 
			
		||||
		{
 | 
			
		||||
		set_data_mode(DataMode::Writing);
 | 
			
		||||
		begin_writing(false);
 | 
			
		||||
		for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) {
 | 
			
		||||
			write_byte(0);
 | 
			
		||||
		}
 | 
			
		||||
		WAIT_FOR_EVENT(Event::DataWritten);
 | 
			
		||||
 | 
			
		||||
		if(is_double_density_)
 | 
			
		||||
		{
 | 
			
		||||
			crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
 | 
			
		||||
		if(get_is_double_density()) {
 | 
			
		||||
			get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
 | 
			
		||||
			for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
 | 
			
		||||
			write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			crc_generator_.reset();
 | 
			
		||||
			crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
 | 
			
		||||
		} else {
 | 
			
		||||
			get_crc_generator().reset();
 | 
			
		||||
			get_crc_generator().add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
 | 
			
		||||
			write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -715,8 +530,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		*/
 | 
			
		||||
		write_byte(data_);
 | 
			
		||||
		distance_into_section_++;
 | 
			
		||||
		if(distance_into_section_ == 128 << header_[3])
 | 
			
		||||
		{
 | 
			
		||||
		if(distance_into_section_ == 128 << header_[3]) {
 | 
			
		||||
			goto type2_write_crc;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -724,8 +538,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
			status.data_request = true;
 | 
			
		||||
		});
 | 
			
		||||
		WAIT_FOR_EVENT(Event::DataWritten);
 | 
			
		||||
		if(status_.data_request)
 | 
			
		||||
		{
 | 
			
		||||
		if(status_.data_request) {
 | 
			
		||||
			end_writing();
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.lost_data = true;
 | 
			
		||||
@@ -736,17 +549,12 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		goto type2_write_loop;
 | 
			
		||||
 | 
			
		||||
	type2_write_crc:
 | 
			
		||||
		{
 | 
			
		||||
			uint16_t crc = crc_generator_.get_value();
 | 
			
		||||
			write_byte(crc >> 8);
 | 
			
		||||
			write_byte(crc & 0xff);
 | 
			
		||||
		}
 | 
			
		||||
		write_crc();
 | 
			
		||||
		write_byte(0xff);
 | 
			
		||||
		WAIT_FOR_EVENT(Event::DataWritten);
 | 
			
		||||
		end_writing();
 | 
			
		||||
 | 
			
		||||
		if(command_ & 0x10)
 | 
			
		||||
		{
 | 
			
		||||
		if(command_ & 0x10) {
 | 
			
		||||
			sector_++;
 | 
			
		||||
			goto test_type2_write_protection;
 | 
			
		||||
		}
 | 
			
		||||
@@ -780,11 +588,11 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
	begin_type3_load_head:
 | 
			
		||||
		set_head_load_request(true);
 | 
			
		||||
		if(head_is_loaded_) goto type3_test_delay;
 | 
			
		||||
		WAIT_FOR_EVENT(Event::HeadLoad);
 | 
			
		||||
		WAIT_FOR_EVENT(Event1770::HeadLoad);
 | 
			
		||||
		goto type3_test_delay;
 | 
			
		||||
 | 
			
		||||
	begin_type3_spin_up:
 | 
			
		||||
		if((command_&0x08) || get_motor_on()) goto type3_test_delay;
 | 
			
		||||
		if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay;
 | 
			
		||||
		SPIN_UP();
 | 
			
		||||
 | 
			
		||||
	type3_test_delay:
 | 
			
		||||
@@ -801,30 +609,25 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		distance_into_section_ = 0;
 | 
			
		||||
 | 
			
		||||
	read_address_get_header:
 | 
			
		||||
		WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
 | 
			
		||||
		if(new_event_type == Event::Token)
 | 
			
		||||
		{
 | 
			
		||||
			if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }
 | 
			
		||||
			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte)
 | 
			
		||||
			{
 | 
			
		||||
				if(status_.data_request)
 | 
			
		||||
				{
 | 
			
		||||
		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
 | 
			
		||||
		if(new_event_type == static_cast<int>(Event::Token)) {
 | 
			
		||||
			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
 | 
			
		||||
			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
 | 
			
		||||
				if(status_.data_request) {
 | 
			
		||||
					update_status([] (Status &status) {
 | 
			
		||||
						status.lost_data = true;
 | 
			
		||||
					});
 | 
			
		||||
					goto wait_for_command;
 | 
			
		||||
				}
 | 
			
		||||
				header_[distance_into_section_ - 1] = data_ = latest_token_.byte_value;
 | 
			
		||||
				header_[distance_into_section_ - 1] = data_ = get_latest_token().byte_value;
 | 
			
		||||
				track_ = header_[0];
 | 
			
		||||
				update_status([] (Status &status) {
 | 
			
		||||
					status.data_request = true;
 | 
			
		||||
				});
 | 
			
		||||
				distance_into_section_++;
 | 
			
		||||
 | 
			
		||||
				if(distance_into_section_ == 7)
 | 
			
		||||
				{
 | 
			
		||||
					if(crc_generator_.get_value())
 | 
			
		||||
					{
 | 
			
		||||
				if(distance_into_section_ == 7) {
 | 
			
		||||
					if(get_crc_generator().get_value()) {
 | 
			
		||||
						update_status([] (Status &status) {
 | 
			
		||||
							status.crc_error = true;
 | 
			
		||||
						});
 | 
			
		||||
@@ -834,8 +637,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(index_hole_count_ == 6)
 | 
			
		||||
		{
 | 
			
		||||
		if(index_hole_count_ == 6) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.record_not_found = true;
 | 
			
		||||
			});
 | 
			
		||||
@@ -848,19 +650,17 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
		index_hole_count_ = 0;
 | 
			
		||||
 | 
			
		||||
	read_track_read_byte:
 | 
			
		||||
		WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
 | 
			
		||||
		if(index_hole_count_)
 | 
			
		||||
		{
 | 
			
		||||
		WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
 | 
			
		||||
		if(index_hole_count_) {
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
		if(status_.data_request)
 | 
			
		||||
		{
 | 
			
		||||
		if(status_.data_request) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.lost_data = true;
 | 
			
		||||
			});
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
		data_ = latest_token_.byte_value;
 | 
			
		||||
		data_ = get_latest_token().byte_value;
 | 
			
		||||
		update_status([] (Status &status) {
 | 
			
		||||
			status.data_request = true;
 | 
			
		||||
		});
 | 
			
		||||
@@ -872,9 +672,7 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
			status.lost_data = false;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
	write_track_test_write_protect:
 | 
			
		||||
		if(get_drive_is_read_only())
 | 
			
		||||
		{
 | 
			
		||||
		if(get_drive().get_is_read_only()) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.write_protect = true;
 | 
			
		||||
			});
 | 
			
		||||
@@ -885,49 +683,41 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
			status.data_request = true;
 | 
			
		||||
		});
 | 
			
		||||
		WAIT_FOR_BYTES(3);
 | 
			
		||||
		if(status_.data_request)
 | 
			
		||||
		{
 | 
			
		||||
		if(status_.data_request) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.lost_data = true;
 | 
			
		||||
			});
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		WAIT_FOR_EVENT(Event::IndexHoleTarget);
 | 
			
		||||
		begin_writing();
 | 
			
		||||
		WAIT_FOR_EVENT(Event1770::IndexHoleTarget);
 | 
			
		||||
		begin_writing(true);
 | 
			
		||||
		index_hole_count_ = 0;
 | 
			
		||||
 | 
			
		||||
	write_track_write_loop:
 | 
			
		||||
		if(is_double_density_)
 | 
			
		||||
		{
 | 
			
		||||
			switch(data_)
 | 
			
		||||
			{
 | 
			
		||||
		if(get_is_double_density()) {
 | 
			
		||||
			switch(data_) {
 | 
			
		||||
				case 0xf5:
 | 
			
		||||
					write_raw_short(Storage::Encodings::MFM::MFMSync);
 | 
			
		||||
					crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
 | 
			
		||||
					get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
 | 
			
		||||
				break;
 | 
			
		||||
				case 0xf6:
 | 
			
		||||
					write_raw_short(Storage::Encodings::MFM::MFMIndexSync);
 | 
			
		||||
				break;
 | 
			
		||||
				case 0xff: {
 | 
			
		||||
					uint16_t crc = crc_generator_.get_value();
 | 
			
		||||
					write_byte(crc >> 8);
 | 
			
		||||
					write_byte(crc & 0xff);
 | 
			
		||||
				} break;
 | 
			
		||||
				case 0xff:
 | 
			
		||||
					write_crc();
 | 
			
		||||
				break;
 | 
			
		||||
				default:
 | 
			
		||||
					write_byte(data_);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			switch(data_)
 | 
			
		||||
			{
 | 
			
		||||
		} else {
 | 
			
		||||
			switch(data_) {
 | 
			
		||||
				case 0xf8: case 0xf9: case 0xfa: case 0xfb:
 | 
			
		||||
				case 0xfd: case 0xfe:
 | 
			
		||||
					// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
 | 
			
		||||
					write_raw_short(
 | 
			
		||||
						(uint16_t)(
 | 
			
		||||
						static_cast<uint16_t>(
 | 
			
		||||
							0xa022 |
 | 
			
		||||
							((data_ & 0x80) << 7) |
 | 
			
		||||
							((data_ & 0x40) << 6) |
 | 
			
		||||
@@ -939,17 +729,15 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
							(data_ & 0x01)
 | 
			
		||||
						)
 | 
			
		||||
					);
 | 
			
		||||
					crc_generator_.reset();
 | 
			
		||||
					crc_generator_.add(data_);
 | 
			
		||||
					get_crc_generator().reset();
 | 
			
		||||
					get_crc_generator().add(data_);
 | 
			
		||||
				break;
 | 
			
		||||
				case 0xfc:
 | 
			
		||||
					write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark);
 | 
			
		||||
				break;
 | 
			
		||||
				case 0xf7: {
 | 
			
		||||
					uint16_t crc = crc_generator_.get_value();
 | 
			
		||||
					write_byte(crc >> 8);
 | 
			
		||||
					write_byte(crc & 0xff);
 | 
			
		||||
				} break;
 | 
			
		||||
				case 0xf7:
 | 
			
		||||
					write_crc();
 | 
			
		||||
				break;
 | 
			
		||||
				default:
 | 
			
		||||
					write_byte(data_);
 | 
			
		||||
				break;
 | 
			
		||||
@@ -960,16 +748,14 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
			status.data_request = true;
 | 
			
		||||
		});
 | 
			
		||||
		WAIT_FOR_EVENT(Event::DataWritten);
 | 
			
		||||
		if(status_.data_request)
 | 
			
		||||
		{
 | 
			
		||||
		if(status_.data_request) {
 | 
			
		||||
			update_status([] (Status &status) {
 | 
			
		||||
				status.lost_data = true;
 | 
			
		||||
			});
 | 
			
		||||
			end_writing();
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
		if(index_hole_count_)
 | 
			
		||||
		{
 | 
			
		||||
		if(index_hole_count_) {
 | 
			
		||||
			end_writing();
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
		}
 | 
			
		||||
@@ -979,10 +765,8 @@ void WD1770::posit_event(Event new_event_type)
 | 
			
		||||
	END_SECTION()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::update_status(std::function<void(Status &)> updater)
 | 
			
		||||
{
 | 
			
		||||
	if(delegate_)
 | 
			
		||||
	{
 | 
			
		||||
void WD1770::update_status(std::function<void(Status &)> updater) {
 | 
			
		||||
	if(delegate_) {
 | 
			
		||||
		Status old_status = status_;
 | 
			
		||||
		updater(status_);
 | 
			
		||||
		bool did_change =
 | 
			
		||||
@@ -994,38 +778,9 @@ void WD1770::update_status(std::function<void(Status &)> updater)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::set_head_load_request(bool head_load) {}
 | 
			
		||||
void WD1770::set_motor_on(bool motor_on) {}
 | 
			
		||||
 | 
			
		||||
void WD1770::set_head_loaded(bool head_loaded)
 | 
			
		||||
{
 | 
			
		||||
void WD1770::set_head_loaded(bool head_loaded) {
 | 
			
		||||
	head_is_loaded_ = head_loaded;
 | 
			
		||||
	if(head_loaded) posit_event(Event::HeadLoad);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::write_bit(int bit)
 | 
			
		||||
{
 | 
			
		||||
	if(is_double_density_)
 | 
			
		||||
	{
 | 
			
		||||
		Controller::write_bit(!bit && !last_bit_);
 | 
			
		||||
		Controller::write_bit(!!bit);
 | 
			
		||||
		last_bit_ = bit;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		Controller::write_bit(true);
 | 
			
		||||
		Controller::write_bit(!!bit);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::write_byte(uint8_t byte)
 | 
			
		||||
{
 | 
			
		||||
	for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
 | 
			
		||||
	crc_generator_.add(byte);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WD1770::write_raw_short(uint16_t value)
 | 
			
		||||
{
 | 
			
		||||
	for(int c = 0; c < 16; c++)
 | 
			
		||||
	{
 | 
			
		||||
		Controller::write_bit(!!((value << c)&0x8000));
 | 
			
		||||
	}
 | 
			
		||||
	if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,26 +9,41 @@
 | 
			
		||||
#ifndef _770_hpp
 | 
			
		||||
#define _770_hpp
 | 
			
		||||
 | 
			
		||||
#include "../../Storage/Disk/DiskController.hpp"
 | 
			
		||||
#include "../../NumberTheory/CRC.hpp"
 | 
			
		||||
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
 | 
			
		||||
 | 
			
		||||
namespace WD {
 | 
			
		||||
 | 
			
		||||
class WD1770: public Storage::Disk::Controller {
 | 
			
		||||
/*!
 | 
			
		||||
	Provides an emulation of various Western Digital drive controllers, including the
 | 
			
		||||
	WD1770, WD1772, FDC1773 and FDC1793.
 | 
			
		||||
*/
 | 
			
		||||
class WD1770: public Storage::Disk::MFMController {
 | 
			
		||||
	public:
 | 
			
		||||
		enum Personality {
 | 
			
		||||
			P1770,	// implies automatic motor-on management with Type 2 commands offering a spin-up disable
 | 
			
		||||
			P1770,	// implies automatic motor-on management, with Type 2 commands offering a spin-up disable
 | 
			
		||||
			P1772,	// as per the 1770, with different stepping rates
 | 
			
		||||
			P1773,	// implements the side number-testing logic of the 1793; omits spin-up/loading logic
 | 
			
		||||
			P1793	// implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Constructs an instance of the drive controller that behaves according to personality @c p.
 | 
			
		||||
			@param p The type of controller to emulate.
 | 
			
		||||
		*/
 | 
			
		||||
		WD1770(Personality p);
 | 
			
		||||
 | 
			
		||||
		void set_is_double_density(bool is_double_density);
 | 
			
		||||
		/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
 | 
			
		||||
		using Storage::Disk::MFMController::set_is_double_density;
 | 
			
		||||
 | 
			
		||||
		/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
 | 
			
		||||
		void set_register(int address, uint8_t value);
 | 
			
		||||
 | 
			
		||||
		/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
 | 
			
		||||
		uint8_t get_register(int address);
 | 
			
		||||
 | 
			
		||||
		void run_for_cycles(unsigned int number_of_cycles);
 | 
			
		||||
		/// Runs the controller for @c number_of_cycles cycles.
 | 
			
		||||
		void run_for(const Cycles cycles);
 | 
			
		||||
		using Storage::Disk::Controller::run_for;
 | 
			
		||||
 | 
			
		||||
		enum Flag: uint8_t {
 | 
			
		||||
			NotReady		= 0x80,
 | 
			
		||||
@@ -47,8 +62,12 @@ class WD1770: public Storage::Disk::Controller {
 | 
			
		||||
			Busy			= 0x01
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/// @returns The current value of the IRQ line output.
 | 
			
		||||
		inline bool get_interrupt_request_line()		{	return status_.interrupt_request;	}
 | 
			
		||||
 | 
			
		||||
		/// @returns The current value of the DRQ line output.
 | 
			
		||||
		inline bool get_data_request_line()				{	return status_.data_request;		}
 | 
			
		||||
 | 
			
		||||
		class Delegate {
 | 
			
		||||
			public:
 | 
			
		||||
				virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
 | 
			
		||||
@@ -57,6 +76,7 @@ class WD1770: public Storage::Disk::Controller {
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		virtual void set_head_load_request(bool head_load);
 | 
			
		||||
		virtual void set_motor_on(bool motor_on);
 | 
			
		||||
		void set_head_loaded(bool head_loaded);
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
@@ -65,20 +85,19 @@ class WD1770: public Storage::Disk::Controller {
 | 
			
		||||
		inline bool has_head_load_line() { return (personality_ == P1793 ); }
 | 
			
		||||
 | 
			
		||||
		struct Status {
 | 
			
		||||
			Status();
 | 
			
		||||
			bool write_protect;
 | 
			
		||||
			bool record_type;
 | 
			
		||||
			bool spin_up;
 | 
			
		||||
			bool record_not_found;
 | 
			
		||||
			bool crc_error;
 | 
			
		||||
			bool seek_error;
 | 
			
		||||
			bool lost_data;
 | 
			
		||||
			bool data_request;
 | 
			
		||||
			bool interrupt_request;
 | 
			
		||||
			bool busy;
 | 
			
		||||
			bool write_protect = false;
 | 
			
		||||
			bool record_type = false;
 | 
			
		||||
			bool spin_up = false;
 | 
			
		||||
			bool record_not_found = false;
 | 
			
		||||
			bool crc_error = false;
 | 
			
		||||
			bool seek_error = false;
 | 
			
		||||
			bool lost_data = false;
 | 
			
		||||
			bool data_request = false;
 | 
			
		||||
			bool interrupt_request = false;
 | 
			
		||||
			bool busy = false;
 | 
			
		||||
			enum {
 | 
			
		||||
				One, Two, Three
 | 
			
		||||
			} type;
 | 
			
		||||
			} type = One;
 | 
			
		||||
		} status_;
 | 
			
		||||
		uint8_t track_;
 | 
			
		||||
		uint8_t sector_;
 | 
			
		||||
@@ -86,67 +105,33 @@ class WD1770: public Storage::Disk::Controller {
 | 
			
		||||
		uint8_t command_;
 | 
			
		||||
 | 
			
		||||
		int index_hole_count_;
 | 
			
		||||
		int index_hole_count_target_;
 | 
			
		||||
		int bits_since_token_;
 | 
			
		||||
		int index_hole_count_target_ = -1;
 | 
			
		||||
		int distance_into_section_;
 | 
			
		||||
		bool is_awaiting_marker_value_;
 | 
			
		||||
 | 
			
		||||
		int step_direction_;
 | 
			
		||||
		void update_status(std::function<void(Status &)> updater);
 | 
			
		||||
 | 
			
		||||
		// Tokeniser
 | 
			
		||||
		enum DataMode {
 | 
			
		||||
			Scanning,
 | 
			
		||||
			Reading,
 | 
			
		||||
			Writing
 | 
			
		||||
		} data_mode_;
 | 
			
		||||
		bool is_double_density_;
 | 
			
		||||
		int shift_register_;
 | 
			
		||||
		struct Token {
 | 
			
		||||
			enum Type {
 | 
			
		||||
				Index, ID, Data, DeletedData, Sync, Byte
 | 
			
		||||
			} type;
 | 
			
		||||
			uint8_t byte_value;
 | 
			
		||||
		} latest_token_;
 | 
			
		||||
 | 
			
		||||
		// Events
 | 
			
		||||
		enum Event: int {
 | 
			
		||||
			Command			= (1 << 0),	// Indicates receipt of a new command.
 | 
			
		||||
			Token			= (1 << 1),	// Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details.
 | 
			
		||||
			IndexHole		= (1 << 2),	// Indicates the passing of a physical index hole.
 | 
			
		||||
			HeadLoad		= (1 << 3),	// Indicates the head has been loaded (1973 only).
 | 
			
		||||
			DataWritten		= (1 << 4),	// Indicates that all queued bits have been written
 | 
			
		||||
 | 
			
		||||
		enum Event1770: int {
 | 
			
		||||
			Command			= (1 << 3),	// Indicates receipt of a new command.
 | 
			
		||||
			HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only).
 | 
			
		||||
			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out.
 | 
			
		||||
			IndexHoleTarget	= (1 << 6)	// Indicates that index_hole_count_ has reached index_hole_count_target_.
 | 
			
		||||
			IndexHoleTarget	= (1 << 6),	// Indicates that index_hole_count_ has reached index_hole_count_target_.
 | 
			
		||||
			ForceInterrupt	= (1 << 7)	// Indicates a forced interrupt.
 | 
			
		||||
		};
 | 
			
		||||
		void posit_event(Event type);
 | 
			
		||||
		void posit_event(int type);
 | 
			
		||||
		int interesting_event_mask_;
 | 
			
		||||
		int resume_point_;
 | 
			
		||||
		int delay_time_;
 | 
			
		||||
 | 
			
		||||
		// Output
 | 
			
		||||
		int last_bit_;
 | 
			
		||||
		void write_bit(int bit);
 | 
			
		||||
		void write_byte(uint8_t byte);
 | 
			
		||||
		void write_raw_short(uint16_t value);
 | 
			
		||||
		int resume_point_ = 0;
 | 
			
		||||
		unsigned int delay_time_ = 0;
 | 
			
		||||
 | 
			
		||||
		// ID buffer
 | 
			
		||||
		uint8_t header_[6];
 | 
			
		||||
 | 
			
		||||
		// CRC generator
 | 
			
		||||
		NumberTheory::CRC16 crc_generator_;
 | 
			
		||||
 | 
			
		||||
		// 1793 head-loading logic
 | 
			
		||||
		bool head_is_loaded_;
 | 
			
		||||
		bool head_is_loaded_ = false;
 | 
			
		||||
 | 
			
		||||
		// delegate
 | 
			
		||||
		Delegate *delegate_;
 | 
			
		||||
 | 
			
		||||
		// Storage::Disk::Controller
 | 
			
		||||
		virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
 | 
			
		||||
		virtual void process_index_hole();
 | 
			
		||||
		virtual void process_write_completed();
 | 
			
		||||
		Delegate *delegate_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,83 @@
 | 
			
		||||
#include <typeinfo>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
#include "Implementation/6522Storage.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
			
		||||
 | 
			
		||||
namespace MOS {
 | 
			
		||||
namespace MOS6522 {
 | 
			
		||||
 | 
			
		||||
enum Port {
 | 
			
		||||
	A = 0,
 | 
			
		||||
	B = 1
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum Line {
 | 
			
		||||
	One = 0,
 | 
			
		||||
	Two = 1
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a
 | 
			
		||||
	6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring.
 | 
			
		||||
*/
 | 
			
		||||
class PortHandler {
 | 
			
		||||
	public:
 | 
			
		||||
		/// Requests the current input value of @c port from the port handler.
 | 
			
		||||
		uint8_t get_port_input(Port port)										{	return 0xff;	}
 | 
			
		||||
 | 
			
		||||
		/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
 | 
			
		||||
		void set_port_output(Port port, uint8_t value, uint8_t direction_mask)	{}
 | 
			
		||||
 | 
			
		||||
		/// Sets the current logical output level for line @c line on port @c port.
 | 
			
		||||
		void set_control_line_output(Port port, Line line, bool value)			{}
 | 
			
		||||
 | 
			
		||||
		/// Sets the current logical value of the interrupt line.
 | 
			
		||||
		void set_interrupt_status(bool status)									{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds
 | 
			
		||||
	a virtual level of indirection for receiving changes to the interrupt line.
 | 
			
		||||
*/
 | 
			
		||||
class IRQDelegatePortHandler: public PortHandler {
 | 
			
		||||
	public:
 | 
			
		||||
		class Delegate {
 | 
			
		||||
			public:
 | 
			
		||||
				/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
 | 
			
		||||
				virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/// Sets the delegate that will receive notification of changes in the interrupt line.
 | 
			
		||||
		void set_interrupt_delegate(Delegate *delegate);
 | 
			
		||||
 | 
			
		||||
		/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
 | 
			
		||||
		void set_interrupt_status(bool new_status);
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		Delegate *delegate_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MOS6522Base: public MOS6522Storage {
 | 
			
		||||
	public:
 | 
			
		||||
		/// Sets the input value of line @c line on port @c port.
 | 
			
		||||
		void set_control_line_input(Port port, Line line, bool value);
 | 
			
		||||
 | 
			
		||||
		/// Runs for a specified number of half cycles.
 | 
			
		||||
		void run_for(const HalfCycles half_cycles);
 | 
			
		||||
 | 
			
		||||
		/// Runs for a specified number of cycles.
 | 
			
		||||
		void run_for(const Cycles cycles);
 | 
			
		||||
 | 
			
		||||
		/// @returns @c true if the IRQ line is currently active; @c false otherwise.
 | 
			
		||||
		bool get_interrupt_line();
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		inline void do_phase1();
 | 
			
		||||
		inline void do_phase2();
 | 
			
		||||
		virtual void reevaluate_interrupts() = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
 | 
			
		||||
@@ -26,398 +102,27 @@ namespace MOS {
 | 
			
		||||
	Consumers should derive their own curiously-recurring-template-pattern subclass,
 | 
			
		||||
	implementing bus communications as required.
 | 
			
		||||
*/
 | 
			
		||||
template <class T> class MOS6522 {
 | 
			
		||||
	private:
 | 
			
		||||
		enum InterruptFlag: uint8_t {
 | 
			
		||||
			CA2ActiveEdge	= 1 << 0,
 | 
			
		||||
			CA1ActiveEdge	= 1 << 1,
 | 
			
		||||
			ShiftRegister	= 1 << 2,
 | 
			
		||||
			CB2ActiveEdge	= 1 << 3,
 | 
			
		||||
			CB1ActiveEdge	= 1 << 4,
 | 
			
		||||
			Timer2			= 1 << 5,
 | 
			
		||||
			Timer1			= 1 << 6,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
template <class T> class MOS6522: public MOS6522Base {
 | 
			
		||||
	public:
 | 
			
		||||
		enum Port {
 | 
			
		||||
			A = 0,
 | 
			
		||||
			B = 1
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		enum Line {
 | 
			
		||||
			One = 0,
 | 
			
		||||
			Two = 1
 | 
			
		||||
		};
 | 
			
		||||
		MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
 | 
			
		||||
		MOS6522(const MOS6522 &) = delete;
 | 
			
		||||
 | 
			
		||||
		/*! Sets a register value. */
 | 
			
		||||
		inline void set_register(int address, uint8_t value)
 | 
			
		||||
		{
 | 
			
		||||
			address &= 0xf;
 | 
			
		||||
//			printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
 | 
			
		||||
			switch(address)
 | 
			
		||||
			{
 | 
			
		||||
				case 0x0:
 | 
			
		||||
					registers_.output[1] = value;
 | 
			
		||||
					static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]);	// TODO: handshake
 | 
			
		||||
 | 
			
		||||
					registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				break;
 | 
			
		||||
				case 0xf:
 | 
			
		||||
				case 0x1:
 | 
			
		||||
					registers_.output[0] = value;
 | 
			
		||||
					static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]);	// TODO: handshake
 | 
			
		||||
 | 
			
		||||
					registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				break;
 | 
			
		||||
//					// No handshake, so write directly
 | 
			
		||||
//					registers_.output[0] = value;
 | 
			
		||||
//					static_cast<T *>(this)->set_port_output(0, value);
 | 
			
		||||
//				break;
 | 
			
		||||
 | 
			
		||||
				case 0x2:
 | 
			
		||||
					registers_.data_direction[1] = value;
 | 
			
		||||
				break;
 | 
			
		||||
				case 0x3:
 | 
			
		||||
					registers_.data_direction[0] = value;
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				// Timer 1
 | 
			
		||||
				case 0x6:	case 0x4:	registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;	break;
 | 
			
		||||
				case 0x5:	case 0x7:
 | 
			
		||||
					registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
 | 
			
		||||
					registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
			
		||||
					if(address == 0x05)
 | 
			
		||||
					{
 | 
			
		||||
						registers_.next_timer[0] = registers_.timer_latch[0];
 | 
			
		||||
						timer_is_running_[0] = true;
 | 
			
		||||
					}
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				// Timer 2
 | 
			
		||||
				case 0x8:	registers_.timer_latch[1] = value;	break;
 | 
			
		||||
				case 0x9:
 | 
			
		||||
					registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
			
		||||
					registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
 | 
			
		||||
					timer_is_running_[1] = true;
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				// Shift
 | 
			
		||||
				case 0xa:	registers_.shift = value;				break;
 | 
			
		||||
 | 
			
		||||
				// Control
 | 
			
		||||
				case 0xb:
 | 
			
		||||
					registers_.auxiliary_control = value;
 | 
			
		||||
				break;
 | 
			
		||||
				case 0xc:
 | 
			
		||||
//					printf("Peripheral control %02x\n", value);
 | 
			
		||||
					registers_.peripheral_control = value;
 | 
			
		||||
 | 
			
		||||
					// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
 | 
			
		||||
					if(value & 0x08)
 | 
			
		||||
					{
 | 
			
		||||
						switch(value & 0x0e)
 | 
			
		||||
						{
 | 
			
		||||
							default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
 | 
			
		||||
							case 0x0c:	static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false);		break;
 | 
			
		||||
							case 0x0e:	static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true);		break;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					if(value & 0x80)
 | 
			
		||||
					{
 | 
			
		||||
						switch(value & 0xe0)
 | 
			
		||||
						{
 | 
			
		||||
							default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
 | 
			
		||||
							case 0xc0:	static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false);		break;
 | 
			
		||||
							case 0xe0:	static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true);		break;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				// Interrupt control
 | 
			
		||||
				case 0xd:
 | 
			
		||||
					registers_.interrupt_flags &= ~value;
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				break;
 | 
			
		||||
				case 0xe:
 | 
			
		||||
					if(value&0x80)
 | 
			
		||||
						registers_.interrupt_enable |= value;
 | 
			
		||||
					else
 | 
			
		||||
						registers_.interrupt_enable &= ~value;
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		void set_register(int address, uint8_t value);
 | 
			
		||||
 | 
			
		||||
		/*! Gets a register value. */
 | 
			
		||||
		inline uint8_t get_register(int address)
 | 
			
		||||
		{
 | 
			
		||||
			address &= 0xf;
 | 
			
		||||
//			printf("6522 %p: %d\n", this, address);
 | 
			
		||||
			switch(address)
 | 
			
		||||
			{
 | 
			
		||||
				case 0x0:
 | 
			
		||||
					registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
 | 
			
		||||
				case 0xf:	// TODO: handshake, latching
 | 
			
		||||
				case 0x1:
 | 
			
		||||
					registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
 | 
			
		||||
 | 
			
		||||
				case 0x2:	return registers_.data_direction[1];
 | 
			
		||||
				case 0x3:	return registers_.data_direction[0];
 | 
			
		||||
 | 
			
		||||
				// Timer 1
 | 
			
		||||
				case 0x4:
 | 
			
		||||
					registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				return registers_.timer[0] & 0x00ff;
 | 
			
		||||
				case 0x5:	return registers_.timer[0] >> 8;
 | 
			
		||||
				case 0x6:	return registers_.timer_latch[0] & 0x00ff;
 | 
			
		||||
				case 0x7:	return registers_.timer_latch[0] >> 8;
 | 
			
		||||
 | 
			
		||||
				// Timer 2
 | 
			
		||||
				case 0x8:
 | 
			
		||||
					registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
			
		||||
					reevaluate_interrupts();
 | 
			
		||||
				return registers_.timer[1] & 0x00ff;
 | 
			
		||||
				case 0x9:	return registers_.timer[1] >> 8;
 | 
			
		||||
 | 
			
		||||
				case 0xa:	return registers_.shift;
 | 
			
		||||
 | 
			
		||||
				case 0xb:	return registers_.auxiliary_control;
 | 
			
		||||
				case 0xc:	return registers_.peripheral_control;
 | 
			
		||||
 | 
			
		||||
				case 0xd:	return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
 | 
			
		||||
				case 0xe:	return registers_.interrupt_enable | 0x80;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline void set_control_line_input(Port port, Line line, bool value)
 | 
			
		||||
		{
 | 
			
		||||
			switch(line)
 | 
			
		||||
			{
 | 
			
		||||
				case Line::One:
 | 
			
		||||
					if(	value != control_inputs_[port].line_one &&
 | 
			
		||||
						value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
 | 
			
		||||
					)
 | 
			
		||||
					{
 | 
			
		||||
						registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
 | 
			
		||||
						reevaluate_interrupts();
 | 
			
		||||
					}
 | 
			
		||||
					control_inputs_[port].line_one = value;
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case Line::Two:
 | 
			
		||||
					// TODO: output modes, but probably elsewhere?
 | 
			
		||||
					if(	value != control_inputs_[port].line_two &&							// i.e. value has changed ...
 | 
			
		||||
						!(registers_.peripheral_control & (port ? 0x80 : 0x08)) &&			// ... and line is input ...
 | 
			
		||||
						value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04))	// ... and it's either high or low, as required
 | 
			
		||||
					)
 | 
			
		||||
					{
 | 
			
		||||
						registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
 | 
			
		||||
						reevaluate_interrupts();
 | 
			
		||||
					}
 | 
			
		||||
					control_inputs_[port].line_two = value;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
#define phase2()	\
 | 
			
		||||
	registers_.last_timer[0] = registers_.timer[0];\
 | 
			
		||||
	registers_.last_timer[1] = registers_.timer[1];\
 | 
			
		||||
\
 | 
			
		||||
	if(registers_.timer_needs_reload)\
 | 
			
		||||
	{\
 | 
			
		||||
		registers_.timer_needs_reload = false;\
 | 
			
		||||
		registers_.timer[0] = registers_.timer_latch[0];\
 | 
			
		||||
	}\
 | 
			
		||||
	else\
 | 
			
		||||
		registers_.timer[0] --;\
 | 
			
		||||
\
 | 
			
		||||
	registers_.timer[1] --; \
 | 
			
		||||
	if(registers_.next_timer[0] >= 0) { registers_.timer[0] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
 | 
			
		||||
	if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
 | 
			
		||||
 | 
			
		||||
	// IRQ is raised on the half cycle after overflow
 | 
			
		||||
#define phase1()	\
 | 
			
		||||
	if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1])\
 | 
			
		||||
	{\
 | 
			
		||||
		timer_is_running_[1] = false;\
 | 
			
		||||
		registers_.interrupt_flags |= InterruptFlag::Timer2;\
 | 
			
		||||
		reevaluate_interrupts();\
 | 
			
		||||
	}\
 | 
			
		||||
\
 | 
			
		||||
	if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0])\
 | 
			
		||||
	{\
 | 
			
		||||
		registers_.interrupt_flags |= InterruptFlag::Timer1;\
 | 
			
		||||
		reevaluate_interrupts();\
 | 
			
		||||
\
 | 
			
		||||
		if(registers_.auxiliary_control&0x40)\
 | 
			
		||||
			registers_.timer_needs_reload = true;\
 | 
			
		||||
		else\
 | 
			
		||||
			timer_is_running_[0] = false;\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Runs for a specified number of half cycles.
 | 
			
		||||
 | 
			
		||||
			Although the original chip accepts only a phase-2 input, timer reloads are specified as occuring
 | 
			
		||||
			1.5 cycles after the timer hits zero. It therefore may be necessary to emulate at half-cycle precision.
 | 
			
		||||
 | 
			
		||||
			The first emulated half-cycle will be the period between the trailing edge of a phase-2 input and the
 | 
			
		||||
			next rising edge. So it should align with a full system's phase-1. The next emulated half-cycle will be
 | 
			
		||||
			that which occurs during phase-2.
 | 
			
		||||
 | 
			
		||||
			Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
 | 
			
		||||
			intermingle usage.
 | 
			
		||||
		*/
 | 
			
		||||
		inline void run_for_half_cycles(unsigned int number_of_cycles)
 | 
			
		||||
		{
 | 
			
		||||
			if(is_phase2_)
 | 
			
		||||
			{
 | 
			
		||||
				phase2();
 | 
			
		||||
				number_of_cycles--;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			while(number_of_cycles >= 2)
 | 
			
		||||
			{
 | 
			
		||||
				phase1();
 | 
			
		||||
				phase2();
 | 
			
		||||
				number_of_cycles -= 2;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(number_of_cycles)
 | 
			
		||||
			{
 | 
			
		||||
				phase1();
 | 
			
		||||
				is_phase2_ = true;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				is_phase2_ = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Runs for a specified number of cycles.
 | 
			
		||||
 | 
			
		||||
			Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
 | 
			
		||||
			intermingle usage.
 | 
			
		||||
		*/
 | 
			
		||||
		inline void run_for_cycles(unsigned int number_of_cycles)
 | 
			
		||||
		{
 | 
			
		||||
			while(number_of_cycles--)
 | 
			
		||||
			{
 | 
			
		||||
				phase1();
 | 
			
		||||
				phase2();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
#undef phase1
 | 
			
		||||
#undef phase2
 | 
			
		||||
 | 
			
		||||
		/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
 | 
			
		||||
		inline bool get_interrupt_line()
 | 
			
		||||
		{
 | 
			
		||||
			uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
 | 
			
		||||
			return !!interrupt_status;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		MOS6522() :
 | 
			
		||||
			timer_is_running_{false, false},
 | 
			
		||||
			last_posted_interrupt_status_(false),
 | 
			
		||||
			is_phase2_(false)
 | 
			
		||||
		{}
 | 
			
		||||
		uint8_t get_register(int address);
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		// Expected to be overridden
 | 
			
		||||
		uint8_t get_port_input(Port port)										{	return 0xff;	}
 | 
			
		||||
		void set_port_output(Port port, uint8_t value, uint8_t direction_mask)	{}
 | 
			
		||||
		void set_control_line_output(Port port, Line line, bool value)			{}
 | 
			
		||||
		void set_interrupt_status(bool status)									{}
 | 
			
		||||
		T &bus_handler_;
 | 
			
		||||
 | 
			
		||||
		// Input/output multiplexer
 | 
			
		||||
		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output)
 | 
			
		||||
		{
 | 
			
		||||
			uint8_t input = static_cast<T *>(this)->get_port_input(port);
 | 
			
		||||
			return (input & ~output_mask) | (output & output_mask);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Phase toggle
 | 
			
		||||
		bool is_phase2_;
 | 
			
		||||
 | 
			
		||||
		// Delegate and communications
 | 
			
		||||
		bool last_posted_interrupt_status_;
 | 
			
		||||
		inline void reevaluate_interrupts()
 | 
			
		||||
		{
 | 
			
		||||
			bool new_interrupt_status = get_interrupt_line();
 | 
			
		||||
			if(new_interrupt_status != last_posted_interrupt_status_)
 | 
			
		||||
			{
 | 
			
		||||
				last_posted_interrupt_status_ = new_interrupt_status;
 | 
			
		||||
				static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// The registers
 | 
			
		||||
		struct Registers {
 | 
			
		||||
			uint8_t output[2], input[2], data_direction[2];
 | 
			
		||||
			uint16_t timer[2], timer_latch[2], last_timer[2];
 | 
			
		||||
			int next_timer[2];
 | 
			
		||||
			uint8_t shift;
 | 
			
		||||
			uint8_t auxiliary_control, peripheral_control;
 | 
			
		||||
			uint8_t interrupt_flags, interrupt_enable;
 | 
			
		||||
			bool timer_needs_reload;
 | 
			
		||||
 | 
			
		||||
			// "A  low  reset  (RES)  input  clears  all  R6522  internal registers to logic 0"
 | 
			
		||||
			Registers() :
 | 
			
		||||
				output{0, 0}, input{0, 0}, data_direction{0, 0},
 | 
			
		||||
				auxiliary_control(0), peripheral_control(0),
 | 
			
		||||
				interrupt_flags(0), interrupt_enable(0),
 | 
			
		||||
				last_timer{0, 0}, timer_needs_reload(false),
 | 
			
		||||
				next_timer{-1, -1} {}
 | 
			
		||||
		} registers_;
 | 
			
		||||
 | 
			
		||||
		// control state
 | 
			
		||||
		struct {
 | 
			
		||||
			bool line_one, line_two;
 | 
			
		||||
		} control_inputs_[2];
 | 
			
		||||
 | 
			
		||||
		// Internal state other than the registers
 | 
			
		||||
		bool timer_is_running_[2];
 | 
			
		||||
		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
 | 
			
		||||
		inline void reevaluate_interrupts();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate
 | 
			
		||||
	that will receive IRQ line change notifications.
 | 
			
		||||
*/
 | 
			
		||||
class MOS6522IRQDelegate {
 | 
			
		||||
	public:
 | 
			
		||||
		class Delegate {
 | 
			
		||||
			public:
 | 
			
		||||
				virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		inline void set_interrupt_delegate(Delegate *delegate)
 | 
			
		||||
		{
 | 
			
		||||
			delegate_ = delegate;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline void set_interrupt_status(bool new_status)
 | 
			
		||||
		{
 | 
			
		||||
			if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		Delegate *delegate_;
 | 
			
		||||
};
 | 
			
		||||
#include "Implementation/6522Implementation.hpp"
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* _522_hpp */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								Components/6522/Implementation/6522Base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								Components/6522/Implementation/6522Base.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
//
 | 
			
		||||
//  6522Base.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/09/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "../6522.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace MOS::MOS6522;
 | 
			
		||||
 | 
			
		||||
void MOS6522Base::set_control_line_input(Port port, Line line, bool value) {
 | 
			
		||||
	switch(line) {
 | 
			
		||||
		case Line::One:
 | 
			
		||||
			if(	value != control_inputs_[port].line_one &&
 | 
			
		||||
				value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
 | 
			
		||||
			) {
 | 
			
		||||
				registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
 | 
			
		||||
				reevaluate_interrupts();
 | 
			
		||||
			}
 | 
			
		||||
			control_inputs_[port].line_one = value;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case Line::Two:
 | 
			
		||||
			// TODO: output modes, but probably elsewhere?
 | 
			
		||||
			if(	value != control_inputs_[port].line_two &&							// i.e. value has changed ...
 | 
			
		||||
				!(registers_.peripheral_control & (port ? 0x80 : 0x08)) &&			// ... and line is input ...
 | 
			
		||||
				value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04))	// ... and it's either high or low, as required
 | 
			
		||||
			) {
 | 
			
		||||
				registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
 | 
			
		||||
				reevaluate_interrupts();
 | 
			
		||||
			}
 | 
			
		||||
			control_inputs_[port].line_two = value;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MOS6522Base::do_phase2() {
 | 
			
		||||
	registers_.last_timer[0] = registers_.timer[0];
 | 
			
		||||
	registers_.last_timer[1] = registers_.timer[1];
 | 
			
		||||
 | 
			
		||||
	if(registers_.timer_needs_reload) {
 | 
			
		||||
		registers_.timer_needs_reload = false;
 | 
			
		||||
		registers_.timer[0] = registers_.timer_latch[0];
 | 
			
		||||
	} else {
 | 
			
		||||
		registers_.timer[0] --;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	registers_.timer[1] --;
 | 
			
		||||
	if(registers_.next_timer[0] >= 0) {
 | 
			
		||||
		registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
 | 
			
		||||
		registers_.next_timer[0] = -1;
 | 
			
		||||
	}
 | 
			
		||||
	if(registers_.next_timer[1] >= 0) {
 | 
			
		||||
		registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
 | 
			
		||||
		registers_.next_timer[1] = -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MOS6522Base::do_phase1() {
 | 
			
		||||
	// IRQ is raised on the half cycle after overflow
 | 
			
		||||
	if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
 | 
			
		||||
		timer_is_running_[1] = false;
 | 
			
		||||
		registers_.interrupt_flags |= InterruptFlag::Timer2;
 | 
			
		||||
		reevaluate_interrupts();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
 | 
			
		||||
		registers_.interrupt_flags |= InterruptFlag::Timer1;
 | 
			
		||||
		reevaluate_interrupts();
 | 
			
		||||
 | 
			
		||||
		if(registers_.auxiliary_control&0x40)
 | 
			
		||||
			registers_.timer_needs_reload = true;
 | 
			
		||||
		else
 | 
			
		||||
			timer_is_running_[0] = false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Runs for a specified number of half cycles. */
 | 
			
		||||
void MOS6522Base::run_for(const HalfCycles half_cycles) {
 | 
			
		||||
	int number_of_half_cycles = half_cycles.as_int();
 | 
			
		||||
 | 
			
		||||
	if(is_phase2_) {
 | 
			
		||||
		do_phase2();
 | 
			
		||||
		number_of_half_cycles--;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	while(number_of_half_cycles >= 2) {
 | 
			
		||||
		do_phase1();
 | 
			
		||||
		do_phase2();
 | 
			
		||||
		number_of_half_cycles -= 2;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(number_of_half_cycles) {
 | 
			
		||||
		do_phase1();
 | 
			
		||||
		is_phase2_ = true;
 | 
			
		||||
	} else {
 | 
			
		||||
		is_phase2_ = false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Runs for a specified number of cycles. */
 | 
			
		||||
void MOS6522Base::run_for(const Cycles cycles) {
 | 
			
		||||
	int number_of_cycles = cycles.as_int();
 | 
			
		||||
	while(number_of_cycles--) {
 | 
			
		||||
		do_phase1();
 | 
			
		||||
		do_phase2();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
 | 
			
		||||
bool MOS6522Base::get_interrupt_line() {
 | 
			
		||||
	uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
 | 
			
		||||
	return !!interrupt_status;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										155
									
								
								Components/6522/Implementation/6522Implementation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								Components/6522/Implementation/6522Implementation.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
//
 | 
			
		||||
//  Implementation.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/09/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
 | 
			
		||||
	address &= 0xf;
 | 
			
		||||
	switch(address) {
 | 
			
		||||
		case 0x0:
 | 
			
		||||
			registers_.output[1] = value;
 | 
			
		||||
			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);	// TODO: handshake
 | 
			
		||||
 | 
			
		||||
			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		break;
 | 
			
		||||
		case 0xf:
 | 
			
		||||
		case 0x1:
 | 
			
		||||
			registers_.output[0] = value;
 | 
			
		||||
			bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);	// TODO: handshake
 | 
			
		||||
 | 
			
		||||
			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case 0x2:
 | 
			
		||||
			registers_.data_direction[1] = value;
 | 
			
		||||
		break;
 | 
			
		||||
		case 0x3:
 | 
			
		||||
			registers_.data_direction[0] = value;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		// Timer 1
 | 
			
		||||
		case 0x6:	case 0x4:	registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;	break;
 | 
			
		||||
		case 0x5:	case 0x7:
 | 
			
		||||
			registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
 | 
			
		||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
			
		||||
			if(address == 0x05) {
 | 
			
		||||
				registers_.next_timer[0] = registers_.timer_latch[0];
 | 
			
		||||
				timer_is_running_[0] = true;
 | 
			
		||||
			}
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		// Timer 2
 | 
			
		||||
		case 0x8:	registers_.timer_latch[1] = value;	break;
 | 
			
		||||
		case 0x9:
 | 
			
		||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
			
		||||
			registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
 | 
			
		||||
			timer_is_running_[1] = true;
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		// Shift
 | 
			
		||||
		case 0xa:	registers_.shift = value;				break;
 | 
			
		||||
 | 
			
		||||
		// Control
 | 
			
		||||
		case 0xb:
 | 
			
		||||
			registers_.auxiliary_control = value;
 | 
			
		||||
		break;
 | 
			
		||||
		case 0xc:
 | 
			
		||||
//			printf("Peripheral control %02x\n", value);
 | 
			
		||||
			registers_.peripheral_control = value;
 | 
			
		||||
 | 
			
		||||
			// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
 | 
			
		||||
			if(value & 0x08) {
 | 
			
		||||
				switch(value & 0x0e) {
 | 
			
		||||
					default: printf("Unimplemented control line mode %d\n", (value >> 1)&7);		break;
 | 
			
		||||
					case 0x0c:	bus_handler_.set_control_line_output(Port::A, Line::Two, false);	break;
 | 
			
		||||
					case 0x0e:	bus_handler_.set_control_line_output(Port::A, Line::Two, true);		break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if(value & 0x80) {
 | 
			
		||||
				switch(value & 0xe0) {
 | 
			
		||||
					default: printf("Unimplemented control line mode %d\n", (value >> 5)&7);		break;
 | 
			
		||||
					case 0xc0:	bus_handler_.set_control_line_output(Port::B, Line::Two, false);	break;
 | 
			
		||||
					case 0xe0:	bus_handler_.set_control_line_output(Port::B, Line::Two, true);		break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		// Interrupt control
 | 
			
		||||
		case 0xd:
 | 
			
		||||
			registers_.interrupt_flags &= ~value;
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		break;
 | 
			
		||||
		case 0xe:
 | 
			
		||||
			if(value&0x80)
 | 
			
		||||
				registers_.interrupt_enable |= value;
 | 
			
		||||
			else
 | 
			
		||||
				registers_.interrupt_enable &= ~value;
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
 | 
			
		||||
	address &= 0xf;
 | 
			
		||||
	switch(address) {
 | 
			
		||||
		case 0x0:
 | 
			
		||||
			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
 | 
			
		||||
		case 0xf:	// TODO: handshake, latching
 | 
			
		||||
		case 0x1:
 | 
			
		||||
			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
 | 
			
		||||
 | 
			
		||||
		case 0x2:	return registers_.data_direction[1];
 | 
			
		||||
		case 0x3:	return registers_.data_direction[0];
 | 
			
		||||
 | 
			
		||||
		// Timer 1
 | 
			
		||||
		case 0x4:
 | 
			
		||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		return registers_.timer[0] & 0x00ff;
 | 
			
		||||
		case 0x5:	return registers_.timer[0] >> 8;
 | 
			
		||||
		case 0x6:	return registers_.timer_latch[0] & 0x00ff;
 | 
			
		||||
		case 0x7:	return registers_.timer_latch[0] >> 8;
 | 
			
		||||
 | 
			
		||||
		// Timer 2
 | 
			
		||||
		case 0x8:
 | 
			
		||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
			
		||||
			reevaluate_interrupts();
 | 
			
		||||
		return registers_.timer[1] & 0x00ff;
 | 
			
		||||
		case 0x9:	return registers_.timer[1] >> 8;
 | 
			
		||||
 | 
			
		||||
		case 0xa:	return registers_.shift;
 | 
			
		||||
 | 
			
		||||
		case 0xb:	return registers_.auxiliary_control;
 | 
			
		||||
		case 0xc:	return registers_.peripheral_control;
 | 
			
		||||
 | 
			
		||||
		case 0xd:	return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
 | 
			
		||||
		case 0xe:	return registers_.interrupt_enable | 0x80;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0xff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
 | 
			
		||||
	uint8_t input = bus_handler_.get_port_input(port);
 | 
			
		||||
	return (input & ~output_mask) | (output & output_mask);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delegate and communications
 | 
			
		||||
template <typename T> void MOS6522<T>::reevaluate_interrupts() {
 | 
			
		||||
	bool new_interrupt_status = get_interrupt_line();
 | 
			
		||||
	if(new_interrupt_status != last_posted_interrupt_status_) {
 | 
			
		||||
		last_posted_interrupt_status_ = new_interrupt_status;
 | 
			
		||||
		bus_handler_.set_interrupt_status(new_interrupt_status);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								Components/6522/Implementation/6522Storage.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Components/6522/Implementation/6522Storage.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
//
 | 
			
		||||
//  6522Storage.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/09/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef _522Storage_hpp
 | 
			
		||||
#define _522Storage_hpp
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace MOS {
 | 
			
		||||
namespace MOS6522 {
 | 
			
		||||
 | 
			
		||||
class MOS6522Storage {
 | 
			
		||||
	protected:
 | 
			
		||||
		// Phase toggle
 | 
			
		||||
		bool is_phase2_ = false;
 | 
			
		||||
 | 
			
		||||
		// The registers
 | 
			
		||||
		struct Registers {
 | 
			
		||||
			// "A  low  reset  (RES)  input  clears  all  R6522  internal registers to logic 0"
 | 
			
		||||
			uint8_t output[2] = {0, 0};
 | 
			
		||||
			uint8_t input[2] = {0, 0};
 | 
			
		||||
			uint8_t data_direction[2] = {0, 0};
 | 
			
		||||
			uint16_t timer[2] = {0, 0};
 | 
			
		||||
			uint16_t timer_latch[2] = {0, 0};
 | 
			
		||||
			uint16_t last_timer[2] = {0, 0};
 | 
			
		||||
			int next_timer[2] = {-1, -1};
 | 
			
		||||
			uint8_t shift = 0;
 | 
			
		||||
			uint8_t auxiliary_control = 0;
 | 
			
		||||
			uint8_t peripheral_control = 0;
 | 
			
		||||
			uint8_t interrupt_flags = 0;
 | 
			
		||||
			uint8_t interrupt_enable = 0;
 | 
			
		||||
			bool timer_needs_reload = false;
 | 
			
		||||
		} registers_;
 | 
			
		||||
 | 
			
		||||
		// control state
 | 
			
		||||
		struct {
 | 
			
		||||
			bool line_one = false;
 | 
			
		||||
			bool line_two = false;
 | 
			
		||||
		} control_inputs_[2];
 | 
			
		||||
 | 
			
		||||
		bool timer_is_running_[2] = {false, false};
 | 
			
		||||
		bool last_posted_interrupt_status_ = false;
 | 
			
		||||
 | 
			
		||||
		enum InterruptFlag: uint8_t {
 | 
			
		||||
			CA2ActiveEdge	= 1 << 0,
 | 
			
		||||
			CA1ActiveEdge	= 1 << 1,
 | 
			
		||||
			ShiftRegister	= 1 << 2,
 | 
			
		||||
			CB2ActiveEdge	= 1 << 3,
 | 
			
		||||
			CB1ActiveEdge	= 1 << 4,
 | 
			
		||||
			Timer2			= 1 << 5,
 | 
			
		||||
			Timer1			= 1 << 6,
 | 
			
		||||
		};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* _522Storage_hpp */
 | 
			
		||||
							
								
								
									
										19
									
								
								Components/6522/Implementation/IRQDelegatePortHandler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Components/6522/Implementation/IRQDelegatePortHandler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
//
 | 
			
		||||
//  IRQDelegatePortHandler.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/09/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "../6522.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace MOS::MOS6522;
 | 
			
		||||
 | 
			
		||||
void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
 | 
			
		||||
	delegate_ = delegate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
 | 
			
		||||
	if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,8 @@
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
			
		||||
 | 
			
		||||
namespace MOS {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
@@ -30,8 +32,7 @@ template <class T> class MOS6532 {
 | 
			
		||||
		inline void set_ram(uint16_t address, uint8_t value)	{	ram_[address&0x7f] = value;		}
 | 
			
		||||
		inline uint8_t get_ram(uint16_t address)				{	return ram_[address & 0x7f];	}
 | 
			
		||||
 | 
			
		||||
		inline void set_register(int address, uint8_t value)
 | 
			
		||||
		{
 | 
			
		||||
		inline void set_register(int address, uint8_t value) {
 | 
			
		||||
			const uint8_t decodedAddress = address & 0x07;
 | 
			
		||||
			switch(decodedAddress) {
 | 
			
		||||
				// Port output
 | 
			
		||||
@@ -48,16 +49,13 @@ template <class T> class MOS6532 {
 | 
			
		||||
 | 
			
		||||
				// The timer and edge detect control
 | 
			
		||||
				case 0x04: case 0x05: case 0x06: case 0x07:
 | 
			
		||||
					if(address & 0x10)
 | 
			
		||||
					{
 | 
			
		||||
					if(address & 0x10) {
 | 
			
		||||
						timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07);	// i.e. 0, 3, 6, 10
 | 
			
		||||
						timer_.value = ((unsigned int)(value) << timer_.activeShift) | ((1 << timer_.activeShift)-1);
 | 
			
		||||
						timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
 | 
			
		||||
						timer_.interrupt_enabled = !!(address&0x08);
 | 
			
		||||
						interrupt_status_ &= ~InterruptFlag::Timer;
 | 
			
		||||
						evaluate_interrupts();
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
					} else {
 | 
			
		||||
						a7_interrupt_.enabled = !!(address&0x2);
 | 
			
		||||
						a7_interrupt_.active_on_positive = !!(address & 0x01);
 | 
			
		||||
					}
 | 
			
		||||
@@ -65,13 +63,11 @@ template <class T> class MOS6532 {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline uint8_t get_register(int address)
 | 
			
		||||
		{
 | 
			
		||||
		inline uint8_t get_register(int address) {
 | 
			
		||||
			const uint8_t decodedAddress = address & 0x7;
 | 
			
		||||
			switch(decodedAddress) {
 | 
			
		||||
				// Port input
 | 
			
		||||
				case 0x00: case 0x02:
 | 
			
		||||
				{
 | 
			
		||||
				case 0x00: case 0x02: {
 | 
			
		||||
					const int port = decodedAddress / 2;
 | 
			
		||||
					uint8_t input = static_cast<T *>(this)->get_port_input(port);
 | 
			
		||||
					return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
 | 
			
		||||
@@ -82,9 +78,8 @@ template <class T> class MOS6532 {
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				// Timer and interrupt control
 | 
			
		||||
				case 0x04: case 0x06:
 | 
			
		||||
				{
 | 
			
		||||
					uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
 | 
			
		||||
				case 0x04: case 0x06: {
 | 
			
		||||
					uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
 | 
			
		||||
					timer_.interrupt_enabled = !!(address&0x08);
 | 
			
		||||
					interrupt_status_ &= ~InterruptFlag::Timer;
 | 
			
		||||
					evaluate_interrupts();
 | 
			
		||||
@@ -99,8 +94,7 @@ template <class T> class MOS6532 {
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 0x05: case 0x07:
 | 
			
		||||
				{
 | 
			
		||||
				case 0x05: case 0x07: {
 | 
			
		||||
					uint8_t value = interrupt_status_;
 | 
			
		||||
					interrupt_status_ &= ~InterruptFlag::PA7;
 | 
			
		||||
					evaluate_interrupts();
 | 
			
		||||
@@ -112,41 +106,35 @@ template <class T> class MOS6532 {
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline void run_for_cycles(unsigned int number_of_cycles)
 | 
			
		||||
		{
 | 
			
		||||
		inline void run_for(const Cycles cycles) {
 | 
			
		||||
			unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
 | 
			
		||||
 | 
			
		||||
			// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
 | 
			
		||||
			if(timer_.value >= number_of_cycles) {
 | 
			
		||||
				timer_.value -= number_of_cycles;
 | 
			
		||||
			} else {
 | 
			
		||||
				number_of_cycles -= timer_.value;
 | 
			
		||||
				timer_.value = 0x100 - number_of_cycles;
 | 
			
		||||
				timer_.value = (0x100 - number_of_cycles) & 0xff;
 | 
			
		||||
				timer_.activeShift = 0;
 | 
			
		||||
				interrupt_status_ |= InterruptFlag::Timer;
 | 
			
		||||
				evaluate_interrupts();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		MOS6532() :
 | 
			
		||||
			interrupt_status_(0),
 | 
			
		||||
			port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
 | 
			
		||||
			a7_interrupt_({.last_port_value = 0, .enabled = false}),
 | 
			
		||||
			interrupt_line_(false)
 | 
			
		||||
		{}
 | 
			
		||||
		MOS6532() {
 | 
			
		||||
			timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline void set_port_did_change(int port)
 | 
			
		||||
		{
 | 
			
		||||
			if(!port)
 | 
			
		||||
			{
 | 
			
		||||
		inline void set_port_did_change(int port) {
 | 
			
		||||
			if(!port) {
 | 
			
		||||
				uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask);
 | 
			
		||||
				uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
 | 
			
		||||
				a7_interrupt_.last_port_value = new_port_a_value;
 | 
			
		||||
				if(difference&0x80)
 | 
			
		||||
				{
 | 
			
		||||
				if(difference&0x80) {
 | 
			
		||||
					if(
 | 
			
		||||
						((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
 | 
			
		||||
						(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
 | 
			
		||||
					)
 | 
			
		||||
					{
 | 
			
		||||
					) {
 | 
			
		||||
						interrupt_status_ |= InterruptFlag::PA7;
 | 
			
		||||
						evaluate_interrupts();
 | 
			
		||||
					}
 | 
			
		||||
@@ -154,8 +142,7 @@ template <class T> class MOS6532 {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline bool get_inerrupt_line()
 | 
			
		||||
		{
 | 
			
		||||
		inline bool get_inerrupt_line() {
 | 
			
		||||
			return interrupt_line_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -164,34 +151,33 @@ template <class T> class MOS6532 {
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			unsigned int value;
 | 
			
		||||
			unsigned int activeShift, writtenShift;
 | 
			
		||||
			bool interrupt_enabled;
 | 
			
		||||
			unsigned int activeShift = 10, writtenShift = 10;
 | 
			
		||||
			bool interrupt_enabled = false;
 | 
			
		||||
		} timer_;
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			bool enabled;
 | 
			
		||||
			bool active_on_positive;
 | 
			
		||||
			uint8_t last_port_value;
 | 
			
		||||
			bool enabled = false;
 | 
			
		||||
			bool active_on_positive = false;
 | 
			
		||||
			uint8_t last_port_value = 0;
 | 
			
		||||
		} a7_interrupt_;
 | 
			
		||||
 | 
			
		||||
		struct {
 | 
			
		||||
			uint8_t output_mask, output;
 | 
			
		||||
			uint8_t output_mask = 0, output = 0;
 | 
			
		||||
		} port_[2];
 | 
			
		||||
 | 
			
		||||
		uint8_t interrupt_status_;
 | 
			
		||||
		uint8_t interrupt_status_ = 0;
 | 
			
		||||
		enum InterruptFlag: uint8_t {
 | 
			
		||||
			Timer = 0x80,
 | 
			
		||||
			PA7 = 0x40
 | 
			
		||||
		};
 | 
			
		||||
		bool interrupt_line_;
 | 
			
		||||
		bool interrupt_line_ = false;
 | 
			
		||||
 | 
			
		||||
		// expected to be overridden
 | 
			
		||||
		uint8_t get_port_input(int port)										{	return 0xff;	}
 | 
			
		||||
		void set_port_output(int port, uint8_t value, uint8_t output_mask)		{}
 | 
			
		||||
		void set_irq_line(bool new_value)										{}
 | 
			
		||||
 | 
			
		||||
		inline void evaluate_interrupts()
 | 
			
		||||
		{
 | 
			
		||||
		inline void evaluate_interrupts() {
 | 
			
		||||
			interrupt_line_ =
 | 
			
		||||
				((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
 | 
			
		||||
				((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,25 +8,22 @@
 | 
			
		||||
 | 
			
		||||
#include "6560.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
using namespace MOS;
 | 
			
		||||
 | 
			
		||||
Speaker::Speaker() :
 | 
			
		||||
	volume_(0),
 | 
			
		||||
	control_registers_{0, 0, 0, 0},
 | 
			
		||||
	shift_registers_{0, 0, 0, 0},
 | 
			
		||||
	counters_{2, 1, 0, 0}	// create a slight phase offset for the three channels
 | 
			
		||||
{}
 | 
			
		||||
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
 | 
			
		||||
	audio_queue_(audio_queue) {}
 | 
			
		||||
 | 
			
		||||
void Speaker::set_volume(uint8_t volume)
 | 
			
		||||
{
 | 
			
		||||
	enqueue([=]() {
 | 
			
		||||
 | 
			
		||||
void AudioGenerator::set_volume(uint8_t volume) {
 | 
			
		||||
	audio_queue_.defer([=]() {
 | 
			
		||||
		volume_ = volume;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Speaker::set_control(int channel, uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
	enqueue([=]() {
 | 
			
		||||
void AudioGenerator::set_control(int channel, uint8_t value) {
 | 
			
		||||
	audio_queue_.defer([=]() {
 | 
			
		||||
		control_registers_[channel] = value;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
@@ -101,17 +98,15 @@ static uint8_t noise_pattern[] = {
 | 
			
		||||
 | 
			
		||||
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
 | 
			
		||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
 | 
			
		||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
 | 
			
		||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
 | 
			
		||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
 | 
			
		||||
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
 | 
			
		||||
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
 | 
			
		||||
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
 | 
			
		||||
// means every second cycle, etc.
 | 
			
		||||
 | 
			
		||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
 | 
			
		||||
{
 | 
			
		||||
	for(unsigned int c = 0; c < number_of_samples; c++)
 | 
			
		||||
	{
 | 
			
		||||
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
			
		||||
	for(unsigned int c = 0; c < number_of_samples; c++) {
 | 
			
		||||
		update(0, 2, shift);
 | 
			
		||||
		update(1, 1, shift);
 | 
			
		||||
		update(2, 0, shift);
 | 
			
		||||
@@ -128,10 +123,8 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Speaker::skip_samples(unsigned int number_of_samples)
 | 
			
		||||
{
 | 
			
		||||
	for(unsigned int c = 0; c < number_of_samples; c++)
 | 
			
		||||
	{
 | 
			
		||||
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
 | 
			
		||||
	for(unsigned int c = 0; c < number_of_samples; c++) {
 | 
			
		||||
		update(0, 2, shift);
 | 
			
		||||
		update(1, 1, shift);
 | 
			
		||||
		update(2, 0, shift);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,27 +9,32 @@
 | 
			
		||||
#ifndef _560_hpp
 | 
			
		||||
#define _560_hpp
 | 
			
		||||
 | 
			
		||||
#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
			
		||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
 | 
			
		||||
#include "../../Outputs/CRT/CRT.hpp"
 | 
			
		||||
#include "../../Outputs/Speaker.hpp"
 | 
			
		||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
 | 
			
		||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
 | 
			
		||||
 | 
			
		||||
namespace MOS {
 | 
			
		||||
 | 
			
		||||
// audio state
 | 
			
		||||
class Speaker: public ::Outputs::Filter<Speaker> {
 | 
			
		||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
 | 
			
		||||
	public:
 | 
			
		||||
		Speaker();
 | 
			
		||||
		AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
 | 
			
		||||
 | 
			
		||||
		void set_volume(uint8_t volume);
 | 
			
		||||
		void set_control(int channel, uint8_t value);
 | 
			
		||||
 | 
			
		||||
		void get_samples(unsigned int number_of_samples, int16_t *target);
 | 
			
		||||
		void skip_samples(unsigned int number_of_samples);
 | 
			
		||||
		void get_samples(std::size_t number_of_samples, int16_t *target);
 | 
			
		||||
		void skip_samples(std::size_t number_of_samples);
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		unsigned int counters_[4];
 | 
			
		||||
		unsigned int shift_registers_[4];
 | 
			
		||||
		uint8_t control_registers_[4];
 | 
			
		||||
		uint8_t volume_;
 | 
			
		||||
		Concurrency::DeferringAsyncTaskQueue &audio_queue_;
 | 
			
		||||
 | 
			
		||||
		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels
 | 
			
		||||
		unsigned int shift_registers_[4] = {0, 0, 0, 0};
 | 
			
		||||
		uint8_t control_registers_[4] = {0, 0, 0, 0};
 | 
			
		||||
		uint8_t volume_ = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
@@ -43,37 +48,34 @@ class Speaker: public ::Outputs::Filter<Speaker> {
 | 
			
		||||
template <class T> class MOS6560 {
 | 
			
		||||
	public:
 | 
			
		||||
		MOS6560() :
 | 
			
		||||
			crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
 | 
			
		||||
			speaker_(new Speaker),
 | 
			
		||||
			horizontal_counter_(0),
 | 
			
		||||
			vertical_counter_(0),
 | 
			
		||||
			cycles_since_speaker_update_(0),
 | 
			
		||||
			is_odd_frame_(false),
 | 
			
		||||
			is_odd_line_(false)
 | 
			
		||||
				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
 | 
			
		||||
				audio_generator_(audio_queue_),
 | 
			
		||||
				speaker_(audio_generator_)
 | 
			
		||||
		{
 | 
			
		||||
			crt_->set_composite_sampling_function(
 | 
			
		||||
				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
 | 
			
		||||
				"{"
 | 
			
		||||
					"uint c = texture(texID, coordinate).r;"
 | 
			
		||||
					"float y = float(c >> 4) / 4.0;"
 | 
			
		||||
					"uint yC = c & 15u;"
 | 
			
		||||
					"float phaseOffset = 6.283185308 * float(yC) / 16.0;"
 | 
			
		||||
					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
 | 
			
		||||
					"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
 | 
			
		||||
 | 
			
		||||
					"float chroma = cos(phase + phaseOffset);"
 | 
			
		||||
					"return mix(y, step(yC, 14) * chroma, amplitude);"
 | 
			
		||||
					"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
 | 
			
		||||
				"}");
 | 
			
		||||
 | 
			
		||||
			// default to NTSC
 | 
			
		||||
			set_output_mode(OutputMode::NTSC);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void set_clock_rate(double clock_rate)
 | 
			
		||||
		{
 | 
			
		||||
			speaker_->set_input_rate((float)(clock_rate / 4.0));
 | 
			
		||||
		void set_clock_rate(double clock_rate) {
 | 
			
		||||
			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
 | 
			
		||||
		std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
 | 
			
		||||
		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 {
 | 
			
		||||
			PAL, NTSC
 | 
			
		||||
@@ -82,29 +84,39 @@ template <class T> class MOS6560 {
 | 
			
		||||
		/*!
 | 
			
		||||
			Sets the output mode to either PAL or NTSC.
 | 
			
		||||
		*/
 | 
			
		||||
		void set_output_mode(OutputMode output_mode)
 | 
			
		||||
		{
 | 
			
		||||
		void set_output_mode(OutputMode output_mode) {
 | 
			
		||||
			output_mode_ = output_mode;
 | 
			
		||||
			uint8_t luminances[16] = {		// range is 0–4
 | 
			
		||||
				0, 4, 1, 3, 2, 2, 1, 3,
 | 
			
		||||
				2, 1, 2, 1, 2, 3, 2, 3
 | 
			
		||||
 | 
			
		||||
			// Lumunances are encoded trivially: on a 0–255 scale.
 | 
			
		||||
			const uint8_t luminances[16] = {
 | 
			
		||||
				0,		255,	109,	189,
 | 
			
		||||
				199,	144,	159,	161,
 | 
			
		||||
				126,	227,	227,	207,
 | 
			
		||||
				235,	173,	188,	196
 | 
			
		||||
			};
 | 
			
		||||
			uint8_t pal_chrominances[16] = {	// range is 0–15; 15 is a special case meaning "no chrominance"
 | 
			
		||||
				15, 15, 5, 13, 2, 10, 0, 8,
 | 
			
		||||
				6, 7, 5, 13, 2, 10, 0, 8,
 | 
			
		||||
 | 
			
		||||
			// Chrominances are encoded such that 0–128 is a complete revolution of phase;
 | 
			
		||||
			// anything above 191 disables the colour subcarrier. Phase is relative to the
 | 
			
		||||
			// colour burst, so 0 is green.
 | 
			
		||||
			const uint8_t pal_chrominances[16] = {
 | 
			
		||||
				255,	255,	40,		112,
 | 
			
		||||
				8,		88,		120,	56,
 | 
			
		||||
				40,		48,		40,		112,
 | 
			
		||||
				8,		88,		120,	56,
 | 
			
		||||
			};
 | 
			
		||||
			uint8_t ntsc_chrominances[16] = {
 | 
			
		||||
				15, 15, 2, 10, 4, 12, 6, 14,
 | 
			
		||||
				0, 8, 2, 10, 4, 12, 6, 14,
 | 
			
		||||
			const uint8_t ntsc_chrominances[16] = {
 | 
			
		||||
				255,	255,	8,		72,
 | 
			
		||||
				32,		88,		48,		112,
 | 
			
		||||
				0,		0,		8,		72,
 | 
			
		||||
				32,		88,		48,		112,
 | 
			
		||||
			};
 | 
			
		||||
			uint8_t *chrominances;
 | 
			
		||||
			const uint8_t *chrominances;
 | 
			
		||||
			Outputs::CRT::DisplayType display_type;
 | 
			
		||||
 | 
			
		||||
			switch(output_mode)
 | 
			
		||||
			{
 | 
			
		||||
				case OutputMode::PAL:
 | 
			
		||||
			switch(output_mode) {
 | 
			
		||||
				default:
 | 
			
		||||
					chrominances = pal_chrominances;
 | 
			
		||||
					display_type = Outputs::CRT::PAL50;
 | 
			
		||||
					display_type = Outputs::CRT::DisplayType::PAL50;
 | 
			
		||||
					timing_.cycles_per_line = 71;
 | 
			
		||||
					timing_.line_counter_increment_offset = 0;
 | 
			
		||||
					timing_.lines_per_progressive_field = 312;
 | 
			
		||||
@@ -113,7 +125,7 @@ template <class T> class MOS6560 {
 | 
			
		||||
 | 
			
		||||
				case OutputMode::NTSC:
 | 
			
		||||
					chrominances = ntsc_chrominances;
 | 
			
		||||
					display_type = Outputs::CRT::NTSC60;
 | 
			
		||||
					display_type = Outputs::CRT::DisplayType::NTSC60;
 | 
			
		||||
					timing_.cycles_per_line = 65;
 | 
			
		||||
					timing_.line_counter_increment_offset = 65 - 33;	// TODO: this is a bit of a hack; separate vertical and horizontal counting
 | 
			
		||||
					timing_.lines_per_progressive_field = 261;
 | 
			
		||||
@@ -121,11 +133,10 @@ template <class T> class MOS6560 {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
 | 
			
		||||
//			crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
 | 
			
		||||
			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:
 | 
			
		||||
//					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
 | 
			
		||||
//				break;
 | 
			
		||||
@@ -134,32 +145,30 @@ template <class T> class MOS6560 {
 | 
			
		||||
//				break;
 | 
			
		||||
//			}
 | 
			
		||||
 | 
			
		||||
			for(int c = 0; c < 16; c++)
 | 
			
		||||
			{
 | 
			
		||||
				colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
 | 
			
		||||
			for(int c = 0; c < 16; c++) {
 | 
			
		||||
				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
 | 
			
		||||
				colour[0] = luminances[c];
 | 
			
		||||
				colour[1] = chrominances[c];
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Runs for cycles. Derr.
 | 
			
		||||
		*/
 | 
			
		||||
		inline void run_for_cycles(unsigned int number_of_cycles)
 | 
			
		||||
		{
 | 
			
		||||
		inline void run_for(const Cycles cycles) {
 | 
			
		||||
			// keep track of the amount of time since the speaker was updated; lazy updates are applied
 | 
			
		||||
			cycles_since_speaker_update_ += number_of_cycles;
 | 
			
		||||
			cycles_since_speaker_update_ += cycles;
 | 
			
		||||
 | 
			
		||||
			while(number_of_cycles--)
 | 
			
		||||
			{
 | 
			
		||||
			int number_of_cycles = cycles.as_int();
 | 
			
		||||
			while(number_of_cycles--) {
 | 
			
		||||
				// keep an old copy of the vertical count because that test is a cycle later than the actual changes
 | 
			
		||||
				int previous_vertical_counter = vertical_counter_;
 | 
			
		||||
 | 
			
		||||
				// keep track of internal time relative to this scanline
 | 
			
		||||
				horizontal_counter_++;
 | 
			
		||||
				full_frame_counter_++;
 | 
			
		||||
				if(horizontal_counter_ == timing_.cycles_per_line)
 | 
			
		||||
				{
 | 
			
		||||
					if(horizontal_drawing_latch_)
 | 
			
		||||
					{
 | 
			
		||||
				if(horizontal_counter_ == timing_.cycles_per_line) {
 | 
			
		||||
					if(horizontal_drawing_latch_) {
 | 
			
		||||
						current_character_row_++;
 | 
			
		||||
						if(
 | 
			
		||||
							(current_character_row_ == 16) ||
 | 
			
		||||
@@ -179,8 +188,7 @@ template <class T> class MOS6560 {
 | 
			
		||||
					horizontal_drawing_latch_ = false;
 | 
			
		||||
 | 
			
		||||
					vertical_counter_ ++;
 | 
			
		||||
					if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field))
 | 
			
		||||
					{
 | 
			
		||||
					if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
 | 
			
		||||
						vertical_counter_ = 0;
 | 
			
		||||
						full_frame_counter_ = 0;
 | 
			
		||||
 | 
			
		||||
@@ -198,11 +206,9 @@ template <class T> class MOS6560 {
 | 
			
		||||
				horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
 | 
			
		||||
 | 
			
		||||
				if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
 | 
			
		||||
				switch(pixel_line_cycle_)
 | 
			
		||||
				{
 | 
			
		||||
				switch(pixel_line_cycle_) {
 | 
			
		||||
					case -1:
 | 
			
		||||
						if(horizontal_drawing_latch_)
 | 
			
		||||
						{
 | 
			
		||||
						if(horizontal_drawing_latch_) {
 | 
			
		||||
							pixel_line_cycle_ = 0;
 | 
			
		||||
							video_matrix_address_counter_ = base_video_matrix_address_counter_;
 | 
			
		||||
						}
 | 
			
		||||
@@ -213,15 +219,11 @@ template <class T> class MOS6560 {
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				uint16_t fetch_address = 0x1c;
 | 
			
		||||
				if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2)
 | 
			
		||||
				{
 | 
			
		||||
					if(column_counter_&1)
 | 
			
		||||
					{
 | 
			
		||||
				if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
 | 
			
		||||
					if(column_counter_&1) {
 | 
			
		||||
						fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
 | 
			
		||||
					} else {
 | 
			
		||||
						fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
 | 
			
		||||
						video_matrix_address_counter_++;
 | 
			
		||||
						if(
 | 
			
		||||
							(current_character_row_ == 15) ||
 | 
			
		||||
@@ -244,8 +246,7 @@ template <class T> class MOS6560 {
 | 
			
		||||
				// determine output state; colour burst and sync timing are currently a guess
 | 
			
		||||
				if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst;
 | 
			
		||||
				else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state_ = State::Sync;
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
				else {
 | 
			
		||||
					this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
@@ -262,12 +263,10 @@ template <class T> class MOS6560 {
 | 
			
		||||
					this_state_ = State::Sync;
 | 
			
		||||
 | 
			
		||||
				// update the CRT
 | 
			
		||||
				if(this_state_ != output_state_)
 | 
			
		||||
				{
 | 
			
		||||
					switch(output_state_)
 | 
			
		||||
					{
 | 
			
		||||
				if(this_state_ != output_state_) {
 | 
			
		||||
					switch(output_state_) {
 | 
			
		||||
						case State::Sync:			crt_->output_sync(cycles_in_state_ * 4);														break;
 | 
			
		||||
						case State::ColourBurst:	crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0);	break;
 | 
			
		||||
						case State::ColourBurst:	crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0);		break;
 | 
			
		||||
						case State::Border:			output_border(cycles_in_state_ * 4);															break;
 | 
			
		||||
						case State::Pixels:			crt_->output_data(cycles_in_state_ * 4, 1);														break;
 | 
			
		||||
					}
 | 
			
		||||
@@ -275,32 +274,24 @@ template <class T> class MOS6560 {
 | 
			
		||||
					cycles_in_state_ = 0;
 | 
			
		||||
 | 
			
		||||
					pixel_pointer = nullptr;
 | 
			
		||||
					if(output_state_ == State::Pixels)
 | 
			
		||||
					{
 | 
			
		||||
						pixel_pointer = crt_->allocate_write_area(260);
 | 
			
		||||
					if(output_state_ == State::Pixels) {
 | 
			
		||||
						pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				cycles_in_state_++;
 | 
			
		||||
 | 
			
		||||
				if(this_state_ == State::Pixels)
 | 
			
		||||
				{
 | 
			
		||||
					if(column_counter_&1)
 | 
			
		||||
					{
 | 
			
		||||
				if(this_state_ == State::Pixels) {
 | 
			
		||||
					if(column_counter_&1) {
 | 
			
		||||
						character_value_ = pixel_data;
 | 
			
		||||
 | 
			
		||||
						if(pixel_pointer)
 | 
			
		||||
						{
 | 
			
		||||
							uint8_t cell_colour = colours_[character_colour_ & 0x7];
 | 
			
		||||
							if(!(character_colour_&0x8))
 | 
			
		||||
							{
 | 
			
		||||
								uint8_t colours[2];
 | 
			
		||||
								if(registers_.invertedCells)
 | 
			
		||||
								{
 | 
			
		||||
						if(pixel_pointer) {
 | 
			
		||||
							uint16_t cell_colour = colours_[character_colour_ & 0x7];
 | 
			
		||||
							if(!(character_colour_&0x8)) {
 | 
			
		||||
								uint16_t colours[2];
 | 
			
		||||
								if(registers_.invertedCells) {
 | 
			
		||||
									colours[0] = cell_colour;
 | 
			
		||||
									colours[1] = registers_.backgroundColour;
 | 
			
		||||
								}
 | 
			
		||||
								else
 | 
			
		||||
								{
 | 
			
		||||
								} else {
 | 
			
		||||
									colours[0] = registers_.backgroundColour;
 | 
			
		||||
									colours[1] = cell_colour;
 | 
			
		||||
								}
 | 
			
		||||
@@ -312,10 +303,8 @@ template <class T> class MOS6560 {
 | 
			
		||||
								pixel_pointer[5] = colours[(character_value_ >> 2)&1];
 | 
			
		||||
								pixel_pointer[6] = colours[(character_value_ >> 1)&1];
 | 
			
		||||
								pixel_pointer[7] = colours[(character_value_ >> 0)&1];
 | 
			
		||||
							}
 | 
			
		||||
							else
 | 
			
		||||
							{
 | 
			
		||||
								uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
 | 
			
		||||
							} else {
 | 
			
		||||
								uint16_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
 | 
			
		||||
								pixel_pointer[0] =
 | 
			
		||||
								pixel_pointer[1] = colours[(character_value_ >> 6)&3];
 | 
			
		||||
								pixel_pointer[2] =
 | 
			
		||||
@@ -325,11 +314,10 @@ template <class T> class MOS6560 {
 | 
			
		||||
								pixel_pointer[6] =
 | 
			
		||||
								pixel_pointer[7] = colours[(character_value_ >> 0)&3];
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							pixel_pointer += 8;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
					} else {
 | 
			
		||||
						character_code_ = pixel_data;
 | 
			
		||||
						character_colour_ = colour_data;
 | 
			
		||||
					}
 | 
			
		||||
@@ -342,17 +330,18 @@ template <class T> class MOS6560 {
 | 
			
		||||
		/*!
 | 
			
		||||
			Causes the 6560 to flush as much pending CRT and speaker communications as possible.
 | 
			
		||||
		*/
 | 
			
		||||
		inline void synchronise() { update_audio(); speaker_->flush(); }
 | 
			
		||||
		inline void flush() {
 | 
			
		||||
			update_audio();
 | 
			
		||||
			audio_queue_.perform();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Writes to a 6560 register.
 | 
			
		||||
		*/
 | 
			
		||||
		void set_register(int address, uint8_t value)
 | 
			
		||||
		{
 | 
			
		||||
		void set_register(int address, uint8_t value) {
 | 
			
		||||
			address &= 0xf;
 | 
			
		||||
			registers_.direct_values[address] = value;
 | 
			
		||||
			switch(address)
 | 
			
		||||
			{
 | 
			
		||||
			switch(address) {
 | 
			
		||||
				case 0x0:
 | 
			
		||||
					registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
 | 
			
		||||
					registers_.first_column_location = value & 0x7f;
 | 
			
		||||
@@ -364,7 +353,7 @@ template <class T> class MOS6560 {
 | 
			
		||||
 | 
			
		||||
				case 0x2:
 | 
			
		||||
					registers_.number_of_columns = value & 0x7f;
 | 
			
		||||
					registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
 | 
			
		||||
					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 0x3:
 | 
			
		||||
@@ -373,8 +362,8 @@ template <class T> class MOS6560 {
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 0x5:
 | 
			
		||||
					registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
 | 
			
		||||
					registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
 | 
			
		||||
					registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
 | 
			
		||||
					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 0xa:
 | 
			
		||||
@@ -382,20 +371,18 @@ template <class T> class MOS6560 {
 | 
			
		||||
				case 0xc:
 | 
			
		||||
				case 0xd:
 | 
			
		||||
					update_audio();
 | 
			
		||||
					speaker_->set_control(address - 0xa, value);
 | 
			
		||||
					audio_generator_.set_control(address - 0xa, value);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 0xe:
 | 
			
		||||
					update_audio();
 | 
			
		||||
					registers_.auxiliary_colour = colours_[value >> 4];
 | 
			
		||||
					speaker_->set_volume(value & 0xf);
 | 
			
		||||
					audio_generator_.set_volume(value & 0xf);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 0xf:
 | 
			
		||||
				{
 | 
			
		||||
					uint8_t new_border_colour = colours_[value & 0x07];
 | 
			
		||||
					if(this_state_ == State::Border && new_border_colour != registers_.borderColour)
 | 
			
		||||
					{
 | 
			
		||||
				case 0xf: {
 | 
			
		||||
					uint16_t new_border_colour = colours_[value & 0x07];
 | 
			
		||||
					if(this_state_ == State::Border && new_border_colour != registers_.borderColour) {
 | 
			
		||||
						output_border(cycles_in_state_ * 4);
 | 
			
		||||
						cycles_in_state_ = 0;
 | 
			
		||||
					}
 | 
			
		||||
@@ -415,27 +402,26 @@ template <class T> class MOS6560 {
 | 
			
		||||
		/*
 | 
			
		||||
			Reads from a 6560 register.
 | 
			
		||||
		*/
 | 
			
		||||
		uint8_t get_register(int address)
 | 
			
		||||
		{
 | 
			
		||||
		uint8_t get_register(int address) {
 | 
			
		||||
			address &= 0xf;
 | 
			
		||||
			int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
 | 
			
		||||
			switch(address)
 | 
			
		||||
			{
 | 
			
		||||
			switch(address) {
 | 
			
		||||
				default: return registers_.direct_values[address];
 | 
			
		||||
				case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
 | 
			
		||||
				case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
 | 
			
		||||
				case 0x04: return (current_line >> 1) & 0xff;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::shared_ptr<Outputs::CRT::CRT> crt_;
 | 
			
		||||
		std::unique_ptr<Outputs::CRT::CRT> crt_;
 | 
			
		||||
 | 
			
		||||
		std::shared_ptr<Speaker> speaker_;
 | 
			
		||||
		unsigned int cycles_since_speaker_update_;
 | 
			
		||||
		void update_audio()
 | 
			
		||||
		{
 | 
			
		||||
			speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
 | 
			
		||||
			cycles_since_speaker_update_ &= 3;
 | 
			
		||||
		Concurrency::DeferringAsyncTaskQueue audio_queue_;
 | 
			
		||||
		AudioGenerator audio_generator_;
 | 
			
		||||
		Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
 | 
			
		||||
 | 
			
		||||
		Cycles cycles_since_speaker_update_;
 | 
			
		||||
		void update_audio() {
 | 
			
		||||
			speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// register state
 | 
			
		||||
@@ -444,7 +430,7 @@ template <class T> class MOS6560 {
 | 
			
		||||
			uint8_t first_column_location, first_row_location;
 | 
			
		||||
			uint8_t number_of_columns, number_of_rows;
 | 
			
		||||
			uint16_t character_cell_start_address, video_matrix_start_address;
 | 
			
		||||
			uint8_t backgroundColour, borderColour, auxiliary_colour;
 | 
			
		||||
			uint16_t backgroundColour, borderColour, auxiliary_colour;
 | 
			
		||||
			bool invertedCells;
 | 
			
		||||
 | 
			
		||||
			uint8_t direct_values[16];
 | 
			
		||||
@@ -457,7 +443,7 @@ template <class T> class MOS6560 {
 | 
			
		||||
		unsigned int cycles_in_state_;
 | 
			
		||||
 | 
			
		||||
		// counters that cover an entire field
 | 
			
		||||
		int horizontal_counter_, vertical_counter_, full_frame_counter_;
 | 
			
		||||
		int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
 | 
			
		||||
 | 
			
		||||
		// latches dictating start and length of drawing
 | 
			
		||||
		bool vertical_drawing_latch_, horizontal_drawing_latch_;
 | 
			
		||||
@@ -472,15 +458,14 @@ template <class T> class MOS6560 {
 | 
			
		||||
		// data latched from the bus
 | 
			
		||||
		uint8_t character_code_, character_colour_, character_value_;
 | 
			
		||||
 | 
			
		||||
		bool is_odd_frame_, is_odd_line_;
 | 
			
		||||
		bool is_odd_frame_ = false, is_odd_line_ = false;
 | 
			
		||||
 | 
			
		||||
		// lookup table from 6560 colour index to appropriate PAL/NTSC value
 | 
			
		||||
		uint8_t colours_[16];
 | 
			
		||||
		uint16_t colours_[16];
 | 
			
		||||
 | 
			
		||||
		uint8_t *pixel_pointer;
 | 
			
		||||
		void output_border(unsigned int number_of_cycles)
 | 
			
		||||
		{
 | 
			
		||||
			uint8_t *colour_pointer = crt_->allocate_write_area(1);
 | 
			
		||||
		uint16_t *pixel_pointer;
 | 
			
		||||
		void output_border(unsigned int number_of_cycles) {
 | 
			
		||||
			uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
 | 
			
		||||
			if(colour_pointer) *colour_pointer = registers_.borderColour;
 | 
			
		||||
			crt_->output_level(number_of_cycles);
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										275
									
								
								Components/6845/CRTC6845.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								Components/6845/CRTC6845.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,275 @@
 | 
			
		||||
//
 | 
			
		||||
//  CRTC6845.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 31/07/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef CRTC6845_hpp
 | 
			
		||||
#define CRTC6845_hpp
 | 
			
		||||
 | 
			
		||||
#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
namespace Motorola {
 | 
			
		||||
namespace CRTC {
 | 
			
		||||
 | 
			
		||||
struct BusState {
 | 
			
		||||
	bool display_enable = false;
 | 
			
		||||
	bool hsync = false;
 | 
			
		||||
	bool vsync = false;
 | 
			
		||||
	bool cursor = false;
 | 
			
		||||
	uint16_t refresh_address = 0;
 | 
			
		||||
	uint16_t row_address = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BusHandler {
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Performs the first phase of a 6845 bus cycle; this is the phase in which it is intended that
 | 
			
		||||
			systems using the 6845 respect the bus state and produce pixels, sync or whatever they require.
 | 
			
		||||
		*/
 | 
			
		||||
		void perform_bus_cycle_phase1(const BusState &) {}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated
 | 
			
		||||
			directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore
 | 
			
		||||
			implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without
 | 
			
		||||
			having to wait until the next cycle has begun.
 | 
			
		||||
		*/
 | 
			
		||||
		void perform_bus_cycle_phase2(const BusState &) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum Personality {
 | 
			
		||||
	HD6845S,	// Type 0 in CPC parlance. Zero-width HSYNC available, no status, programmable VSYNC length.
 | 
			
		||||
				// Considered exactly identical to the UM6845, so this enum covers both.
 | 
			
		||||
	UM6845R,	// Type 1 in CPC parlance. Status register, fixed-length VSYNC.
 | 
			
		||||
	MC6845,		// Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
 | 
			
		||||
	AMS40226	// Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
 | 
			
		||||
 | 
			
		||||
template <class T> class CRTC6845 {
 | 
			
		||||
	public:
 | 
			
		||||
 | 
			
		||||
		CRTC6845(Personality p, T &bus_handler) noexcept :
 | 
			
		||||
			personality_(p), bus_handler_(bus_handler), status_(0) {}
 | 
			
		||||
 | 
			
		||||
		void select_register(uint8_t r) {
 | 
			
		||||
			selected_register_ = r;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uint8_t get_status() {
 | 
			
		||||
			switch(personality_) {
 | 
			
		||||
				case UM6845R:	return status_ | (bus_state_.vsync ? 0x20 : 0x00);
 | 
			
		||||
				case AMS40226:	return get_register();
 | 
			
		||||
				default:		return 0xff;
 | 
			
		||||
			}
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uint8_t get_register() {
 | 
			
		||||
			if(selected_register_ == 31) status_ &= ~0x80;
 | 
			
		||||
			if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
 | 
			
		||||
 | 
			
		||||
			if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_;
 | 
			
		||||
			if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
 | 
			
		||||
			return registers_[selected_register_];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void set_register(uint8_t value) {
 | 
			
		||||
			static uint8_t masks[] = {
 | 
			
		||||
				0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f,
 | 
			
		||||
				0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
 | 
			
		||||
			if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) {
 | 
			
		||||
				switch((value >> 4)&3) {
 | 
			
		||||
					default:	display_skew_mask_ = 1;		break;
 | 
			
		||||
					case 1:		display_skew_mask_ = 2;		break;
 | 
			
		||||
					case 2:		display_skew_mask_ = 4;		break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(selected_register_ < 16) {
 | 
			
		||||
				registers_[selected_register_] = value & masks[selected_register_];
 | 
			
		||||
			}
 | 
			
		||||
			if(selected_register_ == 31 && personality_ == UM6845R) {
 | 
			
		||||
				dummy_register_ = value;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void trigger_light_pen() {
 | 
			
		||||
			registers_[17] = bus_state_.refresh_address & 0xff;
 | 
			
		||||
			registers_[16] = bus_state_.refresh_address >> 8;
 | 
			
		||||
			status_ |= 0x40;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void run_for(Cycles cycles) {
 | 
			
		||||
			int cyles_remaining = cycles.as_int();
 | 
			
		||||
			while(cyles_remaining--) {
 | 
			
		||||
				// check for end of visible characters
 | 
			
		||||
				if(character_counter_ == registers_[1]) {
 | 
			
		||||
					// TODO: consider skew in character_is_visible_. Or maybe defer until perform_bus_cycle?
 | 
			
		||||
					character_is_visible_ = false;
 | 
			
		||||
					end_of_line_address_ = bus_state_.refresh_address;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				perform_bus_cycle_phase1();
 | 
			
		||||
				bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff;
 | 
			
		||||
 | 
			
		||||
				// check for end-of-line
 | 
			
		||||
				if(character_counter_ == registers_[0]) {
 | 
			
		||||
					character_counter_ = 0;
 | 
			
		||||
					do_end_of_line();
 | 
			
		||||
					character_is_visible_ = true;
 | 
			
		||||
				} else {
 | 
			
		||||
					// increment counter
 | 
			
		||||
					character_counter_++;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// check for start of horizontal sync
 | 
			
		||||
				if(character_counter_ == registers_[2]) {
 | 
			
		||||
					hsync_counter_ = 0;
 | 
			
		||||
					bus_state_.hsync = true;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// check for end of horizontal sync; note that a sync time of zero will result in an immediate
 | 
			
		||||
				// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero
 | 
			
		||||
				// will end up counting as 16 as it won't be checked until after overflow.
 | 
			
		||||
				if(bus_state_.hsync) {
 | 
			
		||||
					switch(personality_) {
 | 
			
		||||
						case HD6845S:
 | 
			
		||||
						case UM6845R:
 | 
			
		||||
							bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
 | 
			
		||||
							hsync_counter_ = (hsync_counter_ + 1) & 15;
 | 
			
		||||
						break;
 | 
			
		||||
						default:
 | 
			
		||||
							hsync_counter_ = (hsync_counter_ + 1) & 15;
 | 
			
		||||
							bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				perform_bus_cycle_phase2();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const BusState &get_bus_state() const {
 | 
			
		||||
			return bus_state_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		inline void perform_bus_cycle_phase1() {
 | 
			
		||||
			// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
 | 
			
		||||
			character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
 | 
			
		||||
			bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
 | 
			
		||||
			bus_handler_.perform_bus_cycle_phase1(bus_state_);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline void perform_bus_cycle_phase2() {
 | 
			
		||||
			bus_handler_.perform_bus_cycle_phase2(bus_state_);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline void do_end_of_line() {
 | 
			
		||||
			// check for end of vertical sync
 | 
			
		||||
			if(bus_state_.vsync) {
 | 
			
		||||
				vsync_counter_ = (vsync_counter_ + 1) & 15;
 | 
			
		||||
				// on the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs
 | 
			
		||||
				// always use a vertical sync count of 16.
 | 
			
		||||
				switch(personality_) {
 | 
			
		||||
					case HD6845S:
 | 
			
		||||
					case AMS40226:
 | 
			
		||||
						bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4);
 | 
			
		||||
					break;
 | 
			
		||||
					default:
 | 
			
		||||
						bus_state_.vsync = vsync_counter_ != 0;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(is_in_adjustment_period_) {
 | 
			
		||||
				line_counter_++;
 | 
			
		||||
				if(line_counter_ == registers_[5]) {
 | 
			
		||||
					is_in_adjustment_period_ = false;
 | 
			
		||||
					do_end_of_frame();
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// advance vertical counter
 | 
			
		||||
				if(bus_state_.row_address == registers_[9]) {
 | 
			
		||||
					bus_state_.row_address = 0;
 | 
			
		||||
					line_address_ = end_of_line_address_;
 | 
			
		||||
 | 
			
		||||
					// check for entry into the overflow area
 | 
			
		||||
					if(line_counter_ == registers_[4]) {
 | 
			
		||||
						if(registers_[5]) {
 | 
			
		||||
							line_counter_ = 0;
 | 
			
		||||
							is_in_adjustment_period_ = true;
 | 
			
		||||
						} else {
 | 
			
		||||
							do_end_of_frame();
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						line_counter_ = (line_counter_ + 1) & 0x7f;
 | 
			
		||||
 | 
			
		||||
						// check for start of vertical sync
 | 
			
		||||
						if(line_counter_ == registers_[7]) {
 | 
			
		||||
							bus_state_.vsync = true;
 | 
			
		||||
							vsync_counter_ = 0;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						// check for end of visible lines
 | 
			
		||||
						if(line_counter_ == registers_[6]) {
 | 
			
		||||
							line_is_visible_ = false;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			bus_state_.refresh_address = line_address_;
 | 
			
		||||
			character_counter_ = 0;
 | 
			
		||||
			character_is_visible_ = (registers_[1] != 0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inline void do_end_of_frame() {
 | 
			
		||||
			line_counter_ = 0;
 | 
			
		||||
			line_is_visible_ = true;
 | 
			
		||||
			line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
 | 
			
		||||
			bus_state_.refresh_address = line_address_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Personality personality_;
 | 
			
		||||
		T &bus_handler_;
 | 
			
		||||
		BusState bus_state_;
 | 
			
		||||
 | 
			
		||||
		uint8_t registers_[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 | 
			
		||||
		uint8_t dummy_register_ = 0;
 | 
			
		||||
		int selected_register_ = 0;
 | 
			
		||||
 | 
			
		||||
		uint8_t character_counter_ = 0;
 | 
			
		||||
		uint8_t line_counter_ = 0;
 | 
			
		||||
 | 
			
		||||
		bool character_is_visible_ = false, line_is_visible_ = false;
 | 
			
		||||
 | 
			
		||||
		int hsync_counter_ = 0;
 | 
			
		||||
		int vsync_counter_ = 0;
 | 
			
		||||
		bool is_in_adjustment_period_ = false;
 | 
			
		||||
 | 
			
		||||
		uint16_t line_address_ = 0;
 | 
			
		||||
		uint16_t end_of_line_address_ = 0;
 | 
			
		||||
		uint8_t status_ = 0;
 | 
			
		||||
 | 
			
		||||
		int display_skew_mask_ = 1;
 | 
			
		||||
		unsigned int character_is_visible_shifter_ = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* CRTC6845_hpp */
 | 
			
		||||
							
								
								
									
										92
									
								
								Components/8255/i8255.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Components/8255/i8255.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
//
 | 
			
		||||
//  i8255.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 01/08/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef i8255_hpp
 | 
			
		||||
#define i8255_hpp
 | 
			
		||||
 | 
			
		||||
namespace Intel {
 | 
			
		||||
namespace i8255 {
 | 
			
		||||
 | 
			
		||||
class PortHandler {
 | 
			
		||||
	public:
 | 
			
		||||
		void set_value(int port, uint8_t value) {}
 | 
			
		||||
		uint8_t get_value(int port) { return 0xff; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: Modes 1 and 2.
 | 
			
		||||
template <class T> class i8255 {
 | 
			
		||||
	public:
 | 
			
		||||
		i8255(T &port_handler) : control_(0), outputs_{0, 0, 0}, port_handler_(port_handler) {}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Stores the value @c value to the register at @c address. If this causes a change in 8255 output
 | 
			
		||||
			then the PortHandler will be informed.
 | 
			
		||||
		*/
 | 
			
		||||
		void set_register(int address, uint8_t value) {
 | 
			
		||||
			switch(address & 3) {
 | 
			
		||||
				case 0:
 | 
			
		||||
					if(!(control_ & 0x10)) {
 | 
			
		||||
						// TODO: so what would output be when switching from input to output mode?
 | 
			
		||||
						outputs_[0] = value; port_handler_.set_value(0, value);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 1:
 | 
			
		||||
					if(!(control_ & 0x02)) {
 | 
			
		||||
						outputs_[1] = value; port_handler_.set_value(1, value);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 2:	outputs_[2] = value; port_handler_.set_value(2, value);	break;
 | 
			
		||||
				case 3:
 | 
			
		||||
					if(value & 0x80) {
 | 
			
		||||
						control_ = value;
 | 
			
		||||
					} else {
 | 
			
		||||
						if(value & 1) {
 | 
			
		||||
							outputs_[2] |= 1 << ((value >> 1)&7);
 | 
			
		||||
						} else {
 | 
			
		||||
							outputs_[2] &= ~(1 << ((value >> 1)&7));
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					update_outputs();
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Obtains the current value for the register at @c address. If this provides a reading
 | 
			
		||||
			of input then the PortHandler will be queried.
 | 
			
		||||
		*/
 | 
			
		||||
		uint8_t get_register(int address) {
 | 
			
		||||
			switch(address & 3) {
 | 
			
		||||
				case 0:	return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0];
 | 
			
		||||
				case 1:	return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1];
 | 
			
		||||
				case 2:	{
 | 
			
		||||
					if(!(control_ & 0x09)) return outputs_[2];
 | 
			
		||||
					uint8_t input = port_handler_.get_value(2);
 | 
			
		||||
					return ((control_ & 0x01) ? (input & 0x0f) : (outputs_[2] & 0x0f)) | ((control_ & 0x08) ? (input & 0xf0) : (outputs_[2] & 0xf0));
 | 
			
		||||
				}
 | 
			
		||||
				case 3:	return control_;
 | 
			
		||||
			}
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void update_outputs() {
 | 
			
		||||
			if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
 | 
			
		||||
			if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
 | 
			
		||||
			port_handler_.set_value(2, outputs_[2]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uint8_t control_;
 | 
			
		||||
		uint8_t outputs_[3];
 | 
			
		||||
		T &port_handler_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* i8255_hpp */
 | 
			
		||||
							
								
								
									
										872
									
								
								Components/8272/i8272.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										872
									
								
								Components/8272/i8272.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,872 @@
 | 
			
		||||
//
 | 
			
		||||
//  i8272.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 05/08/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "i8272.hpp"
 | 
			
		||||
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
using namespace Intel::i8272;
 | 
			
		||||
 | 
			
		||||
#define SetDataRequest()				(main_status_ |= 0x80)
 | 
			
		||||
#define ResetDataRequest()				(main_status_ &= ~0x80)
 | 
			
		||||
#define DataRequest()					(main_status_ & 0x80)
 | 
			
		||||
 | 
			
		||||
#define SetDataDirectionToProcessor()	(main_status_ |= 0x40)
 | 
			
		||||
#define SetDataDirectionFromProcessor()	(main_status_ &= ~0x40)
 | 
			
		||||
#define DataDirectionToProcessor()		(main_status_ & 0x40)
 | 
			
		||||
 | 
			
		||||
#define SetNonDMAExecution()			(main_status_ |= 0x20)
 | 
			
		||||
#define ResetNonDMAExecution()			(main_status_ &= ~0x20)
 | 
			
		||||
 | 
			
		||||
#define SetBusy()						(main_status_ |= 0x10)
 | 
			
		||||
#define ResetBusy()						(main_status_ &= ~0x10)
 | 
			
		||||
#define Busy()							(main_status_ & 0x10)
 | 
			
		||||
 | 
			
		||||
#define SetAbnormalTermination()		(status_[0] |= 0x40)
 | 
			
		||||
#define SetInvalidCommand()				(status_[0] |= 0x80)
 | 
			
		||||
#define SetReadyChanged()				(status_[0] |= 0xc0)
 | 
			
		||||
#define SetSeekEnd()					(status_[0] |= 0x20)
 | 
			
		||||
#define SetEquipmentCheck()				(status_[0] |= 0x10)
 | 
			
		||||
#define SetNotReady()					(status_[0] |= 0x08)
 | 
			
		||||
#define SetSide2()						(status_[0] |= 0x04)
 | 
			
		||||
 | 
			
		||||
#define SetEndOfCylinder()				(status_[1] |= 0x80)
 | 
			
		||||
#define SetDataError()					(status_[1] |= 0x20)
 | 
			
		||||
#define SetOverrun()					(status_[1] |= 0x10)
 | 
			
		||||
#define SetNoData()						(status_[1] |= 0x04)
 | 
			
		||||
#define SetNotWriteable()				(status_[1] |= 0x02)
 | 
			
		||||
#define SetMissingAddressMark()			(status_[1] |= 0x01)
 | 
			
		||||
 | 
			
		||||
#define SetControlMark()				(status_[2] |= 0x40)
 | 
			
		||||
#define ClearControlMark()				(status_[2] &= ~0x40)
 | 
			
		||||
#define ControlMark()					(status_[2] & 0x40)
 | 
			
		||||
 | 
			
		||||
#define SetDataFieldDataError()			(status_[2] |= 0x20)
 | 
			
		||||
#define SetWrongCyinder()				(status_[2] |= 0x10)
 | 
			
		||||
#define SetScanEqualHit()				(status_[2] |= 0x08)
 | 
			
		||||
#define SetScanNotSatisfied()			(status_[2] |= 0x04)
 | 
			
		||||
#define SetBadCylinder()				(status_[2] |= 0x02)
 | 
			
		||||
#define SetMissingDataAddressMark()		(status_[2] |= 0x01)
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
	const uint8_t CommandReadData = 0x06;
 | 
			
		||||
	const uint8_t CommandReadDeletedData = 0x0c;
 | 
			
		||||
 | 
			
		||||
	const uint8_t CommandWriteData = 0x05;
 | 
			
		||||
	const uint8_t CommandWriteDeletedData = 0x09;
 | 
			
		||||
 | 
			
		||||
	const uint8_t CommandReadTrack = 0x02;
 | 
			
		||||
	const uint8_t CommandReadID = 0x0a;
 | 
			
		||||
	const uint8_t CommandFormatTrack = 0x0d;
 | 
			
		||||
 | 
			
		||||
	const uint8_t CommandScanLow = 0x11;
 | 
			
		||||
	const uint8_t CommandScanLowOrEqual = 0x19;
 | 
			
		||||
	const uint8_t CommandScanHighOrEqual = 0x1d;
 | 
			
		||||
 | 
			
		||||
	const uint8_t CommandRecalibrate = 0x07;
 | 
			
		||||
	const uint8_t CommandSeek = 0x0f;
 | 
			
		||||
 | 
			
		||||
	const uint8_t CommandSenseInterruptStatus = 0x08;
 | 
			
		||||
	const uint8_t CommandSpecify = 0x03;
 | 
			
		||||
	const uint8_t CommandSenseDriveStatus = 0x04;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
 | 
			
		||||
	Storage::Disk::MFMController(clock_rate),
 | 
			
		||||
	bus_handler_(bus_handler) {
 | 
			
		||||
	posit_event(static_cast<int>(Event8272::CommandByte));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool i8272::is_sleeping() {
 | 
			
		||||
	return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void i8272::run_for(Cycles cycles) {
 | 
			
		||||
	Storage::Disk::MFMController::run_for(cycles);
 | 
			
		||||
 | 
			
		||||
	if(is_sleeping_) return;
 | 
			
		||||
 | 
			
		||||
	// check for an expired timer
 | 
			
		||||
	if(delay_time_ > 0) {
 | 
			
		||||
		if(cycles.as_int() >= delay_time_) {
 | 
			
		||||
			delay_time_ = 0;
 | 
			
		||||
			posit_event(static_cast<int>(Event8272::Timer));
 | 
			
		||||
		} else {
 | 
			
		||||
			delay_time_ -= cycles.as_int();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update seek status of any drives presently seeking
 | 
			
		||||
	if(drives_seeking_) {
 | 
			
		||||
		int drives_left = drives_seeking_;
 | 
			
		||||
		for(int c = 0; c < 4; c++) {
 | 
			
		||||
			if(drives_[c].phase == Drive::Seeking) {
 | 
			
		||||
				drives_[c].step_rate_counter += cycles.as_int();
 | 
			
		||||
				int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
 | 
			
		||||
				drives_[c].step_rate_counter %= (8000 * step_rate_time_);
 | 
			
		||||
				while(steps--) {
 | 
			
		||||
					// Perform a step.
 | 
			
		||||
					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
 | 
			
		||||
					printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
 | 
			
		||||
					select_drive(c);
 | 
			
		||||
					get_drive().step(direction);
 | 
			
		||||
					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
 | 
			
		||||
 | 
			
		||||
					// Check for completion.
 | 
			
		||||
					if(seek_is_satisfied(c)) {
 | 
			
		||||
						drives_[c].phase = Drive::CompletedSeeking;
 | 
			
		||||
						drives_seeking_--;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				drives_left--;
 | 
			
		||||
				if(!drives_left) break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check for any head unloads
 | 
			
		||||
	if(head_timers_running_) {
 | 
			
		||||
		int timers_left = head_timers_running_;
 | 
			
		||||
		for(int c = 0; c < 8; c++) {
 | 
			
		||||
			int drive = (c >> 1);
 | 
			
		||||
			int head = c&1;
 | 
			
		||||
 | 
			
		||||
			if(drives_[drive].head_unload_delay[head] > 0) {
 | 
			
		||||
				if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) {
 | 
			
		||||
					drives_[drive].head_unload_delay[head] = 0;
 | 
			
		||||
					drives_[drive].head_is_loaded[head] = false;
 | 
			
		||||
					head_timers_running_--;
 | 
			
		||||
				} else {
 | 
			
		||||
					drives_[drive].head_unload_delay[head] -= cycles.as_int();
 | 
			
		||||
				}
 | 
			
		||||
				timers_left--;
 | 
			
		||||
				if(!timers_left) break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check for busy plus ready disabled
 | 
			
		||||
	if(is_executing_ && !get_drive().get_is_ready()) {
 | 
			
		||||
		posit_event(static_cast<int>(Event8272::NoLongerReady));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
 | 
			
		||||
	if(is_sleeping_) update_sleep_observer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void i8272::set_register(int address, uint8_t value) {
 | 
			
		||||
	// don't consider attempted sets to the status register
 | 
			
		||||
	if(!address) return;
 | 
			
		||||
 | 
			
		||||
	// if not ready for commands, do nothing
 | 
			
		||||
	if(!DataRequest() || DataDirectionToProcessor()) return;
 | 
			
		||||
 | 
			
		||||
	if(expects_input_) {
 | 
			
		||||
		input_ = value;
 | 
			
		||||
		has_input_ = true;
 | 
			
		||||
		ResetDataRequest();
 | 
			
		||||
	} else {
 | 
			
		||||
		// accumulate latest byte in the command byte sequence
 | 
			
		||||
		command_.push_back(value);
 | 
			
		||||
		posit_event(static_cast<int>(Event8272::CommandByte));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t i8272::get_register(int address) {
 | 
			
		||||
	if(address) {
 | 
			
		||||
		if(result_stack_.empty()) return 0xff;
 | 
			
		||||
		uint8_t result = result_stack_.back();
 | 
			
		||||
		result_stack_.pop_back();
 | 
			
		||||
		if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	} else {
 | 
			
		||||
		return main_status_;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define BEGIN_SECTION()	switch(resume_point_) { default:
 | 
			
		||||
#define END_SECTION()	}
 | 
			
		||||
 | 
			
		||||
#define MS_TO_CYCLES(x)			x * 8000
 | 
			
		||||
#define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
 | 
			
		||||
#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
 | 
			
		||||
 | 
			
		||||
#define PASTE(x, y) x##y
 | 
			
		||||
#define CONCAT(x, y) PASTE(x, y)
 | 
			
		||||
 | 
			
		||||
#define FIND_HEADER()	\
 | 
			
		||||
	set_data_mode(DataMode::Scanning);	\
 | 
			
		||||
	CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
 | 
			
		||||
	if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; }	\
 | 
			
		||||
	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\
 | 
			
		||||
	\
 | 
			
		||||
	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\
 | 
			
		||||
	CONCAT(header_found, __LINE__):	(void)0;\
 | 
			
		||||
 | 
			
		||||
#define FIND_DATA()	\
 | 
			
		||||
	set_data_mode(DataMode::Scanning);	\
 | 
			
		||||
	CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
 | 
			
		||||
	if(event_type == static_cast<int>(Event::Token)) { \
 | 
			
		||||
		if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__);	\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#define READ_HEADER()	\
 | 
			
		||||
	distance_into_section_ = 0;	\
 | 
			
		||||
	set_data_mode(DataMode::Reading);	\
 | 
			
		||||
	CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
 | 
			
		||||
	header_[distance_into_section_] = get_latest_token().byte_value;	\
 | 
			
		||||
	distance_into_section_++; \
 | 
			
		||||
	if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__);	\
 | 
			
		||||
 | 
			
		||||
#define SET_DRIVE_HEAD_MFM()	\
 | 
			
		||||
	active_drive_ = command_[1]&3;	\
 | 
			
		||||
	active_head_ = (command_[1] >> 2)&1;	\
 | 
			
		||||
	status_[0] = (command_[1]&7);	\
 | 
			
		||||
	select_drive(active_drive_);	\
 | 
			
		||||
	get_drive().set_head(active_head_);	\
 | 
			
		||||
	set_is_double_density(command_[0] & 0x40);
 | 
			
		||||
 | 
			
		||||
#define WAIT_FOR_BYTES(n) \
 | 
			
		||||
	distance_into_section_ = 0;	\
 | 
			
		||||
	CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token);	\
 | 
			
		||||
	if(get_latest_token().type == Token::Byte) distance_into_section_++;	\
 | 
			
		||||
	if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__);
 | 
			
		||||
 | 
			
		||||
#define LOAD_HEAD()	\
 | 
			
		||||
	if(!drives_[active_drive_].head_is_loaded[active_head_]) {	\
 | 
			
		||||
		drives_[active_drive_].head_is_loaded[active_head_] = true;	\
 | 
			
		||||
		WAIT_FOR_TIME(head_load_time_);	\
 | 
			
		||||
	} else {	\
 | 
			
		||||
		if(drives_[active_drive_].head_unload_delay[active_head_] > 0) {	\
 | 
			
		||||
			drives_[active_drive_].head_unload_delay[active_head_] = 0;	\
 | 
			
		||||
			head_timers_running_--;	\
 | 
			
		||||
		}	\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#define SCHEDULE_HEAD_UNLOAD()	\
 | 
			
		||||
	if(drives_[active_drive_].head_is_loaded[active_head_]) {\
 | 
			
		||||
		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\
 | 
			
		||||
			head_timers_running_++;	\
 | 
			
		||||
			is_sleeping_ = false;	\
 | 
			
		||||
			update_sleep_observer();	\
 | 
			
		||||
		}	\
 | 
			
		||||
		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
void i8272::posit_event(int event_type) {
 | 
			
		||||
	if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
 | 
			
		||||
	if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
 | 
			
		||||
		SetNotReady();
 | 
			
		||||
		goto abort;
 | 
			
		||||
	}
 | 
			
		||||
	if(!(interesting_event_mask_ & event_type)) return;
 | 
			
		||||
	interesting_event_mask_ &= ~event_type;
 | 
			
		||||
 | 
			
		||||
	BEGIN_SECTION();
 | 
			
		||||
 | 
			
		||||
	// Resets busy and non-DMA execution, clears the command buffer, sets the data mode to scanning and flows
 | 
			
		||||
	// into wait_for_complete_command_sequence.
 | 
			
		||||
	wait_for_command:
 | 
			
		||||
			expects_input_ = false;
 | 
			
		||||
			set_data_mode(Storage::Disk::MFMController::DataMode::Scanning);
 | 
			
		||||
			ResetBusy();
 | 
			
		||||
			ResetNonDMAExecution();
 | 
			
		||||
			command_.clear();
 | 
			
		||||
 | 
			
		||||
	// Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes
 | 
			
		||||
	// until it has a quantity that make up an entire command, then resets the data request bit and
 | 
			
		||||
	// branches to that command.
 | 
			
		||||
	wait_for_complete_command_sequence:
 | 
			
		||||
			SetDataRequest();
 | 
			
		||||
			SetDataDirectionFromProcessor();
 | 
			
		||||
			WAIT_FOR_EVENT(Event8272::CommandByte)
 | 
			
		||||
			SetBusy();
 | 
			
		||||
 | 
			
		||||
			static const std::size_t required_lengths[32] = {
 | 
			
		||||
				0,	0,	9,	3,	2,	9,	9,	2,
 | 
			
		||||
				1,	9,	2,	0,	9,	6,	0,	3,
 | 
			
		||||
				0,	9,	0,	0,	0,	0,	0,	0,
 | 
			
		||||
				0,	9,	0,	0,	0,	9,	0,	0,
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			if(command_.size() < required_lengths[command_[0] & 0x1f]) goto wait_for_complete_command_sequence;
 | 
			
		||||
			if(command_.size() == 9) {
 | 
			
		||||
				cylinder_ = command_[2];
 | 
			
		||||
				head_ = command_[3];
 | 
			
		||||
				sector_ = command_[4];
 | 
			
		||||
				size_ = command_[5];
 | 
			
		||||
			}
 | 
			
		||||
			ResetDataRequest();
 | 
			
		||||
			status_[0] = status_[1] = status_[2] = 0;
 | 
			
		||||
 | 
			
		||||
			// If this is not clearly a command that's safe to carry out in parallel to a seek, end all seeks.
 | 
			
		||||
			switch(command_[0] & 0x1f) {
 | 
			
		||||
				case CommandReadData:
 | 
			
		||||
				case CommandReadDeletedData:
 | 
			
		||||
				case CommandWriteData:
 | 
			
		||||
				case CommandWriteDeletedData:
 | 
			
		||||
				case CommandReadTrack:
 | 
			
		||||
				case CommandReadID:
 | 
			
		||||
				case CommandFormatTrack:
 | 
			
		||||
				case CommandScanLow:
 | 
			
		||||
				case CommandScanLowOrEqual:
 | 
			
		||||
				case CommandScanHighOrEqual:
 | 
			
		||||
					is_access_command_ = true;
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				default:
 | 
			
		||||
					is_access_command_ = false;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(is_access_command_) {
 | 
			
		||||
				for(int c = 0; c < 4; c++) {
 | 
			
		||||
					if(drives_[c].phase == Drive::Seeking) {
 | 
			
		||||
						drives_[c].phase = Drive::NotSeeking;
 | 
			
		||||
						drives_seeking_--;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
 | 
			
		||||
				// cylinder, head, sector and size registers from the command stream.
 | 
			
		||||
				is_executing_ = true;
 | 
			
		||||
				if(!dma_mode_) SetNonDMAExecution();
 | 
			
		||||
				SET_DRIVE_HEAD_MFM();
 | 
			
		||||
				LOAD_HEAD();
 | 
			
		||||
				if(!get_drive().get_is_ready()) {
 | 
			
		||||
					SetNotReady();
 | 
			
		||||
					goto abort;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Jump to the proper place.
 | 
			
		||||
			switch(command_[0] & 0x1f) {
 | 
			
		||||
				case CommandReadData:
 | 
			
		||||
				case CommandReadDeletedData:
 | 
			
		||||
					goto read_data;
 | 
			
		||||
 | 
			
		||||
				case CommandWriteData:
 | 
			
		||||
				case CommandWriteDeletedData:
 | 
			
		||||
					goto write_data;
 | 
			
		||||
 | 
			
		||||
				case CommandReadTrack:				goto read_track;
 | 
			
		||||
				case CommandReadID:					goto read_id;
 | 
			
		||||
				case CommandFormatTrack:			goto format_track;
 | 
			
		||||
 | 
			
		||||
				case CommandScanLow:				goto scan_low;
 | 
			
		||||
				case CommandScanLowOrEqual:			goto scan_low_or_equal;
 | 
			
		||||
				case CommandScanHighOrEqual:		goto scan_high_or_equal;
 | 
			
		||||
 | 
			
		||||
				case CommandRecalibrate:			goto recalibrate;
 | 
			
		||||
				case CommandSeek:					goto seek;
 | 
			
		||||
 | 
			
		||||
				case CommandSenseInterruptStatus:	goto sense_interrupt_status;
 | 
			
		||||
				case CommandSpecify:				goto specify;
 | 
			
		||||
				case CommandSenseDriveStatus:		goto sense_drive_status;
 | 
			
		||||
 | 
			
		||||
				default:							goto invalid;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	// Decodes drive, head and density, loads the head, loads the internal cylinder, head, sector and size registers,
 | 
			
		||||
	// and searches for a sector that meets those criteria. If one is found, inspects the instruction in use and
 | 
			
		||||
	// jumps to an appropriate handler.
 | 
			
		||||
	read_write_find_header:
 | 
			
		||||
 | 
			
		||||
		// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until
 | 
			
		||||
		// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
 | 
			
		||||
		// values in the internal registers.
 | 
			
		||||
			index_hole_limit_ = 2;
 | 
			
		||||
//			printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_);
 | 
			
		||||
		find_next_sector:
 | 
			
		||||
			FIND_HEADER();
 | 
			
		||||
			if(!index_hole_limit_) {
 | 
			
		||||
				// Two index holes have passed wihout finding the header sought.
 | 
			
		||||
//				printf("Not found\n");
 | 
			
		||||
				SetNoData();
 | 
			
		||||
				goto abort;
 | 
			
		||||
			}
 | 
			
		||||
			index_hole_count_ = 0;
 | 
			
		||||
//			printf("Header\n");
 | 
			
		||||
			READ_HEADER();
 | 
			
		||||
			if(index_hole_count_) {
 | 
			
		||||
				// This implies an index hole was sighted within the header. Error out.
 | 
			
		||||
				SetEndOfCylinder();
 | 
			
		||||
				goto abort;
 | 
			
		||||
			}
 | 
			
		||||
			if(get_crc_generator().get_value()) {
 | 
			
		||||
				// This implies a CRC error in the header; mark as such but continue.
 | 
			
		||||
				SetDataError();
 | 
			
		||||
			}
 | 
			
		||||
//			printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
 | 
			
		||||
			if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
 | 
			
		||||
 | 
			
		||||
			// Branch to whatever is supposed to happen next
 | 
			
		||||
//			printf("Proceeding\n");
 | 
			
		||||
			switch(command_[0] & 0x1f) {
 | 
			
		||||
				case CommandReadData:
 | 
			
		||||
				case CommandReadDeletedData:
 | 
			
		||||
				goto read_data_found_header;
 | 
			
		||||
 | 
			
		||||
				case CommandWriteData:	// write data
 | 
			
		||||
				case CommandWriteDeletedData:	// write deleted data
 | 
			
		||||
				goto write_data_found_header;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// Performs the read data or read deleted data command.
 | 
			
		||||
	read_data:
 | 
			
		||||
			printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
 | 
			
		||||
		read_next_data:
 | 
			
		||||
			goto read_write_find_header;
 | 
			
		||||
 | 
			
		||||
		// Finds the next data block and sets data mode to reading, setting an error flag if the on-disk deleted
 | 
			
		||||
		// flag doesn't match the sort the command was looking for.
 | 
			
		||||
		read_data_found_header:
 | 
			
		||||
			FIND_DATA();
 | 
			
		||||
			ClearControlMark();
 | 
			
		||||
			if(event_type == static_cast<int>(Event::Token)) {
 | 
			
		||||
				if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
 | 
			
		||||
					// Something other than a data mark came next — impliedly an ID or index mark.
 | 
			
		||||
					SetMissingAddressMark();
 | 
			
		||||
					SetMissingDataAddressMark();
 | 
			
		||||
					goto abort;	// TODO: or read_next_data?
 | 
			
		||||
				} else {
 | 
			
		||||
					if((get_latest_token().type == Token::Data) != ((command_[0] & 0x1f) == CommandReadData)) {
 | 
			
		||||
						if(!(command_[0]&0x20)) {
 | 
			
		||||
							// SK is not set; set the error flag but read this sector before finishing.
 | 
			
		||||
							SetControlMark();
 | 
			
		||||
						} else {
 | 
			
		||||
							// SK is set; skip this sector.
 | 
			
		||||
							goto read_next_data;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// An index hole appeared before the data mark.
 | 
			
		||||
				SetEndOfCylinder();
 | 
			
		||||
				goto abort;	// TODO: or read_next_data?
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			distance_into_section_ = 0;
 | 
			
		||||
			set_data_mode(Reading);
 | 
			
		||||
 | 
			
		||||
		// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting
 | 
			
		||||
		// data request once the byte has been taken. Continues until all bytes have been read.
 | 
			
		||||
		//
 | 
			
		||||
		// TODO: consider DTL.
 | 
			
		||||
		read_data_get_byte:
 | 
			
		||||
			WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
 | 
			
		||||
			if(event_type == static_cast<int>(Event::Token)) {
 | 
			
		||||
				result_stack_.push_back(get_latest_token().byte_value);
 | 
			
		||||
				distance_into_section_++;
 | 
			
		||||
				SetDataRequest();
 | 
			
		||||
				SetDataDirectionToProcessor();
 | 
			
		||||
				WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
 | 
			
		||||
			}
 | 
			
		||||
			switch(event_type) {
 | 
			
		||||
				case static_cast<int>(Event8272::ResultEmpty):	// The caller read the byte in time; proceed as normal.
 | 
			
		||||
					ResetDataRequest();
 | 
			
		||||
					if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
 | 
			
		||||
				break;
 | 
			
		||||
				case static_cast<int>(Event::Token):				// The caller hasn't read the old byte yet and a new one has arrived
 | 
			
		||||
					SetOverrun();
 | 
			
		||||
					goto abort;
 | 
			
		||||
				break;
 | 
			
		||||
				case static_cast<int>(Event::IndexHole):
 | 
			
		||||
					SetEndOfCylinder();
 | 
			
		||||
					goto abort;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		// read CRC, without transferring it, then check it
 | 
			
		||||
			WAIT_FOR_EVENT(Event::Token);
 | 
			
		||||
			WAIT_FOR_EVENT(Event::Token);
 | 
			
		||||
			if(get_crc_generator().get_value()) {
 | 
			
		||||
				// This implies a CRC error in the sector; mark as such and temrinate.
 | 
			
		||||
				SetDataError();
 | 
			
		||||
				SetDataFieldDataError();
 | 
			
		||||
				goto abort;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		// check whether that's it: either the final requested sector has been read, or because
 | 
			
		||||
		// a sector that was [/wasn't] marked as deleted when it shouldn't [/should] have been
 | 
			
		||||
			if(sector_ != command_[6] && !ControlMark()) {
 | 
			
		||||
				sector_++;
 | 
			
		||||
				goto read_next_data;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N
 | 
			
		||||
			goto post_st012chrn;
 | 
			
		||||
 | 
			
		||||
	write_data:
 | 
			
		||||
			printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
 | 
			
		||||
 | 
			
		||||
			if(get_drive().get_is_read_only()) {
 | 
			
		||||
				SetNotWriteable();
 | 
			
		||||
				goto abort;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		write_next_data:
 | 
			
		||||
			goto read_write_find_header;
 | 
			
		||||
 | 
			
		||||
		write_data_found_header:
 | 
			
		||||
			WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11);
 | 
			
		||||
			begin_writing(true);
 | 
			
		||||
 | 
			
		||||
			write_id_data_joiner((command_[0] & 0x1f) == CommandWriteDeletedData, true);
 | 
			
		||||
 | 
			
		||||
			SetDataDirectionFromProcessor();
 | 
			
		||||
			SetDataRequest();
 | 
			
		||||
			expects_input_ = true;
 | 
			
		||||
			distance_into_section_ = 0;
 | 
			
		||||
 | 
			
		||||
		write_loop:
 | 
			
		||||
			WAIT_FOR_EVENT(Event::DataWritten);
 | 
			
		||||
			if(!has_input_) {
 | 
			
		||||
				SetOverrun();
 | 
			
		||||
				goto abort;
 | 
			
		||||
			}
 | 
			
		||||
			write_byte(input_);
 | 
			
		||||
			has_input_ = false;
 | 
			
		||||
			distance_into_section_++;
 | 
			
		||||
			if(distance_into_section_ < (128 << size_)) {
 | 
			
		||||
				SetDataRequest();
 | 
			
		||||
				goto write_loop;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			printf("Wrote %d bytes\n", distance_into_section_);
 | 
			
		||||
			write_crc();
 | 
			
		||||
			expects_input_ = false;
 | 
			
		||||
			WAIT_FOR_EVENT(Event::DataWritten);
 | 
			
		||||
			end_writing();
 | 
			
		||||
 | 
			
		||||
			if(sector_ != command_[6]) {
 | 
			
		||||
				sector_++;
 | 
			
		||||
				goto write_next_data;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		goto post_st012chrn;
 | 
			
		||||
 | 
			
		||||
	// Performs the read ID command.
 | 
			
		||||
	read_id:
 | 
			
		||||
		// Establishes the drive and head being addressed, and whether in double density mode.
 | 
			
		||||
			printf("Read ID [%02x %02x]\n", command_[0], command_[1]);
 | 
			
		||||
 | 
			
		||||
		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
 | 
			
		||||
		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
 | 
			
		||||
			index_hole_limit_ = 2;
 | 
			
		||||
			FIND_HEADER();
 | 
			
		||||
			if(!index_hole_limit_) {
 | 
			
		||||
				SetMissingAddressMark();
 | 
			
		||||
				goto abort;
 | 
			
		||||
			}
 | 
			
		||||
			READ_HEADER();
 | 
			
		||||
 | 
			
		||||
		// Sets internal registers from the discovered header and posts the standard ST0, ST1, ST2, C, H, R, N.
 | 
			
		||||
			cylinder_ = header_[0];
 | 
			
		||||
			head_ = header_[1];
 | 
			
		||||
			sector_ = header_[2];
 | 
			
		||||
			size_ = header_[3];
 | 
			
		||||
 | 
			
		||||
			goto post_st012chrn;
 | 
			
		||||
 | 
			
		||||
	// Performs read track.
 | 
			
		||||
	read_track:
 | 
			
		||||
			printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]);
 | 
			
		||||
 | 
			
		||||
			// Wait for the index hole.
 | 
			
		||||
			WAIT_FOR_EVENT(Event::IndexHole);
 | 
			
		||||
 | 
			
		||||
			sector_ = 0;
 | 
			
		||||
			index_hole_limit_ = 2;
 | 
			
		||||
 | 
			
		||||
		// While not index hole again, stream all sector contents until EOT sectors have been read.
 | 
			
		||||
		read_track_next_sector:
 | 
			
		||||
			FIND_HEADER();
 | 
			
		||||
			if(!index_hole_limit_) {
 | 
			
		||||
				if(!sector_) {
 | 
			
		||||
					SetMissingAddressMark();
 | 
			
		||||
					goto abort;
 | 
			
		||||
				} else {
 | 
			
		||||
					goto post_st012chrn;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			READ_HEADER();
 | 
			
		||||
 | 
			
		||||
			FIND_DATA();
 | 
			
		||||
			distance_into_section_ = 0;
 | 
			
		||||
			SetDataDirectionToProcessor();
 | 
			
		||||
		read_track_get_byte:
 | 
			
		||||
			WAIT_FOR_EVENT(Event::Token);
 | 
			
		||||
			result_stack_.push_back(get_latest_token().byte_value);
 | 
			
		||||
			distance_into_section_++;
 | 
			
		||||
			SetDataRequest();
 | 
			
		||||
			// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
 | 
			
		||||
			WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
 | 
			
		||||
			ResetDataRequest();
 | 
			
		||||
			if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
 | 
			
		||||
 | 
			
		||||
			sector_++;
 | 
			
		||||
			if(sector_ < command_[6]) goto read_track_next_sector;
 | 
			
		||||
 | 
			
		||||
			goto post_st012chrn;
 | 
			
		||||
 | 
			
		||||
	// Performs format [/write] track.
 | 
			
		||||
	format_track:
 | 
			
		||||
			printf("Format track\n");
 | 
			
		||||
			if(get_drive().get_is_read_only()) {
 | 
			
		||||
				SetNotWriteable();
 | 
			
		||||
				goto abort;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Wait for the index hole.
 | 
			
		||||
			WAIT_FOR_EVENT(Event::IndexHole);
 | 
			
		||||
			index_hole_count_ = 0;
 | 
			
		||||
			begin_writing(true);
 | 
			
		||||
 | 
			
		||||
			// Write start-of-track.
 | 
			
		||||
			write_start_of_track();
 | 
			
		||||
			WAIT_FOR_EVENT(Event::DataWritten);
 | 
			
		||||
			sector_ = 0;
 | 
			
		||||
 | 
			
		||||
		format_track_write_sector:
 | 
			
		||||
			write_id_joiner();
 | 
			
		||||
 | 
			
		||||
			// Write the sector header, obtaining its contents
 | 
			
		||||
			// from the processor.
 | 
			
		||||
			SetDataDirectionFromProcessor();
 | 
			
		||||
			SetDataRequest();
 | 
			
		||||
			expects_input_ = true;
 | 
			
		||||
			distance_into_section_ = 0;
 | 
			
		||||
		format_track_write_header:
 | 
			
		||||
			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
 | 
			
		||||
			switch(event_type) {
 | 
			
		||||
				case static_cast<int>(Event::IndexHole):
 | 
			
		||||
					SetOverrun();
 | 
			
		||||
					goto abort;
 | 
			
		||||
				break;
 | 
			
		||||
				case static_cast<int>(Event::DataWritten):
 | 
			
		||||
					header_[distance_into_section_] = input_;
 | 
			
		||||
					write_byte(input_);
 | 
			
		||||
					has_input_ = false;
 | 
			
		||||
					distance_into_section_++;
 | 
			
		||||
					if(distance_into_section_ < 4) {
 | 
			
		||||
						SetDataRequest();
 | 
			
		||||
						goto format_track_write_header;
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
 | 
			
		||||
			write_crc();
 | 
			
		||||
 | 
			
		||||
			// Write the sector body.
 | 
			
		||||
			write_id_data_joiner(false, false);
 | 
			
		||||
			write_n_bytes(128 << command_[2], command_[5]);
 | 
			
		||||
			write_crc();
 | 
			
		||||
 | 
			
		||||
			// Write the prescribed gap.
 | 
			
		||||
			write_n_bytes(command_[4], get_is_double_density() ? 0x4e : 0xff);
 | 
			
		||||
 | 
			
		||||
			// Consider repeating.
 | 
			
		||||
			sector_++;
 | 
			
		||||
			if(sector_ < command_[3] && !index_hole_count_)
 | 
			
		||||
				goto format_track_write_sector;
 | 
			
		||||
 | 
			
		||||
			// Otherwise, pad out to the index hole.
 | 
			
		||||
		format_track_pad:
 | 
			
		||||
			write_byte(get_is_double_density() ? 0x4e : 0xff);
 | 
			
		||||
			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
 | 
			
		||||
			if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
 | 
			
		||||
 | 
			
		||||
			end_writing();
 | 
			
		||||
 | 
			
		||||
			cylinder_ = header_[0];
 | 
			
		||||
			head_ = header_[1];
 | 
			
		||||
			sector_ = header_[2] + 1;
 | 
			
		||||
			size_ = header_[3];
 | 
			
		||||
 | 
			
		||||
		goto post_st012chrn;
 | 
			
		||||
 | 
			
		||||
	scan_low:
 | 
			
		||||
		printf("Scan low unimplemented!!\n");
 | 
			
		||||
		goto wait_for_command;
 | 
			
		||||
 | 
			
		||||
	scan_low_or_equal:
 | 
			
		||||
		printf("Scan low or equal unimplemented!!\n");
 | 
			
		||||
		goto wait_for_command;
 | 
			
		||||
 | 
			
		||||
	scan_high_or_equal:
 | 
			
		||||
		printf("Scan high or equal unimplemented!!\n");
 | 
			
		||||
		goto wait_for_command;
 | 
			
		||||
 | 
			
		||||
	// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
 | 
			
		||||
	// occurs in ::run_for; this merely establishes that seeking should be ongoing.
 | 
			
		||||
	recalibrate:
 | 
			
		||||
	seek:
 | 
			
		||||
			{
 | 
			
		||||
				int drive = command_[1]&3;
 | 
			
		||||
				select_drive(drive);
 | 
			
		||||
 | 
			
		||||
				// Increment the seeking count if this drive wasn't already seeking.
 | 
			
		||||
				if(drives_[drive].phase != Drive::Seeking) {
 | 
			
		||||
					drives_seeking_++;
 | 
			
		||||
					is_sleeping_ = false;
 | 
			
		||||
					update_sleep_observer();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
 | 
			
		||||
				// in could damage your drive motor).
 | 
			
		||||
				drives_[drive].phase = Drive::Seeking;
 | 
			
		||||
				drives_[drive].step_rate_counter = 8000 * step_rate_time_;
 | 
			
		||||
				drives_[drive].steps_taken = 0;
 | 
			
		||||
				drives_[drive].seek_failed = false;
 | 
			
		||||
				main_status_ |= 1 << (command_[1]&3);
 | 
			
		||||
 | 
			
		||||
				// If this is a seek, set the processor-supplied target location; otherwise it is a recalibrate,
 | 
			
		||||
				// which means resetting the current state now but aiming to hit '-1' (which the stepping code
 | 
			
		||||
				// up in run_for understands to mean 'keep going until track 0 is active').
 | 
			
		||||
				if(command_.size() > 2) {
 | 
			
		||||
					drives_[drive].target_head_position = command_[2];
 | 
			
		||||
					printf("Seek to %02x\n", command_[2]);
 | 
			
		||||
				} else {
 | 
			
		||||
					drives_[drive].target_head_position = -1;
 | 
			
		||||
					drives_[drive].head_position = 0;
 | 
			
		||||
					printf("Recalibrate\n");
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Check whether any steps are even needed; if not then mark as completed already.
 | 
			
		||||
				if(seek_is_satisfied(drive)) {
 | 
			
		||||
					drives_[drive].phase = Drive::CompletedSeeking;
 | 
			
		||||
					drives_seeking_--;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
 | 
			
		||||
	// Performs sense interrupt status.
 | 
			
		||||
	sense_interrupt_status:
 | 
			
		||||
			printf("Sense interrupt status\n");
 | 
			
		||||
			{
 | 
			
		||||
				// Find the first drive that is in the CompletedSeeking state.
 | 
			
		||||
				int found_drive = -1;
 | 
			
		||||
				for(int c = 0; c < 4; c++) {
 | 
			
		||||
					if(drives_[c].phase == Drive::CompletedSeeking) {
 | 
			
		||||
						found_drive = c;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// If a drive was found, return its results. Otherwise return a single 0x80.
 | 
			
		||||
				if(found_drive != -1) {
 | 
			
		||||
					drives_[found_drive].phase = Drive::NotSeeking;
 | 
			
		||||
					status_[0] = static_cast<uint8_t>(found_drive);
 | 
			
		||||
					main_status_ &= ~(1 << found_drive);
 | 
			
		||||
					SetSeekEnd();
 | 
			
		||||
 | 
			
		||||
					result_stack_ = { drives_[found_drive].head_position, status_[0]};
 | 
			
		||||
				} else {
 | 
			
		||||
					result_stack_ = { 0x80 };
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			goto post_result;
 | 
			
		||||
 | 
			
		||||
	// Performs specify.
 | 
			
		||||
	specify:
 | 
			
		||||
		// Just store the values, and terminate the command.
 | 
			
		||||
			printf("Specify\n");
 | 
			
		||||
			step_rate_time_ = 16 - (command_[1] >> 4);			// i.e. 1 to 16ms
 | 
			
		||||
			head_unload_time_ = (command_[1] & 0x0f) << 4;		// i.e. 16 to 240ms
 | 
			
		||||
			head_load_time_ = command_[2] & ~1;					// i.e. 2 to 254 ms in increments of 2ms
 | 
			
		||||
 | 
			
		||||
			if(!head_unload_time_) head_unload_time_ = 16;
 | 
			
		||||
			if(!head_load_time_) head_load_time_ = 2;
 | 
			
		||||
			dma_mode_ = !(command_[2] & 1);
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
 | 
			
		||||
	sense_drive_status:
 | 
			
		||||
			printf("Sense drive status\n");
 | 
			
		||||
			{
 | 
			
		||||
				int drive = command_[1] & 3;
 | 
			
		||||
				select_drive(drive);
 | 
			
		||||
				result_stack_= {
 | 
			
		||||
					static_cast<uint8_t>(
 | 
			
		||||
						(command_[1] & 7) |	// drive and head number
 | 
			
		||||
						0x08 |				// single sided
 | 
			
		||||
						(get_drive().get_is_track_zero() ? 0x10 : 0x00)	|
 | 
			
		||||
						(get_drive().get_is_ready() ? 0x20 : 0x00)		|
 | 
			
		||||
						(get_drive().get_is_read_only() ? 0x40 : 0x00)
 | 
			
		||||
					)
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
			goto post_result;
 | 
			
		||||
 | 
			
		||||
	// Performs any invalid command.
 | 
			
		||||
	invalid:
 | 
			
		||||
			// A no-op, but posts ST0 (but which ST0?)
 | 
			
		||||
			result_stack_ = {0x80};
 | 
			
		||||
			goto post_result;
 | 
			
		||||
 | 
			
		||||
	// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
 | 
			
		||||
	abort:
 | 
			
		||||
		end_writing();
 | 
			
		||||
		SetAbnormalTermination();
 | 
			
		||||
		goto post_st012chrn;
 | 
			
		||||
 | 
			
		||||
	// Posts ST0, ST1, ST2, C, H, R and N as a result phase.
 | 
			
		||||
	post_st012chrn:
 | 
			
		||||
			SCHEDULE_HEAD_UNLOAD();
 | 
			
		||||
 | 
			
		||||
			result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
 | 
			
		||||
 | 
			
		||||
			goto post_result;
 | 
			
		||||
 | 
			
		||||
	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the
 | 
			
		||||
	// last thing in it will be returned first.
 | 
			
		||||
	post_result:
 | 
			
		||||
			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
 | 
			
		||||
			for(std::size_t c = 0; c < result_stack_.size(); c++) {
 | 
			
		||||
				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
 | 
			
		||||
			}
 | 
			
		||||
			printf("\n");
 | 
			
		||||
 | 
			
		||||
			// Set ready to send data to the processor, no longer in non-DMA execution phase.
 | 
			
		||||
			is_executing_ = false;
 | 
			
		||||
			ResetNonDMAExecution();
 | 
			
		||||
			SetDataRequest();
 | 
			
		||||
			SetDataDirectionToProcessor();
 | 
			
		||||
 | 
			
		||||
			// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
 | 
			
		||||
			// until the processor has read all result bytes.
 | 
			
		||||
			WAIT_FOR_EVENT(Event8272::ResultEmpty);
 | 
			
		||||
 | 
			
		||||
			// Reset data direction and end the command.
 | 
			
		||||
			goto wait_for_command;
 | 
			
		||||
 | 
			
		||||
	END_SECTION()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool i8272::seek_is_satisfied(int drive) {
 | 
			
		||||
	return	(drives_[drive].target_head_position == drives_[drive].head_position) ||
 | 
			
		||||
			(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void i8272::set_dma_acknowledge(bool dack) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void i8272::set_terminal_count(bool tc) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void i8272::set_data_input(uint8_t value) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t i8272::get_data_output() {
 | 
			
		||||
	return 0xff;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										135
									
								
								Components/8272/i8272.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								Components/8272/i8272.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
//
 | 
			
		||||
//  i8272.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 05/08/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef i8272_hpp
 | 
			
		||||
#define i8272_hpp
 | 
			
		||||
 | 
			
		||||
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Intel {
 | 
			
		||||
namespace i8272 {
 | 
			
		||||
 | 
			
		||||
class BusHandler {
 | 
			
		||||
	public:
 | 
			
		||||
		virtual void set_dma_data_request(bool drq) {}
 | 
			
		||||
		virtual void set_interrupt(bool irq) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class i8272: public Storage::Disk::MFMController {
 | 
			
		||||
	public:
 | 
			
		||||
		i8272(BusHandler &bus_handler, Cycles clock_rate);
 | 
			
		||||
 | 
			
		||||
		void run_for(Cycles);
 | 
			
		||||
 | 
			
		||||
		void set_data_input(uint8_t value);
 | 
			
		||||
		uint8_t get_data_output();
 | 
			
		||||
 | 
			
		||||
		void set_register(int address, uint8_t value);
 | 
			
		||||
		uint8_t get_register(int address);
 | 
			
		||||
 | 
			
		||||
		void set_dma_acknowledge(bool dack);
 | 
			
		||||
		void set_terminal_count(bool tc);
 | 
			
		||||
 | 
			
		||||
		bool is_sleeping();
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		virtual void select_drive(int number) = 0;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		// The bus handler, for interrupt and DMA-driven usage.
 | 
			
		||||
		BusHandler &bus_handler_;
 | 
			
		||||
		std::unique_ptr<BusHandler> allocated_bus_handler_;
 | 
			
		||||
 | 
			
		||||
		// Status registers.
 | 
			
		||||
		uint8_t main_status_ = 0;
 | 
			
		||||
		uint8_t status_[3] = {0, 0, 0};
 | 
			
		||||
 | 
			
		||||
		// A buffer for accumulating the incoming command, and one for accumulating the result.
 | 
			
		||||
		std::vector<uint8_t> command_;
 | 
			
		||||
		std::vector<uint8_t> result_stack_;
 | 
			
		||||
		uint8_t input_ = 0;
 | 
			
		||||
		bool has_input_ = false;
 | 
			
		||||
		bool expects_input_ = false;
 | 
			
		||||
 | 
			
		||||
		// Event stream: the 8272-specific events, plus the current event state.
 | 
			
		||||
		enum class Event8272: int {
 | 
			
		||||
			CommandByte	= (1 << 3),
 | 
			
		||||
			Timer = (1 << 4),
 | 
			
		||||
			ResultEmpty = (1 << 5),
 | 
			
		||||
			NoLongerReady = (1 << 6)
 | 
			
		||||
		};
 | 
			
		||||
		void posit_event(int type);
 | 
			
		||||
		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
 | 
			
		||||
		int resume_point_ = 0;
 | 
			
		||||
		bool is_access_command_ = false;
 | 
			
		||||
 | 
			
		||||
		// The counter used for ::Timer events.
 | 
			
		||||
		int delay_time_ = 0;
 | 
			
		||||
 | 
			
		||||
		// The connected drives.
 | 
			
		||||
		struct Drive {
 | 
			
		||||
			uint8_t head_position = 0;
 | 
			
		||||
 | 
			
		||||
			// Seeking: persistent state.
 | 
			
		||||
			enum Phase {
 | 
			
		||||
				NotSeeking,
 | 
			
		||||
				Seeking,
 | 
			
		||||
				CompletedSeeking
 | 
			
		||||
			} phase = NotSeeking;
 | 
			
		||||
			bool did_seek = false;
 | 
			
		||||
			bool seek_failed = false;
 | 
			
		||||
 | 
			
		||||
			// Seeking: transient state.
 | 
			
		||||
			int step_rate_counter = 0;
 | 
			
		||||
			int steps_taken = 0;
 | 
			
		||||
			int target_head_position = 0;	// either an actual number, or -1 to indicate to step until track zero
 | 
			
		||||
 | 
			
		||||
			// Head state.
 | 
			
		||||
			int head_unload_delay[2] = {0, 0};
 | 
			
		||||
			bool head_is_loaded[2] = {false, false};
 | 
			
		||||
 | 
			
		||||
		} drives_[4];
 | 
			
		||||
		int drives_seeking_ = 0;
 | 
			
		||||
 | 
			
		||||
		/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
 | 
			
		||||
		bool seek_is_satisfied(int drive);
 | 
			
		||||
 | 
			
		||||
		// User-supplied parameters; as per the specify command.
 | 
			
		||||
		int step_rate_time_ = 1;
 | 
			
		||||
		int head_unload_time_ = 1;
 | 
			
		||||
		int head_load_time_ = 1;
 | 
			
		||||
		bool dma_mode_ = false;
 | 
			
		||||
		bool is_executing_ = false;
 | 
			
		||||
 | 
			
		||||
		// A count of head unload timers currently running.
 | 
			
		||||
		int head_timers_running_ = 0;
 | 
			
		||||
 | 
			
		||||
		// Transient storage and counters used while reading the disk.
 | 
			
		||||
		uint8_t header_[6] = {0, 0, 0, 0, 0, 0};
 | 
			
		||||
		int distance_into_section_ = 0;
 | 
			
		||||
		int index_hole_count_ = 0, index_hole_limit_ = 0;
 | 
			
		||||
 | 
			
		||||
		// Keeps track of the drive and head in use during commands.
 | 
			
		||||
		int active_drive_ = 0;
 | 
			
		||||
		int active_head_ = 0;
 | 
			
		||||
 | 
			
		||||
		// Internal registers.
 | 
			
		||||
		uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
 | 
			
		||||
 | 
			
		||||
		// Master switch on not performing any work.
 | 
			
		||||
		bool is_sleeping_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* i8272_hpp */
 | 
			
		||||
							
								
								
									
										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_output_device(Outputs::CRT::OutputDevice::Monitor);
 | 
			
		||||
	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,25 +8,15 @@
 | 
			
		||||
 | 
			
		||||
#include "AY38910.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace GI;
 | 
			
		||||
#include <cmath>
 | 
			
		||||
 | 
			
		||||
AY38910::AY38910() :
 | 
			
		||||
	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}
 | 
			
		||||
{
 | 
			
		||||
	output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
 | 
			
		||||
using namespace GI::AY38910;
 | 
			
		||||
 | 
			
		||||
AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
 | 
			
		||||
	// set up envelope lookup tables
 | 
			
		||||
	for(int c = 0; c < 16; c++)
 | 
			
		||||
	{
 | 
			
		||||
		for(int p = 0; p < 32; p++)
 | 
			
		||||
		{
 | 
			
		||||
			switch(c)
 | 
			
		||||
			{
 | 
			
		||||
	for(int c = 0; c < 16; c++) {
 | 
			
		||||
		for(int p = 0; p < 32; p++) {
 | 
			
		||||
			switch(c) {
 | 
			
		||||
				case 0: case 1: case 2: case 3: case 9:
 | 
			
		||||
					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
 | 
			
		||||
					envelope_overflow_masks_[c] = 0x1f;
 | 
			
		||||
@@ -69,34 +59,24 @@ AY38910::AY38910() :
 | 
			
		||||
	// set up volume lookup table
 | 
			
		||||
	float max_volume = 8192;
 | 
			
		||||
	float root_two = sqrtf(2.0f);
 | 
			
		||||
	for(int v = 0; v < 16; v++)
 | 
			
		||||
	{
 | 
			
		||||
		volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
 | 
			
		||||
	for(int v = 0; v < 16; v++) {
 | 
			
		||||
		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
 | 
			
		||||
	}
 | 
			
		||||
	volumes_[0] = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::set_clock_rate(double clock_rate)
 | 
			
		||||
{
 | 
			
		||||
	set_input_rate((float)clock_rate);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
 | 
			
		||||
{
 | 
			
		||||
	int c = 0;
 | 
			
		||||
	while((master_divider_&15) && c < number_of_samples)
 | 
			
		||||
	{
 | 
			
		||||
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
			
		||||
	std::size_t c = 0;
 | 
			
		||||
	while((master_divider_&7) && c < number_of_samples) {
 | 
			
		||||
		target[c] = output_volume_;
 | 
			
		||||
		master_divider_++;
 | 
			
		||||
		c++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	while(c < number_of_samples)
 | 
			
		||||
	{
 | 
			
		||||
	while(c < number_of_samples) {
 | 
			
		||||
#define step_channel(c) \
 | 
			
		||||
	if(tone_counters_[c]) tone_counters_[c]--;\
 | 
			
		||||
	else\
 | 
			
		||||
	{\
 | 
			
		||||
	else {\
 | 
			
		||||
		tone_outputs_[c] ^= 1;\
 | 
			
		||||
		tone_counters_[c] = tone_periods_[c];\
 | 
			
		||||
	}
 | 
			
		||||
@@ -111,8 +91,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
 | 
			
		||||
		// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
 | 
			
		||||
		// it into the official 17 upon divider underflow.
 | 
			
		||||
		if(noise_counter_) noise_counter_--;
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
		else {
 | 
			
		||||
			noise_counter_ = noise_period_;
 | 
			
		||||
			noise_output_ ^= noise_shift_register_&1;
 | 
			
		||||
			noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
 | 
			
		||||
@@ -122,8 +101,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
 | 
			
		||||
		// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
 | 
			
		||||
		// implementing non-repeating patterns by locking them to table position 0x1f.
 | 
			
		||||
		if(envelope_divider_) envelope_divider_--;
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
		else {
 | 
			
		||||
			envelope_divider_ = envelope_period_;
 | 
			
		||||
			envelope_position_ ++;
 | 
			
		||||
			if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
 | 
			
		||||
@@ -131,19 +109,17 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
 | 
			
		||||
 | 
			
		||||
		evaluate_output_volume();
 | 
			
		||||
 | 
			
		||||
		for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
 | 
			
		||||
		{
 | 
			
		||||
		for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
 | 
			
		||||
			target[c] = output_volume_;
 | 
			
		||||
			c++;
 | 
			
		||||
			master_divider_++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	master_divider_ &= 15;
 | 
			
		||||
	master_divider_ &= 7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::evaluate_output_volume()
 | 
			
		||||
{
 | 
			
		||||
void AY38910::evaluate_output_volume() {
 | 
			
		||||
	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
 | 
			
		||||
 | 
			
		||||
	// The output level for a channel is:
 | 
			
		||||
@@ -173,54 +149,53 @@ void AY38910::evaluate_output_volume()
 | 
			
		||||
#undef channel_volume
 | 
			
		||||
 | 
			
		||||
	// Mix additively.
 | 
			
		||||
	output_volume_ = (int16_t)(
 | 
			
		||||
	output_volume_ = static_cast<int16_t>(
 | 
			
		||||
		volumes_[volumes[0]] * channel_levels[0] +
 | 
			
		||||
		volumes_[volumes[1]] * channel_levels[1] +
 | 
			
		||||
		volumes_[volumes[2]] * channel_levels[2]
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::select_register(uint8_t r)
 | 
			
		||||
{
 | 
			
		||||
	selected_register_ = r & 0xf;
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::set_register_value(uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
// MARK: - Register manipulation
 | 
			
		||||
 | 
			
		||||
void AY38910::select_register(uint8_t r) {
 | 
			
		||||
	selected_register_ = r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::set_register_value(uint8_t value) {
 | 
			
		||||
	if(selected_register_ > 15) return;
 | 
			
		||||
	registers_[selected_register_] = value;
 | 
			
		||||
	if(selected_register_ < 14)
 | 
			
		||||
	{
 | 
			
		||||
	if(selected_register_ < 14) {
 | 
			
		||||
		int selected_register = selected_register_;
 | 
			
		||||
		enqueue([=] () {
 | 
			
		||||
		task_queue_.defer([=] () {
 | 
			
		||||
			uint8_t masked_value = value;
 | 
			
		||||
			switch(selected_register)
 | 
			
		||||
			{
 | 
			
		||||
			switch(selected_register) {
 | 
			
		||||
				case 0: case 2: case 4:
 | 
			
		||||
				case 1: case 3: case 5:
 | 
			
		||||
				{
 | 
			
		||||
				case 1: case 3: case 5: {
 | 
			
		||||
					int channel = selected_register >> 1;
 | 
			
		||||
 | 
			
		||||
					if(selected_register & 1)
 | 
			
		||||
						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
 | 
			
		||||
						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
 | 
			
		||||
					else
 | 
			
		||||
						tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
 | 
			
		||||
					tone_counters_[channel] = tone_periods_[channel];
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 6:
 | 
			
		||||
					noise_period_ = value & 0x1f;
 | 
			
		||||
					noise_counter_ = noise_period_;
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 11:
 | 
			
		||||
					envelope_period_ = (envelope_period_ & ~0xff) | value;
 | 
			
		||||
					envelope_divider_ = envelope_period_;
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 12:
 | 
			
		||||
					envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
 | 
			
		||||
					envelope_divider_ = envelope_period_;
 | 
			
		||||
					envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case 13:
 | 
			
		||||
@@ -231,61 +206,75 @@ void AY38910::set_register_value(uint8_t value)
 | 
			
		||||
			output_registers_[selected_register] = masked_value;
 | 
			
		||||
			evaluate_output_volume();
 | 
			
		||||
		});
 | 
			
		||||
	} else {
 | 
			
		||||
		if(port_handler_) port_handler_->set_port_output(selected_register_ == 15, value);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t AY38910::get_register_value()
 | 
			
		||||
{
 | 
			
		||||
	// This table ensures that bits that aren't defined within the AY are returned as 1s
 | 
			
		||||
	// when read. I can't find documentation on this and don't have a machine to test, so
 | 
			
		||||
	// this is provisionally a guess. TODO: investigate.
 | 
			
		||||
uint8_t AY38910::get_register_value() {
 | 
			
		||||
	// This table ensures that bits that aren't defined within the AY are returned as 0s
 | 
			
		||||
	// when read, conforming to CPC-sourced unit tests.
 | 
			
		||||
	const uint8_t register_masks[16] = {
 | 
			
		||||
		0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0xe0, 0x00,
 | 
			
		||||
		0xe0, 0xe0, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x00
 | 
			
		||||
		0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff,
 | 
			
		||||
		0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return registers_[selected_register_] | register_masks[selected_register_];
 | 
			
		||||
	if(selected_register_ > 15) return 0xff;
 | 
			
		||||
	switch(selected_register_) {
 | 
			
		||||
		default:	return registers_[selected_register_] & register_masks[selected_register_];
 | 
			
		||||
		case 14:	return (registers_[0x7] & 0x40) ? registers_[14] : port_inputs_[0];
 | 
			
		||||
		case 15:	return (registers_[0x7] & 0x80) ? registers_[15] : port_inputs_[1];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t AY38910::get_port_output(bool port_b)
 | 
			
		||||
{
 | 
			
		||||
// MARK: - Port handling
 | 
			
		||||
 | 
			
		||||
uint8_t AY38910::get_port_output(bool port_b) {
 | 
			
		||||
	return registers_[port_b ? 15 : 14];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::set_data_input(uint8_t r)
 | 
			
		||||
{
 | 
			
		||||
	data_input_ = r;
 | 
			
		||||
// MARK: - Bus handling
 | 
			
		||||
 | 
			
		||||
void AY38910::set_port_handler(PortHandler *handler) {
 | 
			
		||||
	port_handler_ = handler;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t AY38910::get_data_output()
 | 
			
		||||
{
 | 
			
		||||
void AY38910::set_data_input(uint8_t r) {
 | 
			
		||||
	data_input_ = r;
 | 
			
		||||
	update_bus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t AY38910::get_data_output() {
 | 
			
		||||
	if(control_state_ == Read && selected_register_ >= 14) {
 | 
			
		||||
		if(port_handler_) {
 | 
			
		||||
			return port_handler_->get_port_input(selected_register_ == 15);
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return data_output_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::set_control_lines(ControlLines control_lines)
 | 
			
		||||
{
 | 
			
		||||
	ControlState new_state;
 | 
			
		||||
	switch((int)control_lines)
 | 
			
		||||
	{
 | 
			
		||||
		default:					new_state = Inactive;		break;
 | 
			
		||||
void AY38910::set_control_lines(ControlLines control_lines) {
 | 
			
		||||
	switch(static_cast<int>(control_lines)) {
 | 
			
		||||
		default:					control_state_ = Inactive;		break;
 | 
			
		||||
 | 
			
		||||
		case (int)(BCDIR | BC2 | BC1):
 | 
			
		||||
		case BCDIR:
 | 
			
		||||
		case BC1:					new_state = LatchAddress;	break;
 | 
			
		||||
		case static_cast<int>(BDIR | BC2 | BC1):
 | 
			
		||||
		case BDIR:
 | 
			
		||||
		case BC1:					control_state_ = LatchAddress;	break;
 | 
			
		||||
 | 
			
		||||
		case (int)(BC2 | BC1):		new_state = Read;			break;
 | 
			
		||||
		case (int)(BCDIR | BC2):	new_state = Write;			break;
 | 
			
		||||
		case static_cast<int>(BC2 | BC1):		control_state_ = Read;			break;
 | 
			
		||||
		case static_cast<int>(BDIR | BC2):		control_state_ = Write;			break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(new_state != control_state_)
 | 
			
		||||
	{
 | 
			
		||||
		control_state_ = new_state;
 | 
			
		||||
		switch(new_state)
 | 
			
		||||
		{
 | 
			
		||||
			default: break;
 | 
			
		||||
			case LatchAddress:	select_register(data_input_);			break;
 | 
			
		||||
			case Write:			set_register_value(data_input_);		break;
 | 
			
		||||
			case Read:			data_output_ = get_register_value();	break;
 | 
			
		||||
		}
 | 
			
		||||
	update_bus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AY38910::update_bus() {
 | 
			
		||||
	switch(control_state_) {
 | 
			
		||||
		default: break;
 | 
			
		||||
		case LatchAddress:	select_register(data_input_);			break;
 | 
			
		||||
		case Write:			set_register_value(data_input_);		break;
 | 
			
		||||
		case Read:			data_output_ = get_register_value();	break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,28 +9,57 @@
 | 
			
		||||
#ifndef 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 AY38910 {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	A port handler provides all input for an AY's two 8-bit ports, and may optionally receive
 | 
			
		||||
	active notification of changes in output.
 | 
			
		||||
 | 
			
		||||
	Machines with an AY without ports or with nothing wired to them need not supply a port handler.
 | 
			
		||||
	Machines that use the AY ports as output but for which polling for changes is acceptable can
 | 
			
		||||
	instead use AY38910.get_port_output.
 | 
			
		||||
*/
 | 
			
		||||
class PortHandler {
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Requests the current input on an AY port.
 | 
			
		||||
 | 
			
		||||
			@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
 | 
			
		||||
		*/
 | 
			
		||||
		virtual uint8_t get_port_input(bool port_b) {
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Requests the current input on an AY port.
 | 
			
		||||
 | 
			
		||||
			@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
 | 
			
		||||
		*/
 | 
			
		||||
		virtual void set_port_output(bool port_b, uint8_t value) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Names the control lines used as input to the AY, which uses CP1600 bus semantics.
 | 
			
		||||
*/
 | 
			
		||||
enum ControlLines {
 | 
			
		||||
	BC1		= (1 << 0),
 | 
			
		||||
	BC2		= (1 << 1),
 | 
			
		||||
	BDIR	= (1 << 2)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
 | 
			
		||||
	noise generator and a volume envelope generator, which also provides two bidirectional
 | 
			
		||||
	interface ports.
 | 
			
		||||
*/
 | 
			
		||||
class AY38910: public ::Outputs::Filter<AY38910> {
 | 
			
		||||
class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
			
		||||
	public:
 | 
			
		||||
		/// Creates a new AY38910.
 | 
			
		||||
		AY38910();
 | 
			
		||||
 | 
			
		||||
		/// Sets the clock rate at which this AY38910 will be run.
 | 
			
		||||
		void set_clock_rate(double clock_rate);
 | 
			
		||||
 | 
			
		||||
		enum ControlLines {
 | 
			
		||||
			BC1		= (1 << 0),
 | 
			
		||||
			BC2		= (1 << 1),
 | 
			
		||||
			BCDIR	= (1 << 2)
 | 
			
		||||
		};
 | 
			
		||||
		AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
 | 
			
		||||
 | 
			
		||||
		/// Sets the value the AY would read from its data lines if it were not outputting.
 | 
			
		||||
		void set_data_input(uint8_t r);
 | 
			
		||||
@@ -38,7 +67,7 @@ class AY38910: public ::Outputs::Filter<AY38910> {
 | 
			
		||||
		/// Gets the value that would appear on the data lines if only the AY is outputting.
 | 
			
		||||
		uint8_t get_data_output();
 | 
			
		||||
 | 
			
		||||
		/// Sets the
 | 
			
		||||
		/// Sets the current control line state, as a bit field.
 | 
			
		||||
		void set_control_lines(ControlLines control_lines);
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
@@ -47,27 +76,39 @@ class AY38910: public ::Outputs::Filter<AY38910> {
 | 
			
		||||
		*/
 | 
			
		||||
		uint8_t get_port_output(bool port_b);
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Sets the port handler, which will receive a call every time the AY either wants to sample
 | 
			
		||||
			input or else declare new output. As a convenience, current port output can be obtained
 | 
			
		||||
			without installing a port handler via get_port_output.
 | 
			
		||||
		*/
 | 
			
		||||
		void set_port_handler(PortHandler *);
 | 
			
		||||
 | 
			
		||||
		// 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();
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		int selected_register_;
 | 
			
		||||
		uint8_t registers_[16], output_registers_[16];
 | 
			
		||||
		Concurrency::DeferringAsyncTaskQueue &task_queue_;
 | 
			
		||||
 | 
			
		||||
		int master_divider_;
 | 
			
		||||
		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];
 | 
			
		||||
 | 
			
		||||
		int tone_periods_[3];
 | 
			
		||||
		int tone_counters_[3];
 | 
			
		||||
		int tone_outputs_[3];
 | 
			
		||||
		int master_divider_ = 0;
 | 
			
		||||
 | 
			
		||||
		int noise_period_;
 | 
			
		||||
		int noise_counter_;
 | 
			
		||||
		int noise_shift_register_;
 | 
			
		||||
		int noise_output_;
 | 
			
		||||
		int tone_periods_[3] = {0, 0, 0};
 | 
			
		||||
		int tone_counters_[3] = {0, 0, 0};
 | 
			
		||||
		int tone_outputs_[3] = {0, 0, 0};
 | 
			
		||||
 | 
			
		||||
		int envelope_period_;
 | 
			
		||||
		int envelope_divider_;
 | 
			
		||||
		int envelope_position_;
 | 
			
		||||
		int noise_period_ = 0;
 | 
			
		||||
		int noise_counter_ = 0;
 | 
			
		||||
		int noise_shift_register_ = 0xffff;
 | 
			
		||||
		int noise_output_ = 0;
 | 
			
		||||
 | 
			
		||||
		int envelope_period_ = 0;
 | 
			
		||||
		int envelope_divider_ = 0;
 | 
			
		||||
		int envelope_position_ = 0;
 | 
			
		||||
		int envelope_shapes_[16][32];
 | 
			
		||||
		int envelope_overflow_masks_[16];
 | 
			
		||||
 | 
			
		||||
@@ -88,8 +129,12 @@ class AY38910: public ::Outputs::Filter<AY38910> {
 | 
			
		||||
 | 
			
		||||
		int16_t output_volume_;
 | 
			
		||||
		inline void evaluate_output_volume();
 | 
			
		||||
 | 
			
		||||
		inline void update_bus();
 | 
			
		||||
		PortHandler *port_handler_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* AY_3_8910_hpp */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										107
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
//
 | 
			
		||||
//  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] = output_volume_;
 | 
			
		||||
		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] = output_volume_;
 | 
			
		||||
			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() {
 | 
			
		||||
	output_volume_ =
 | 
			
		||||
		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
 | 
			
		||||
			)
 | 
			
		||||
		);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t SCC::read(uint16_t address) {
 | 
			
		||||
	address &= 0xff;
 | 
			
		||||
	if(address < 0x80) {
 | 
			
		||||
		return ram_[address];
 | 
			
		||||
	}
 | 
			
		||||
	return 0xff;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
//
 | 
			
		||||
//  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);
 | 
			
		||||
 | 
			
		||||
		/// 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;
 | 
			
		||||
		int16_t output_volume_ = 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 */
 | 
			
		||||
							
								
								
									
										157
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
//
 | 
			
		||||
//  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) {
 | 
			
		||||
	// Build a volume table.
 | 
			
		||||
	double multiplier = pow(10.0, -0.1);
 | 
			
		||||
	double volume = 8191.0f;
 | 
			
		||||
	for(int c = 0; c < 16; ++c) {
 | 
			
		||||
		volumes_[c] = (int)round(volume);
 | 
			
		||||
		volume *= multiplier;
 | 
			
		||||
	}
 | 
			
		||||
	volumes_[15] = 0;
 | 
			
		||||
	evaluate_output_volume();
 | 
			
		||||
 | 
			
		||||
	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_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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
//
 | 
			
		||||
//  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();
 | 
			
		||||
 | 
			
		||||
	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 */
 | 
			
		||||
@@ -19,26 +19,21 @@ AsyncTaskQueue::AsyncTaskQueue()
 | 
			
		||||
	serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
 | 
			
		||||
#else
 | 
			
		||||
	thread_.reset(new std::thread([this]() {
 | 
			
		||||
		while(!should_destruct_)
 | 
			
		||||
		{
 | 
			
		||||
		while(!should_destruct_) {
 | 
			
		||||
			std::function<void(void)> next_function;
 | 
			
		||||
 | 
			
		||||
			// Take lock, check for a new task
 | 
			
		||||
			std::unique_lock<std::mutex> lock(queue_mutex_);
 | 
			
		||||
			if(!pending_tasks_.empty())
 | 
			
		||||
			{
 | 
			
		||||
			if(!pending_tasks_.empty()) {
 | 
			
		||||
				next_function = pending_tasks_.front();
 | 
			
		||||
				pending_tasks_.pop_front();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(next_function)
 | 
			
		||||
			{
 | 
			
		||||
			if(next_function) {
 | 
			
		||||
				// If there is a task, release lock and perform it
 | 
			
		||||
				lock.unlock();
 | 
			
		||||
				next_function();
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
			} else {
 | 
			
		||||
				// If there isn't a task, atomically block on the processing condition and release the lock
 | 
			
		||||
				// until there's something pending (and then release it again via scope)
 | 
			
		||||
				processing_condition_.wait(lock);
 | 
			
		||||
@@ -48,10 +43,10 @@ AsyncTaskQueue::AsyncTaskQueue()
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AsyncTaskQueue::~AsyncTaskQueue()
 | 
			
		||||
{
 | 
			
		||||
AsyncTaskQueue::~AsyncTaskQueue() {
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
	dispatch_release(serial_dispatch_queue_);
 | 
			
		||||
	serial_dispatch_queue_ = nullptr;
 | 
			
		||||
#else
 | 
			
		||||
	should_destruct_ = true;
 | 
			
		||||
	enqueue([](){});
 | 
			
		||||
@@ -60,8 +55,7 @@ AsyncTaskQueue::~AsyncTaskQueue()
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
 | 
			
		||||
{
 | 
			
		||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
	dispatch_async(serial_dispatch_queue_, ^{function();});
 | 
			
		||||
#else
 | 
			
		||||
@@ -71,8 +65,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function)
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AsyncTaskQueue::flush()
 | 
			
		||||
{
 | 
			
		||||
void AsyncTaskQueue::flush() {
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
	dispatch_sync(serial_dispatch_queue_, ^{});
 | 
			
		||||
#else
 | 
			
		||||
@@ -86,3 +79,25 @@ void AsyncTaskQueue::flush()
 | 
			
		||||
	flush_condition->wait(lock);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
 | 
			
		||||
	perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
#define AsyncTaskQueue_hpp
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <condition_variable>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <condition_variable>
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
#include <dispatch/dispatch.h>
 | 
			
		||||
@@ -22,14 +24,13 @@ namespace Concurrency {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
 | 
			
		||||
	to be performed serially and asynchronously from the caller. A caller may also request to synchronise,
 | 
			
		||||
	to be performed serially and asynchronously from the caller. A caller may also request to flush,
 | 
			
		||||
	causing it to block until all previously-enqueued functions are complete.
 | 
			
		||||
*/
 | 
			
		||||
class AsyncTaskQueue {
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		AsyncTaskQueue();
 | 
			
		||||
		~AsyncTaskQueue();
 | 
			
		||||
		virtual ~AsyncTaskQueue();
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Adds @c function to the queue.
 | 
			
		||||
@@ -58,6 +59,39 @@ class AsyncTaskQueue {
 | 
			
		||||
#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 */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								Concurrency/BestEffortUpdater.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Concurrency/BestEffortUpdater.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
//
 | 
			
		||||
//  BestEffortUpdater.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/10/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "BestEffortUpdater.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cmath>
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
	// Perform an update only if one is not currently ongoing.
 | 
			
		||||
	if(!update_is_ongoing_.test_and_set()) {
 | 
			
		||||
		async_task_queue_.enqueue([this]() {
 | 
			
		||||
			// Get time now using the highest-resolution clock provided by the implementation, and determine
 | 
			
		||||
			// the duration since the last time this section was entered.
 | 
			
		||||
			const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
 | 
			
		||||
			const auto elapsed = now - previous_time_point_;
 | 
			
		||||
			previous_time_point_ = now;
 | 
			
		||||
 | 
			
		||||
			if(has_previous_time_point_) {
 | 
			
		||||
				// 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 —
 | 
			
		||||
				// 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();
 | 
			
		||||
				if(duration > 0) {
 | 
			
		||||
					double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
 | 
			
		||||
					error_ = fmod(cycles, 1.0);
 | 
			
		||||
 | 
			
		||||
					if(delegate_) {
 | 
			
		||||
						delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
 | 
			
		||||
					}
 | 
			
		||||
					has_skipped_ = false;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				has_previous_time_point_ = true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Allow furthers updates to occur.
 | 
			
		||||
			update_is_ongoing_.clear();
 | 
			
		||||
		});
 | 
			
		||||
	} else {
 | 
			
		||||
		async_task_queue_.enqueue([this]() {
 | 
			
		||||
			has_skipped_ = true;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BestEffortUpdater::flush() {
 | 
			
		||||
	async_task_queue_.flush();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BestEffortUpdater::set_delegate(Delegate *const delegate) {
 | 
			
		||||
	async_task_queue_.enqueue([this, delegate]() {
 | 
			
		||||
		delegate_ = delegate;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
 | 
			
		||||
	async_task_queue_.enqueue([this, clock_rate]() {
 | 
			
		||||
		this->clock_rate_ = clock_rate;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								Concurrency/BestEffortUpdater.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Concurrency/BestEffortUpdater.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
//
 | 
			
		||||
//  BestEffortUpdater.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 04/10/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef BestEffortUpdater_hpp
 | 
			
		||||
#define BestEffortUpdater_hpp
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
#include "AsyncTaskQueue.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Concurrency {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Accepts timing cues from multiple threads and ensures that a delegate receives calls to total
 | 
			
		||||
	a certain number of cycles per second, that those calls are strictly serialised, and that no
 | 
			
		||||
	backlog of calls accrues.
 | 
			
		||||
 | 
			
		||||
	No guarantees about the thread that the delegate will be called on are made.
 | 
			
		||||
*/
 | 
			
		||||
class BestEffortUpdater {
 | 
			
		||||
	public:
 | 
			
		||||
		BestEffortUpdater();
 | 
			
		||||
		~BestEffortUpdater();
 | 
			
		||||
 | 
			
		||||
		/// A delegate receives timing cues.
 | 
			
		||||
		struct Delegate {
 | 
			
		||||
			virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/// Sets the current 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.
 | 
			
		||||
			The call is asynchronous; this method will return immediately.
 | 
			
		||||
		*/
 | 
			
		||||
		void update();
 | 
			
		||||
 | 
			
		||||
		/// Blocks until any ongoing update is complete.
 | 
			
		||||
		void flush();
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::atomic_flag update_is_ongoing_;
 | 
			
		||||
		AsyncTaskQueue async_task_queue_;
 | 
			
		||||
 | 
			
		||||
		std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
 | 
			
		||||
		bool has_previous_time_point_ = false;
 | 
			
		||||
		double error_ = 0.0;
 | 
			
		||||
		bool has_skipped_ = false;
 | 
			
		||||
 | 
			
		||||
		Delegate *delegate_ = nullptr;
 | 
			
		||||
		double clock_rate_ = 1.0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* BestEffortUpdater_hpp */
 | 
			
		||||
							
								
								
									
										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 */
 | 
			
		||||
							
								
								
									
										76
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
//
 | 
			
		||||
//  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 & DisplayRGBComposite)			options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"}));
 | 
			
		||||
	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) {
 | 
			
		||||
	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection((selection == Display::RGB) ? "rgb" : "composite"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 == "composite") {
 | 
			
		||||
			result = Configurable::Display::Composite;
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
//
 | 
			
		||||
//  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 {
 | 
			
		||||
	DisplayRGBComposite			= (1 << 0),
 | 
			
		||||
	QuickLoadTape				= (1 << 1),
 | 
			
		||||
	AutomaticTapeMotorControl	= (1 << 2)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class Display {
 | 
			
		||||
	RGB,
 | 
			
		||||
	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 */
 | 
			
		||||
							
								
								
									
										68
									
								
								Inputs/Joystick.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Inputs/Joystick.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
//
 | 
			
		||||
//  Joystick.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 14/10/2017.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef Joystick_hpp
 | 
			
		||||
#define Joystick_hpp
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Inputs {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides an intermediate idealised model of a simple joystick, allowing a host
 | 
			
		||||
	machine to toggle states, while an interested party either observes or polls.
 | 
			
		||||
*/
 | 
			
		||||
class Joystick {
 | 
			
		||||
	public:
 | 
			
		||||
		virtual ~Joystick() {}
 | 
			
		||||
 | 
			
		||||
		struct DigitalInput {
 | 
			
		||||
			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.
 | 
			
		||||
		virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0;
 | 
			
		||||
		virtual void reset_all_inputs() {
 | 
			
		||||
			for(const auto &input: get_inputs()) {
 | 
			
		||||
				set_digital_input(input, false);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Joystick_hpp */
 | 
			
		||||
							
								
								
									
										38
									
								
								Inputs/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Inputs/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
//
 | 
			
		||||
//  Keyboard.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 10/9/17.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Keyboard.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Inputs;
 | 
			
		||||
 | 
			
		||||
Keyboard::Keyboard() {}
 | 
			
		||||
 | 
			
		||||
void Keyboard::set_key_pressed(Key key, bool is_pressed) {
 | 
			
		||||
	std::size_t key_offset = static_cast<std::size_t>(key);
 | 
			
		||||
	if(key_offset >= key_states_.size()) {
 | 
			
		||||
		key_states_.resize(key_offset+1, false);
 | 
			
		||||
	}
 | 
			
		||||
	key_states_[key_offset] = is_pressed;
 | 
			
		||||
 | 
			
		||||
	if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Keyboard::reset_all_keys() {
 | 
			
		||||
	std::fill(key_states_.begin(), key_states_.end(), false);
 | 
			
		||||
	if(delegate_) delegate_->reset_all_keys(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Keyboard::set_delegate(Delegate *delegate) {
 | 
			
		||||
	delegate_ = delegate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Keyboard::get_key_state(Key key) {
 | 
			
		||||
	std::size_t key_offset = static_cast<std::size_t>(key);
 | 
			
		||||
	if(key_offset >= key_states_.size()) return false;
 | 
			
		||||
	return key_states_[key_offset];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								Inputs/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Inputs/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
//
 | 
			
		||||
//  Keyboard.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 10/9/17.
 | 
			
		||||
//  Copyright © 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef Keyboard_hpp
 | 
			
		||||
#define Keyboard_hpp
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Inputs {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides an intermediate idealised model of a modern-era computer keyboard
 | 
			
		||||
	(so, heavily indebted to the current Windows and Mac layouts), allowing a host
 | 
			
		||||
	machine to toggle states, while an interested party either observes or polls.
 | 
			
		||||
*/
 | 
			
		||||
class Keyboard {
 | 
			
		||||
	public:
 | 
			
		||||
		Keyboard();
 | 
			
		||||
 | 
			
		||||
		enum class Key {
 | 
			
		||||
			Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
 | 
			
		||||
			BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
 | 
			
		||||
			Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
 | 
			
		||||
			CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
 | 
			
		||||
			LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
 | 
			
		||||
			LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
 | 
			
		||||
			Left, Right, Up, Down,
 | 
			
		||||
			Insert, Home, PageUp, Delete, End, PageDown,
 | 
			
		||||
			NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete,
 | 
			
		||||
			KeyPad7, KeyPad8, KeyPad9, KeyPadPlus,
 | 
			
		||||
			KeyPad4, KeyPad5, KeyPad6, KeyPadMinus,
 | 
			
		||||
			KeyPad1, KeyPad2, KeyPad3, KeyPadEnter,
 | 
			
		||||
			KeyPad0, KeyPadDecimalPoint, KeyPadEquals,
 | 
			
		||||
			Help
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// Host interface.
 | 
			
		||||
		virtual void set_key_pressed(Key key, bool is_pressed);
 | 
			
		||||
		virtual void reset_all_keys();
 | 
			
		||||
 | 
			
		||||
		// Delegate interface.
 | 
			
		||||
		struct Delegate {
 | 
			
		||||
			virtual void keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
 | 
			
		||||
			virtual void reset_all_keys(Keyboard *keyboard) = 0;
 | 
			
		||||
		};
 | 
			
		||||
		void set_delegate(Delegate *delegate);
 | 
			
		||||
		bool get_key_state(Key key);
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<bool> key_states_;
 | 
			
		||||
		Delegate *delegate_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Keyboard_hpp */
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user