mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			534 Commits
		
	
	
		
			2017-11-03
			...
			2018-04-07
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 506b4da6c3 | ||
|  | 10f637d2cf | ||
|  | 0bab7c88f0 | ||
|  | 78c612ca17 | ||
|  | e1c4035812 | ||
|  | eb6d6c8033 | ||
|  | 7bf88565ce | ||
|  | ee10155296 | ||
|  | cc49140f6f | ||
|  | 3e846f89a1 | ||
|  | 5782cab2a0 | ||
|  | 8c511e2b76 | ||
|  | ec72fb3baf | ||
|  | bab1440f5c | ||
|  | 60c1da6a66 | ||
|  | a849b3f2e4 | ||
|  | dbe3c5c3f8 | ||
|  | 60cf6b3cfd | ||
|  | 5044aac337 | ||
|  | 36e0cb29c0 | ||
|  | c0b4dd65da | ||
|  | d061ea232b | ||
|  | 49feca4ddf | ||
|  | 46b1c57bf4 | ||
|  | eaf1482182 | ||
|  | d3418550eb | ||
|  | 3ffa9e2751 | ||
|  | c697dd78f0 | ||
|  | 7dac791290 | ||
|  | cde2faeda6 | ||
|  | 69f520428d | ||
|  | 80c84ddd75 | ||
|  | fca8a58b36 | ||
|  | 33084899d0 | ||
|  | 7b381a8b6b | ||
|  | 9c75689a8d | ||
|  | 0ee40e8556 | ||
|  | 8b45377b89 | ||
|  | f6fb368d88 | ||
|  | 183a5379de | ||
|  | 912791d3d4 | ||
|  | 163a61dd63 | ||
|  | 207d462dbf | ||
|  | 33281b9d89 | ||
|  | 389979923e | ||
|  | 067174965e | ||
|  | 286259c83b | ||
|  | e1aa3e5a7f | ||
|  | 78e1c2851a | ||
|  | 0869213c55 | ||
|  | f3fe16215a | ||
|  | ec353ce663 | ||
|  | b7ff5ef9dd | ||
|  | 3b26e0a7c5 | ||
|  | 6d464557a0 | ||
|  | a776bec46a | ||
|  | a2da51c30b | ||
|  | 8067bf548a | ||
|  | 62b0645ed0 | ||
|  | 39a94874ae | ||
|  | e15d6717a1 | ||
|  | 37ef46e7bb | ||
|  | 70c09b3031 | ||
|  | 9378fbb0df | ||
|  | 2118b9c0cd | ||
|  | d0c53de250 | ||
|  | d98507eab0 | ||
|  | 760c75103e | ||
|  | 4407fd2f1f | ||
|  | 7fcd243be0 | ||
|  | 3165e9d82e | ||
|  | 6656a08c60 | ||
|  | 76661c0b51 | ||
|  | 3bb496f9ae | ||
|  | 45be1c19df | ||
|  | a301964bd0 | ||
|  | eea6858121 | ||
|  | 2a320fdf56 | ||
|  | 4695296ef2 | ||
|  | 0fdbbeca1d | ||
|  | 34cc39ad65 | ||
|  | 3d0c832a21 | ||
|  | 1acdab9448 | ||
|  | 93e85c5c4a | ||
|  | ab98189d25 | ||
|  | cd0fb7624b | ||
|  | bae38497bb | ||
|  | 29921bfa8d | ||
|  | 2712702461 | ||
|  | a3fa9440d1 | ||
|  | 6419b0e619 | ||
|  | 58e5b6e3f1 | ||
|  | 682c3d8079 | ||
|  | da3d65c18f | ||
|  | ece3a05504 | ||
|  | 927697b0f0 | ||
|  | 74dfc80b0f | ||
|  | a7f229bc4b | ||
|  | 89bec2919f | ||
|  | 78eaecb29e | ||
|  | d410aea856 | ||
|  | 6b1eef572b | ||
|  | 719f5d79c2 | ||
|  | 48737a32a7 | ||
|  | 53f05efb2d | ||
|  | 0e73ba4b3e | ||
|  | f0f9d5a6af | ||
|  | 03501df9e5 | ||
|  | dd6f85d4db | ||
|  | 1804ea6849 | ||
|  | c8657e08f4 | ||
|  | a942e1319b | ||
|  | 9e0a56b4f0 | ||
|  | 9abc020818 | ||
|  | 2dade8d353 | ||
|  | 1100dc6993 | ||
|  | f212b18511 | ||
|  | a6ca69550f | ||
|  | 2452641844 | ||
|  | c82af4b814 | ||
|  | fdef914137 | ||
|  | dfcc502a88 | ||
|  | 1c6faaae88 | ||
|  | 35c8a0dd8c | ||
|  | 38feedaf6a | ||
|  | 0a2f908af4 | ||
|  | 705d53cc21 | ||
|  | 35b18d58af | ||
|  | 3c5a8d9ff3 | ||
|  | 7ca02be578 | ||
|  | ea13c7dd32 | ||
|  | fdfd72a42c | ||
|  | da97bf95c0 | ||
|  | bdfc36427c | ||
|  | 74dfe56d2b | ||
|  | 6cce9aa54e | ||
|  | ba68b7247b | ||
|  | b02e4fbbf6 | ||
|  | 59b4c7314d | ||
|  | d328589bd0 | ||
|  | b05d2b26bf | ||
|  | 86239469e7 | ||
|  | 7890506b16 | ||
|  | 83f73c3f02 | ||
|  | 87760297fc | ||
|  | 5b854d51e7 | ||
|  | d4df101ab6 | ||
|  | 0ad2676640 | ||
|  | a074ee2071 | ||
|  | 204d5cc964 | ||
|  | 23d15a4d6c | ||
|  | 23c47e21de | ||
|  | 5530b96446 | ||
|  | 99d28a172b | ||
|  | d83178f29d | ||
|  | d9d5ffdaa2 | ||
|  | cabad6fc05 | ||
|  | a4dc9c0403 | ||
|  | 270723ae72 | ||
|  | b215cf83d5 | ||
|  | f237dcf904 | ||
|  | fc81bfa59b | ||
|  | 832ac173ae | ||
|  | 3673cfe9be | ||
|  | 6aaef97158 | ||
|  | b0ab617393 | ||
|  | 6780b0bf11 | ||
|  | 9c0a440c38 | ||
|  | 2439f5aee5 | ||
|  | 8265f289bd | ||
|  | 9728bea0a7 | ||
|  | fc9e84c72e | ||
|  | 7d75e864b1 | ||
|  | a005dabbe3 | ||
|  | c8a4432c63 | ||
|  | 7b420d56e3 | ||
|  | ddf1bf3cbf | ||
|  | 7ea4ca00dc | ||
|  | 6b8c223804 | ||
|  | 23105956d6 | ||
|  | d751b7e2cb | ||
|  | f02989649c | ||
|  | dcf313a833 | ||
|  | 9960121b08 | ||
|  | 8eea55b51c | ||
|  | e1cab52c84 | ||
|  | eb39617ad0 | ||
|  | 43b682a5af | ||
|  | 043fd5d404 | ||
|  | d63a95983d | ||
|  | 4cf258f952 | ||
|  | 4e720d57b2 | ||
|  | c12aaea747 | ||
|  | ca48497e87 | ||
|  | d493ea4bca | ||
|  | e025674eb2 | ||
|  | f2519f4fd7 | ||
|  | db914d8c56 | ||
|  | 66faed4008 | ||
|  | 11abc99ef8 | ||
|  | 21efb32b6f | ||
|  | 622a04aec8 | ||
|  | d360b2c62d | ||
|  | 6a112edc18 | ||
|  | 8fb4409ebb | ||
|  | d213341d9c | ||
|  | c2f1306d85 | ||
|  | 2143ea6f12 | ||
|  | edb30b3c6c | ||
|  | 234e4f6f66 | ||
|  | ce2d3c6e82 | ||
|  | 46c76b9c07 | ||
|  | 583c3cfe7d | ||
|  | e13312dcc5 | ||
|  | d9e49c0d5f | ||
|  | 8a370cc1ac | ||
|  | cdae0fa593 | ||
|  | 765c0d4ff8 | ||
|  | 4cf2e16b5c | ||
|  | 9cbd61e709 | ||
|  | 0202c7afb2 | ||
|  | c187c5a637 | ||
|  | 23c34a8c14 | ||
|  | 93ece2aec7 | ||
|  | e12ab8fe2e | ||
|  | 2fe0ceb52a | ||
|  | f354c12c81 | ||
|  | def82cba49 | ||
|  | e7bc7b94c9 | ||
|  | aafdff49be | ||
|  | 4ef583813a | ||
|  | 9f97fb738e | ||
|  | 4e124047c6 | ||
|  | 6eb56a1564 | ||
|  | 35fc0a5c16 | ||
|  | b36c917810 | ||
|  | a5ac8c824e | ||
|  | 0ccc104027 | ||
|  | 8be6cb827b | ||
|  | 2f59226300 | ||
|  | 793ef68206 | ||
|  | 513c067f94 | ||
|  | 999a0c22d4 | ||
|  | 5d0832613f | ||
|  | 2ffde4c3c2 | ||
|  | 57ddfcd645 | ||
|  | fc16e8eb8c | ||
|  | 655b971976 | ||
|  | 3e1d8ea082 | ||
|  | 772c320d5a | ||
|  | bcc7ad0c30 | ||
|  | 73b4e1722b | ||
|  | 185cd3c123 | ||
|  | ed564cb810 | ||
|  | b78ece1f1e | ||
|  | c8367a017f | ||
|  | 344a12566b | ||
|  | c07113ea95 | ||
|  | bc2879c412 | ||
|  | 1d47b55729 | ||
|  | db25b4554b | ||
|  | 05b95ea2e0 | ||
|  | 250f7bf6b0 | ||
|  | 34db35b500 | ||
|  | f75590253d | ||
|  | 4f6abc9059 | ||
|  | c70dbc6a49 | ||
|  | 1c255b9e7d | ||
|  | 188bfa9c18 | ||
|  | c7f8f37822 | ||
|  | 4a19dbb8cb | ||
|  | bf0601123b | ||
|  | 9339f3413f | ||
|  | c18517be4b | ||
|  | eef34adcbd | ||
|  | 769d9dfbb9 | ||
|  | 6a0bb83716 | ||
|  | 6da8a3e24b | ||
|  | e349161a53 | ||
|  | d5b1a9d918 | ||
|  | 76af0228dd | ||
|  | 2cc1a2684a | ||
|  | 98a9d57c0b | ||
|  | c481293aca | ||
|  | 5fd0a2b9ea | ||
|  | 11b73a9c0b | ||
|  | c4950574ea | ||
|  | 0b297f2972 | ||
|  | f9f870ad2d | ||
|  | cbba6a5595 | ||
|  | 0a079b0f94 | ||
|  | 9a7e974579 | ||
|  | f4d414d6e4 | ||
|  | b4bfcd4279 | ||
|  | e8ddff0ee0 | ||
|  | b61fab9df7 | ||
|  | 28fb1ce2ae | ||
|  | b9b107ee85 | ||
|  | f17758e7f9 | ||
|  | 0bb24075b6 | ||
|  | db6d9b59d0 | ||
|  | 51e82c10c5 | ||
|  | 2d892da225 | ||
|  | b99ba2bc02 | ||
|  | d36e9d0b0d | ||
|  | 2dc1d4443e | ||
|  | f8a2459c91 | ||
|  | ac80d10cd8 | ||
|  | eb6b612052 | ||
|  | d66a33f249 | ||
|  | ec4c259695 | ||
|  | ad50b6b1fb | ||
|  | 3da323c657 | ||
|  | aca7842ca4 | ||
|  | 38c912b968 | ||
|  | 7a52e7d6d2 | ||
|  | c36de4f640 | ||
|  | 504772bcda | ||
|  | 5d0c33d545 | ||
|  | 7bc1bcd493 | ||
|  | b0616ee10c | ||
|  | da57df55e8 | ||
|  | 4daea1121b | ||
|  | afcdd64d5e | ||
|  | 798cdba979 | ||
|  | f957344ac4 | ||
|  | b3fbd0f352 | ||
|  | 042edc72f7 | ||
|  | 943418c434 | ||
|  | 7d7e2538bd | ||
|  | 7a544731e2 | ||
|  | e1914b4f16 | ||
|  | 202958303e | ||
|  | 57b060ac3c | ||
|  | 8653eb8b55 | ||
|  | a4f0a260fd | ||
|  | d4a53e82bb | ||
|  | 6eedc99286 | ||
|  | ec266d6c8e | ||
|  | e3a5218e78 | ||
|  | a473338abe | ||
|  | ae21782adc | ||
|  | ee44d671e7 | ||
|  | 3766bef962 | ||
|  | ad3df36c20 | ||
|  | 38b11893e8 | ||
|  | e4534775b0 | ||
|  | fe7fc6b22e | ||
|  | fe0cdc8d69 | ||
|  | 7f8a13a409 | ||
|  | ca26ce8400 | ||
|  | d3dd8f3f2a | ||
|  | 3c8d2d579d | ||
|  | edcbb3dfed | ||
|  | 9c8158753e | ||
|  | 5da9cb2957 | ||
|  | 54c845b6e2 | ||
|  | ee84f33ab5 | ||
|  | f0f149c018 | ||
|  | 7dfbe4bb93 | ||
|  | aa4eef41d8 | ||
|  | 69ec8a362e | ||
|  | ecd7d4731b | ||
|  | 563aa051e4 | ||
|  | 642bb8333f | ||
|  | c558e86e03 | ||
|  | dbb14ea2e2 | ||
|  | 173e16b107 | ||
|  | 7d2adad67e | ||
|  | d33612def5 | ||
|  | 9cb6ca3440 | ||
|  | e957e40b14 | ||
|  | 7a8a43a96a | ||
|  | 0eb5dd9688 | ||
|  | a14b53a9ab | ||
|  | 576d554a2c | ||
|  | 68a2895753 | ||
|  | f90b3f06aa | ||
|  | f067fa9923 | ||
|  | ee9f89ccb5 | ||
|  | 573a9c6fb2 | ||
|  | a46a37fba9 | ||
|  | 324b57c054 | ||
|  | ae50ca9ab2 | ||
|  | 6e4bde00d3 | ||
|  | d4d0dd87c9 | ||
|  | 221c05ca76 | ||
|  | ff21ff90eb | ||
|  | fcf295fd68 | ||
|  | 2008dec1ed | ||
|  | b4f3c41aae | ||
|  | 90c4e3726f | ||
|  | c83b3cefbc | ||
|  | a8ac51da73 | ||
|  | bc65ba3f9b | ||
|  | 79674fdbd3 | ||
|  | adea4711f1 | ||
|  | bded406caa | ||
|  | 85085a6375 | ||
|  | d122d598d3 | ||
|  | d6192b8c58 | ||
|  | f02d4dbb59 | ||
|  | f3818991f6 | ||
|  | c7dd6247f0 | ||
|  | 99e17600d7 | ||
|  | 1d821ad459 | ||
|  | c60a9ee3c3 | ||
|  | ffcbd1e94d | ||
|  | 6c8b503402 | ||
|  | 55e1d25966 | ||
|  | 0bdd776114 | ||
|  | c1b7bceec8 | ||
|  | dc4f58e40c | ||
|  | 3b8cdd620c | ||
|  | 3365ff0200 | ||
|  | 89c3e2ba5a | ||
|  | c6306db47c | ||
|  | 8ddc64c82a | ||
|  | b887cb7255 | ||
|  | d54ee2af82 | ||
|  | 723c113186 | ||
|  | c368c4443e | ||
|  | 7b25b03cd5 | ||
|  | 9961d13e2d | ||
|  | 29b5ccc767 | ||
|  | 90af395df2 | ||
|  | 6f8d4d6c5c | ||
|  | 63381ff505 | ||
|  | 2ea050556b | ||
|  | 8dcac6561e | ||
|  | 90d33949f9 | ||
|  | d3e68914dd | ||
|  | 82ad0354c4 | ||
|  | 073e439518 | ||
|  | 27b123549b | ||
|  | de9db724a7 | ||
|  | 532ea35ee9 | ||
|  | e9ddec35d6 | ||
|  | 7647f8089b | ||
|  | f00f0353a6 | ||
|  | e19ae5d43d | ||
|  | 0a9622435c | ||
|  | f704932475 | ||
|  | d0f096a20b | ||
|  | 949d0f3928 | ||
|  | a2d48223c3 | ||
|  | fc080c773f | ||
|  | adb3811847 | ||
|  | dbbea78b76 | ||
|  | fd96e3e657 | ||
|  | 06d81b3a97 | ||
|  | 88551607a6 | ||
|  | 2a9dccff26 | ||
|  | 1027f85683 | ||
|  | 9bb9cb4a65 | ||
|  | 2de80646ec | ||
|  | bf4ed57f68 | ||
|  | 9578f3dc44 | ||
|  | a97c478a34 | ||
|  | e0113d5dce | ||
|  | 980cf541d2 | ||
|  | 69c983f9ee | ||
|  | 70039d22f1 | ||
|  | ebdb80c908 | ||
|  | 0eaac99d74 | ||
|  | 792061a82b | ||
|  | d2ba7d7430 | ||
|  | 8713cfa613 | ||
|  | aa77be1c10 | ||
|  | e6aa2321cd | ||
|  | c827d14d97 | ||
|  | 2979d19621 | ||
|  | 282e5c9d3e | ||
|  | ede47d4ba7 | ||
|  | 5408efe9b5 | ||
|  | d6141cb020 | ||
|  | 198d0fd1de | ||
|  | 6d80856f02 | ||
|  | 4778616fd7 | ||
|  | 2e025d85eb | ||
|  | 61f2191c86 | ||
|  | c1eab8d5f3 | ||
|  | 91d2d59ae5 | ||
|  | 5aef81cf24 | ||
|  | 3550196bed | ||
|  | bce58683fa | ||
|  | c91a5875b2 | ||
|  | 2e15fab651 | ||
|  | 6a176082a0 | ||
|  | fd346bac3e | ||
|  | 25e9dcc800 | ||
|  | 792cbb1536 | ||
|  | 2e12370251 | ||
|  | 7adc25694a | ||
|  | ca80da7fbe | ||
|  | f853d87884 | ||
|  | 524087805f | ||
|  | 916eb96b47 | ||
|  | 4add2c1051 | ||
|  | cb0f58ab7a | ||
|  | d9e56711ce | ||
|  | d60692b6fd | ||
|  | 5b6ea35d96 | ||
|  | 4cbc87a17d | ||
|  | 46e7c199b2 | ||
|  | ff7ba526fb | ||
|  | a825da3715 | ||
|  | fabaf4e607 | ||
|  | 153067c018 | ||
|  | f7f2736d4d | ||
|  | a16ca65825 | ||
|  | cb015c83e1 | ||
|  | 2203499215 | ||
|  | c0055a5a5f | ||
|  | 62218e81bf | ||
|  | c45d4831ec | ||
|  | 9fd33bdfde | ||
|  | 6e1d69581c | ||
|  | f95515ae81 | ||
|  | 09c855a659 | ||
|  | 16c96b605a | ||
|  | e10d369e53 | ||
|  | 0d1b63a8c5 | ||
|  | ddcdd07dd0 | ||
|  | 35da3edf60 | ||
|  | d605022ea3 | ||
|  | 0da78065ce | ||
|  | 4b68c372c6 | ||
|  | 13406fedd8 | ||
|  | a209ae76ca | ||
|  | 0116d7f071 | ||
|  | 512e877d06 | ||
|  | 1e1efcdcb8 | ||
|  | bc2f58e9de | 
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -18,9 +18,14 @@ DerivedData | |||||||
| *.xcuserstate | *.xcuserstate | ||||||
| .DS_Store | .DS_Store | ||||||
|  |  | ||||||
| # Exclude system ROMs | # Exclude system ROMs and unit test ROMs | ||||||
| ROMImages/* | ROMImages/* | ||||||
| OSBindings/Mac/Clock SignalTests/Atari\ ROMs | OSBindings/Mac/Clock SignalTests/Atari ROMs | ||||||
|  | OSBindings/Mac/Clock SignalTests/MSX ROMs | ||||||
|  |  | ||||||
|  | # Exclude intermediate build products | ||||||
|  | *.o | ||||||
|  | .sconsign.dblite | ||||||
|  |  | ||||||
| # CocoaPods | # CocoaPods | ||||||
| # | # | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,5 +1,13 @@ | |||||||
| language: objective-c | # language: objective-c | ||||||
| osx_image: xcode8.2 | # osx_image: xcode8.2 | ||||||
| xcode_project: OSBindings/Mac/Clock Signal.xcodeproj | # xcode_project: OSBindings/Mac/Clock Signal.xcodeproj | ||||||
| xcode_scheme: Clock Signal | # xcode_scheme: Clock Signal | ||||||
| xcode_sdk: macosx10.12 | # xcode_sdk: macosx10.12 | ||||||
|  |  | ||||||
|  | language: cpp | ||||||
|  | before_install: | ||||||
|  | 	- sudo apt-get install libsdl2-dev | ||||||
|  | script: cd OSBindings/SDL && scons | ||||||
|  | compiler: | ||||||
|  | 	- clang | ||||||
|  | 	- gcc | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								Analyser/Dynamic/ConfidenceCounter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Analyser/Dynamic/ConfidenceCounter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceCounter.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "ConfidenceCounter.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | float ConfidenceCounter::get_confidence() { | ||||||
|  | 	return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfidenceCounter::add_hit() { | ||||||
|  | 	hits_++; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfidenceCounter::add_miss() { | ||||||
|  | 	misses_++; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfidenceCounter::add_equivocal() { | ||||||
|  | 	if(hits_ > misses_) { | ||||||
|  | 		hits_++; | ||||||
|  | 		misses_++; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								Analyser/Dynamic/ConfidenceCounter.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Analyser/Dynamic/ConfidenceCounter.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceCounter.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ConfidenceCounter_hpp | ||||||
|  | #define ConfidenceCounter_hpp | ||||||
|  |  | ||||||
|  | #include "ConfidenceSource.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a confidence source that calculates its probability by virtual of a history of events. | ||||||
|  |  | ||||||
|  | 	The initial value of the confidence counter is 0.5. | ||||||
|  | */ | ||||||
|  | class ConfidenceCounter: public ConfidenceSource { | ||||||
|  | 	public: | ||||||
|  | 		/*! @returns The computed probability, based on the history of events. */ | ||||||
|  | 		float get_confidence() override; | ||||||
|  |  | ||||||
|  | 		/*! Records an event that implies this is the appropriate class — pushes probability up towards 1.0. */ | ||||||
|  | 		void add_hit(); | ||||||
|  |  | ||||||
|  | 		/*! Records an event that implies this is not the appropriate class — pushes probability down towards 0.0. */ | ||||||
|  | 		void add_miss(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Records an event that could be correct but isn't necessarily so; which can push probability | ||||||
|  | 			down towards 0.5, but will never push it upwards. | ||||||
|  | 		*/ | ||||||
|  | 		void add_equivocal(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int hits_ = 1; | ||||||
|  | 		int misses_ = 1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ConfidenceCounter_hpp */ | ||||||
							
								
								
									
										28
									
								
								Analyser/Dynamic/ConfidenceSource.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Dynamic/ConfidenceSource.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceSource.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ConfidenceSource_hpp | ||||||
|  | #define ConfidenceSource_hpp | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides an abstract interface through which objects can declare the probability | ||||||
|  | 	that they are the proper target for their input; e.g. if an Acorn Electron is asked | ||||||
|  | 	to run an Atari 2600 program then its confidence should shrink towards 0.0; if the | ||||||
|  | 	program is handed to an Atari 2600 then its confidence should grow towards 1.0. | ||||||
|  | */ | ||||||
|  | struct ConfidenceSource { | ||||||
|  | 	virtual float get_confidence() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ConfidenceSource_hpp */ | ||||||
							
								
								
									
										28
									
								
								Analyser/Dynamic/ConfidenceSummary.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Dynamic/ConfidenceSummary.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceSummary.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "ConfidenceSummary.hpp" | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <numeric> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) : | ||||||
|  | 	sources_(sources), weights_(weights) { | ||||||
|  | 	assert(weights.size() == sources.size()); | ||||||
|  | 	weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ConfidenceSummary::get_confidence() { | ||||||
|  | 	float result = 0.0f; | ||||||
|  | 	for(std::size_t index = 0; index < sources_.size(); ++index) { | ||||||
|  | 		result += sources_[index]->get_confidence() * weights_[index]; | ||||||
|  | 	} | ||||||
|  | 	return result / weight_sum_; | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								Analyser/Dynamic/ConfidenceSummary.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Analyser/Dynamic/ConfidenceSummary.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceSummary.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ConfidenceSummary_hpp | ||||||
|  | #define ConfidenceSummary_hpp | ||||||
|  |  | ||||||
|  | #include "ConfidenceSource.hpp" | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Summaries a collection of confidence sources by calculating their weighted sum. | ||||||
|  | */ | ||||||
|  | class ConfidenceSummary: public ConfidenceSource { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Instantiates a summary that will produce the weighted sum of | ||||||
|  | 			@c sources, each using the corresponding entry of @c weights. | ||||||
|  |  | ||||||
|  | 			Requires that @c sources and @c weights are of the same length. | ||||||
|  | 		*/ | ||||||
|  | 		ConfidenceSummary( | ||||||
|  | 			const std::vector<ConfidenceSource *> &sources, | ||||||
|  | 			const std::vector<float> &weights); | ||||||
|  |  | ||||||
|  | 		/*! @returns The weighted sum of all sources. */ | ||||||
|  | 		float get_confidence() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<ConfidenceSource *> sources_; | ||||||
|  | 		std::vector<float> weights_; | ||||||
|  | 		float weight_sum_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ConfidenceSummary_hpp */ | ||||||
| @@ -0,0 +1,90 @@ | |||||||
|  | // | ||||||
|  | //  MultiCRTMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiCRTMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <mutex> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) : | ||||||
|  | 	machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) { | ||||||
|  | 	speaker_ = MultiSpeaker::create(machines); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) { | ||||||
|  | 	// Apply a blunt force parallelisation of the machines; each run_for is dispatched | ||||||
|  | 	// to a separate queue and this queue will block until all are done. | ||||||
|  | 	volatile std::size_t outstanding_machines; | ||||||
|  | 	std::condition_variable condition; | ||||||
|  | 	std::mutex mutex; | ||||||
|  | 	{ | ||||||
|  | 		std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 		std::lock_guard<std::mutex> lock(mutex); | ||||||
|  | 		outstanding_machines = machines_.size(); | ||||||
|  |  | ||||||
|  | 		for(std::size_t index = 0; index < machines_.size(); ++index) { | ||||||
|  | 			CRTMachine::Machine *crt_machine = machines_[index]->crt_machine(); | ||||||
|  | 			queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() { | ||||||
|  | 				if(crt_machine) function(crt_machine); | ||||||
|  |  | ||||||
|  | 				std::lock_guard<std::mutex> lock(mutex); | ||||||
|  | 				outstanding_machines--; | ||||||
|  | 				condition.notify_all(); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::unique_lock<std::mutex> lock(mutex); | ||||||
|  | 	condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) { | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 	for(const auto &machine: machines_) { | ||||||
|  | 		CRTMachine::Machine *crt_machine = machine->crt_machine(); | ||||||
|  | 		if(crt_machine) function(crt_machine); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::setup_output(float aspect_ratio) { | ||||||
|  | 	perform_serial([=](::CRTMachine::Machine *machine) { | ||||||
|  | 		machine->setup_output(aspect_ratio); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::close_output() { | ||||||
|  | 	perform_serial([=](::CRTMachine::Machine *machine) { | ||||||
|  | 		machine->close_output(); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Outputs::CRT::CRT *MultiCRTMachine::get_crt() { | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); | ||||||
|  | 	return crt_machine ? crt_machine->get_crt() : nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() { | ||||||
|  | 	return speaker_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::run_for(Time::Seconds duration) { | ||||||
|  | 	perform_parallel([=](::CRTMachine::Machine *machine) { | ||||||
|  | 		if(machine->get_confidence() >= 0.01f) machine->run_for(duration); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	if(delegate_) delegate_->multi_crt_did_run_machines(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::did_change_machine_order() { | ||||||
|  | 	if(speaker_) { | ||||||
|  | 		speaker_->set_new_front_machine(machines_.front().get()); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,89 @@ | |||||||
|  | // | ||||||
|  | //  MultiCRTMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiCRTMachine_hpp | ||||||
|  | #define MultiCRTMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  | #include "../../../../Machines/CRTMachine.hpp" | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "MultiSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the CRT machine interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Keeps a reference to the original vector of machines; will access it only after | ||||||
|  | 	acquiring a supplied mutex. The owner should also call did_change_machine_order() | ||||||
|  | 	if the order of machines changes. | ||||||
|  | */ | ||||||
|  | class MultiCRTMachine: public CRTMachine::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine | ||||||
|  | 			uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that | ||||||
|  | 			are necessary to bridge the gap between one machine and the next. | ||||||
|  | 		*/ | ||||||
|  | 		void did_change_machine_order(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provides a mechanism by which a delegate can be informed each time a call to run_for has | ||||||
|  | 			been received. | ||||||
|  | 		*/ | ||||||
|  | 		struct Delegate { | ||||||
|  | 			virtual void multi_crt_did_run_machines() = 0; | ||||||
|  | 		}; | ||||||
|  | 		/// Sets @c delegate as the receiver of delegate messages. | ||||||
|  | 		void set_delegate(Delegate *delegate) { | ||||||
|  | 			delegate_ = delegate; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Below is the standard CRTMachine::Machine interface; see there for documentation. | ||||||
|  | 		void setup_output(float aspect_ratio) override; | ||||||
|  | 		void close_output() override; | ||||||
|  | 		Outputs::CRT::CRT *get_crt() override; | ||||||
|  | 		Outputs::Speaker::Speaker *get_speaker() override; | ||||||
|  | 		void run_for(Time::Seconds duration) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void run_for(const Cycles cycles) override {} | ||||||
|  | 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | ||||||
|  | 		std::mutex &machines_mutex_; | ||||||
|  | 		std::vector<Concurrency::AsyncTaskQueue> queues_; | ||||||
|  | 		MultiSpeaker *speaker_ = nullptr; | ||||||
|  | 		Delegate *delegate_ = nullptr; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Performs a parallel for operation across all machines, performing the supplied | ||||||
|  | 			function on each and returning only once all applications have completed. | ||||||
|  |  | ||||||
|  | 			No guarantees are extended as to which thread operations will occur on. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Performs a serial for operation across all machines, performing the supplied | ||||||
|  | 			function on each on the calling thread. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_serial(const std::function<void(::CRTMachine::Machine *)> &); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* MultiCRTMachine_hpp */ | ||||||
| @@ -0,0 +1,64 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurable.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiConfigurable.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		Configurable::Device *device = machine->configurable_device(); | ||||||
|  | 		if(device) devices_.push_back(device); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() { | ||||||
|  | 	std::vector<std::unique_ptr<Configurable::Option>> options; | ||||||
|  |  | ||||||
|  | 	// Produce the list of unique options. | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options(); | ||||||
|  | 		for(auto &option : device_options) { | ||||||
|  | 			if(std::find(options.begin(), options.end(), option) == options.end()) { | ||||||
|  | 				options.push_back(std::move(option)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return options; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) { | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		device->set_selections(selection_by_option); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Configurable::SelectionSet MultiConfigurable::get_accurate_selections() { | ||||||
|  | 	Configurable::SelectionSet set; | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		Configurable::SelectionSet device_set = device->get_accurate_selections(); | ||||||
|  | 		for(auto &selection : device_set) { | ||||||
|  | 			set.insert(std::move(selection)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return set; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() { | ||||||
|  | 	Configurable::SelectionSet set; | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		Configurable::SelectionSet device_set = device->get_user_friendly_selections(); | ||||||
|  | 		for(auto &selection : device_set) { | ||||||
|  | 			set.insert(std::move(selection)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return set; | ||||||
|  | } | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurable.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiConfigurable_hpp | ||||||
|  | #define MultiConfigurable_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the configurable interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | class MultiConfigurable: public Configurable::Device { | ||||||
|  | 	public: | ||||||
|  | 		MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard Configurable::Device interface; see there for documentation. | ||||||
|  | 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override; | ||||||
|  | 		void set_selections(const Configurable::SelectionSet &selection_by_option) override; | ||||||
|  | 		Configurable::SelectionSet get_accurate_selections() override; | ||||||
|  | 		Configurable::SelectionSet get_user_friendly_selections() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<Configurable::Device *> devices_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiConfigurable_hpp */ | ||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurationTarget.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiConfigurationTarget.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		ConfigurationTarget::Machine *configuration_target = machine->configuration_target(); | ||||||
|  | 		if(configuration_target) targets_.push_back(configuration_target); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) { | ||||||
|  | 	bool inserted = false; | ||||||
|  | 	for(const auto &target : targets_) { | ||||||
|  | 		inserted |= target->insert_media(media); | ||||||
|  | 	} | ||||||
|  | 	return inserted; | ||||||
|  | } | ||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurationTarget.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiConfigurationTarget_hpp | ||||||
|  | #define MultiConfigurationTarget_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/ConfigurationTarget.hpp" | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the configuration target interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | struct MultiConfigurationTarget: public ConfigurationTarget::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard ConfigurationTarget::Machine interface; see there for documentation. | ||||||
|  | 		void configure_as_target(const Analyser::Static::Target *target) override; | ||||||
|  | 		bool insert_media(const Analyser::Static::Media &media) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<ConfigurationTarget::Machine *> targets_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiConfigurationTarget_hpp */ | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | // | ||||||
|  | //  MultiJoystickMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiJoystickMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | class MultiJoystick: public Inputs::Joystick { | ||||||
|  | 	public: | ||||||
|  | 		MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) { | ||||||
|  | 			for(const auto &machine: machines) { | ||||||
|  | 				const auto &joysticks = machine->get_joysticks(); | ||||||
|  | 				if(joysticks.size() >= index) { | ||||||
|  | 					joysticks_.push_back(joysticks[index].get()); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<DigitalInput> get_inputs() override { | ||||||
|  | 			std::vector<DigitalInput> inputs; | ||||||
|  |  | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				std::vector<DigitalInput> joystick_inputs = joystick->get_inputs(); | ||||||
|  | 				for(const auto &input: joystick_inputs) { | ||||||
|  | 					if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) { | ||||||
|  | 						inputs.push_back(input); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return inputs; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->set_digital_input(digital_input, is_active); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		void reset_all_inputs() override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->reset_all_inputs(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<Inputs::Joystick *> joysticks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	std::size_t total_joysticks = 0; | ||||||
|  | 	std::vector<JoystickMachine::Machine *> joystick_machines; | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); | ||||||
|  | 		if(joystick_machine) { | ||||||
|  | 			joystick_machines.push_back(joystick_machine); | ||||||
|  | 			total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size()); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | 	for(std::size_t index = 0; index < total_joysticks; ++index) { | ||||||
|  | 		joysticks_.emplace_back(new MultiJoystick(joystick_machines, index)); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() { | ||||||
|  | 	return joysticks_; | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | // | ||||||
|  | //  MultiJoystickMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiJoystickMachine_hpp | ||||||
|  | #define MultiJoystickMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the joystick machine interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | class MultiJoystickMachine: public JoystickMachine::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard JoystickMachine::Machine interface; see there for documentation. | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiJoystickMachine_hpp */ | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | //  MultiKeyboardMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiKeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine(); | ||||||
|  | 		if(keyboard_machine) machines_.push_back(keyboard_machine); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::clear_all_keys() { | ||||||
|  | 	for(const auto &machine: machines_) { | ||||||
|  | 		machine->clear_all_keys(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) { | ||||||
|  |     for(const auto &machine: machines_) { | ||||||
|  |         machine->set_key_state(key, is_pressed); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::type_string(const std::string &string) { | ||||||
|  |     for(const auto &machine: machines_) { | ||||||
|  |         machine->type_string(string); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) { | ||||||
|  |     for(const auto &machine: machines_) { | ||||||
|  | 		uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key); | ||||||
|  | 		if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | // | ||||||
|  | //  MultiKeyboardMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiKeyboardMachine_hpp | ||||||
|  | #define MultiKeyboardMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  | #include "../../../../Machines/KeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the keyboard machine interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | class MultiKeyboardMachine: public KeyboardMachine::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard KeyboardMachine::Machine interface; see there for documentation. | ||||||
|  | 		void clear_all_keys() override; | ||||||
|  | 		void set_key_state(uint16_t key, bool is_pressed) override; | ||||||
|  | 		void type_string(const std::string &) override; | ||||||
|  | 		void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<::KeyboardMachine::Machine *> machines_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiKeyboardMachine_hpp */ | ||||||
| @@ -0,0 +1,76 @@ | |||||||
|  | // | ||||||
|  | //  MultiSpeaker.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 18/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiSpeaker.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	std::vector<Outputs::Speaker::Speaker *> speakers; | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker(); | ||||||
|  | 		if(speaker) speakers.push_back(speaker); | ||||||
|  | 	} | ||||||
|  | 	if(speakers.empty()) return nullptr; | ||||||
|  |  | ||||||
|  | 	return new MultiSpeaker(speakers); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers) : | ||||||
|  | 	speakers_(speakers), front_speaker_(speakers.front()) { | ||||||
|  | 	for(const auto &speaker: speakers_) { | ||||||
|  | 		speaker->set_delegate(this); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) { | ||||||
|  | 	float ideal = 0.0f; | ||||||
|  | 	for(const auto &speaker: speakers_) { | ||||||
|  | 		ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ideal / static_cast<float>(speakers_.size()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) { | ||||||
|  | 	for(const auto &speaker: speakers_) { | ||||||
|  | 		speaker->set_output_rate(cycles_per_second, buffer_size); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) { | ||||||
|  | 	delegate_ = delegate; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { | ||||||
|  | 	if(!delegate_) return; | ||||||
|  | 	{ | ||||||
|  | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 		if(speaker != front_speaker_) return; | ||||||
|  | 	} | ||||||
|  | 	delegate_->speaker_did_complete_samples(this, buffer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) { | ||||||
|  | 	if(!delegate_) return; | ||||||
|  | 	{ | ||||||
|  | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 		if(speaker != front_speaker_) return; | ||||||
|  | 	} | ||||||
|  | 	delegate_->speaker_did_change_input_clock(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { | ||||||
|  | 	{ | ||||||
|  | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 		front_speaker_ = machine->crt_machine()->get_speaker(); | ||||||
|  | 	} | ||||||
|  | 	if(delegate_) { | ||||||
|  | 		delegate_->speaker_did_change_input_clock(this); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,59 @@ | |||||||
|  | // | ||||||
|  | //  MultiSpeaker.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 18/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiSpeaker_hpp | ||||||
|  | #define MultiSpeaker_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  | #include "../../../../Outputs/Speaker/Speaker.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order | ||||||
|  | 	transparently to connect a single caller to multiple destinations. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; expects the owner to keep it | ||||||
|  | 	abreast of the current frontmost machine. | ||||||
|  | */ | ||||||
|  | class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Provides a construction mechanism that may return nullptr, in the case that all included | ||||||
|  | 			machines return nullptr as their speaker. | ||||||
|  | 		*/ | ||||||
|  | 		static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		/// This class requires the caller to nominate changes in the frontmost machine. | ||||||
|  | 		void set_new_front_machine(::Machine::DynamicMachine *machine); | ||||||
|  |  | ||||||
|  | 		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. | ||||||
|  | 		float get_ideal_clock_rate_in_range(float minimum, float maximum) override; | ||||||
|  | 		void set_output_rate(float cycles_per_second, int buffer_size) override; | ||||||
|  | 		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override; | ||||||
|  | 		void speaker_did_change_input_clock(Speaker *speaker) override; | ||||||
|  | 		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers); | ||||||
|  |  | ||||||
|  | 		std::vector<Outputs::Speaker::Speaker *> speakers_; | ||||||
|  | 		Outputs::Speaker::Speaker *front_speaker_ = nullptr; | ||||||
|  | 		Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr; | ||||||
|  | 		std::mutex front_speaker_mutex_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiSpeaker_hpp */ | ||||||
							
								
								
									
										109
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | // | ||||||
|  | //  MultiMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 28/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | ||||||
|  | 	machines_(std::move(machines)), | ||||||
|  | 	configurable_(machines_), | ||||||
|  | 	configuration_target_(machines_), | ||||||
|  | 	crt_machine_(machines_, machines_mutex_), | ||||||
|  | 	joystick_machine_(machines), | ||||||
|  | 	keyboard_machine_(machines_) { | ||||||
|  | 	crt_machine_.set_delegate(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ConfigurationTarget::Machine *MultiMachine::configuration_target() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->configuration_target(); | ||||||
|  | 	} else { | ||||||
|  | 		return &configuration_target_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CRTMachine::Machine *MultiMachine::crt_machine() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->crt_machine(); | ||||||
|  | 	} else { | ||||||
|  | 		return &crt_machine_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | JoystickMachine::Machine *MultiMachine::joystick_machine() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->joystick_machine(); | ||||||
|  | 	} else { | ||||||
|  | 		return &joystick_machine_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | KeyboardMachine::Machine *MultiMachine::keyboard_machine() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->keyboard_machine(); | ||||||
|  | 	} else { | ||||||
|  | 		return &keyboard_machine_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Configurable::Device *MultiMachine::configurable_device() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->configurable_device(); | ||||||
|  | 	} else { | ||||||
|  | 		return &configurable_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) { | ||||||
|  | 	return | ||||||
|  | 		(machines.front()->crt_machine()->get_confidence() > 0.9f) || | ||||||
|  | 		(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiMachine::multi_crt_did_run_machines() { | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | #ifdef DEBUG | ||||||
|  | 	for(const auto &machine: machines_) { | ||||||
|  | 		CRTMachine::Machine *crt = machine->crt_machine(); | ||||||
|  | 		printf("%0.2f ", crt->get_confidence()); | ||||||
|  | 		crt->print_type(); | ||||||
|  | 		printf("; "); | ||||||
|  | 	} | ||||||
|  | 	printf("\n"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | 	DynamicMachine *front = machines_.front().get(); | ||||||
|  | 	std::stable_sort(machines_.begin(), machines_.end(), | ||||||
|  |         [] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){ | ||||||
|  | 		    CRTMachine::Machine *lhs_crt = lhs->crt_machine(); | ||||||
|  | 		    CRTMachine::Machine *rhs_crt = rhs->crt_machine(); | ||||||
|  | 		    return lhs_crt->get_confidence() > rhs_crt->get_confidence(); | ||||||
|  | 	    }); | ||||||
|  |  | ||||||
|  | 	if(machines_.front().get() != front) { | ||||||
|  | 		crt_machine_.did_change_machine_order(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(would_collapse(machines_)) { | ||||||
|  | 		pick_first(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiMachine::pick_first() { | ||||||
|  | 	has_picked_ = true; | ||||||
|  | //	machines_.erase(machines_.begin() + 1, machines_.end()); | ||||||
|  | 	// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to | ||||||
|  | 	// request a close_output while the context is active. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void *MultiMachine::raw_pointer() { | ||||||
|  | 	return nullptr; | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | // | ||||||
|  | //  MultiMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 28/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiMachine_hpp | ||||||
|  | #define MultiMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "Implementation/MultiConfigurable.hpp" | ||||||
|  | #include "Implementation/MultiConfigurationTarget.hpp" | ||||||
|  | #include "Implementation/MultiCRTMachine.hpp" | ||||||
|  | #include "Implementation/MultiJoystickMachine.hpp" | ||||||
|  | #include "Implementation/MultiKeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides the same interface as to a single machine, while multiplexing all | ||||||
|  | 	underlying calls to an array of real dynamic machines. | ||||||
|  |  | ||||||
|  | 	Calls to crt_machine->get_crt will return that for the frontmost machine; | ||||||
|  | 	anything installed as the speaker's delegate will similarly receive | ||||||
|  | 	feedback only from that machine. | ||||||
|  |  | ||||||
|  | 	Following each crt_machine->run_for, reorders the supplied machines by | ||||||
|  | 	confidence. | ||||||
|  |  | ||||||
|  | 	If confidence for any machine becomes disproportionately low compared to | ||||||
|  | 	the others in the set, that machine stops running. | ||||||
|  | */ | ||||||
|  | class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Allows a potential MultiMachine creator to enquire as to whether there's any benefit in | ||||||
|  | 			requesting this class as a proxy. | ||||||
|  |  | ||||||
|  | 			@returns @c true if the multimachine would discard all but the first machine in this list; | ||||||
|  | 				@c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines); | ||||||
|  | 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | ||||||
|  |  | ||||||
|  | 		ConfigurationTarget::Machine *configuration_target() override; | ||||||
|  | 		CRTMachine::Machine *crt_machine() override; | ||||||
|  | 		JoystickMachine::Machine *joystick_machine() override; | ||||||
|  | 		KeyboardMachine::Machine *keyboard_machine() override; | ||||||
|  | 		Configurable::Device *configurable_device() override; | ||||||
|  | 		void *raw_pointer() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void multi_crt_did_run_machines() override; | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<DynamicMachine>> machines_; | ||||||
|  | 		std::mutex machines_mutex_; | ||||||
|  |  | ||||||
|  | 		MultiConfigurable configurable_; | ||||||
|  | 		MultiConfigurationTarget configuration_target_; | ||||||
|  | 		MultiCRTMachine crt_machine_; | ||||||
|  | 		MultiJoystickMachine joystick_machine_; | ||||||
|  | 		MultiKeyboardMachine keyboard_machine_; | ||||||
|  |  | ||||||
|  | 		void pick_first(); | ||||||
|  | 		bool has_picked_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiMachine_hpp */ | ||||||
							
								
								
									
										27
									
								
								Analyser/Machines.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Machines.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  Machines.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 24/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_h | ||||||
|  | #define Machines_h | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  |  | ||||||
|  | enum class Machine { | ||||||
|  | 	AmstradCPC, | ||||||
|  | 	Atari2600, | ||||||
|  | 	ColecoVision, | ||||||
|  | 	Electron, | ||||||
|  | 	MSX, | ||||||
|  | 	Oric, | ||||||
|  | 	Vic20, | ||||||
|  | 	ZX8081 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Machines_h */ | ||||||
| @@ -7,14 +7,16 @@ | |||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "../../Storage/Disk/Controller/DiskController.hpp" | 
 | ||||||
| #include "../../Storage/Disk/Encodings/MFM/Parser.hpp" | #include "../../../Storage/Disk/Controller/DiskController.hpp" | ||||||
| #include "../../NumberTheory/CRC.hpp" | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
|  | #include "../../../NumberTheory/CRC.hpp" | ||||||
|  | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Acorn; | using namespace Analyser::Static::Acorn; | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||||
| 	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | 	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | ||||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||||
| 	Storage::Encodings::MFM::Parser parser(false, disk); | 	Storage::Encodings::MFM::Parser parser(false, disk); | ||||||
| @@ -41,9 +43,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | |||||||
| 		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break; | 		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// DFS files are stored contiguously, and listed in descending order of distance from track 0.
 | 	for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) { | ||||||
| 	// So iterating backwards implies the least amount of seeking.
 |  | ||||||
| 	for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) { |  | ||||||
| 		File new_file; | 		File new_file; | ||||||
| 		char name[10]; | 		char name[10]; | ||||||
| 		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]); | 		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]); | ||||||
| @@ -54,7 +54,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | |||||||
| 
 | 
 | ||||||
| 		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)); | 		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); | 		int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8); | ||||||
| 		new_file.data.reserve(static_cast<size_t>(data_length)); | 		new_file.data.reserve(static_cast<std::size_t>(data_length)); | ||||||
| 
 | 
 | ||||||
| 		if(start_sector < 2) continue; | 		if(start_sector < 2) continue; | ||||||
| 		while(data_length > 0) { | 		while(data_length > 0) { | ||||||
| @@ -69,12 +69,12 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | |||||||
| 			new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector); | 			new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector); | ||||||
| 			data_length -= length_from_sector; | 			data_length -= length_from_sector; | ||||||
| 		} | 		} | ||||||
| 		if(!data_length) catalogue->files.push_front(new_file); | 		if(!data_length) catalogue->files.push_back(new_file); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return catalogue; | 	return catalogue; | ||||||
| } | } | ||||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
| 
 | 
 | ||||||
| @@ -10,15 +10,16 @@ | |||||||
| #define StaticAnalyser_Acorn_Disk_hpp | #define StaticAnalyser_Acorn_Disk_hpp | ||||||
| 
 | 
 | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| /// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
 | /// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
 | ||||||
| struct Catalogue { | struct Catalogue { | ||||||
| 	std::string name; | 	std::string name; | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	enum class BootOption { | 	enum class BootOption { | ||||||
| 		None, | 		None, | ||||||
| 		LoadBOOT, | 		LoadBOOT, | ||||||
| @@ -30,6 +31,7 @@ struct Catalogue { | |||||||
| std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk); | std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk); | ||||||
| std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk); | std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -9,12 +9,12 @@ | |||||||
| #ifndef StaticAnalyser_Acorn_File_hpp | #ifndef StaticAnalyser_Acorn_File_hpp | ||||||
| #define StaticAnalyser_Acorn_File_hpp | #define StaticAnalyser_Acorn_File_hpp | ||||||
| 
 | 
 | ||||||
| #include <list> |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| struct File { | struct File { | ||||||
| @@ -38,9 +38,10 @@ struct File { | |||||||
| 		std::vector<uint8_t> data; | 		std::vector<uint8_t> data; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	std::list<Chunk> chunks; | 	std::vector<Chunk> chunks; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -10,22 +10,23 @@ | |||||||
| 
 | 
 | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Acorn; | using namespace Analyser::Static::Acorn; | ||||||
| 
 | 
 | ||||||
| static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||||
| 		AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | 		AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
| 	std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges; | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges; | ||||||
| 
 | 
 | ||||||
| 	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) { | 	for(const auto &cartridge : cartridges) { | ||||||
| 		const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments(); | 		const auto &segments = cartridge->get_segments(); | ||||||
| 
 | 
 | ||||||
| 		// only one mapped item is allowed
 | 		// only one mapped item is allowed
 | ||||||
| 		if(segments.size() != 1) continue; | 		if(segments.size() != 1) continue; | ||||||
| 
 | 
 | ||||||
| 		// which must be 16 kb in size
 | 		// which must be 8 or 16 kb in size
 | ||||||
| 		Storage::Cartridge::Cartridge::Segment segment = segments.front(); | 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
| 		if(segment.data.size() != 0x4000) continue; | 		if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue; | ||||||
| 
 | 
 | ||||||
| 		// is a copyright string present?
 | 		// is a copyright string present?
 | ||||||
| 		uint8_t copyright_offset = segment.data[7]; | 		uint8_t copyright_offset = segment.data[7]; | ||||||
| @@ -56,21 +57,21 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 	return acorn_cartridges; | 	return acorn_cartridges; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) { | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::Electron; | 	target->machine = Machine::Electron; | ||||||
| 	target.probability = 1.0; // TODO: a proper estimation
 | 	target->confidence = 0.5; // TODO: a proper estimation
 | ||||||
| 	target.acorn.has_dfs = false; | 	target->has_dfs = false; | ||||||
| 	target.acorn.has_adfs = false; | 	target->has_adfs = false; | ||||||
| 	target.acorn.should_shift_restart = false; | 	target->should_shift_restart = false; | ||||||
| 
 | 
 | ||||||
| 	// strip out inappropriate cartridges
 | 	// strip out inappropriate cartridges
 | ||||||
| 	target.media.cartridges = AcornCartridgesFrom(media.cartridges); | 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | ||||||
| 
 | 
 | ||||||
| 	// if there are any tapes, attempt to get data from the first
 | 	// if there are any tapes, attempt to get data from the first
 | ||||||
| 	if(media.tapes.size() > 0) { | 	if(media.tapes.size() > 0) { | ||||||
| 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | ||||||
| 		std::list<File> files = GetFiles(tape); | 		std::vector<File> files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 
 | 
 | ||||||
| 		// continue if there are any files
 | 		// continue if there are any files
 | ||||||
| @@ -82,9 +83,9 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de | |||||||
| 
 | 
 | ||||||
| 			// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
 | 			// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
 | ||||||
| 			// so that's also justification to *RUN
 | 			// so that's also justification to *RUN
 | ||||||
| 			size_t pointer = 0; | 			std::size_t pointer = 0; | ||||||
| 			uint8_t *data = &files.front().data[0]; | 			uint8_t *data = &files.front().data[0]; | ||||||
| 			size_t data_size = files.front().data.size(); | 			std::size_t data_size = files.front().data.size(); | ||||||
| 			while(1) { | 			while(1) { | ||||||
| 				if(pointer >= data_size-1 || data[pointer] != 13) { | 				if(pointer >= data_size-1 || data[pointer] != 13) { | ||||||
| 					is_basic = false; | 					is_basic = false; | ||||||
| @@ -96,9 +97,9 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de | |||||||
| 
 | 
 | ||||||
| 			// Inspect first file. If it's protected or doesn't look like BASIC
 | 			// Inspect first file. If it's protected or doesn't look like BASIC
 | ||||||
| 			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | 			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | ||||||
| 			target.loadingCommand = is_basic ? "CHAIN\"\"\n" : "*RUN\n"; | 			target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n"; | ||||||
| 
 | 
 | ||||||
| 			target.media.tapes = media.tapes; | 			target->media.tapes = media.tapes; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -108,18 +109,19 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de | |||||||
| 		dfs_catalogue = GetDFSCatalogue(disk); | 		dfs_catalogue = GetDFSCatalogue(disk); | ||||||
| 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | ||||||
| 		if(dfs_catalogue || adfs_catalogue) { | 		if(dfs_catalogue || adfs_catalogue) { | ||||||
| 			target.media.disks = media.disks; | 			target->media.disks = media.disks; | ||||||
| 			target.acorn.has_dfs = !!dfs_catalogue; | 			target->has_dfs = !!dfs_catalogue; | ||||||
| 			target.acorn.has_adfs = !!adfs_catalogue; | 			target->has_adfs = !!adfs_catalogue; | ||||||
| 
 | 
 | ||||||
| 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | ||||||
| 			if(bootOption != Catalogue::BootOption::None) | 			if(bootOption != Catalogue::BootOption::None) | ||||||
| 				target.acorn.should_shift_restart = true; | 				target->should_shift_restart = true; | ||||||
| 			else | 			else | ||||||
| 				target.loadingCommand = "*CAT\n"; | 				target->loading_command = "*CAT\n"; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size()) | 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) { | ||||||
| 		destination.push_back(target); | 		destination.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -9,10 +9,11 @@ | |||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| 
 | 
 | ||||||
| #include <deque> | #include <deque> | ||||||
| #include "../../NumberTheory/CRC.hpp" |  | ||||||
| #include "../../Storage/Tape/Parsers/Acorn.hpp" |  | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Acorn; | #include "../../../NumberTheory/CRC.hpp" | ||||||
|  | #include "../../../Storage/Tape/Parsers/Acorn.hpp" | ||||||
|  | 
 | ||||||
|  | using namespace Analyser::Static::Acorn; | ||||||
| 
 | 
 | ||||||
| static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) { | static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) { | ||||||
| 	std::unique_ptr<File::Chunk> new_chunk(new File::Chunk); | 	std::unique_ptr<File::Chunk> new_chunk(new File::Chunk); | ||||||
| @@ -38,7 +39,7 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage:: | |||||||
| 
 | 
 | ||||||
| 	// read out name
 | 	// read out name
 | ||||||
| 	char name[11]; | 	char name[11]; | ||||||
| 	size_t name_ptr = 0; | 	std::size_t name_ptr = 0; | ||||||
| 	while(!tape->is_at_end() && name_ptr < sizeof(name)) { | 	while(!tape->is_at_end() && name_ptr < sizeof(name)) { | ||||||
| 		name[name_ptr] = (char)parser.get_next_byte(tape); | 		name[name_ptr] = (char)parser.get_next_byte(tape); | ||||||
| 		if(!name[name_ptr]) break; | 		if(!name[name_ptr]) break; | ||||||
| @@ -118,7 +119,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) { | |||||||
| 	return file; | 	return file; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	Storage::Tape::Acorn::Parser parser; | 	Storage::Tape::Acorn::Parser parser; | ||||||
| 
 | 
 | ||||||
| 	// populate chunk list
 | 	// populate chunk list
 | ||||||
| @@ -131,7 +132,7 @@ std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::T | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// decompose into file list
 | 	// decompose into file list
 | ||||||
| 	std::list<File> file_list; | 	std::vector<File> file_list; | ||||||
| 
 | 
 | ||||||
| 	while(chunk_list.size()) { | 	while(chunk_list.size()) { | ||||||
| 		std::unique_ptr<File> next_file = GetNextFile(chunk_list); | 		std::unique_ptr<File> next_file = GetNextFile(chunk_list); | ||||||
| @@ -12,13 +12,15 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| 
 | 
 | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										28
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Acorn_Target_h | ||||||
|  | #define Analyser_Static_Acorn_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Acorn { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool has_adfs = false; | ||||||
|  | 	bool has_dfs = false; | ||||||
|  | 	bool should_shift_restart = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Acorn_Target_h */ | ||||||
| @@ -8,13 +8,18 @@ | |||||||
| 
 | 
 | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Disk/Parsers/CPM.hpp" | #include <algorithm> | ||||||
| #include "../../Storage/Disk/Encodings/MFM/Parser.hpp" | #include <cstring> | ||||||
|  | 
 | ||||||
|  | #include "Target.hpp" | ||||||
|  | 
 | ||||||
|  | #include "../../../Storage/Disk/Parsers/CPM.hpp" | ||||||
|  | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
| 
 | 
 | ||||||
| static bool strcmp_insensitive(const char *a, const char *b) { | static bool strcmp_insensitive(const char *a, const char *b) { | ||||||
| 	if(strlen(a) != strlen(b)) return false; | 	if(std::strlen(a) != std::strlen(b)) return false; | ||||||
| 	while(*a) { | 	while(*a) { | ||||||
| 		if(tolower(*a) != towlower(*b)) return false; | 		if(std::tolower(*a) != std::tolower(*b)) return false; | ||||||
| 		a++; | 		a++; | ||||||
| 		b++; | 		b++; | ||||||
| 	} | 	} | ||||||
| @@ -55,7 +60,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) { | |||||||
| 
 | 
 | ||||||
| static void InspectCatalogue( | static void InspectCatalogue( | ||||||
| 	const Storage::Disk::CPM::Catalogue &catalogue, | 	const Storage::Disk::CPM::Catalogue &catalogue, | ||||||
| 	StaticAnalyser::Target &target) { | 	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) { | ||||||
| 
 | 
 | ||||||
| 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | ||||||
| 	candidate_files.reserve(catalogue.files.size()); | 	candidate_files.reserve(catalogue.files.size()); | ||||||
| @@ -92,7 +97,7 @@ static void InspectCatalogue( | |||||||
| 
 | 
 | ||||||
| 	// If there's just one file, run that.
 | 	// If there's just one file, run that.
 | ||||||
| 	if(candidate_files.size() == 1) { | 	if(candidate_files.size() == 1) { | ||||||
| 		target.loadingCommand = RunCommandFor(*candidate_files[0]); | 		target->loading_command = RunCommandFor(*candidate_files[0]); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -101,10 +106,10 @@ static void InspectCatalogue( | |||||||
| 	int basic_files = 0; | 	int basic_files = 0; | ||||||
| 	int implicit_suffixed_files = 0; | 	int implicit_suffixed_files = 0; | ||||||
| 
 | 
 | ||||||
| 	size_t last_basic_file = 0; | 	std::size_t last_basic_file = 0; | ||||||
| 	size_t last_implicit_suffixed_file = 0; | 	std::size_t last_implicit_suffixed_file = 0; | ||||||
| 
 | 
 | ||||||
| 	for(size_t c = 0; c < candidate_files.size(); c++) { | 	for(std::size_t c = 0; c < candidate_files.size(); c++) { | ||||||
| 		// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
 | 		// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
 | ||||||
| 		if(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ") | 		if(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ") | ||||||
| 			continue; | 			continue; | ||||||
| @@ -122,16 +127,16 @@ static void InspectCatalogue( | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if(basic_files == 1 || implicit_suffixed_files == 1) { | 	if(basic_files == 1 || implicit_suffixed_files == 1) { | ||||||
| 		size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file; | 		std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file; | ||||||
| 		target.loadingCommand = RunCommandFor(*candidate_files[selected_file]); | 		target->loading_command = RunCommandFor(*candidate_files[selected_file]); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// One more guess: if only one remaining candidate file has a different name than the others,
 | 	// One more guess: if only one remaining candidate file has a different name than the others,
 | ||||||
| 	// assume it is intended to stand out.
 | 	// assume it is intended to stand out.
 | ||||||
| 	std::map<std::string, int> name_counts; | 	std::map<std::string, int> name_counts; | ||||||
| 	std::map<std::string, size_t> indices_by_name; | 	std::map<std::string, std::size_t> indices_by_name; | ||||||
| 	size_t index = 0; | 	std::size_t index = 0; | ||||||
| 	for(auto file : candidate_files) { | 	for(auto file : candidate_files) { | ||||||
| 		name_counts[file->name]++; | 		name_counts[file->name]++; | ||||||
| 		indices_by_name[file->name] = index; | 		indices_by_name[file->name] = index; | ||||||
| @@ -140,24 +145,24 @@ static void InspectCatalogue( | |||||||
| 	if(name_counts.size() == 2) { | 	if(name_counts.size() == 2) { | ||||||
| 		for(auto &pair : name_counts) { | 		for(auto &pair : name_counts) { | ||||||
| 			if(pair.second == 1) { | 			if(pair.second == 1) { | ||||||
| 				target.loadingCommand = RunCommandFor(*candidate_files[indices_by_name[pair.first]]); | 				target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Desperation.
 | 	// Desperation.
 | ||||||
| 	target.loadingCommand = "cat\n"; | 	target->loading_command = "cat\n"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) { | static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) { | ||||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
| 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | ||||||
| 	if(boot_sector != nullptr && !boot_sector->samples.empty()) { | 	if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) { | ||||||
| 		// Check that the first 64 bytes of the sector aren't identical; if they are then probably
 | 		// Check that the first 64 bytes of the sector aren't identical; if they are then probably
 | ||||||
| 		// this disk was formatted and the filler byte never replaced.
 | 		// this disk was formatted and the filler byte never replaced.
 | ||||||
| 		bool matched = true; | 		bool matched = true; | ||||||
| 		for(size_t c = 1; c < 64; c++) { | 		for(std::size_t c = 1; c < 64; c++) { | ||||||
| 			if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) { | 			if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) { | ||||||
| 				matched = false; | 				matched = false; | ||||||
| 				break; | 				break; | ||||||
| @@ -166,7 +171,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St | |||||||
| 
 | 
 | ||||||
| 		// This is a system disk, then launch it as though it were CP/M.
 | 		// This is a system disk, then launch it as though it were CP/M.
 | ||||||
| 		if(!matched) { | 		if(!matched) { | ||||||
| 			target.loadingCommand = "|cpm\n"; | 			target->loading_command = "|cpm\n"; | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -174,24 +179,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) { | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::AmstradCPC; | 	target->machine = Machine::AmstradCPC; | ||||||
| 	target.probability = 1.0; | 	target->confidence = 0.5; | ||||||
| 	target.media.disks = media.disks; |  | ||||||
| 	target.media.tapes = media.tapes; |  | ||||||
| 	target.media.cartridges = media.cartridges; |  | ||||||
| 
 | 
 | ||||||
| 	target.amstradcpc.model = AmstradCPCModel::CPC6128; | 	target->model = Target::Model::CPC6128; | ||||||
|  | 
 | ||||||
|  | 	if(!media.tapes.empty()) { | ||||||
|  | 		// TODO: which of these are actually potentially CPC tapes?
 | ||||||
|  | 		target->media.tapes = media.tapes; | ||||||
| 
 | 
 | ||||||
| 	if(!target.media.tapes.empty()) { |  | ||||||
| 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | ||||||
| 		// enter and responding to the follow-on prompt to press a key, so just type for
 | 		// enter and responding to the follow-on prompt to press a key, so just type for
 | ||||||
| 		// a while. Yuck!
 | 		// a while. Yuck!
 | ||||||
| 		target.loadingCommand = "|tape\nrun\"\n1234567890"; | 		target->loading_command = "|tape\nrun\"\n1234567890"; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(!target.media.disks.empty()) { | 	if(!media.disks.empty()) { | ||||||
| 		Storage::Disk::CPM::ParameterBlock data_format; | 		Storage::Disk::CPM::ParameterBlock data_format; | ||||||
| 		data_format.sectors_per_track = 9; | 		data_format.sectors_per_track = 9; | ||||||
| 		data_format.tracks = 40; | 		data_format.tracks = 40; | ||||||
| @@ -200,11 +205,6 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target | |||||||
| 		data_format.catalogue_allocation_bitmap = 0xc000; | 		data_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 		data_format.reserved_tracks = 0; | 		data_format.reserved_tracks = 0; | ||||||
| 
 | 
 | ||||||
| 		std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), data_format); |  | ||||||
| 		if(data_catalogue) { |  | ||||||
| 			InspectCatalogue(*data_catalogue, target); |  | ||||||
| 		} else { |  | ||||||
| 			if(!CheckBootSector(target.media.disks.front(), target)) { |  | ||||||
| 		Storage::Disk::CPM::ParameterBlock system_format; | 		Storage::Disk::CPM::ParameterBlock system_format; | ||||||
| 		system_format.sectors_per_track = 9; | 		system_format.sectors_per_track = 9; | ||||||
| 		system_format.tracks = 40; | 		system_format.tracks = 40; | ||||||
| @@ -213,13 +213,32 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target | |||||||
| 		system_format.catalogue_allocation_bitmap = 0xc000; | 		system_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 		system_format.reserved_tracks = 2; | 		system_format.reserved_tracks = 2; | ||||||
| 
 | 
 | ||||||
| 				std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), system_format); | 		for(const auto &disk: media.disks) { | ||||||
|  | 			// Check for an ordinary catalogue.
 | ||||||
|  | 			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format); | ||||||
|  | 			if(data_catalogue) { | ||||||
|  | 				InspectCatalogue(*data_catalogue, target); | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Failing that check for a boot sector.
 | ||||||
|  | 			if(CheckBootSector(disk, target)) { | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Failing that check for a system catalogue.
 | ||||||
|  | 			std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format); | ||||||
| 			if(system_catalogue) { | 			if(system_catalogue) { | ||||||
| 				InspectCatalogue(*system_catalogue, target); | 				InspectCatalogue(*system_catalogue, target); | ||||||
| 				} | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	destination.push_back(target); | 	// If any media survived, add the target.
 | ||||||
|  | 	if(!target->media.empty()) | ||||||
|  | 		destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										33
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_AmstradCPC_Target_h | ||||||
|  | #define Analyser_Static_AmstradCPC_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class Model { | ||||||
|  | 		CPC464, | ||||||
|  | 		CPC664, | ||||||
|  | 		CPC6128 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Model model = Model::CPC464; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_AmstradCPC_Target_h */ | ||||||
| @@ -8,11 +8,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../Disassembler/Disassembler6502.hpp" | #include "Target.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Atari; | #include "../Disassembler/6502.hpp" | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | using namespace Analyser::Static::Atari; | ||||||
|  | 
 | ||||||
|  | static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | ||||||
| 	uint16_t entry_address, break_address; | 	uint16_t entry_address, break_address; | ||||||
| 
 | 
 | ||||||
| @@ -22,21 +24,21 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const | |||||||
| 	// a CommaVid start address needs to be outside of its RAM
 | 	// a CommaVid start address needs to be outside of its RAM
 | ||||||
| 	if(entry_address < 0x1800 || break_address < 0x1800) return; | 	if(entry_address < 0x1800 || break_address < 0x1800) return; | ||||||
| 
 | 
 | ||||||
| 	std::function<size_t(uint16_t address)> high_location_mapper = [](uint16_t address) { | 	std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) { | ||||||
| 		address &= 0x1fff; | 		address &= 0x1fff; | ||||||
| 		return static_cast<size_t>(address - 0x1800); | 		return static_cast<std::size_t>(address - 0x1800); | ||||||
| 	}; | 	}; | ||||||
| 	StaticAnalyser::MOS6502::Disassembly high_location_disassembly = | 	Analyser::Static::MOS6502::Disassembly high_location_disassembly = | ||||||
| 		StaticAnalyser::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address}); | 		Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address}); | ||||||
| 
 | 
 | ||||||
| 	// assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 	// assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | ||||||
| 	// large amounts of memory
 | 	// large amounts of memory
 | ||||||
| 	bool has_wide_area_store = false; | 	bool has_wide_area_store = false; | ||||||
| 	for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) { | 	for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) { | ||||||
| 		if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA) { | 		if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) { | ||||||
| 			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect; | 			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect; | ||||||
| 			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX; | 			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX; | ||||||
| 			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY; | 			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY; | ||||||
| 
 | 
 | ||||||
| 			if(has_wide_area_store) break; | 			if(has_wide_area_store) break; | ||||||
| 		} | 		} | ||||||
| @@ -46,10 +48,10 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const | |||||||
| 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | ||||||
| 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | ||||||
| 	// attempts to modify itself but it probably doesn't
 | 	// attempts to modify itself but it probably doesn't
 | ||||||
| 	if(has_wide_area_store) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid; | 	if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | ||||||
| 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | ||||||
| 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | ||||||
| @@ -58,12 +60,12 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const | |||||||
| 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | ||||||
| 		segment.data[0] == 0x78 | 		segment.data[0] == 0x78 | ||||||
| 	) { | 	) { | ||||||
| 		target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ActivisionStack; | 		target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack; | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// make an assumption that this is the Atari paging model
 | 	// make an assumption that this is the Atari paging model
 | ||||||
| 	target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari8k; | 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k; | ||||||
| 
 | 
 | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -83,13 +85,13 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const | |||||||
| 		tigervision_access_count += masked_address == 0x3f; | 		tigervision_access_count += masked_address == 0x3f; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(parker_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ParkerBros; | 	if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros; | ||||||
| 	else if(tigervision_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision; | 	else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is the Atari paging model
 | 	// make an assumption that this is the Atari paging model
 | ||||||
| 	target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari16k; | 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k; | ||||||
| 
 | 
 | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -104,17 +106,17 @@ static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const | |||||||
| 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(mnetwork_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::MNetwork; | 	if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor64kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is a Tigervision if there is a write to 3F
 | 	// make an assumption that this is a Tigervision if there is a write to 3F
 | ||||||
| 	target.atari.paging_model = | 	target.paging_model = | ||||||
| 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | ||||||
| 			StaticAnalyser::Atari2600PagingModel::Tigervision : StaticAnalyser::Atari2600PagingModel::MegaBoy; | 			Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	if(segment.data.size() == 2048) { | 	if(segment.data.size() == 2048) { | ||||||
| 		DeterminePagingFor2kCartridge(target, segment); | 		DeterminePagingFor2kCartridge(target, segment); | ||||||
| 		return; | 		return; | ||||||
| @@ -125,29 +127,29 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St | |||||||
| 	entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8)); | 	entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8)); | ||||||
| 	break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8)); | 	break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8)); | ||||||
| 
 | 
 | ||||||
| 	std::function<size_t(uint16_t address)> address_mapper = [](uint16_t address) { | 	std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) { | ||||||
| 		if(!(address & 0x1000)) return static_cast<size_t>(-1); | 		if(!(address & 0x1000)) return static_cast<std::size_t>(-1); | ||||||
| 		return static_cast<size_t>(address & 0xfff); | 		return static_cast<std::size_t>(address & 0xfff); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end()); | 	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end()); | ||||||
| 	StaticAnalyser::MOS6502::Disassembly disassembly = StaticAnalyser::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address}); | 	Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address}); | ||||||
| 
 | 
 | ||||||
| 	switch(segment.data.size()) { | 	switch(segment.data.size()) { | ||||||
| 		case 8192: | 		case 8192: | ||||||
| 			DeterminePagingFor8kCartridge(target, segment, disassembly); | 			DeterminePagingFor8kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 10495: | 		case 10495: | ||||||
| 			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Pitfall2; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2; | ||||||
| 		break; | 		break; | ||||||
| 		case 12288: | 		case 12288: | ||||||
| 			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CBSRamPlus; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus; | ||||||
| 		break; | 		break; | ||||||
| 		case 16384: | 		case 16384: | ||||||
| 			DeterminePagingFor16kCartridge(target, segment, disassembly); | 			DeterminePagingFor16kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 32768: | 		case 32768: | ||||||
| 			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari32k; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k; | ||||||
| 		break; | 		break; | ||||||
| 		case 65536: | 		case 65536: | ||||||
| 			DeterminePagingFor64kCartridge(target, segment, disassembly); | 			DeterminePagingFor64kCartridge(target, segment, disassembly); | ||||||
| @@ -159,43 +161,43 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St | |||||||
| 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | ||||||
| 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | ||||||
| 	// next 128 bytes. So check for that.
 | 	// next 128 bytes. So check for that.
 | ||||||
| 	if(	target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::CBSRamPlus && | 	if(	target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus && | ||||||
| 		target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork) { | 		target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) { | ||||||
| 		bool has_superchip = true; | 		bool has_superchip = true; | ||||||
| 		for(size_t address = 0; address < 128; address++) { | 		for(std::size_t address = 0; address < 128; address++) { | ||||||
| 			if(segment.data[address] != segment.data[address+128]) { | 			if(segment.data[address] != segment.data[address+128]) { | ||||||
| 				has_superchip = false; | 				has_superchip = false; | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		target.atari.uses_superchip = has_superchip; | 		target.uses_superchip = has_superchip; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// check for a Tigervision or Tigervision-esque scheme
 | 	// check for a Tigervision or Tigervision-esque scheme
 | ||||||
| 	if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096) { | 	if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) { | ||||||
| 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | ||||||
| 		if(looks_like_tigervision) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision; | 		if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) { | ||||||
| 	// TODO: sanity checking; is this image really for an Atari 2600.
 | 	// TODO: sanity checking; is this image really for an Atari 2600?
 | ||||||
| 	Target target; | 	std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target); | ||||||
| 	target.machine = Target::Atari2600; | 	target->machine = Machine::Atari2600; | ||||||
| 	target.probability = 1.0; | 	target->confidence = 0.5; | ||||||
| 	target.media.cartridges = media.cartridges; | 	target->media.cartridges = media.cartridges; | ||||||
| 	target.atari.paging_model = Atari2600PagingModel::None; | 	target->paging_model = Analyser::Static::Atari::Target::PagingModel::None; | ||||||
| 	target.atari.uses_superchip = false; | 	target->uses_superchip = false; | ||||||
| 
 | 
 | ||||||
| 	// try to figure out the paging scheme
 | 	// try to figure out the paging scheme
 | ||||||
| 	if(!media.cartridges.empty()) { | 	if(!media.cartridges.empty()) { | ||||||
| 		const std::list<Storage::Cartridge::Cartridge::Segment> &segments = media.cartridges.front()->get_segments(); | 		const auto &segments = media.cartridges.front()->get_segments(); | ||||||
| 
 | 
 | ||||||
| 		if(segments.size() == 1) { | 		if(segments.size() == 1) { | ||||||
| 			const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | 			const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
| 			DeterminePagingForCartridge(target, segment); | 			DeterminePagingForCartridge(*target, segment); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	destination.push_back(target); | 	destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Atari { | namespace Atari { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										43
									
								
								Analyser/Static/Atari/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Analyser/Static/Atari/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Atari_Target_h | ||||||
|  | #define Analyser_Static_Atari_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Atari { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class PagingModel { | ||||||
|  | 		None, | ||||||
|  | 		CommaVid, | ||||||
|  | 		Atari8k, | ||||||
|  | 		Atari16k, | ||||||
|  | 		Atari32k, | ||||||
|  | 		ActivisionStack, | ||||||
|  | 		ParkerBros, | ||||||
|  | 		Tigervision, | ||||||
|  | 		CBSRamPlus, | ||||||
|  | 		MNetwork, | ||||||
|  | 		MegaBoy, | ||||||
|  | 		Pitfall2 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// TODO: shouldn't these be properties of the cartridge? | ||||||
|  | 	PagingModel paging_model = PagingModel::None; | ||||||
|  | 	bool uses_superchip = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Atari_Target_h */ | ||||||
							
								
								
									
										62
									
								
								Analyser/Static/Coleco/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Analyser/Static/Coleco/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||||
|  | 		ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
|  | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges; | ||||||
|  |  | ||||||
|  | 	for(const auto &cartridge : cartridges) { | ||||||
|  | 		const auto &segments = cartridge->get_segments(); | ||||||
|  |  | ||||||
|  | 		// only one mapped item is allowed | ||||||
|  | 		if(segments.size() != 1) continue; | ||||||
|  |  | ||||||
|  | 		// which must be 8, 12, 16, 24 or 32 kb in size | ||||||
|  | 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
|  | 		const std::size_t data_size = segment.data.size(); | ||||||
|  | 		const std::size_t overflow = data_size&8191; | ||||||
|  | 		if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue; | ||||||
|  | 		if(data_size < 8192) continue; | ||||||
|  |  | ||||||
|  | 		// the two bytes that will be first must be 0xaa and 0x55, either way around | ||||||
|  | 		auto *start = &segment.data[0]; | ||||||
|  | 		if((data_size & static_cast<std::size_t>(~8191)) > 32768) { | ||||||
|  | 			start = &segment.data[segment.data.size() - 16384]; | ||||||
|  | 		} | ||||||
|  | 		if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue; | ||||||
|  | 		if(start[0] == start[1]) continue; | ||||||
|  |  | ||||||
|  | 		// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768. | ||||||
|  | 		if(!overflow) { | ||||||
|  | 			coleco_cartridges.push_back(cartridge); | ||||||
|  | 		} else { | ||||||
|  | 			// Size down to a multiple of 8kb and apply the start address. | ||||||
|  | 			std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||||
|  |  | ||||||
|  | 			std::vector<uint8_t> truncated_data; | ||||||
|  | 			std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191; | ||||||
|  | 			truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); | ||||||
|  | 			output_segments.emplace_back(0x8000, truncated_data); | ||||||
|  |  | ||||||
|  | 			coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return coleco_cartridges; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
|  | 	std::unique_ptr<Target> target(new Target); | ||||||
|  | 	target->machine = Machine::ColecoVision; | ||||||
|  | 	target->confidence = 1.0f - 1.0f / 32768.0f; | ||||||
|  | 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | ||||||
|  | 	if(!target->media.empty()) | ||||||
|  | 		destination.push_back(std::move(target)); | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								Analyser/Static/Coleco/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Analyser/Static/Coleco/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp | ||||||
|  | #define StaticAnalyser_Coleco_StaticAnalyser_hpp | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Coleco { | ||||||
|  |  | ||||||
|  | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* StaticAnalyser_hpp */ | ||||||
| @@ -7,15 +7,15 @@ | |||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "../../Storage/Disk/Controller/DiskController.hpp" | #include "../../../Storage/Disk/Controller/DiskController.hpp" | ||||||
| #include "../../Storage/Disk/Encodings/CommodoreGCR.hpp" | #include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp" | ||||||
| #include "../../Storage/Data/Commodore.hpp" | #include "../../../Storage/Data/Commodore.hpp" | ||||||
| 
 | 
 | ||||||
| #include <limits> | #include <limits> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <array> | #include <array> | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| 
 | 
 | ||||||
| class CommodoreGCRParser: public Storage::Disk::Controller { | class CommodoreGCRParser: public Storage::Disk::Controller { | ||||||
| 	public: | 	public: | ||||||
| @@ -71,19 +71,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 			bit_count_++; | 			bit_count_++; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		unsigned int proceed_to_next_block() { | 		unsigned int proceed_to_next_block(int max_index_count) { | ||||||
| 			// find GCR lead-in
 | 			// find GCR lead-in
 | ||||||
| 			proceed_to_shift_value(0x3ff); | 			proceed_to_shift_value(0x3ff); | ||||||
| 			if(shift_register_ != 0x3ff) return 0xff; | 			if(shift_register_ != 0x3ff) return 0xff; | ||||||
| 
 | 
 | ||||||
| 			// find end of lead-in
 | 			// find end of lead-in
 | ||||||
| 			while(shift_register_ == 0x3ff && index_count_ < 2) { | 			while(shift_register_ == 0x3ff && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// continue for a further nine bits
 | 			// continue for a further nine bits
 | ||||||
| 			bit_count_ = 0; | 			bit_count_ = 0; | ||||||
| 			while(bit_count_ < 9 && index_count_ < 2) { | 			while(bit_count_ < 9 && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| @@ -97,8 +97,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void proceed_to_shift_value(unsigned int shift_value) { | 		void proceed_to_shift_value(unsigned int shift_value) { | ||||||
| 			index_count_ = 0; | 			const int max_index_count = index_count_ + 2; | ||||||
| 			while(shift_register_ != shift_value && index_count_ < 2) { | 			while(shift_register_ != shift_value && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -124,13 +124,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 
 | 
 | ||||||
| 		std::shared_ptr<Sector> get_next_sector() { | 		std::shared_ptr<Sector> get_next_sector() { | ||||||
| 			std::shared_ptr<Sector> sector(new Sector); | 			std::shared_ptr<Sector> sector(new Sector); | ||||||
| 			index_count_ = 0; | 			const int max_index_count = index_count_ + 2; | ||||||
| 
 | 
 | ||||||
| 			while(index_count_ < 2) { | 			while(index_count_ < max_index_count) { | ||||||
| 				// look for a sector header
 | 				// look for a sector header
 | ||||||
| 				while(1) { | 				while(1) { | ||||||
| 					if(proceed_to_next_block() == 0x08) break; | 					if(proceed_to_next_block(max_index_count) == 0x08) break; | ||||||
| 					if(index_count_ >= 2) return nullptr; | 					if(index_count_ >= max_index_count) return nullptr; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				// get sector details, skip if this looks malformed
 | 				// get sector details, skip if this looks malformed
 | ||||||
| @@ -144,12 +144,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 
 | 
 | ||||||
| 				// look for the following data
 | 				// look for the following data
 | ||||||
| 				while(1) { | 				while(1) { | ||||||
| 					if(proceed_to_next_block() == 0x07) break; | 					if(proceed_to_next_block(max_index_count) == 0x07) break; | ||||||
| 					if(index_count_ >= 2) return nullptr; | 					if(index_count_ >= max_index_count) return nullptr; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				checksum = 0; | 				checksum = 0; | ||||||
| 				for(size_t c = 0; c < 256; c++) { | 				for(std::size_t c = 0; c < 256; c++) { | ||||||
| 					sector->data[c] = static_cast<uint8_t>(get_next_byte()); | 					sector->data[c] = static_cast<uint8_t>(get_next_byte()); | ||||||
| 					checksum ^= sector->data[c]; | 					checksum ^= sector->data[c]; | ||||||
| 				} | 				} | ||||||
| @@ -165,8 +165,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) { | std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	CommodoreGCRParser parser; | 	CommodoreGCRParser parser; | ||||||
| 	parser.drive->set_disk(disk); | 	parser.drive->set_disk(disk); | ||||||
| 
 | 
 | ||||||
| @@ -188,7 +188,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// parse directory
 | 	// parse directory
 | ||||||
| 	size_t header_pointer = static_cast<size_t>(-32); | 	std::size_t header_pointer = static_cast<std::size_t>(-32); | ||||||
| 	while(header_pointer+32+31 < directory.size()) { | 	while(header_pointer+32+31 < directory.size()) { | ||||||
| 		header_pointer += 32; | 		header_pointer += 32; | ||||||
| 
 | 
 | ||||||
| @@ -207,12 +207,12 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag | |||||||
| 		next_sector = directory[header_pointer + 4]; | 		next_sector = directory[header_pointer + 4]; | ||||||
| 
 | 
 | ||||||
| 		new_file.raw_name.reserve(16); | 		new_file.raw_name.reserve(16); | ||||||
| 		for(size_t c = 0; c < 16; c++) { | 		for(std::size_t c = 0; c < 16; c++) { | ||||||
| 			new_file.raw_name.push_back(directory[header_pointer + 5 + c]); | 			new_file.raw_name.push_back(directory[header_pointer + 5 + c]); | ||||||
| 		} | 		} | ||||||
| 		new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false); | 		new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false); | ||||||
| 
 | 
 | ||||||
| 		size_t number_of_sectors = static_cast<size_t>(directory[header_pointer + 0x1e]) + (static_cast<size_t>(directory[header_pointer + 0x1f]) << 8); | 		std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8); | ||||||
| 		new_file.data.reserve((number_of_sectors - 1) * 254 + 252); | 		new_file.data.reserve((number_of_sectors - 1) * 254 + 252); | ||||||
| 
 | 
 | ||||||
| 		bool is_first_sector = true; | 		bool is_first_sector = true; | ||||||
| @@ -9,17 +9,19 @@ | |||||||
| #ifndef StaticAnalyser_Commodore_Disk_hpp | #ifndef StaticAnalyser_Commodore_Disk_hpp | ||||||
| #define StaticAnalyser_Commodore_Disk_hpp | #define StaticAnalyser_Commodore_Disk_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include <list> |  | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | #include <vector> | ||||||
|  | 
 | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | } | ||||||
| 
 | 
 | ||||||
| #endif /* Disk_hpp */ | #endif /* Disk_hpp */ | ||||||
| @@ -8,7 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| 
 | 
 | ||||||
| bool StaticAnalyser::Commodore::File::is_basic() { | bool Analyser::Static::Commodore::File::is_basic() { | ||||||
| 	// BASIC files are always relocatable (?)
 | 	// BASIC files are always relocatable (?)
 | ||||||
| 	if(type != File::RelocatableProgram) return false; | 	if(type != File::RelocatableProgram) return false; | ||||||
| 
 | 
 | ||||||
| @@ -23,7 +23,7 @@ bool StaticAnalyser::Commodore::File::is_basic() { | |||||||
| 	//		... null-terminated code ...
 | 	//		... null-terminated code ...
 | ||||||
| 	//	(with a next line address of 0000 indicating end of program)ß
 | 	//	(with a next line address of 0000 indicating end of program)ß
 | ||||||
| 	while(1) { | 	while(1) { | ||||||
| 		if(line_address - starting_address >= data.size() + 2) break; | 		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break; | ||||||
| 
 | 
 | ||||||
| 		uint16_t next_line_address = data[line_address - starting_address]; | 		uint16_t next_line_address = data[line_address - starting_address]; | ||||||
| 		next_line_address |= data[line_address - starting_address + 1] << 8; | 		next_line_address |= data[line_address - starting_address + 1] << 8; | ||||||
| @@ -33,7 +33,7 @@ bool StaticAnalyser::Commodore::File::is_basic() { | |||||||
| 		} | 		} | ||||||
| 		if(next_line_address < line_address + 5) break; | 		if(next_line_address < line_address + 5) break; | ||||||
| 
 | 
 | ||||||
| 		if(line_address - starting_address >= data.size() + 5) break; | 		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break; | ||||||
| 		uint16_t next_line_number = data[line_address - starting_address + 2]; | 		uint16_t next_line_number = data[line_address - starting_address + 2]; | ||||||
| 		next_line_number |= data[line_address - starting_address + 3] << 8; | 		next_line_number |= data[line_address - starting_address + 3] << 8; | ||||||
| 
 | 
 | ||||||
| @@ -12,18 +12,17 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| struct File { | struct File { | ||||||
| 	File() : is_closed(false), is_locked(false) {} |  | ||||||
| 
 |  | ||||||
| 	std::wstring name; | 	std::wstring name; | ||||||
| 	std::vector<uint8_t> raw_name; | 	std::vector<uint8_t> raw_name; | ||||||
| 	uint16_t starting_address; | 	uint16_t starting_address; | ||||||
| 	uint16_t ending_address; | 	uint16_t ending_address; | ||||||
| 	bool is_locked; | 	bool is_locked = false; | ||||||
| 	bool is_closed; | 	bool is_closed = false; | ||||||
| 	enum { | 	enum { | ||||||
| 		RelocatableProgram, | 		RelocatableProgram, | ||||||
| 		NonRelocatableProgram, | 		NonRelocatableProgram, | ||||||
| @@ -36,6 +35,7 @@ struct File { | |||||||
| 	bool is_basic(); | 	bool is_basic(); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,21 +8,23 @@ | |||||||
| 
 | 
 | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
|  | #include "Disk.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| #include "Disk.hpp" | #include "Target.hpp" | ||||||
| #include "../../Storage/Cartridge/Encodings/CommodoreROM.hpp" | #include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp" | ||||||
| 
 | 
 | ||||||
|  | #include <algorithm> | ||||||
| #include <sstream> | #include <sstream> | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| 
 | 
 | ||||||
| static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||||
| 		Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | 		Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
| 	std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges; | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges; | ||||||
| 
 | 
 | ||||||
| 	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) { | 	for(const auto &cartridge : cartridges) { | ||||||
| 		const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments(); | 		const auto &segments = cartridge->get_segments(); | ||||||
| 
 | 
 | ||||||
| 		// only one mapped item is allowed
 | 		// only one mapped item is allowed
 | ||||||
| 		if(segments.size() != 1) continue; | 		if(segments.size() != 1) continue; | ||||||
| @@ -38,42 +40,42 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 	return vic20_cartridges; | 	return vic20_cartridges; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination, const std::string &file_name) { | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::Vic20;	// TODO: machine estimation
 | 	target->machine = Machine::Vic20;	// TODO: machine estimation
 | ||||||
| 	target.probability = 1.0; // TODO: a proper estimation
 | 	target->confidence = 0.5; // TODO: a proper estimation
 | ||||||
| 
 | 
 | ||||||
| 	int device = 0; | 	int device = 0; | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	bool is_disk = false; | 	bool is_disk = false; | ||||||
| 
 | 
 | ||||||
| 	// strip out inappropriate cartridges
 | 	// strip out inappropriate cartridges
 | ||||||
| 	target.media.cartridges = Vic20CartridgesFrom(media.cartridges); | 	target->media.cartridges = Vic20CartridgesFrom(media.cartridges); | ||||||
| 
 | 
 | ||||||
| 	// check disks
 | 	// check disks
 | ||||||
| 	for(auto &disk : media.disks) { | 	for(auto &disk : media.disks) { | ||||||
| 		std::list<File> disk_files = GetFiles(disk); | 		std::vector<File> disk_files = GetFiles(disk); | ||||||
| 		if(!disk_files.empty()) { | 		if(!disk_files.empty()) { | ||||||
| 			is_disk = true; | 			is_disk = true; | ||||||
| 			files.splice(files.end(), disk_files); | 			files.insert(files.end(), disk_files.begin(), disk_files.end()); | ||||||
| 			target.media.disks.push_back(disk); | 			target->media.disks.push_back(disk); | ||||||
| 			if(!device) device = 8; | 			if(!device) device = 8; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// check tapes
 | 	// check tapes
 | ||||||
| 	for(auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::list<File> tape_files = GetFiles(tape); | 		std::vector<File> tape_files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 		if(!tape_files.empty()) { | 		if(!tape_files.empty()) { | ||||||
| 			files.splice(files.end(), tape_files); | 			files.insert(files.end(), tape_files.begin(), tape_files.end()); | ||||||
| 			target.media.tapes.push_back(tape); | 			target->media.tapes.push_back(tape); | ||||||
| 			if(!device) device = 1; | 			if(!device) device = 1; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(!files.empty()) { | 	if(!files.empty()) { | ||||||
| 		target.vic20.memory_model = Vic20MemoryModel::Unexpanded; | 		target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
| 		std::ostringstream string_stream; | 		std::ostringstream string_stream; | ||||||
| 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | ||||||
|   		if(files.front().is_basic()) { |   		if(files.front().is_basic()) { | ||||||
| @@ -82,23 +84,26 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> | |||||||
| 			string_stream << "1"; | 			string_stream << "1"; | ||||||
| 		} | 		} | ||||||
| 		string_stream << "\nRUN\n"; | 		string_stream << "\nRUN\n"; | ||||||
| 		target.loadingCommand = string_stream.str(); | 		target->loading_command = string_stream.str(); | ||||||
| 
 | 
 | ||||||
| 		// make a first guess based on loading address
 | 		// make a first guess based on loading address
 | ||||||
| 		switch(files.front().starting_address) { | 		switch(files.front().starting_address) { | ||||||
|  | 			default: | ||||||
|  | 				printf("Starting address %04x?\n", files.front().starting_address); | ||||||
| 			case 0x1001: | 			case 0x1001: | ||||||
| 			default: break; | 				target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
|  | 			break; | ||||||
| 			case 0x1201: | 			case 0x1201: | ||||||
| 				target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 				target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||||
| 			break; | 			break; | ||||||
| 			case 0x0401: | 			case 0x0401: | ||||||
| 				target.vic20.memory_model = Vic20MemoryModel::EightKB; | 				target->memory_model = Target::MemoryModel::EightKB; | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// General approach: increase memory size conservatively such that the largest file found will fit.
 | 		// General approach: increase memory size conservatively such that the largest file found will fit.
 | ||||||
| 		for(File &file : files) { | //		for(File &file : files) {
 | ||||||
| 			size_t file_size = file.data.size(); | //			std::size_t file_size = file.data.size();
 | ||||||
| //			bool is_basic = file.is_basic();
 | //			bool is_basic = file.is_basic();
 | ||||||
| 
 | 
 | ||||||
| 			/*if(is_basic)
 | 			/*if(is_basic)
 | ||||||
| @@ -108,9 +113,9 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> | |||||||
| 				// An unexpanded machine has 3583 bytes free for BASIC;
 | 				// An unexpanded machine has 3583 bytes free for BASIC;
 | ||||||
| 				// a 3kb expanded machine has 6655 bytes free.
 | 				// a 3kb expanded machine has 6655 bytes free.
 | ||||||
| 				if(file_size > 6655) | 				if(file_size > 6655) | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | ||||||
| 				else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583) | 				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583) | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::EightKB; | 					target->vic20.memory_model = Vic20MemoryModel::EightKB; | ||||||
| 			} | 			} | ||||||
| 			else | 			else | ||||||
| 			{*/ | 			{*/ | ||||||
| @@ -124,18 +129,26 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> | |||||||
| 				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
 | 				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
 | ||||||
| 				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
 | 				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
 | ||||||
| 				// A 32kb expanded Vic has memory in the entire low 32kb.
 | 				// A 32kb expanded Vic has memory in the entire low 32kb.
 | ||||||
| 				uint16_t starting_address = file.starting_address; | //				uint16_t starting_address = file.starting_address;
 | ||||||
| 
 | 
 | ||||||
| 				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
 | 				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
 | ||||||
| 				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
 | 				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
 | ||||||
| 				if(starting_address + file_size > 0x2000) | //				if(starting_address + file_size > 0x2000)
 | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | //					target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | ||||||
| 				else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) | //				else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
 | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | //					target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | ||||||
|  | //			}
 | ||||||
| //		}
 | //		}
 | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if(!target->media.empty()) { | ||||||
|  | 		// Inspect filename for a region hint.
 | ||||||
|  | 		std::string lowercase_name = file_name; | ||||||
|  | 		std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower); | ||||||
|  | 		if(lowercase_name.find("ntsc") != std::string::npos) { | ||||||
|  | 			target->region = Analyser::Static::Commodore::Target::Region::American; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	if(!target.media.tapes.empty() || !target.media.cartridges.empty() || !target.media.disks.empty()) | 		destination.push_back(std::move(target)); | ||||||
| 		destination.push_back(target); | 	} | ||||||
| } | } | ||||||
| @@ -10,12 +10,15 @@ | |||||||
| #define StaticAnalyser_Commodore_StaticAnalyser_hpp | #define StaticAnalyser_Commodore_StaticAnalyser_hpp | ||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, const std::string &file_name); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,13 +8,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Parsers/Commodore.hpp" | #include "../../../Storage/Tape/Parsers/Commodore.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	Storage::Tape::Commodore::Parser parser; | 	Storage::Tape::Commodore::Parser parser; | ||||||
| 	std::list<File> file_list; | 	std::vector<File> file_list; | ||||||
| 
 | 
 | ||||||
| 	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape); | 	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape); | ||||||
| 
 | 
 | ||||||
| @@ -9,15 +9,16 @@ | |||||||
| #ifndef StaticAnalyser_Commodore_Tape_hpp | #ifndef StaticAnalyser_Commodore_Tape_hpp | ||||||
| #define StaticAnalyser_Commodore_Tape_hpp | #define StaticAnalyser_Commodore_Tape_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include <list> |  | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										42
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Commodore_Target_h | ||||||
|  | #define Analyser_Static_Commodore_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Commodore { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class MemoryModel { | ||||||
|  | 		Unexpanded, | ||||||
|  | 		EightKB, | ||||||
|  | 		ThirtyTwoKB | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	enum class Region { | ||||||
|  | 		American, | ||||||
|  | 		Danish, | ||||||
|  | 		Japanese, | ||||||
|  | 		European, | ||||||
|  | 		Swedish | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||||
|  | 	Region region = Region::European; | ||||||
|  | 	bool has_c1540 = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Commodore_Target_h */ | ||||||
| @@ -6,24 +6,25 @@ | |||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved.
 | //  Copyright © 2016 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Disassembler6502.hpp" | #include "6502.hpp" | ||||||
| #include <map> |  | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::MOS6502; | #include "Kernel.hpp" | ||||||
| 
 | 
 | ||||||
| struct PartialDisassembly { | using namespace Analyser::Static::MOS6502; | ||||||
| 	Disassembly disassembly; | namespace  { | ||||||
| 	std::vector<uint16_t> remaining_entry_points; |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, uint16_t entry_point) { | using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>; | ||||||
|  | 
 | ||||||
|  | struct MOS6502Disassembler { | ||||||
|  | 
 | ||||||
|  | static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) { | ||||||
| 	disassembly.disassembly.internal_calls.insert(entry_point); | 	disassembly.disassembly.internal_calls.insert(entry_point); | ||||||
| 	uint16_t address = entry_point; | 	uint16_t address = entry_point; | ||||||
| 	while(1) { | 	while(true) { | ||||||
| 		size_t local_address = address_mapper(address); | 		std::size_t local_address = address_mapper(address); | ||||||
| 		if(local_address >= memory.size()) return; | 		if(local_address >= memory.size()) return; | ||||||
| 
 | 
 | ||||||
| 		struct Instruction instruction; | 		Instruction instruction; | ||||||
| 		instruction.address = address; | 		instruction.address = address; | ||||||
| 		address++; | 		address++; | ||||||
| 
 | 
 | ||||||
| @@ -233,7 +234,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | |||||||
| 			case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY: | 			case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY: | ||||||
| 			case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY: | 			case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY: | ||||||
| 			case Instruction::Relative: { | 			case Instruction::Relative: { | ||||||
| 				size_t operand_address = address_mapper(address); | 				std::size_t operand_address = address_mapper(address); | ||||||
| 				if(operand_address >= memory.size()) return; | 				if(operand_address >= memory.size()) return; | ||||||
| 				address++; | 				address++; | ||||||
| 
 | 
 | ||||||
| @@ -244,8 +245,8 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | |||||||
| 			// two-byte operands
 | 			// two-byte operands
 | ||||||
| 			case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY: | 			case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY: | ||||||
| 			case Instruction::Indirect: { | 			case Instruction::Indirect: { | ||||||
| 				size_t low_operand_address = address_mapper(address); | 				std::size_t low_operand_address = address_mapper(address); | ||||||
| 				size_t high_operand_address = address_mapper(address + 1); | 				std::size_t high_operand_address = address_mapper(address + 1); | ||||||
| 				if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return; | 				if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return; | ||||||
| 				address += 2; | 				address += 2; | ||||||
| 
 | 
 | ||||||
| @@ -259,7 +260,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | |||||||
| 
 | 
 | ||||||
| 		// TODO: something wider-ranging than this
 | 		// TODO: something wider-ranging than this
 | ||||||
| 		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) { | 		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) { | ||||||
| 			size_t mapped_address = address_mapper(instruction.operand); | 			std::size_t mapped_address = address_mapper(instruction.operand); | ||||||
| 			bool is_external = mapped_address >= memory.size(); | 			bool is_external = mapped_address >= memory.size(); | ||||||
| 
 | 
 | ||||||
| 			switch(instruction.operation) { | 			switch(instruction.operation) { | ||||||
| @@ -307,31 +308,13 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, const std::function<size_t(uint16_t)> &address_mapper, std::vector<uint16_t> entry_points) { | }; | ||||||
| 	PartialDisassembly partialDisassembly; |  | ||||||
| 	partialDisassembly.remaining_entry_points = entry_points; |  | ||||||
| 
 | 
 | ||||||
| 	while(!partialDisassembly.remaining_entry_points.empty()) { | }	// end of anonymous namespace
 | ||||||
| 		// pull the next entry point from the back of the vector
 |  | ||||||
| 		uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back(); |  | ||||||
| 		partialDisassembly.remaining_entry_points.pop_back(); |  | ||||||
| 
 | 
 | ||||||
| 		// if that address has already bene visited, forget about it
 | Disassembly Analyser::Static::MOS6502::Disassemble( | ||||||
| 		if(partialDisassembly.disassembly.instructions_by_address.find(next_entry_point) != partialDisassembly.disassembly.instructions_by_address.end()) continue; | 	const std::vector<uint8_t> &memory, | ||||||
| 
 | 	const std::function<std::size_t(uint16_t)> &address_mapper, | ||||||
| 		// if it's outgoing, log it as such and forget about it; otherwise disassemble
 | 	std::vector<uint16_t> entry_points) { | ||||||
| 		size_t mapped_entry_point = address_mapper(next_entry_point); | 	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points); | ||||||
| 		if(mapped_entry_point >= memory.size()) |  | ||||||
| 			partialDisassembly.disassembly.outward_calls.insert(next_entry_point); |  | ||||||
| 		else |  | ||||||
| 			AddToDisassembly(partialDisassembly, memory, address_mapper, next_entry_point); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return std::move(partialDisassembly.disassembly); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::function<size_t(uint16_t)> StaticAnalyser::MOS6502::OffsetMapper(uint16_t start_address) { |  | ||||||
| 	return [start_address](uint16_t argument) { |  | ||||||
| 		return static_cast<size_t>(argument - start_address); |  | ||||||
| 	}; |  | ||||||
| } | } | ||||||
							
								
								
									
										101
									
								
								Analyser/Static/Disassembler/6502.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								Analyser/Static/Disassembler/6502.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | // | ||||||
|  | //  6502.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/11/2016. | ||||||
|  | //  Copyright © 2016 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StaticAnalyser_Disassembler_6502_hpp | ||||||
|  | #define StaticAnalyser_Disassembler_6502_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <functional> | ||||||
|  | #include <map> | ||||||
|  | #include <memory> | ||||||
|  | #include <set> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MOS6502 { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Describes a 6502 instruciton — its address, the operation it performs, its addressing mode | ||||||
|  | 	and its operand, if any. | ||||||
|  | */ | ||||||
|  | struct Instruction { | ||||||
|  | 	/*! The address this instruction starts at. This is a mapped address. */ | ||||||
|  | 	uint16_t address = 0; | ||||||
|  | 	/*! The operation this instruction performs. */ | ||||||
|  | 	enum { | ||||||
|  | 		BRK, JSR, RTI, RTS, JMP, | ||||||
|  | 		CLC, SEC, CLD, SED, CLI, SEI, CLV, | ||||||
|  | 		NOP, | ||||||
|  |  | ||||||
|  | 		SLO, RLA, SRE, RRA, ALR, ARR, | ||||||
|  | 		SAX, LAX, DCP, ISC, | ||||||
|  | 		ANC, XAA, AXS, | ||||||
|  | 		AND, EOR, ORA, BIT, | ||||||
|  | 		ADC, SBC, | ||||||
|  | 		AHX, SHY, SHX, TAS, LAS, | ||||||
|  |  | ||||||
|  | 		LDA, STA, LDX, STX, LDY, STY, | ||||||
|  |  | ||||||
|  | 		BPL, BMI, BVC, BVS, BCC, BCS, BNE, BEQ, | ||||||
|  |  | ||||||
|  | 		CMP, CPX, CPY, | ||||||
|  | 		INC, DEC, DEX, DEY, INX, INY, | ||||||
|  | 		ASL, ROL, LSR, ROR, | ||||||
|  | 		TAX, TXA, TAY, TYA, TSX, TXS, | ||||||
|  | 		PLA, PHA, PLP, PHP, | ||||||
|  |  | ||||||
|  | 		KIL | ||||||
|  | 	} operation = NOP; | ||||||
|  | 	/*! The addressing mode used by the instruction. */ | ||||||
|  | 	enum { | ||||||
|  | 		Absolute, | ||||||
|  | 		AbsoluteX, | ||||||
|  | 		AbsoluteY, | ||||||
|  | 		Immediate, | ||||||
|  | 		Implied, | ||||||
|  | 		ZeroPage, | ||||||
|  | 		ZeroPageX, | ||||||
|  | 		ZeroPageY, | ||||||
|  | 		Indirect, | ||||||
|  | 		IndexedIndirectX, | ||||||
|  | 		IndirectIndexedY, | ||||||
|  | 		Relative, | ||||||
|  | 	} addressing_mode = Implied; | ||||||
|  | 	/*! The instruction's operand, if any. */ | ||||||
|  | 	uint16_t operand = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! Represents the disassembled form of a program. */ | ||||||
|  | struct Disassembly { | ||||||
|  | 	/*! All instructions found, mapped by address. */ | ||||||
|  | 	std::map<uint16_t, Instruction> instructions_by_address; | ||||||
|  | 	/*! The set of all calls or jumps that land outside of the area covered by the data provided for disassembly. */ | ||||||
|  | 	std::set<uint16_t> outward_calls; | ||||||
|  | 	/*! The set of all calls or jumps that land inside of the area covered by the data provided for disassembly. */ | ||||||
|  | 	std::set<uint16_t> internal_calls; | ||||||
|  | 	/*! The sets of all stores, loads and modifies that occur to data outside of the area covered by the data provided for disassembly. */ | ||||||
|  | 	std::set<uint16_t> external_stores, external_loads, external_modifies; | ||||||
|  | 	/*! The sets of all stores, loads and modifies that occur to data inside of the area covered by the data provided for disassembly. */ | ||||||
|  | 	std::set<uint16_t> internal_stores, internal_loads, internal_modifies; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Disassembles the data provided as @c memory, mapping it into the 6502's full address range via the @c address_mapper, | ||||||
|  | 	starting disassembly from each of the @c entry_points. | ||||||
|  | */ | ||||||
|  | Disassembly Disassemble( | ||||||
|  | 	const std::vector<uint8_t> &memory, | ||||||
|  | 	const std::function<std::size_t(uint16_t)> &address_mapper, | ||||||
|  | 	std::vector<uint16_t> entry_points); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Disassembler6502_hpp */ | ||||||
							
								
								
									
										9
									
								
								Analyser/Static/Disassembler/AddressMapper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Analyser/Static/Disassembler/AddressMapper.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | // | ||||||
|  | //  AddressMapper.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 30/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "AddressMapper.hpp" | ||||||
							
								
								
									
										32
									
								
								Analyser/Static/Disassembler/AddressMapper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Analyser/Static/Disassembler/AddressMapper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | // | ||||||
|  | //  AddressMapper.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 30/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef AddressMapper_hpp | ||||||
|  | #define AddressMapper_hpp | ||||||
|  |  | ||||||
|  | #include <functional> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Disassembler { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides an address mapper that relocates a chunk of memory so that it starts at | ||||||
|  | 	address @c start_address. | ||||||
|  | */ | ||||||
|  | template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) { | ||||||
|  | 	return [start_address](T argument) { | ||||||
|  | 		return static_cast<std::size_t>(argument - start_address); | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* AddressMapper_hpp */ | ||||||
							
								
								
									
										52
									
								
								Analyser/Static/Disassembler/Kernel.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Analyser/Static/Disassembler/Kernel.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | // | ||||||
|  | //  Kernel.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 31/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Kernel_hpp | ||||||
|  | #define Kernel_hpp | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Disassembly { | ||||||
|  |  | ||||||
|  | template <typename D, typename S> struct PartialDisassembly { | ||||||
|  | 	D disassembly; | ||||||
|  | 	std::vector<S> remaining_entry_points; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template <typename D, typename S, typename Disassembler> D Disassemble( | ||||||
|  | 	const std::vector<uint8_t> &memory, | ||||||
|  | 	const std::function<std::size_t(S)> &address_mapper, | ||||||
|  | 	std::vector<S> entry_points) { | ||||||
|  | 	PartialDisassembly<D, S> partial_disassembly; | ||||||
|  | 	partial_disassembly.remaining_entry_points = entry_points; | ||||||
|  |  | ||||||
|  | 	while(!partial_disassembly.remaining_entry_points.empty()) { | ||||||
|  | 		// pull the next entry point from the back of the vector | ||||||
|  | 		S next_entry_point = partial_disassembly.remaining_entry_points.back(); | ||||||
|  | 		partial_disassembly.remaining_entry_points.pop_back(); | ||||||
|  |  | ||||||
|  | 		// if that address has already been visited, forget about it | ||||||
|  | 		if(	partial_disassembly.disassembly.instructions_by_address.find(next_entry_point) | ||||||
|  | 			!= partial_disassembly.disassembly.instructions_by_address.end()) continue; | ||||||
|  |  | ||||||
|  | 		// if it's outgoing, log it as such and forget about it; otherwise disassemble | ||||||
|  | 		std::size_t mapped_entry_point = address_mapper(next_entry_point); | ||||||
|  | 		if(mapped_entry_point >= memory.size()) | ||||||
|  | 			partial_disassembly.disassembly.outward_calls.insert(next_entry_point); | ||||||
|  | 		else | ||||||
|  | 			Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return partial_disassembly.disassembly; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Kernel_hpp */ | ||||||
							
								
								
									
										619
									
								
								Analyser/Static/Disassembler/Z80.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										619
									
								
								Analyser/Static/Disassembler/Z80.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,619 @@ | |||||||
|  | // | ||||||
|  | //  Z80.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 30/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Z80.hpp" | ||||||
|  |  | ||||||
|  | #include "Kernel.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Static::Z80; | ||||||
|  | namespace  { | ||||||
|  |  | ||||||
|  | using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>; | ||||||
|  |  | ||||||
|  | class Accessor { | ||||||
|  | 	public: | ||||||
|  | 		Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) : | ||||||
|  | 			memory_(memory), address_mapper_(address_mapper), address_(address) {} | ||||||
|  |  | ||||||
|  | 		uint8_t byte() { | ||||||
|  | 			std::size_t mapped_address = address_mapper_(address_); | ||||||
|  | 			address_++; | ||||||
|  | 			if(mapped_address >= memory_.size()) { | ||||||
|  | 				overrun_ = true; | ||||||
|  | 				return 0xff; | ||||||
|  | 			} | ||||||
|  | 			return memory_[mapped_address]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint16_t word() { | ||||||
|  | 			uint8_t low = byte(); | ||||||
|  | 			uint8_t high = byte(); | ||||||
|  | 			return static_cast<uint16_t>(low | (high << 8)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool overrun() { | ||||||
|  | 			return overrun_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool at_end() { | ||||||
|  | 			std::size_t mapped_address = address_mapper_(address_); | ||||||
|  | 			return mapped_address >= memory_.size(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint16_t address() { | ||||||
|  | 			return address_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		const std::vector<uint8_t> &memory_; | ||||||
|  | 		const std::function<std::size_t(uint16_t)> &address_mapper_; | ||||||
|  | 		uint16_t address_; | ||||||
|  | 		bool overrun_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #define x(v) (v >> 6) | ||||||
|  | #define y(v) ((v >> 3) & 7) | ||||||
|  | #define q(v) ((v >> 3) & 1) | ||||||
|  | #define p(v) ((v >> 4) & 3) | ||||||
|  | #define z(v) (v & 7) | ||||||
|  |  | ||||||
|  | Instruction::Condition condition_table[] = { | ||||||
|  | 	Instruction::Condition::NZ, 	Instruction::Condition::Z, | ||||||
|  | 	Instruction::Condition::NC, 	Instruction::Condition::C, | ||||||
|  | 	Instruction::Condition::PO, 	Instruction::Condition::PE, | ||||||
|  | 	Instruction::Condition::P,		Instruction::Condition::M | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Instruction::Location register_pair_table[] = { | ||||||
|  | 	Instruction::Location::BC, | ||||||
|  | 	Instruction::Location::DE, | ||||||
|  | 	Instruction::Location::HL, | ||||||
|  | 	Instruction::Location::SP | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Instruction::Location register_pair_table2[] = { | ||||||
|  | 	Instruction::Location::BC, | ||||||
|  | 	Instruction::Location::DE, | ||||||
|  | 	Instruction::Location::HL, | ||||||
|  | 	Instruction::Location::AF | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) { | ||||||
|  | 	Instruction::Location register_table[] = { | ||||||
|  | 		Instruction::Location::B,	Instruction::Location::C, | ||||||
|  | 		Instruction::Location::D,	Instruction::Location::E, | ||||||
|  | 		Instruction::Location::H,	Instruction::Location::L, | ||||||
|  | 		Instruction::Location::HL_Indirect, | ||||||
|  | 		Instruction::Location::A | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Instruction::Location location = register_table[offset]; | ||||||
|  | 	if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) { | ||||||
|  | 		instruction.offset = accessor.byte() - 128; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return location; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Instruction::Operation alu_table[] = { | ||||||
|  | 	Instruction::Operation::ADD, | ||||||
|  | 	Instruction::Operation::ADC, | ||||||
|  | 	Instruction::Operation::SUB, | ||||||
|  | 	Instruction::Operation::SBC, | ||||||
|  | 	Instruction::Operation::AND, | ||||||
|  | 	Instruction::Operation::XOR, | ||||||
|  | 	Instruction::Operation::OR, | ||||||
|  | 	Instruction::Operation::CP | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Instruction::Operation rotation_table[] = { | ||||||
|  | 	Instruction::Operation::RLC, | ||||||
|  | 	Instruction::Operation::RRC, | ||||||
|  | 	Instruction::Operation::RL, | ||||||
|  | 	Instruction::Operation::RR, | ||||||
|  | 	Instruction::Operation::SLA, | ||||||
|  | 	Instruction::Operation::SRA, | ||||||
|  | 	Instruction::Operation::SLL, | ||||||
|  | 	Instruction::Operation::SRL | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Instruction::Operation block_table[][4] = { | ||||||
|  | 	{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI}, | ||||||
|  | 	{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD}, | ||||||
|  | 	{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR}, | ||||||
|  | 	{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) { | ||||||
|  | 	const uint8_t operation = accessor.byte(); | ||||||
|  |  | ||||||
|  | 	if(!x(operation)) { | ||||||
|  | 		instruction.operation = rotation_table[y(operation)]; | ||||||
|  | 		instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 	} else { | ||||||
|  | 		instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 		instruction.source = Instruction::Location::Operand; | ||||||
|  | 		instruction.operand = y(operation); | ||||||
|  |  | ||||||
|  | 		switch(x(operation)) { | ||||||
|  | 			case 1:	instruction.operation = Instruction::Operation::BIT;	break; | ||||||
|  | 			case 2:	instruction.operation = Instruction::Operation::RES;	break; | ||||||
|  | 			case 3:	instruction.operation = Instruction::Operation::SET;	break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) { | ||||||
|  | 	const uint8_t operation = accessor.byte(); | ||||||
|  |  | ||||||
|  | 	switch(x(operation)) { | ||||||
|  | 		default: | ||||||
|  | 			instruction.operation = Instruction::Operation::Invalid; | ||||||
|  | 		break; | ||||||
|  | 		case 2: | ||||||
|  | 			if(z(operation) < 4 && y(operation) >= 4) { | ||||||
|  | 				instruction.operation = block_table[y(operation)-4][z(operation)]; | ||||||
|  | 			} else { | ||||||
|  | 				instruction.operation = Instruction::Operation::Invalid; | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  | 		case 3: | ||||||
|  | 			switch(z(operation)) { | ||||||
|  | 				case 0: | ||||||
|  | 					instruction.operation = Instruction::Operation::IN; | ||||||
|  | 					instruction.source = Instruction::Location::BC_Indirect; | ||||||
|  | 					if(y(operation) == 6) { | ||||||
|  | 						instruction.destination = Instruction::Location::None; | ||||||
|  | 					} else { | ||||||
|  | 						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 1: | ||||||
|  | 					instruction.operation = Instruction::Operation::OUT; | ||||||
|  | 					instruction.destination = Instruction::Location::BC_Indirect; | ||||||
|  | 					if(y(operation) == 6) { | ||||||
|  | 						instruction.source = Instruction::Location::None; | ||||||
|  | 					} else { | ||||||
|  | 						instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 2: | ||||||
|  | 					instruction.operation = (y(operation)&1) ? Instruction::Operation::ADC : Instruction::Operation::SBC; | ||||||
|  | 					instruction.destination = Instruction::Location::HL; | ||||||
|  | 					instruction.source = register_pair_table[y(operation) >> 1]; | ||||||
|  | 				break; | ||||||
|  | 				case 3: | ||||||
|  | 					instruction.operation = Instruction::Operation::LD; | ||||||
|  | 					if(q(operation)) { | ||||||
|  | 						instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 						instruction.source = Instruction::Location::Operand_Indirect; | ||||||
|  | 					} else { | ||||||
|  | 						instruction.destination = Instruction::Location::Operand_Indirect; | ||||||
|  | 						instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 					} | ||||||
|  | 					instruction.operand = accessor.word(); | ||||||
|  | 				break; | ||||||
|  | 				case 4: | ||||||
|  | 					instruction.operation = Instruction::Operation::NEG; | ||||||
|  | 				break; | ||||||
|  | 				case 5: | ||||||
|  | 					instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN; | ||||||
|  | 				break; | ||||||
|  | 				case 6: | ||||||
|  | 					instruction.operation = Instruction::Operation::IM; | ||||||
|  | 					instruction.source = Instruction::Location::Operand; | ||||||
|  | 					switch(y(operation)&3) { | ||||||
|  | 						case 0:	instruction.operand = 0;	break; | ||||||
|  | 						case 1:	instruction.operand = 0;	break; | ||||||
|  | 						case 2:	instruction.operand = 1;	break; | ||||||
|  | 						case 3:	instruction.operand = 2;	break; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 7: | ||||||
|  | 					switch(y(operation)) { | ||||||
|  | 						case 0: | ||||||
|  | 							instruction.operation = Instruction::Operation::LD; | ||||||
|  | 							instruction.destination = Instruction::Location::I; | ||||||
|  | 							instruction.source = Instruction::Location::A; | ||||||
|  | 						break; | ||||||
|  | 						case 1: | ||||||
|  | 							instruction.operation = Instruction::Operation::LD; | ||||||
|  | 							instruction.destination = Instruction::Location::R; | ||||||
|  | 							instruction.source = Instruction::Location::A; | ||||||
|  | 						break; | ||||||
|  | 						case 2: | ||||||
|  | 							instruction.operation = Instruction::Operation::LD; | ||||||
|  | 							instruction.destination = Instruction::Location::A; | ||||||
|  | 							instruction.source = Instruction::Location::I; | ||||||
|  | 						break; | ||||||
|  | 						case 3: | ||||||
|  | 							instruction.operation = Instruction::Operation::LD; | ||||||
|  | 							instruction.destination = Instruction::Location::A; | ||||||
|  | 							instruction.source = Instruction::Location::R; | ||||||
|  | 						break; | ||||||
|  | 						case 4:		instruction.operation = Instruction::Operation::RRD;	break; | ||||||
|  | 						case 5:		instruction.operation = Instruction::Operation::RLD;	break; | ||||||
|  | 						default:	instruction.operation = Instruction::Operation::NOP;	break; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DisassembleMainPage(Accessor &accessor, Instruction &instruction) { | ||||||
|  | 	bool needs_indirect_offset = false; | ||||||
|  | 	enum HLSubstitution { | ||||||
|  | 		None, IX, IY | ||||||
|  | 	} hl_substitution = None; | ||||||
|  |  | ||||||
|  | 	while(true) { | ||||||
|  | 		uint8_t operation = accessor.byte(); | ||||||
|  |  | ||||||
|  | 		switch(x(operation)) { | ||||||
|  | 			case 0: | ||||||
|  | 				switch(z(operation)) { | ||||||
|  | 					case 0: | ||||||
|  | 						switch(y(operation)) { | ||||||
|  | 							case 0: instruction.operation = Instruction::Operation::NOP;		break; | ||||||
|  | 							case 1: instruction.operation = Instruction::Operation::EXAFAFd;	break; | ||||||
|  | 							case 2: | ||||||
|  | 								instruction.operation = Instruction::Operation::DJNZ; | ||||||
|  | 								instruction.operand = accessor.byte() - 128; | ||||||
|  | 							break; | ||||||
|  | 							default: | ||||||
|  | 								instruction.operation = Instruction::Operation::JR; | ||||||
|  | 								instruction.operand = accessor.byte() - 128; | ||||||
|  | 								if(y(operation) >= 4) instruction.condition = condition_table[y(operation) - 4]; | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 1: | ||||||
|  | 						if(y(operation)&1) { | ||||||
|  | 							instruction.operation = Instruction::Operation::ADD; | ||||||
|  | 							instruction.destination = Instruction::Location::HL; | ||||||
|  | 							instruction.source = register_pair_table[y(operation) >> 1]; | ||||||
|  | 						} else { | ||||||
|  | 							instruction.operation = Instruction::Operation::LD; | ||||||
|  | 							instruction.destination = register_pair_table[y(operation) >> 1]; | ||||||
|  | 							instruction.source = Instruction::Location::Operand; | ||||||
|  | 							instruction.operand = accessor.word(); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 2: | ||||||
|  | 						switch(y(operation)) { | ||||||
|  | 							case 0: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::BC_Indirect; | ||||||
|  | 								instruction.source = Instruction::Location::A; | ||||||
|  | 							break; | ||||||
|  | 							case 1: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::A; | ||||||
|  | 								instruction.source = Instruction::Location::BC_Indirect; | ||||||
|  | 							break; | ||||||
|  | 							case 2: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::DE_Indirect; | ||||||
|  | 								instruction.source = Instruction::Location::A; | ||||||
|  | 							break; | ||||||
|  | 							case 3: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::A; | ||||||
|  | 								instruction.source = Instruction::Location::DE_Indirect; | ||||||
|  | 							break; | ||||||
|  | 							case 4: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::Operand_Indirect; | ||||||
|  | 								instruction.source = Instruction::Location::HL; | ||||||
|  | 							break; | ||||||
|  | 							case 5: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::HL; | ||||||
|  | 								instruction.source = Instruction::Location::Operand_Indirect; | ||||||
|  | 							break; | ||||||
|  | 							case 6: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::Operand_Indirect; | ||||||
|  | 								instruction.source = Instruction::Location::A; | ||||||
|  | 							break; | ||||||
|  | 							case 7: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::A; | ||||||
|  | 								instruction.source = Instruction::Location::Operand_Indirect; | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if(y(operation) > 3) { | ||||||
|  | 							instruction.operand = accessor.word(); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 3: | ||||||
|  | 						if(y(operation)&1) { | ||||||
|  | 							instruction.operation = Instruction::Operation::DEC; | ||||||
|  | 						} else { | ||||||
|  | 							instruction.operation = Instruction::Operation::INC; | ||||||
|  | 						} | ||||||
|  | 						instruction.source = instruction.destination = register_pair_table[y(operation) >> 1]; | ||||||
|  | 					break; | ||||||
|  | 					case 4: | ||||||
|  | 						instruction.operation = Instruction::Operation::INC; | ||||||
|  | 						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 					break; | ||||||
|  | 					case 5: | ||||||
|  | 						instruction.operation = Instruction::Operation::DEC; | ||||||
|  | 						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 					break; | ||||||
|  | 					case 6: | ||||||
|  | 						instruction.operation = Instruction::Operation::LD; | ||||||
|  | 						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 						instruction.source = Instruction::Location::Operand; | ||||||
|  | 						instruction.operand = accessor.byte(); | ||||||
|  | 					break; | ||||||
|  | 					case 7: | ||||||
|  | 						switch(y(operation)) { | ||||||
|  | 							case 0:	instruction.operation = Instruction::Operation::RLCA;	break; | ||||||
|  | 							case 1:	instruction.operation = Instruction::Operation::RRCA;	break; | ||||||
|  | 							case 2:	instruction.operation = Instruction::Operation::RLA;	break; | ||||||
|  | 							case 3:	instruction.operation = Instruction::Operation::RRA;	break; | ||||||
|  | 							case 4:	instruction.operation = Instruction::Operation::DAA;	break; | ||||||
|  | 							case 5:	instruction.operation = Instruction::Operation::CPL;	break; | ||||||
|  | 							case 6:	instruction.operation = Instruction::Operation::SCF;	break; | ||||||
|  | 							case 7:	instruction.operation = Instruction::Operation::CCF;	break; | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  | 			case 1: | ||||||
|  | 				if(y(operation) == 6 && z(operation) == 6) { | ||||||
|  | 					instruction.operation = Instruction::Operation::HALT; | ||||||
|  | 				} else { | ||||||
|  | 					instruction.operation = Instruction::Operation::LD; | ||||||
|  | 					instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 					instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  | 			case 2: | ||||||
|  | 				instruction.operation = alu_table[y(operation)]; | ||||||
|  | 				instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); | ||||||
|  | 				instruction.destination = Instruction::Location::A; | ||||||
|  | 			break; | ||||||
|  | 			case 3: | ||||||
|  | 				switch(z(operation)) { | ||||||
|  | 					case 0: | ||||||
|  | 						instruction.operation = Instruction::Operation::RET; | ||||||
|  | 						instruction.condition = condition_table[y(operation)]; | ||||||
|  | 					break; | ||||||
|  | 					case 1: | ||||||
|  | 						switch(y(operation)) { | ||||||
|  | 							default: | ||||||
|  | 								instruction.operation = Instruction::Operation::POP; | ||||||
|  | 								instruction.source = register_pair_table2[y(operation) >> 1]; | ||||||
|  | 							break; | ||||||
|  | 							case 1: | ||||||
|  | 								instruction.operation = Instruction::Operation::RET; | ||||||
|  | 							break; | ||||||
|  | 							case 3: | ||||||
|  | 								instruction.operation = Instruction::Operation::EXX; | ||||||
|  | 							break; | ||||||
|  | 							case 5: | ||||||
|  | 								instruction.operation = Instruction::Operation::JP; | ||||||
|  | 								instruction.source = Instruction::Location::HL; | ||||||
|  | 							break; | ||||||
|  | 							case 7: | ||||||
|  | 								instruction.operation = Instruction::Operation::LD; | ||||||
|  | 								instruction.destination = Instruction::Location::SP; | ||||||
|  | 								instruction.source = Instruction::Location::HL; | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 2: | ||||||
|  | 						instruction.operation = Instruction::Operation::JP; | ||||||
|  | 						instruction.condition = condition_table[y(operation)]; | ||||||
|  | 						instruction.operand = accessor.word(); | ||||||
|  | 					break; | ||||||
|  | 					case 3: | ||||||
|  | 						switch(y(operation)) { | ||||||
|  | 							case 0: | ||||||
|  | 								instruction.operation = Instruction::Operation::JP; | ||||||
|  | 								instruction.source = Instruction::Location::Operand; | ||||||
|  | 								instruction.operand = accessor.word(); | ||||||
|  | 							break; | ||||||
|  | 							case 1: | ||||||
|  | 								DisassembleCBPage(accessor, instruction, needs_indirect_offset); | ||||||
|  | 							break; | ||||||
|  | 							case 2: | ||||||
|  | 								instruction.operation = Instruction::Operation::OUT; | ||||||
|  | 								instruction.source = Instruction::Location::A; | ||||||
|  | 								instruction.destination = Instruction::Location::Operand_Indirect; | ||||||
|  | 								instruction.operand = accessor.byte(); | ||||||
|  | 							break; | ||||||
|  | 							case 3: | ||||||
|  | 								instruction.operation = Instruction::Operation::IN; | ||||||
|  | 								instruction.destination = Instruction::Location::A; | ||||||
|  | 								instruction.source = Instruction::Location::Operand_Indirect; | ||||||
|  | 								instruction.operand = accessor.byte(); | ||||||
|  | 							break; | ||||||
|  | 							case 4: | ||||||
|  | 								instruction.operation = Instruction::Operation::EX; | ||||||
|  | 								instruction.destination = Instruction::Location::SP_Indirect; | ||||||
|  | 								instruction.source = Instruction::Location::HL; | ||||||
|  | 							break; | ||||||
|  | 							case 5: | ||||||
|  | 								instruction.operation = Instruction::Operation::EX; | ||||||
|  | 								instruction.destination = Instruction::Location::DE; | ||||||
|  | 								instruction.source = Instruction::Location::HL; | ||||||
|  | 							break; | ||||||
|  | 							case 6: | ||||||
|  | 								instruction.operation = Instruction::Operation::DI; | ||||||
|  | 							break; | ||||||
|  | 							case 7: | ||||||
|  | 								instruction.operation = Instruction::Operation::EI; | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 4: | ||||||
|  | 						instruction.operation = Instruction::Operation::CALL; | ||||||
|  | 						instruction.source = Instruction::Location::Operand_Indirect; | ||||||
|  | 						instruction.operand = accessor.word(); | ||||||
|  | 						instruction.condition = condition_table[y(operation)]; | ||||||
|  | 					break; | ||||||
|  | 					case 5: | ||||||
|  | 						switch(y(operation)) { | ||||||
|  | 							default: | ||||||
|  | 								instruction.operation = Instruction::Operation::PUSH; | ||||||
|  | 								instruction.source = register_pair_table2[y(operation) >> 1]; | ||||||
|  | 							break; | ||||||
|  | 							case 1: | ||||||
|  | 								instruction.operation = Instruction::Operation::CALL; | ||||||
|  | 								instruction.source = Instruction::Location::Operand; | ||||||
|  | 								instruction.operand = accessor.word(); | ||||||
|  | 							break; | ||||||
|  | 							case 3: | ||||||
|  | 								needs_indirect_offset = true; | ||||||
|  | 								hl_substitution = IX; | ||||||
|  | 							continue;	// i.e. repeat loop. | ||||||
|  | 							case 5: | ||||||
|  | 								DisassembleEDPage(accessor, instruction, needs_indirect_offset); | ||||||
|  | 							break; | ||||||
|  | 							case 7: | ||||||
|  | 								needs_indirect_offset = true; | ||||||
|  | 								hl_substitution = IY; | ||||||
|  | 							continue;	// i.e. repeat loop. | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 6: | ||||||
|  | 						instruction.operation = alu_table[y(operation)]; | ||||||
|  | 						instruction.source = Instruction::Location::Operand; | ||||||
|  | 						instruction.destination = Instruction::Location::A; | ||||||
|  | 						instruction.operand = accessor.byte(); | ||||||
|  | 					break; | ||||||
|  | 					case 7: | ||||||
|  | 						instruction.operation = Instruction::Operation::RST; | ||||||
|  | 						instruction.source = Instruction::Location::Operand; | ||||||
|  | 						instruction.operand = y(operation) << 3; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// This while(true) isn't an infinite loop for everything except those paths that opt in | ||||||
|  | 		// via continue. | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Perform IX/IY substitution for HL, if applicable. | ||||||
|  | 	if(hl_substitution != None) { | ||||||
|  | 		// EX DE, HL is not affected. | ||||||
|  | 		if(instruction.operation == Instruction::Operation::EX) return; | ||||||
|  |  | ||||||
|  | 		// If an (HL) is involved, switch it for IX+d or IY+d. | ||||||
|  | 		if(	instruction.source == Instruction::Location::HL_Indirect || | ||||||
|  | 			instruction.destination == Instruction::Location::HL_Indirect) { | ||||||
|  |  | ||||||
|  | 			if(instruction.source == Instruction::Location::HL_Indirect) { | ||||||
|  | 				instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset; | ||||||
|  | 			} | ||||||
|  | 			if(instruction.destination == Instruction::Location::HL_Indirect) { | ||||||
|  | 				instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset; | ||||||
|  | 			} | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Otherwise, switch either of H or L for I[X/Y]h and I[X/Y]l. | ||||||
|  | 		if(instruction.source == Instruction::Location::H) { | ||||||
|  | 			instruction.source = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh; | ||||||
|  | 		} | ||||||
|  | 		if(instruction.source == Instruction::Location::L) { | ||||||
|  | 			instruction.source = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl; | ||||||
|  | 		} | ||||||
|  | 		if(instruction.destination == Instruction::Location::H) { | ||||||
|  | 			instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh; | ||||||
|  | 		} | ||||||
|  | 		if(instruction.destination == Instruction::Location::L) { | ||||||
|  | 			instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct Z80Disassembler { | ||||||
|  | 	static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) { | ||||||
|  | 		disassembly.disassembly.internal_calls.insert(entry_point); | ||||||
|  | 		Accessor accessor(memory, address_mapper, entry_point); | ||||||
|  |  | ||||||
|  | 		while(!accessor.at_end()) { | ||||||
|  | 			Instruction instruction; | ||||||
|  | 			instruction.address = accessor.address(); | ||||||
|  |  | ||||||
|  | 			DisassembleMainPage(accessor, instruction); | ||||||
|  |  | ||||||
|  | 			// If any memory access was invalid, end disassembly. | ||||||
|  | 			if(accessor.overrun()) return; | ||||||
|  |  | ||||||
|  | 			// Store the instruction away. | ||||||
|  | 			disassembly.disassembly.instructions_by_address[instruction.address] = instruction; | ||||||
|  |  | ||||||
|  | 			// Update access tables. | ||||||
|  | 			int access_type = | ||||||
|  | 				((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) | | ||||||
|  | 				((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0); | ||||||
|  | 			uint16_t address = static_cast<uint16_t>(instruction.operand); | ||||||
|  | 			bool is_internal = address_mapper(address) < memory.size(); | ||||||
|  | 			switch(access_type) { | ||||||
|  | 				default: break; | ||||||
|  | 				case 1: | ||||||
|  | 					if(is_internal) { | ||||||
|  | 						disassembly.disassembly.internal_loads.insert(address); | ||||||
|  | 					} else { | ||||||
|  | 						disassembly.disassembly.external_loads.insert(address); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 2: | ||||||
|  | 					if(is_internal) { | ||||||
|  | 						disassembly.disassembly.internal_stores.insert(address); | ||||||
|  | 					} else { | ||||||
|  | 						disassembly.disassembly.external_stores.insert(address); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 3: | ||||||
|  | 					if(is_internal) { | ||||||
|  | 						disassembly.disassembly.internal_modifies.insert(address); | ||||||
|  | 					} else { | ||||||
|  | 						disassembly.disassembly.internal_modifies.insert(address); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Add any (potentially) newly discovered entry point. | ||||||
|  | 			if(	instruction.operation == Instruction::Operation::JP || | ||||||
|  | 				instruction.operation == Instruction::Operation::JR || | ||||||
|  | 				instruction.operation == Instruction::Operation::CALL || | ||||||
|  | 				instruction.operation == Instruction::Operation::RST) { | ||||||
|  | 				disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// This is it if: an unconditional RET, RETI, RETN, JP or JR is found. | ||||||
|  | 			if(instruction.condition != Instruction::Condition::None)	continue; | ||||||
|  |  | ||||||
|  | 			if(instruction.operation == Instruction::Operation::RET)	return; | ||||||
|  | 			if(instruction.operation == Instruction::Operation::RETI)	return; | ||||||
|  | 			if(instruction.operation == Instruction::Operation::RETN)	return; | ||||||
|  | 			if(instruction.operation == Instruction::Operation::JP)		return; | ||||||
|  | 			if(instruction.operation == Instruction::Operation::JR)		return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }	// end of anonymous namespace | ||||||
|  |  | ||||||
|  | Disassembly Analyser::Static::Z80::Disassemble( | ||||||
|  | 	const std::vector<uint8_t> &memory, | ||||||
|  | 	const std::function<std::size_t(uint16_t)> &address_mapper, | ||||||
|  | 	std::vector<uint16_t> entry_points) { | ||||||
|  | 	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points); | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								Analyser/Static/Disassembler/Z80.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								Analyser/Static/Disassembler/Z80.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | // | ||||||
|  | //  Z80.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 30/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StaticAnalyser_Disassembler_Z80_hpp | ||||||
|  | #define StaticAnalyser_Disassembler_Z80_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <functional> | ||||||
|  | #include <map> | ||||||
|  | #include <set> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Z80 { | ||||||
|  |  | ||||||
|  | struct Instruction { | ||||||
|  | 	/*! The address this instruction starts at. This is a mapped address. */ | ||||||
|  | 	uint16_t address = 0; | ||||||
|  |  | ||||||
|  | 	/*! The operation this instruction performs. */ | ||||||
|  | 	enum class Operation { | ||||||
|  | 		NOP, | ||||||
|  | 		EXAFAFd, EXX, EX, | ||||||
|  | 		LD, HALT, | ||||||
|  | 		ADD, ADC, SUB, SBC, AND, XOR, OR, CP, | ||||||
|  | 		INC, DEC, | ||||||
|  | 		RLCA, RRCA, RLA, RRA, DAA, CPL, SCF, CCF, | ||||||
|  | 		RLD, RRD, | ||||||
|  | 		DJNZ, JR, JP, CALL, RST, RET, RETI, RETN, | ||||||
|  | 		PUSH, POP, | ||||||
|  | 		IN, OUT, | ||||||
|  | 		EI, DI, | ||||||
|  | 		RLC, RRC, RL, RR, SLA, SRA, SLL, SRL, | ||||||
|  | 		BIT, RES, SET, | ||||||
|  | 		LDI, CPI, INI, OUTI, | ||||||
|  | 		LDD, CPD, IND, OUTD, | ||||||
|  | 		LDIR, CPIR, INIR, OTIR, | ||||||
|  | 		LDDR, CPDR, INDR, OTDR, | ||||||
|  | 		NEG, | ||||||
|  | 		IM, | ||||||
|  | 		Invalid | ||||||
|  | 	} operation = Operation::NOP; | ||||||
|  |  | ||||||
|  | 	/*! The condition required for this instruction to take effect. */ | ||||||
|  | 	enum class Condition { | ||||||
|  | 		None, NZ, Z, NC, C, PO, PE, P, M | ||||||
|  | 	} condition = Condition::None; | ||||||
|  |  | ||||||
|  | 	enum class Location { | ||||||
|  | 		B, C, D, E, H, L, HL_Indirect, A, I, R, | ||||||
|  | 		BC, DE, HL, SP, AF, Operand, | ||||||
|  | 		IX_Indirect_Offset, IY_Indirect_Offset, IXh, IXl, IYh, IYl, | ||||||
|  | 		Operand_Indirect, | ||||||
|  | 		BC_Indirect, DE_Indirect, SP_Indirect, | ||||||
|  | 		None | ||||||
|  | 	}; | ||||||
|  | 	/*! The locations of source data for this instruction. */ | ||||||
|  | 	Location source = Location::None; | ||||||
|  | 	/*! The locations of destination data from this instruction. */ | ||||||
|  | 	Location destination = Location::None; | ||||||
|  | 	/*! The operand, if any; if this is used then it'll be referenced by either the source or destination location. */ | ||||||
|  | 	int operand = 0; | ||||||
|  | 	/*! The offset to apply, if any; applies to IX_Indirect_Offset and IY_Indirect_Offset locations. */ | ||||||
|  | 	int offset = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct Disassembly { | ||||||
|  | 	std::map<uint16_t, Instruction> instructions_by_address; | ||||||
|  | 	std::set<uint16_t> outward_calls; | ||||||
|  | 	std::set<uint16_t> internal_calls; | ||||||
|  | 	std::set<uint16_t> external_stores, external_loads, external_modifies; | ||||||
|  | 	std::set<uint16_t> internal_stores, internal_loads, internal_modifies; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Disassembly Disassemble( | ||||||
|  | 	const std::vector<uint8_t> &memory, | ||||||
|  | 	const std::function<std::size_t(uint16_t)> &address_mapper, | ||||||
|  | 	std::vector<uint16_t> entry_points); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* StaticAnalyser_Disassembler_Z80_hpp */ | ||||||
							
								
								
									
										40
									
								
								Analyser/Static/MSX/Cartridge.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Analyser/Static/MSX/Cartridge.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // | ||||||
|  | //  Cartridge.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Cartridge_hpp | ||||||
|  | #define Cartridge_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Storage/Cartridge/Cartridge.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Extends the base cartridge class by adding a (guess at) the banking scheme. | ||||||
|  | */ | ||||||
|  | struct Cartridge: public ::Storage::Cartridge::Cartridge { | ||||||
|  | 	enum Type { | ||||||
|  | 		None, | ||||||
|  | 		Konami, | ||||||
|  | 		KonamiWithSCC, | ||||||
|  | 		ASCII8kb, | ||||||
|  | 		ASCII16kb, | ||||||
|  | 		FMPac | ||||||
|  | 	}; | ||||||
|  | 	const Type type; | ||||||
|  |  | ||||||
|  | 	Cartridge(const std::vector<Segment> &segments, Type type) : | ||||||
|  | 		Storage::Cartridge::Cartridge(segments), type(type) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Cartridge_hpp */ | ||||||
							
								
								
									
										295
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,295 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #include "Cartridge.hpp" | ||||||
|  | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
|  | #include "../Disassembler/Z80.hpp" | ||||||
|  | #include "../Disassembler/AddressMapper.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | ||||||
|  | 	const Storage::Cartridge::Cartridge::Segment &segment, | ||||||
|  | 	uint16_t start_address, | ||||||
|  | 	Analyser::Static::MSX::Cartridge::Type type, | ||||||
|  | 	float confidence) { | ||||||
|  |  | ||||||
|  | 	// Size down to a multiple of 8kb in size and apply the start address. | ||||||
|  | 	std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||||
|  | 	if(segment.data.size() & 0x1fff) { | ||||||
|  | 		std::vector<uint8_t> truncated_data; | ||||||
|  | 		std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff; | ||||||
|  | 		truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); | ||||||
|  | 		output_segments.emplace_back(start_address, truncated_data); | ||||||
|  | 	} else { | ||||||
|  | 		output_segments.emplace_back(start_address, segment.data); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target); | ||||||
|  | 	target->machine = Analyser::Machine::MSX; | ||||||
|  | 	target->confidence = confidence; | ||||||
|  |  | ||||||
|  | 	if(type == Analyser::Static::MSX::Cartridge::Type::None) { | ||||||
|  | 		target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | ||||||
|  | 	} else { | ||||||
|  | 		target->media.cartridges.emplace_back(new Analyser::Static::MSX::Cartridge(output_segments, type)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return target; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Expected standard cartridge format: | ||||||
|  |  | ||||||
|  | 		DEFB "AB" ; expansion ROM header | ||||||
|  | 		DEFW initcode ; start of the init code, 0 if no initcode | ||||||
|  | 		DEFW callstat; pointer to CALL statement handler, 0 if no such handler | ||||||
|  | 		DEFW device; pointer to expansion device handler, 0 if no such handler | ||||||
|  | 		DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram | ||||||
|  | 		DEFS 6,0 ; room reserved for future extensions | ||||||
|  |  | ||||||
|  | 	MSX cartridges often include banking hardware; those games were marketed as MegaROMs. The file | ||||||
|  | 	format that the MSX community has decided upon doesn't retain the type of hardware included, so | ||||||
|  | 	this analyser has to guess. | ||||||
|  |  | ||||||
|  | 	(additional audio hardware is also sometimes included, but it's implied by the banking hardware) | ||||||
|  | */ | ||||||
|  | static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom( | ||||||
|  | 	const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
|  | 	// No cartridges implies no targets. | ||||||
|  | 	if(cartridges.empty()) { | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::vector<std::unique_ptr<Analyser::Static::Target>> targets; | ||||||
|  | 	for(const auto &cartridge : cartridges) { | ||||||
|  | 		const auto &segments = cartridge->get_segments(); | ||||||
|  |  | ||||||
|  | 		// Only one mapped item is allowed. | ||||||
|  | 		if(segments.size() != 1) continue; | ||||||
|  |  | ||||||
|  | 		// Which must be no more than 63 bytes larger than a multiple of 8 kb in size. | ||||||
|  | 		Storage::Cartridge::Cartridge::Segment segment = segments.front(); | ||||||
|  | 		const size_t data_size = segment.data.size(); | ||||||
|  | 		if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue; | ||||||
|  |  | ||||||
|  | 		// Check for a ROM header at address 0; if it's not found then try 0x4000 | ||||||
|  | 		// and adjust the start address; | ||||||
|  | 		uint16_t start_address = 0; | ||||||
|  | 		bool found_start = false; | ||||||
|  | 		if(segment.data[0] == 0x41 && segment.data[1] == 0x42) { | ||||||
|  | 			start_address = 0x4000; | ||||||
|  | 			found_start = true; | ||||||
|  | 		} else if(segment.data.size() >= 0x8000 && segment.data[0x4000] == 0x41 && segment.data[0x4001] == 0x42) { | ||||||
|  | 			start_address = 0; | ||||||
|  | 			found_start = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Reject cartridge if the ROM header wasn't found. | ||||||
|  | 		if(!found_start) continue; | ||||||
|  |  | ||||||
|  | 		uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8)); | ||||||
|  | 		// TODO: check for a rational init address? | ||||||
|  |  | ||||||
|  | 		// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on. | ||||||
|  | 		if(data_size <= 0xc000) { | ||||||
|  | 			targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0)); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// If this ROM is greater than 48kb in size then some sort of MegaROM scheme must | ||||||
|  | 		// be at play; disassemble to try to figure it out. | ||||||
|  | 		std::vector<uint8_t> first_8k; | ||||||
|  | 		first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192); | ||||||
|  | 		Analyser::Static::Z80::Disassembly disassembly = | ||||||
|  | 			Analyser::Static::Z80::Disassemble( | ||||||
|  | 				first_8k, | ||||||
|  | 				Analyser::Static::Disassembler::OffsetMapper(start_address), | ||||||
|  | 				{ init_address } | ||||||
|  | 			); | ||||||
|  |  | ||||||
|  | //		// Look for a indirect store followed by an unconditional JP or CALL into another | ||||||
|  | //		// segment, that's a fairly explicit sign where found. | ||||||
|  | 		using Instruction = Analyser::Static::Z80::Instruction; | ||||||
|  | 		std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address; | ||||||
|  | 		bool is_ascii = false; | ||||||
|  | //		auto iterator = instructions.begin(); | ||||||
|  | //		while(iterator != instructions.end()) { | ||||||
|  | //			auto next_iterator = iterator; | ||||||
|  | //			next_iterator++; | ||||||
|  | //			if(next_iterator == instructions.end()) break; | ||||||
|  | // | ||||||
|  | //			if(	iterator->second.operation == Instruction::Operation::LD && | ||||||
|  | //				iterator->second.destination == Instruction::Location::Operand_Indirect && | ||||||
|  | //				( | ||||||
|  | //					iterator->second.operand == 0x5000 || | ||||||
|  | //					iterator->second.operand == 0x6000 || | ||||||
|  | //					iterator->second.operand == 0x6800 || | ||||||
|  | //					iterator->second.operand == 0x7000 || | ||||||
|  | //					iterator->second.operand == 0x77ff || | ||||||
|  | //					iterator->second.operand == 0x7800 || | ||||||
|  | //					iterator->second.operand == 0x8000 || | ||||||
|  | //					iterator->second.operand == 0x9000 || | ||||||
|  | //					iterator->second.operand == 0xa000 | ||||||
|  | //				) && | ||||||
|  | //				( | ||||||
|  | //					next_iterator->second.operation == Instruction::Operation::CALL || | ||||||
|  | //					next_iterator->second.operation == Instruction::Operation::JP | ||||||
|  | //				) && | ||||||
|  | //				((next_iterator->second.operand >> 13) != (0x4000 >> 13)) | ||||||
|  | //			) { | ||||||
|  | //				const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand); | ||||||
|  | //				switch(iterator->second.operand) { | ||||||
|  | //					case 0x6000: | ||||||
|  | //						if(address >= 0x6000 && address < 0x8000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x6800: | ||||||
|  | //						if(address >= 0x6000 && address < 0x6800) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x7000: | ||||||
|  | //						if(address >= 0x6000 && address < 0x8000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //						if(address >= 0x7000 && address < 0x7800) { | ||||||
|  | //							is_ascii = true; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x77ff: | ||||||
|  | //						if(address >= 0x7000 && address < 0x7800) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x7800: | ||||||
|  | //						if(address >= 0xa000 && address < 0xc000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x8000: | ||||||
|  | //						if(address >= 0x8000 && address < 0xa000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x9000: | ||||||
|  | //						if(address >= 0x8000 && address < 0xa000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0xa000: | ||||||
|  | //						if(address >= 0xa000 && address < 0xc000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0xb000: | ||||||
|  | //						if(address >= 0xa000 && address < 0xc000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //				} | ||||||
|  | //			} | ||||||
|  | // | ||||||
|  | //			iterator = next_iterator; | ||||||
|  |  | ||||||
|  | 		// Look for LD (nnnn), A instructions, and collate those addresses. | ||||||
|  | 		std::map<uint16_t, int> address_counts; | ||||||
|  | 		for(const auto &instruction_pair : instructions) { | ||||||
|  | 			if(	instruction_pair.second.operation == Instruction::Operation::LD && | ||||||
|  | 				instruction_pair.second.destination == Instruction::Location::Operand_Indirect && | ||||||
|  | 				instruction_pair.second.source == Instruction::Location::A) { | ||||||
|  | 				address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Weight confidences by number of observed hits. | ||||||
|  | 		float total_hits = | ||||||
|  | 			static_cast<float>( | ||||||
|  | 				address_counts[0x6000] + address_counts[0x6800] + | ||||||
|  | 				address_counts[0x7000] + address_counts[0x7800] + | ||||||
|  | 				address_counts[0x77ff] + address_counts[0x8000] + | ||||||
|  | 				address_counts[0xa000] + address_counts[0x5000] + | ||||||
|  | 				address_counts[0x9000] + address_counts[0xb000] | ||||||
|  | 			); | ||||||
|  |  | ||||||
|  | 		targets.push_back(CartridgeTarget( | ||||||
|  | 			segment, | ||||||
|  | 			start_address, | ||||||
|  | 			Analyser::Static::MSX::Cartridge::ASCII8kb, | ||||||
|  | 			static_cast<float>(	address_counts[0x6000] + | ||||||
|  | 								address_counts[0x6800] + | ||||||
|  | 								address_counts[0x7000] + | ||||||
|  | 								address_counts[0x7800]) / total_hits)); | ||||||
|  | 		targets.push_back(CartridgeTarget( | ||||||
|  | 			segment, | ||||||
|  | 			start_address, | ||||||
|  | 			Analyser::Static::MSX::Cartridge::ASCII16kb, | ||||||
|  | 			static_cast<float>(	address_counts[0x6000] + | ||||||
|  | 								address_counts[0x7000] + | ||||||
|  | 								address_counts[0x77ff]) / total_hits)); | ||||||
|  | 		if(!is_ascii) { | ||||||
|  | 			targets.push_back(CartridgeTarget( | ||||||
|  | 				segment, | ||||||
|  | 				start_address, | ||||||
|  | 				Analyser::Static::MSX::Cartridge::Konami, | ||||||
|  | 				static_cast<float>(	address_counts[0x6000] + | ||||||
|  | 									address_counts[0x8000] + | ||||||
|  | 									address_counts[0xa000]) / total_hits)); | ||||||
|  | 		} | ||||||
|  | 		if(!is_ascii) { | ||||||
|  | 			targets.push_back(CartridgeTarget( | ||||||
|  | 				segment, | ||||||
|  | 				start_address, | ||||||
|  | 				Analyser::Static::MSX::Cartridge::KonamiWithSCC, | ||||||
|  | 				static_cast<float>(	address_counts[0x5000] + | ||||||
|  | 									address_counts[0x7000] + | ||||||
|  | 									address_counts[0x9000] + | ||||||
|  | 									address_counts[0xb000]) / total_hits)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return targets; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) { | ||||||
|  | 	// Append targets for any cartridges that look correct. | ||||||
|  | 	auto cartridge_targets = CartridgeTargetsFrom(media.cartridges); | ||||||
|  | 	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); | ||||||
|  |  | ||||||
|  | 	// Consider building a target for disks and/or tapes. | ||||||
|  | 	std::unique_ptr<Target> target(new Target); | ||||||
|  |  | ||||||
|  | 	// Check tapes for loadable files. | ||||||
|  | 	for(const auto &tape : media.tapes) { | ||||||
|  | 		std::vector<File> files_on_tape = GetFiles(tape); | ||||||
|  | 		if(!files_on_tape.empty()) { | ||||||
|  | 			switch(files_on_tape.front().type) { | ||||||
|  | 				case File::Type::ASCII:				target->loading_command = "RUN\"CAS:\r";		break; | ||||||
|  | 				case File::Type::TokenisedBASIC:	target->loading_command = "CLOAD\rRUN\r";		break; | ||||||
|  | 				case File::Type::Binary:			target->loading_command = "BLOAD\"CAS:\",R\r";	break; | ||||||
|  | 				default: break; | ||||||
|  | 			} | ||||||
|  | 			target->media.tapes.push_back(tape); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Blindly accept disks for now. | ||||||
|  | 	target->media.disks = media.disks; | ||||||
|  | 	target->has_disk_drive = !media.disks.empty(); | ||||||
|  |  | ||||||
|  | 	if(!target->media.empty()) { | ||||||
|  | 		target->machine = Machine::MSX; | ||||||
|  | 		target->confidence = 0.5; | ||||||
|  | 		destination.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								Analyser/Static/MSX/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Analyser/Static/MSX/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StaticAnalyser_MSX_StaticAnalyser_hpp | ||||||
|  | #define StaticAnalyser_MSX_StaticAnalyser_hpp | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */ | ||||||
							
								
								
									
										165
									
								
								Analyser/Static/MSX/Tape.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								Analyser/Static/MSX/Tape.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | |||||||
|  | // | ||||||
|  | //  Tape.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Tape.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Storage/Tape/Parsers/MSX.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Static::MSX; | ||||||
|  |  | ||||||
|  | File::File(File &&rhs) : | ||||||
|  | 	name(std::move(rhs.name)), | ||||||
|  | 	type(rhs.type), | ||||||
|  | 	data(std::move(rhs.data)), | ||||||
|  | 	starting_address(rhs.starting_address), | ||||||
|  | 	entry_address(rhs.entry_address) {} | ||||||
|  |  | ||||||
|  | File::File() : | ||||||
|  | 	type(Type::Binary), | ||||||
|  | 	starting_address(0), | ||||||
|  | 	entry_address(0) {}	// For the sake of initialising in a defined state. | ||||||
|  |  | ||||||
|  | std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
|  | 	std::vector<File> files; | ||||||
|  |  | ||||||
|  | 	Storage::Tape::BinaryTapePlayer tape_player(1000000); | ||||||
|  | 	tape_player.set_motor_control(true); | ||||||
|  | 	tape_player.set_tape(tape); | ||||||
|  |  | ||||||
|  | 	using Parser = Storage::Tape::MSX::Parser; | ||||||
|  |  | ||||||
|  | 	// Get all recognisable files from the tape. | ||||||
|  | 	while(!tape->is_at_end()) { | ||||||
|  | 		// Try to locate and measure a header. | ||||||
|  | 		std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player); | ||||||
|  | 		if(!file_speed) continue; | ||||||
|  |  | ||||||
|  | 		// Check whether what follows is a recognisable file type. | ||||||
|  | 		uint8_t header[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; | ||||||
|  | 		for(std::size_t c = 0; c < sizeof(header); ++c) { | ||||||
|  | 			int next_byte = Parser::get_byte(*file_speed, tape_player); | ||||||
|  | 			if(next_byte == -1) break; | ||||||
|  | 			header[c] = static_cast<uint8_t>(next_byte); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool bytes_are_same = true; | ||||||
|  | 		for(std::size_t c = 1; c < sizeof(header); ++c) | ||||||
|  | 			bytes_are_same &= (header[c] == header[0]); | ||||||
|  |  | ||||||
|  | 		if(!bytes_are_same) continue; | ||||||
|  | 		if(header[0] != 0xd0 && header[0] != 0xd3 && header[0] != 0xea) continue; | ||||||
|  |  | ||||||
|  | 		File file; | ||||||
|  |  | ||||||
|  | 		// Determine file type from information already collected. | ||||||
|  | 		switch(header[0]) { | ||||||
|  | 			case 0xd0:	file.type = File::Type::Binary;			break; | ||||||
|  | 			case 0xd3:	file.type = File::Type::TokenisedBASIC;	break; | ||||||
|  | 			case 0xea:	file.type = File::Type::ASCII;			break; | ||||||
|  | 			default: break;	// Unreachable. | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Read file name. | ||||||
|  | 		char name[7]; | ||||||
|  | 		for(std::size_t c = 1; c < 6; ++c) | ||||||
|  | 			name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player)); | ||||||
|  | 		name[6] = '\0'; | ||||||
|  | 		file.name = name; | ||||||
|  |  | ||||||
|  | 		// ASCII: Read 256-byte segments until one ends with an end-of-file character. | ||||||
|  | 		if(file.type == File::Type::ASCII) { | ||||||
|  | 			while(true) { | ||||||
|  | 				file_speed = Parser::find_header(tape_player); | ||||||
|  | 				if(!file_speed) break; | ||||||
|  | 				int c = 256; | ||||||
|  | 				bool contains_end_of_file = false; | ||||||
|  | 				while(c--) { | ||||||
|  | 					int byte = Parser::get_byte(*file_speed, tape_player); | ||||||
|  | 					if(byte == -1) break; | ||||||
|  | 					contains_end_of_file |= (byte == 0x1a); | ||||||
|  | 					file.data.push_back(static_cast<uint8_t>(byte)); | ||||||
|  | 				} | ||||||
|  | 				if(c != -1) break; | ||||||
|  | 				if(contains_end_of_file) { | ||||||
|  | 					files.push_back(std::move(file)); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Read a single additional segment, using the information at the begging to determine length. | ||||||
|  | 		file_speed = Parser::find_header(tape_player); | ||||||
|  | 		if(!file_speed) continue; | ||||||
|  |  | ||||||
|  | 		// Binary: read start address, end address, entry address, then that many bytes. | ||||||
|  | 		if(file.type == File::Type::Binary) { | ||||||
|  | 			uint8_t locations[6]; | ||||||
|  | 			uint16_t end_address; | ||||||
|  | 			std::size_t c; | ||||||
|  | 			for(c = 0; c < sizeof(locations); ++c) { | ||||||
|  | 				int byte = Parser::get_byte(*file_speed, tape_player); | ||||||
|  | 				if(byte == -1) break; | ||||||
|  | 				locations[c] = static_cast<uint8_t>(byte); | ||||||
|  | 			} | ||||||
|  | 			if(c != sizeof(locations)) continue; | ||||||
|  |  | ||||||
|  | 			file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8)); | ||||||
|  | 			end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8)); | ||||||
|  | 			file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8)); | ||||||
|  |  | ||||||
|  | 			if(end_address < file.starting_address) continue; | ||||||
|  |  | ||||||
|  | 			std::size_t length = end_address - file.starting_address; | ||||||
|  | 			while(length--) { | ||||||
|  | 				int byte = Parser::get_byte(*file_speed, tape_player); | ||||||
|  | 				if(byte == -1) continue; | ||||||
|  | 				file.data.push_back(static_cast<uint8_t>(byte)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			files.push_back(std::move(file)); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Tokenised BASIC, then: keep following 'next line' links from a hypothetical start of | ||||||
|  | 		// 0x8001, until finding the final line. | ||||||
|  | 		uint16_t current_address = 0x8001; | ||||||
|  | 		while(current_address) { | ||||||
|  | 			int next_address_buffer[2]; | ||||||
|  | 			next_address_buffer[0] = Parser::get_byte(*file_speed, tape_player); | ||||||
|  | 			next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player); | ||||||
|  |  | ||||||
|  | 			if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break; | ||||||
|  | 			file.data.push_back(static_cast<uint8_t>(next_address_buffer[0])); | ||||||
|  | 			file.data.push_back(static_cast<uint8_t>(next_address_buffer[1])); | ||||||
|  |  | ||||||
|  | 			uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8)); | ||||||
|  | 			if(!next_address) { | ||||||
|  | 				files.push_back(std::move(file)); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			if(next_address < current_address+2) break; | ||||||
|  |  | ||||||
|  | 			// This line makes sense, so push it all in. | ||||||
|  | 			std::size_t length = next_address - current_address - 2; | ||||||
|  | 			current_address = next_address; | ||||||
|  | 			bool found_error = false; | ||||||
|  | 			while(length--) { | ||||||
|  | 				int byte = Parser::get_byte(*file_speed, tape_player); | ||||||
|  | 				if(byte == -1) { | ||||||
|  | 					found_error = true; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				file.data.push_back(static_cast<uint8_t>(byte)); | ||||||
|  | 			} | ||||||
|  | 			if(found_error) break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return files; | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								Analyser/Static/MSX/Tape.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Analyser/Static/MSX/Tape.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | // | ||||||
|  | //  Tape.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StaticAnalyser_MSX_Tape_hpp | ||||||
|  | #define StaticAnalyser_MSX_Tape_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Storage/Tape/Tape.hpp" | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | struct File { | ||||||
|  | 	std::string name; | ||||||
|  | 	enum Type { | ||||||
|  | 		Binary, | ||||||
|  | 		TokenisedBASIC, | ||||||
|  | 		ASCII | ||||||
|  | 	} type; | ||||||
|  |  | ||||||
|  | 	std::vector<uint8_t> data; | ||||||
|  |  | ||||||
|  | 	uint16_t starting_address;	// Provided only for Type::Binary files. | ||||||
|  | 	uint16_t entry_address;		// Provided only for Type::Binary files. | ||||||
|  |  | ||||||
|  | 	File(File &&rhs); | ||||||
|  | 	File(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* StaticAnalyser_MSX_Tape_hpp */ | ||||||
							
								
								
									
										26
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 02/04/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_MSX_Target_h | ||||||
|  | #define Analyser_Static_MSX_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool has_disk_drive = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_MSX_Target_h */ | ||||||
| @@ -9,11 +9,18 @@ | |||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| #include "../Disassembler/Disassembler6502.hpp" | #include "Target.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Oric; | #include "../Disassembler/6502.hpp" | ||||||
|  | #include "../Disassembler/AddressMapper.hpp" | ||||||
| 
 | 
 | ||||||
| static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
|  | 
 | ||||||
|  | #include <cstring> | ||||||
|  | 
 | ||||||
|  | using namespace Analyser::Static::Oric; | ||||||
|  | 
 | ||||||
|  | static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | ||||||
| 	int score = 0; | 	int score = 0; | ||||||
| 
 | 
 | ||||||
| 	for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1; | 	for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1; | ||||||
| @@ -23,7 +30,7 @@ static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const | |||||||
| 	return score; | 	return score; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	std::set<uint16_t> rom_functions = { | 	std::set<uint16_t> rom_functions = { | ||||||
| 		0x0228,	0x022b, | 		0x0228,	0x022b, | ||||||
| 		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738, | 		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738, | ||||||
| @@ -47,7 +54,7 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) | |||||||
| 	return Score(disassembly, rom_functions, variable_locations); | 	return Score(disassembly, rom_functions, variable_locations); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	std::set<uint16_t> rom_functions = { | 	std::set<uint16_t> rom_functions = { | ||||||
| 		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247, | 		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247, | ||||||
| 		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d, | 		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d, | ||||||
| @@ -72,23 +79,44 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) | |||||||
| 	return Score(disassembly, rom_functions, variable_locations); | 	return Score(disassembly, rom_functions, variable_locations); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &destination) { | static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) { | ||||||
| 	Target target; | 	/*
 | ||||||
| 	target.machine = Target::Oric; | 		The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature. | ||||||
| 	target.probability = 1.0; | 	*/ | ||||||
|  | 	Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2); | ||||||
|  | 	if(!sector) return false; | ||||||
|  | 	if(sector->samples.empty()) return false; | ||||||
|  | 
 | ||||||
|  | 	const std::vector<uint8_t> &first_sample = sector->samples[0]; | ||||||
|  | 	if(first_sample.size() != 256) return false; | ||||||
|  | 
 | ||||||
|  | 	const uint8_t signature[] = { | ||||||
|  | 		0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0, | ||||||
|  | 		0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00, | ||||||
|  | 		0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6, | ||||||
|  | 		0x12, 0x00 | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	return !std::memcmp(signature, first_sample.data(), sizeof(signature)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) { | ||||||
|  | 	std::unique_ptr<Target> target(new Target); | ||||||
|  | 	target->machine = Machine::Oric; | ||||||
|  | 	target->confidence = 0.5; | ||||||
| 
 | 
 | ||||||
| 	int basic10_votes = 0; | 	int basic10_votes = 0; | ||||||
| 	int basic11_votes = 0; | 	int basic11_votes = 0; | ||||||
| 
 | 
 | ||||||
| 	for(auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::list<File> tape_files = GetFiles(tape); | 		std::vector<File> tape_files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 		if(tape_files.size()) { | 		if(tape_files.size()) { | ||||||
| 			for(auto file : tape_files) { | 			for(auto file : tape_files) { | ||||||
| 				if(file.data_type == File::MachineCode) { | 				if(file.data_type == File::MachineCode) { | ||||||
| 					std::vector<uint16_t> entry_points = {file.starting_address}; | 					std::vector<uint16_t> entry_points = {file.starting_address}; | ||||||
| 					StaticAnalyser::MOS6502::Disassembly disassembly = | 					Analyser::Static::MOS6502::Disassembly disassembly = | ||||||
| 						StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::MOS6502::OffsetMapper(file.starting_address), entry_points); | 						Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points); | ||||||
| 
 | 
 | ||||||
| 					int basic10_score = Basic10Score(disassembly); | 					int basic10_score = Basic10Score(disassembly); | ||||||
| 					int basic11_score = Basic11Score(disassembly); | 					int basic11_score = Basic11Score(disassembly); | ||||||
| @@ -96,23 +124,28 @@ void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &des | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			target.media.tapes.push_back(tape); | 			target->media.tapes.push_back(tape); | ||||||
| 			target.loadingCommand = "CLOAD\"\"\n"; | 			target->loading_command = "CLOAD\"\"\n"; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// trust that any disk supplied can be handled by the Microdisc. TODO: check.
 |  | ||||||
| 	if(!media.disks.empty()) { | 	if(!media.disks.empty()) { | ||||||
| 		target.oric.has_microdisc = true; | 		// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
 | ||||||
| 		target.media.disks = media.disks; | 		for(const auto &disk: media.disks) { | ||||||
|  | 			Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
|  | 			if(IsMicrodisc(parser)) { | ||||||
|  | 				target->has_microdrive = true; | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		target.oric.has_microdisc = false; | 		target->has_microdrive = false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// TODO: really this should add two targets if not all votes agree
 | 	// TODO: really this should add two targets if not all votes agree
 | ||||||
| 	target.oric.use_atmos_rom = basic11_votes >= basic10_votes; | 	target->use_atmos_rom = basic11_votes >= basic10_votes; | ||||||
| 	if(target.oric.has_microdisc) target.oric.use_atmos_rom = true; | 	if(target->has_microdrive) target->use_atmos_rom = true; | ||||||
| 
 | 
 | ||||||
| 	if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size()) | 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | ||||||
| 		destination.push_back(target); | 		destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,13 +11,14 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Oric { | namespace Oric { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | } | ||||||
| 
 | 
 | ||||||
| #endif /* StaticAnalyser_hpp */ | #endif /* StaticAnalyser_hpp */ | ||||||
| @@ -7,12 +7,12 @@ | |||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| #include "../../Storage/Tape/Parsers/Oric.hpp" | #include "../../../Storage/Tape/Parsers/Oric.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Oric; | using namespace Analyser::Static::Oric; | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	Storage::Tape::Oric::Parser parser; | 	Storage::Tape::Oric::Parser parser; | ||||||
| 
 | 
 | ||||||
| 	while(!tape->is_at_end()) { | 	while(!tape->is_at_end()) { | ||||||
| @@ -69,9 +69,9 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta | |||||||
| 		new_file.name = file_name; | 		new_file.name = file_name; | ||||||
| 
 | 
 | ||||||
| 		// read body
 | 		// read body
 | ||||||
| 		size_t body_length = new_file.ending_address - new_file.starting_address + 1; | 		std::size_t body_length = new_file.ending_address - new_file.starting_address + 1; | ||||||
| 		new_file.data.reserve(body_length); | 		new_file.data.reserve(body_length); | ||||||
| 		for(size_t c = 0; c < body_length; c++) { | 		for(std::size_t c = 0; c < body_length; c++) { | ||||||
| 			new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast))); | 			new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast))); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @@ -9,12 +9,13 @@ | |||||||
| #ifndef StaticAnalyser_Oric_Tape_hpp | #ifndef StaticAnalyser_Oric_Tape_hpp | ||||||
| #define StaticAnalyser_Oric_Tape_hpp | #define StaticAnalyser_Oric_Tape_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| #include <list> | 
 | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Oric { | namespace Oric { | ||||||
| 
 | 
 | ||||||
| struct File { | struct File { | ||||||
| @@ -30,8 +31,9 @@ struct File { | |||||||
| 	std::vector<uint8_t> data; | 	std::vector<uint8_t> data; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										25
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Oric_Target_h | ||||||
|  | #define Analyser_Static_Oric_Target_h | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Oric { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool use_atmos_rom = false; | ||||||
|  | 	bool has_microdrive = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Oric_Target_h */ | ||||||
							
								
								
									
										175
									
								
								Analyser/Static/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								Analyser/Static/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/08/2016. | ||||||
|  | //  Copyright © 2016 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  | #include <cstdlib> | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | // Analysers | ||||||
|  | #include "Acorn/StaticAnalyser.hpp" | ||||||
|  | #include "AmstradCPC/StaticAnalyser.hpp" | ||||||
|  | #include "Atari/StaticAnalyser.hpp" | ||||||
|  | #include "Coleco/StaticAnalyser.hpp" | ||||||
|  | #include "Commodore/StaticAnalyser.hpp" | ||||||
|  | #include "MSX/StaticAnalyser.hpp" | ||||||
|  | #include "Oric/StaticAnalyser.hpp" | ||||||
|  | #include "ZX8081/StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | // Cartridges | ||||||
|  | #include "../../Storage/Cartridge/Formats/BinaryDump.hpp" | ||||||
|  | #include "../../Storage/Cartridge/Formats/PRG.hpp" | ||||||
|  |  | ||||||
|  | // Disks | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" | ||||||
|  |  | ||||||
|  | // Tapes | ||||||
|  | #include "../../Storage/Tape/Formats/CAS.hpp" | ||||||
|  | #include "../../Storage/Tape/Formats/CommodoreTAP.hpp" | ||||||
|  | #include "../../Storage/Tape/Formats/CSW.hpp" | ||||||
|  | #include "../../Storage/Tape/Formats/OricTAP.hpp" | ||||||
|  | #include "../../Storage/Tape/Formats/TapePRG.hpp" | ||||||
|  | #include "../../Storage/Tape/Formats/TapeUEF.hpp" | ||||||
|  | #include "../../Storage/Tape/Formats/TZX.hpp" | ||||||
|  | #include "../../Storage/Tape/Formats/ZX80O81P.hpp" | ||||||
|  |  | ||||||
|  | // Target Platform Types | ||||||
|  | #include "../../Storage/TargetPlatforms.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Static; | ||||||
|  |  | ||||||
|  | static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) { | ||||||
|  | 	Media result; | ||||||
|  |  | ||||||
|  | 	// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase | ||||||
|  | 	// test as to file format. | ||||||
|  | 	std::string::size_type final_dot = file_name.find_last_of("."); | ||||||
|  | 	if(final_dot == std::string::npos) return result; | ||||||
|  | 	std::string extension = file_name.substr(final_dot + 1); | ||||||
|  | 	std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); | ||||||
|  |  | ||||||
|  | #define Insert(list, class, platforms) \ | ||||||
|  | 	list.emplace_back(new Storage::class(file_name));\ | ||||||
|  | 	potential_platforms |= platforms;\ | ||||||
|  | 	TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\ | ||||||
|  | 	if(distinguisher) potential_platforms &= distinguisher->target_platform_type(); | ||||||
|  |  | ||||||
|  | #define TryInsert(list, class, platforms) \ | ||||||
|  | 	try {\ | ||||||
|  | 		Insert(list, class, platforms) \ | ||||||
|  | 	} catch(...) {} | ||||||
|  |  | ||||||
|  | #define Format(ext, list, class, platforms) \ | ||||||
|  | 	if(extension == ext)	{		\ | ||||||
|  | 		TryInsert(list, class, platforms)	\ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 80 | ||||||
|  | 	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 81 | ||||||
|  | 	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// A26 | ||||||
|  | 	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF | ||||||
|  | 	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)					// BIN | ||||||
|  | 	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS | ||||||
|  | 	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)										// CDT | ||||||
|  | 	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)					// COL | ||||||
|  | 	Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape)											// CSW | ||||||
|  | 	Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)		// D64 | ||||||
|  | 	Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)				// DMK | ||||||
|  | 	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// DSD | ||||||
|  | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)	// DSK (Amstrad CPC) | ||||||
|  | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)			// DSK (MSX) | ||||||
|  | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)		// DSK (Oric) | ||||||
|  | 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)		// G64 | ||||||
|  | 	Format(	"hfe", | ||||||
|  | 			result.disks, | ||||||
|  | 			Disk::DiskImageHolder<Storage::Disk::HFE>, | ||||||
|  | 			TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric) | ||||||
|  | 			// HFE (TODO: switch to AllDisk once the MSX stops being so greedy) | ||||||
|  | 	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// O | ||||||
|  | 	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P | ||||||
|  | 	Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P81 | ||||||
|  |  | ||||||
|  | 	// PRG | ||||||
|  | 	if(extension == "prg") { | ||||||
|  | 		// try instantiating as a ROM; failing that accept as a tape | ||||||
|  | 		try { | ||||||
|  | 			Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore) | ||||||
|  | 		} catch(...) { | ||||||
|  | 			try { | ||||||
|  | 				Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore) | ||||||
|  | 			} catch(...) {} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	Format(	"rom", | ||||||
|  | 			result.cartridges, | ||||||
|  | 			Cartridge::BinaryDump, | ||||||
|  | 			TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX)				// ROM | ||||||
|  | 	Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD | ||||||
|  | 	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore) | ||||||
|  | 	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)										// TAP (Oric) | ||||||
|  | 	Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)												// TSX | ||||||
|  | 	Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081)											// TZX | ||||||
|  | 	Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)											// UEF (tape) | ||||||
|  |  | ||||||
|  | #undef Format | ||||||
|  | #undef Insert | ||||||
|  | #undef TryInsert | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Media Analyser::Static::GetMedia(const std::string &file_name) { | ||||||
|  | 	TargetPlatform::IntType throwaway; | ||||||
|  | 	return GetMediaAndPlatforms(file_name, throwaway); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::string &file_name) { | ||||||
|  | 	std::vector<std::unique_ptr<Target>> targets; | ||||||
|  |  | ||||||
|  | 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the | ||||||
|  | 	// union of all platforms this file might be a target for. | ||||||
|  | 	TargetPlatform::IntType potential_platforms = 0; | ||||||
|  | 	Media media = GetMediaAndPlatforms(file_name, potential_platforms); | ||||||
|  |  | ||||||
|  | 	// Hand off to platform-specific determination of whether these things are actually compatible and, | ||||||
|  | 	// if so, how to load them. | ||||||
|  | 	if(potential_platforms & TargetPlatform::Acorn)			Acorn::AddTargets(media, targets); | ||||||
|  | 	if(potential_platforms & TargetPlatform::AmstradCPC)	AmstradCPC::AddTargets(media, targets); | ||||||
|  | 	if(potential_platforms & TargetPlatform::Atari2600)		Atari::AddTargets(media, targets); | ||||||
|  | 	if(potential_platforms & TargetPlatform::ColecoVision)	Coleco::AddTargets(media, targets); | ||||||
|  | 	if(potential_platforms & TargetPlatform::Commodore)		Commodore::AddTargets(media, targets, file_name); | ||||||
|  | 	if(potential_platforms & TargetPlatform::MSX)			MSX::AddTargets(media, targets); | ||||||
|  | 	if(potential_platforms & TargetPlatform::Oric)			Oric::AddTargets(media, targets); | ||||||
|  | 	if(potential_platforms & TargetPlatform::ZX8081)		ZX8081::AddTargets(media, targets, potential_platforms); | ||||||
|  |  | ||||||
|  | 	// Reset any tapes to their initial position | ||||||
|  | 	for(auto &target : targets) { | ||||||
|  | 		for(auto &tape : target->media.tapes) { | ||||||
|  | 			tape->reset(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers | ||||||
|  | 	// picked their insertion order carefully. | ||||||
|  | 	std::stable_sort(targets.begin(), targets.end(), | ||||||
|  |         [] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) { | ||||||
|  | 		    return a->confidence > b->confidence; | ||||||
|  | 	    }); | ||||||
|  |  | ||||||
|  | 	return targets; | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								Analyser/Static/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Analyser/Static/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/08/2016. | ||||||
|  | //  Copyright © 2016 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StaticAnalyser_hpp | ||||||
|  | #define StaticAnalyser_hpp | ||||||
|  |  | ||||||
|  | #include "../Machines.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  | #include "../../Storage/Disk/Disk.hpp" | ||||||
|  | #include "../../Storage/Cartridge/Cartridge.hpp" | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A list of disks, tapes and cartridges. | ||||||
|  | */ | ||||||
|  | struct Media { | ||||||
|  | 	std::vector<std::shared_ptr<Storage::Disk::Disk>> disks; | ||||||
|  | 	std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes; | ||||||
|  | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges; | ||||||
|  |  | ||||||
|  | 	bool empty() const { | ||||||
|  | 		return disks.empty() && tapes.empty() && cartridges.empty(); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration, | ||||||
|  | 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | ||||||
|  | */ | ||||||
|  | struct Target { | ||||||
|  | 	virtual ~Target() {} | ||||||
|  |  | ||||||
|  | 	Machine machine; | ||||||
|  | 	Media media; | ||||||
|  |  | ||||||
|  | 	float confidence; | ||||||
|  | 	std::string loading_command; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts, through any available means, to return a list of potential targets for the file with the given name. | ||||||
|  |  | ||||||
|  | 	@returns The list of potential targets, sorted from most to least probable. | ||||||
|  | */ | ||||||
|  | std::vector<std::unique_ptr<Target>> GetTargets(const std::string &file_name); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Inspects the supplied file and determines the media included. | ||||||
|  | */ | ||||||
|  | Media GetMedia(const std::string &file_name); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* StaticAnalyser_hpp */ | ||||||
| @@ -11,7 +11,8 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Parsers/ZX8081.hpp" | #include "Target.hpp" | ||||||
|  | #include "../../../Storage/Tape/Parsers/ZX8081.hpp" | ||||||
| 
 | 
 | ||||||
| static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	std::vector<Storage::Data::ZX8081::File> files; | 	std::vector<Storage::Data::ZX8081::File> files; | ||||||
| @@ -27,41 +28,41 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S | |||||||
| 	return files; | 	return files; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::ZX8081::AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms) { | void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination, TargetPlatform::IntType potential_platforms) { | ||||||
| 	if(!media.tapes.empty()) { | 	if(!media.tapes.empty()) { | ||||||
| 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | ||||||
| 		media.tapes.front()->reset(); | 		media.tapes.front()->reset(); | ||||||
| 		if(!files.empty()) { | 		if(!files.empty()) { | ||||||
| 			StaticAnalyser::Target target; | 			Target *target = new Target; | ||||||
| 			target.machine = Target::ZX8081; | 			destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target)); | ||||||
|  | 			target->machine = Machine::ZX8081; | ||||||
| 
 | 
 | ||||||
| 			// Guess the machine type from the file only if it isn't already known.
 | 			// Guess the machine type from the file only if it isn't already known.
 | ||||||
| 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | ||||||
| 				default: | 				default: | ||||||
| 					target.zx8081.isZX81 = files.front().isZX81; | 					target->is_ZX81 = files.front().isZX81; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 				case TargetPlatform::ZX80:	target.zx8081.isZX81 = false;	break; | 				case TargetPlatform::ZX80:	target->is_ZX81 = false;	break; | ||||||
| 				case TargetPlatform::ZX81:	target.zx8081.isZX81 = true;	break; | 				case TargetPlatform::ZX81:	target->is_ZX81 = true;		break; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			/*if(files.front().data.size() > 16384) {
 | 			/*if(files.front().data.size() > 16384) {
 | ||||||
| 				target.zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | 				target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | ||||||
| 			} else*/ if(files.front().data.size() > 1024) { | 			} else*/ if(files.front().data.size() > 1024) { | ||||||
| 				target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB; | 				target->memory_model = Target::MemoryModel::SixteenKB; | ||||||
| 			} else { | 			} else { | ||||||
| 				target.zx8081.memory_model = ZX8081MemoryModel::Unexpanded; | 				target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
| 			} | 			} | ||||||
| 			target.media.tapes = media.tapes; | 			target->media.tapes = media.tapes; | ||||||
| 
 | 
 | ||||||
| 			// TODO: how to run software once loaded? Might require a BASIC detokeniser.
 | 			// TODO: how to run software once loaded? Might require a BASIC detokeniser.
 | ||||||
| 			if(target.zx8081.isZX81) { | 			if(target->is_ZX81) { | ||||||
| 				target.loadingCommand = "J\"\"\n"; | 				target->loading_command = "J\"\"\n"; | ||||||
| 			} else { | 			} else { | ||||||
| 				target.loadingCommand = "W\n"; | 				target->loading_command = "W\n"; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			destination.push_back(target); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -10,13 +10,15 @@ | |||||||
| #define StaticAnalyser_ZX8081_StaticAnalyser_hpp | #define StaticAnalyser_ZX8081_StaticAnalyser_hpp | ||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| #include "../../Storage/TargetPlatforms.hpp" | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace ZX8081 { | namespace ZX8081 { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										34
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_ZX8081_Target_h | ||||||
|  | #define Analyser_Static_ZX8081_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace ZX8081 { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class MemoryModel { | ||||||
|  | 		Unexpanded, | ||||||
|  | 		SixteenKB, | ||||||
|  | 		SixtyFourKB | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||||
|  | 	bool is_ZX81 = false; | ||||||
|  | 	bool ZX80_uses_ZX81_ROM = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_ZX8081_Target_h */ | ||||||
							
								
								
									
										30
									
								
								BUILD.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								BUILD.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | Linux, BSD | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime. | ||||||
|  |  | ||||||
|  | Build: | ||||||
|  |  | ||||||
|  | 	cd OSBindings/SDL | ||||||
|  | 	scons | ||||||
|  |  | ||||||
|  | Optionally: | ||||||
|  |  | ||||||
|  | 	cp clksignal /usr/bin | ||||||
|  |  | ||||||
|  | To launch: | ||||||
|  |  | ||||||
|  | 	clksignal file | ||||||
|  |  | ||||||
|  | Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own. | ||||||
|  |  | ||||||
|  | Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time. | ||||||
|  |  | ||||||
|  | macOS | ||||||
|  | ===== | ||||||
|  |  | ||||||
|  | There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application. | ||||||
|  |  | ||||||
|  | Build: open the Xcode project in OSBindings/Mac and press command+b. | ||||||
|  |  | ||||||
|  | Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building. | ||||||
| @@ -169,13 +169,20 @@ class HalfCycles: public WrappedInt<HalfCycles> { | |||||||
| 			return Cycles(length_ >> 1); | 			return Cycles(length_ >> 1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		///Flushes the whole cycles in @c this, subtracting that many from the total stored here. | 		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. | ||||||
| 		inline Cycles flush_cycles() { | 		inline Cycles flush_cycles() { | ||||||
| 			Cycles result(length_ >> 1); | 			Cycles result(length_ >> 1); | ||||||
| 			length_ &= 1; | 			length_ &= 1; | ||||||
| 			return result; | 			return result; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. | ||||||
|  | 		inline HalfCycles flush() { | ||||||
|  | 			HalfCycles result(length_); | ||||||
|  | 			length_ = 0; | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Severs from @c this the effect of dividing by @c divisor — @c this will end up with | 			Severs from @c this the effect of dividing by @c divisor — @c this will end up with | ||||||
| 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								ClockReceiver/TimeTypes.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ClockReceiver/TimeTypes.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | // | ||||||
|  | //  TimeTypes.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef TimeTypes_h | ||||||
|  | #define TimeTypes_h | ||||||
|  |  | ||||||
|  | namespace Time { | ||||||
|  |  | ||||||
|  | typedef double Seconds; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* TimeTypes_h */ | ||||||
| @@ -11,28 +11,10 @@ | |||||||
|  |  | ||||||
| using namespace WD; | using namespace WD; | ||||||
|  |  | ||||||
| WD1770::Status::Status() : |  | ||||||
| 		type(Status::One), |  | ||||||
| 		write_protect(false), |  | ||||||
| 		record_type(false), |  | ||||||
| 		spin_up(false), |  | ||||||
| 		record_not_found(false), |  | ||||||
| 		crc_error(false), |  | ||||||
| 		seek_error(false), |  | ||||||
| 		lost_data(false), |  | ||||||
| 		data_request(false), |  | ||||||
| 		interrupt_request(false), |  | ||||||
| 		busy(false) {} |  | ||||||
|  |  | ||||||
| WD1770::WD1770(Personality p) : | WD1770::WD1770(Personality p) : | ||||||
| 		Storage::Disk::MFMController(8000000), | 		Storage::Disk::MFMController(8000000), | ||||||
| 		interesting_event_mask_(static_cast<int>(Event1770::Command)), |  | ||||||
| 		resume_point_(0), |  | ||||||
| 		delay_time_(0), |  | ||||||
| 		index_hole_count_target_(-1), |  | ||||||
| 		delegate_(nullptr), |  | ||||||
| 		personality_(p), | 		personality_(p), | ||||||
| 		head_is_loaded_(false) { | 		interesting_event_mask_(static_cast<int>(Event1770::Command)) { | ||||||
| 	set_is_double_density(false); | 	set_is_double_density(false); | ||||||
| 	posit_event(static_cast<int>(Event1770::Command)); | 	posit_event(static_cast<int>(Event1770::Command)); | ||||||
| } | } | ||||||
| @@ -41,10 +23,16 @@ void WD1770::set_register(int address, uint8_t value) { | |||||||
| 	switch(address&3) { | 	switch(address&3) { | ||||||
| 		case 0: { | 		case 0: { | ||||||
| 			if((value&0xf0) == 0xd0) { | 			if((value&0xf0) == 0xd0) { | ||||||
|  | 				if(value == 0xd0) { | ||||||
|  | 					// Force interrupt **immediately**. | ||||||
|  | 					printf("Force interrupt immediately\n"); | ||||||
|  | 					posit_event(static_cast<int>(Event1770::ForceInterrupt)); | ||||||
|  | 				} else { | ||||||
| 					printf("!!!TODO: force interrupt!!!\n"); | 					printf("!!!TODO: force interrupt!!!\n"); | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.type = Status::One; | 						status.type = Status::One; | ||||||
| 					}); | 					}); | ||||||
|  | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				command_ = value; | 				command_ = value; | ||||||
| 				posit_event(static_cast<int>(Event1770::Command)); | 				posit_event(static_cast<int>(Event1770::Command)); | ||||||
| @@ -129,7 +117,7 @@ void WD1770::run_for(const Cycles cycles) { | |||||||
| #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer); | #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer); | ||||||
| #define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; } | #define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; } | ||||||
| #define BEGIN_SECTION()	switch(resume_point_) { default: | #define BEGIN_SECTION()	switch(resume_point_) { default: | ||||||
| #define END_SECTION()	0; } | #define END_SECTION()	(void)0; } | ||||||
|  |  | ||||||
| #define READ_ID()	\ | #define READ_ID()	\ | ||||||
| 		if(new_event_type == static_cast<int>(Event::Token)) {	\ | 		if(new_event_type == static_cast<int>(Event::Token)) {	\ | ||||||
| @@ -187,13 +175,23 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) { | ||||||
|  | 		interesting_event_mask_ = 0; | ||||||
|  | 		resume_point_ = 0; | ||||||
|  | 		update_status([] (Status &status) { | ||||||
|  | 			status.type = Status::One; | ||||||
|  | 			status.data_request = false; | ||||||
|  | 		}); | ||||||
|  | 	} else { | ||||||
| 		if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return; | 		if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return; | ||||||
| 		interesting_event_mask_ &= ~new_event_type; | 		interesting_event_mask_ &= ~new_event_type; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	Status new_status; | 	Status new_status; | ||||||
| 	BEGIN_SECTION() | 	BEGIN_SECTION() | ||||||
|  |  | ||||||
| 	// Wait for a new command, branch to the appropriate handler. | 	// Wait for a new command, branch to the appropriate handler. | ||||||
|  | 	case 0: | ||||||
| 	wait_for_command: | 	wait_for_command: | ||||||
| 		printf("Idle...\n"); | 		printf("Idle...\n"); | ||||||
| 		set_data_mode(DataMode::Scanning); | 		set_data_mode(DataMode::Scanning); | ||||||
| @@ -478,7 +476,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 				sector_++; | 				sector_++; | ||||||
| 				goto test_type2_write_protection; | 				goto test_type2_write_protection; | ||||||
| 			} | 			} | ||||||
| 			printf("Read sector %d\n", sector_); | 			printf("Finished reading sector %d\n", sector_); | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		goto type2_check_crc; | 		goto type2_check_crc; | ||||||
| @@ -674,7 +672,6 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			status.lost_data = false; | 			status.lost_data = false; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 	write_track_test_write_protect: |  | ||||||
| 		if(get_drive().get_is_read_only()) { | 		if(get_drive().get_is_read_only()) { | ||||||
| 			update_status([] (Status &status) { | 			update_status([] (Status &status) { | ||||||
| 				status.write_protect = true; | 				status.write_protect = true; | ||||||
|   | |||||||
| @@ -85,20 +85,19 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
| 		inline bool has_head_load_line() { return (personality_ == P1793 ); } | 		inline bool has_head_load_line() { return (personality_ == P1793 ); } | ||||||
|  |  | ||||||
| 		struct Status { | 		struct Status { | ||||||
| 			Status(); | 			bool write_protect = false; | ||||||
| 			bool write_protect; | 			bool record_type = false; | ||||||
| 			bool record_type; | 			bool spin_up = false; | ||||||
| 			bool spin_up; | 			bool record_not_found = false; | ||||||
| 			bool record_not_found; | 			bool crc_error = false; | ||||||
| 			bool crc_error; | 			bool seek_error = false; | ||||||
| 			bool seek_error; | 			bool lost_data = false; | ||||||
| 			bool lost_data; | 			bool data_request = false; | ||||||
| 			bool data_request; | 			bool interrupt_request = false; | ||||||
| 			bool interrupt_request; | 			bool busy = false; | ||||||
| 			bool busy; |  | ||||||
| 			enum { | 			enum { | ||||||
| 				One, Two, Three | 				One, Two, Three | ||||||
| 			} type; | 			} type = One; | ||||||
| 		} status_; | 		} status_; | ||||||
| 		uint8_t track_; | 		uint8_t track_; | ||||||
| 		uint8_t sector_; | 		uint8_t sector_; | ||||||
| @@ -106,7 +105,7 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
| 		uint8_t command_; | 		uint8_t command_; | ||||||
|  |  | ||||||
| 		int index_hole_count_; | 		int index_hole_count_; | ||||||
| 		int index_hole_count_target_; | 		int index_hole_count_target_ = -1; | ||||||
| 		int distance_into_section_; | 		int distance_into_section_; | ||||||
|  |  | ||||||
| 		int step_direction_; | 		int step_direction_; | ||||||
| @@ -117,21 +116,22 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
| 			Command			= (1 << 3),	// Indicates receipt of a new command. | 			Command			= (1 << 3),	// Indicates receipt of a new command. | ||||||
| 			HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only). | 			HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only). | ||||||
| 			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out. | 			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out. | ||||||
| 			IndexHoleTarget	= (1 << 6)	// Indicates that index_hole_count_ has reached index_hole_count_target_. | 			IndexHoleTarget	= (1 << 6),	// Indicates that index_hole_count_ has reached index_hole_count_target_. | ||||||
|  | 			ForceInterrupt	= (1 << 7)	// Indicates a forced interrupt. | ||||||
| 		}; | 		}; | ||||||
| 		void posit_event(int type); | 		void posit_event(int type); | ||||||
| 		int interesting_event_mask_; | 		int interesting_event_mask_; | ||||||
| 		int resume_point_; | 		int resume_point_ = 0; | ||||||
| 		unsigned int delay_time_; | 		unsigned int delay_time_ = 0; | ||||||
|  |  | ||||||
| 		// ID buffer | 		// ID buffer | ||||||
| 		uint8_t header_[6]; | 		uint8_t header_[6]; | ||||||
|  |  | ||||||
| 		// 1793 head-loading logic | 		// 1793 head-loading logic | ||||||
| 		bool head_is_loaded_; | 		bool head_is_loaded_ = false; | ||||||
|  |  | ||||||
| 		// delegate | 		// delegate | ||||||
| 		Delegate *delegate_; | 		Delegate *delegate_ = nullptr; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -121,12 +121,9 @@ template <class T> class MOS6532 { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		MOS6532() : | 		MOS6532() { | ||||||
| 			interrupt_status_(0), | 			timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10); | ||||||
| 			port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}}, | 		} | ||||||
| 			a7_interrupt_({.last_port_value = 0, .enabled = false}), |  | ||||||
| 			interrupt_line_(false), |  | ||||||
| 			timer_{.value = static_cast<unsigned int>((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {} |  | ||||||
|  |  | ||||||
| 		inline void set_port_did_change(int port) { | 		inline void set_port_did_change(int port) { | ||||||
| 			if(!port) { | 			if(!port) { | ||||||
| @@ -154,26 +151,26 @@ template <class T> class MOS6532 { | |||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
| 			unsigned int value; | 			unsigned int value; | ||||||
| 			unsigned int activeShift, writtenShift; | 			unsigned int activeShift = 10, writtenShift = 10; | ||||||
| 			bool interrupt_enabled; | 			bool interrupt_enabled = false; | ||||||
| 		} timer_; | 		} timer_; | ||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
| 			bool enabled; | 			bool enabled = false; | ||||||
| 			bool active_on_positive; | 			bool active_on_positive = false; | ||||||
| 			uint8_t last_port_value; | 			uint8_t last_port_value = 0; | ||||||
| 		} a7_interrupt_; | 		} a7_interrupt_; | ||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
| 			uint8_t output_mask, output; | 			uint8_t output_mask = 0, output = 0; | ||||||
| 		} port_[2]; | 		} port_[2]; | ||||||
|  |  | ||||||
| 		uint8_t interrupt_status_; | 		uint8_t interrupt_status_ = 0; | ||||||
| 		enum InterruptFlag: uint8_t { | 		enum InterruptFlag: uint8_t { | ||||||
| 			Timer = 0x80, | 			Timer = 0x80, | ||||||
| 			PA7 = 0x40 | 			PA7 = 0x40 | ||||||
| 		}; | 		}; | ||||||
| 		bool interrupt_line_; | 		bool interrupt_line_ = false; | ||||||
|  |  | ||||||
| 		// expected to be overridden | 		// expected to be overridden | ||||||
| 		uint8_t get_port_input(int port)										{	return 0xff;	} | 		uint8_t get_port_input(int port)										{	return 0xff;	} | ||||||
|   | |||||||
| @@ -8,22 +8,22 @@ | |||||||
|  |  | ||||||
| #include "6560.hpp" | #include "6560.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| using namespace MOS; | using namespace MOS; | ||||||
|  |  | ||||||
| Speaker::Speaker() : | AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | ||||||
| 	volume_(0), | 	audio_queue_(audio_queue) {} | ||||||
| 	control_registers_{0, 0, 0, 0}, |  | ||||||
| 	shift_registers_{0, 0, 0, 0}, |  | ||||||
| 	counters_{2, 1, 0, 0} {}	// create a slight phase offset for the three channels |  | ||||||
|  |  | ||||||
| void Speaker::set_volume(uint8_t volume) { |  | ||||||
| 	enqueue([=]() { | void AudioGenerator::set_volume(uint8_t volume) { | ||||||
| 		volume_ = volume; | 	audio_queue_.defer([=]() { | ||||||
|  | 		volume_ = static_cast<int16_t>(volume) * range_multiplier_; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Speaker::set_control(int channel, uint8_t value) { | void AudioGenerator::set_control(int channel, uint8_t value) { | ||||||
| 	enqueue([=]() { | 	audio_queue_.defer([=]() { | ||||||
| 		control_registers_[channel] = value; | 		control_registers_[channel] = value; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -105,7 +105,7 @@ static uint8_t noise_pattern[] = { | |||||||
| // testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e | // testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e | ||||||
| // means every second cycle, etc. | // means every second cycle, etc. | ||||||
|  |  | ||||||
| void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||||
| 		update(0, 2, shift); | 		update(0, 2, shift); | ||||||
| 		update(1, 1, shift); | 		update(1, 1, shift); | ||||||
| @@ -114,16 +114,16 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | |||||||
|  |  | ||||||
| 		// this sums the output of all three sounds channels plus a DC offset for volume; | 		// this sums the output of all three sounds channels plus a DC offset for volume; | ||||||
| 		// TODO: what's the real ratio of this stuff? | 		// TODO: what's the real ratio of this stuff? | ||||||
| 		target[c] = ( | 		target[c] = static_cast<int16_t>( | ||||||
| 			(shift_registers_[0]&1) + | 			(shift_registers_[0]&1) + | ||||||
| 			(shift_registers_[1]&1) + | 			(shift_registers_[1]&1) + | ||||||
| 			(shift_registers_[2]&1) + | 			(shift_registers_[2]&1) + | ||||||
| 			((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) | 			((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) | ||||||
| 		) * volume_ * 700 + volume_ * 44; | 		) * volume_ + (volume_ >> 4); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void Speaker::skip_samples(unsigned int number_of_samples) { | void AudioGenerator::skip_samples(std::size_t number_of_samples) { | ||||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||||
| 		update(0, 2, shift); | 		update(0, 2, shift); | ||||||
| 		update(1, 1, shift); | 		update(1, 1, shift); | ||||||
| @@ -132,6 +132,10 @@ void Speaker::skip_samples(unsigned int number_of_samples) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void AudioGenerator::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	range_multiplier_ = static_cast<int16_t>(range / 64); | ||||||
|  | } | ||||||
|  |  | ||||||
| #undef shift | #undef shift | ||||||
| #undef increment | #undef increment | ||||||
| #undef update | #undef update | ||||||
|   | |||||||
| @@ -9,28 +9,35 @@ | |||||||
| #ifndef _560_hpp | #ifndef _560_hpp | ||||||
| #define _560_hpp | #define _560_hpp | ||||||
|  |  | ||||||
| #include "../../Outputs/CRT/CRT.hpp" |  | ||||||
| #include "../../Outputs/Speaker.hpp" |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  | #include "../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  |  | ||||||
| namespace MOS { | namespace MOS { | ||||||
|  |  | ||||||
| // audio state | // audio state | ||||||
| class Speaker: public ::Outputs::Filter<Speaker> { | class AudioGenerator: public ::Outputs::Speaker::SampleSource { | ||||||
| 	public: | 	public: | ||||||
| 		Speaker(); | 		AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue); | ||||||
|  |  | ||||||
| 		void set_volume(uint8_t volume); | 		void set_volume(uint8_t volume); | ||||||
| 		void set_control(int channel, uint8_t value); | 		void set_control(int channel, uint8_t value); | ||||||
|  |  | ||||||
| 		void get_samples(unsigned int number_of_samples, int16_t *target); | 		// For ::SampleSource. | ||||||
| 		void skip_samples(unsigned int number_of_samples); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
|  | 		void skip_samples(std::size_t number_of_samples); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		unsigned int counters_[4]; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| 		unsigned int shift_registers_[4]; |  | ||||||
| 		uint8_t control_registers_[4]; | 		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels | ||||||
| 		uint8_t volume_; | 		unsigned int shift_registers_[4] = {0, 0, 0, 0}; | ||||||
|  | 		uint8_t control_registers_[4] = {0, 0, 0, 0}; | ||||||
|  | 		int16_t volume_ = 0; | ||||||
|  | 		int16_t range_multiplier_ = 1; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -44,33 +51,42 @@ class Speaker: public ::Outputs::Filter<Speaker> { | |||||||
| template <class T> class MOS6560 { | template <class T> class MOS6560 { | ||||||
| 	public: | 	public: | ||||||
| 		MOS6560() : | 		MOS6560() : | ||||||
| 				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)), | 				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)), | ||||||
| 				speaker_(new Speaker), | 				audio_generator_(audio_queue_), | ||||||
| 				horizontal_counter_(0), | 				speaker_(audio_generator_) | ||||||
| 				vertical_counter_(0), | 		{ | ||||||
| 				cycles_since_speaker_update_(0), | 			crt_->set_svideo_sampling_function( | ||||||
| 				is_odd_frame_(false), | 				"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" | ||||||
| 				is_odd_line_(false) { |  | ||||||
| 			crt_->set_composite_sampling_function( |  | ||||||
| 				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" |  | ||||||
| 				"{" | 				"{" | ||||||
| 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | ||||||
| 					"float phaseOffset = 6.283185308 * 2.0 * yc.y;" |  | ||||||
|  |  | ||||||
| 					"float chroma = cos(phase + phaseOffset);" | 					"float phaseOffset = 6.283185308 * 2.0 * yc.y;" | ||||||
| 					"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);" | 					"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);" | ||||||
|  |  | ||||||
|  | 					"return vec2(yc.x, chroma);" | ||||||
| 				"}"); | 				"}"); | ||||||
|  |  | ||||||
|  | 			// default to s-video output | ||||||
|  | 			crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo); | ||||||
|  |  | ||||||
| 			// default to NTSC | 			// default to NTSC | ||||||
| 			set_output_mode(OutputMode::NTSC); | 			set_output_mode(OutputMode::NTSC); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_clock_rate(double clock_rate) { | 		~MOS6560() { | ||||||
| 			speaker_->set_input_rate(static_cast<float>(clock_rate / 4.0)); | 			audio_queue_.flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; } | 		void set_clock_rate(double clock_rate) { | ||||||
| 		std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; } | 			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::CRT::CRT *get_crt() { return crt_.get(); } | ||||||
|  | 		Outputs::Speaker::Speaker *get_speaker() { return &speaker_; } | ||||||
|  |  | ||||||
|  | 		void set_high_frequency_cutoff(float cutoff) { | ||||||
|  | 			speaker_.set_high_frequency_cutoff(cutoff); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		enum OutputMode { | 		enum OutputMode { | ||||||
| 			PAL, NTSC | 			PAL, NTSC | ||||||
| @@ -82,36 +98,36 @@ template <class T> class MOS6560 { | |||||||
| 		void set_output_mode(OutputMode output_mode) { | 		void set_output_mode(OutputMode output_mode) { | ||||||
| 			output_mode_ = output_mode; | 			output_mode_ = output_mode; | ||||||
|  |  | ||||||
| 			// Lumunances are encoded trivially: on a 0–255 scale. | 			// Luminances are encoded trivially: on a 0–255 scale. | ||||||
| 			const uint8_t luminances[16] = { | 			const uint8_t luminances[16] = { | ||||||
| 				0,		255,	109,	189, | 				0,		255,	60,		189, | ||||||
| 				199,	144,	159,	161, | 				80,		144,	40,		227, | ||||||
| 				126,	227,	227,	207, | 				90,		161,	207,	227, | ||||||
| 				235,	173,	188,	196 | 				200,	196,	160,	196 | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			// Chrominances are encoded such that 0–128 is a complete revolution of phase; | 			// Chrominances are encoded such that 0–128 is a complete revolution of phase; | ||||||
| 			// anything above 191 disables the colour subcarrier. Phase is relative to the | 			// anything above 191 disables the colour subcarrier. Phase is relative to the | ||||||
| 			// colour burst, so 0 is green. | 			// colour burst, so 0 is green. | ||||||
| 			const uint8_t pal_chrominances[16] = { | 			const uint8_t pal_chrominances[16] = { | ||||||
| 				255,	255,	40,		112, | 				255,	255,	37,		101, | ||||||
| 				8,		88,		120,	56, | 				19,		86,		123,	59, | ||||||
| 				40,		48,		40,		112, | 				46,		53,		37,		101, | ||||||
| 				8,		88,		120,	56, | 				19,		86,		123,	59, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t ntsc_chrominances[16] = { | 			const uint8_t ntsc_chrominances[16] = { | ||||||
| 				255,	255,	8,		72, | 				255,	255,	7,		71, | ||||||
| 				32,		88,		48,		112, | 				25,		86,		48,		112, | ||||||
| 				0,		0,		8,		72, | 				0,		119,	7,		71, | ||||||
| 				32,		88,		48,		112, | 				25,		86,		48,		112, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t *chrominances; | 			const uint8_t *chrominances; | ||||||
| 			Outputs::CRT::DisplayType display_type; | 			Outputs::CRT::DisplayType display_type; | ||||||
|  |  | ||||||
| 			switch(output_mode) { | 			switch(output_mode) { | ||||||
| 				case OutputMode::PAL: | 				default: | ||||||
| 					chrominances = pal_chrominances; | 					chrominances = pal_chrominances; | ||||||
| 					display_type = Outputs::CRT::PAL50; | 					display_type = Outputs::CRT::DisplayType::PAL50; | ||||||
| 					timing_.cycles_per_line = 71; | 					timing_.cycles_per_line = 71; | ||||||
| 					timing_.line_counter_increment_offset = 0; | 					timing_.line_counter_increment_offset = 0; | ||||||
| 					timing_.lines_per_progressive_field = 312; | 					timing_.lines_per_progressive_field = 312; | ||||||
| @@ -120,7 +136,7 @@ template <class T> class MOS6560 { | |||||||
|  |  | ||||||
| 				case OutputMode::NTSC: | 				case OutputMode::NTSC: | ||||||
| 					chrominances = ntsc_chrominances; | 					chrominances = ntsc_chrominances; | ||||||
| 					display_type = Outputs::CRT::NTSC60; | 					display_type = Outputs::CRT::DisplayType::NTSC60; | ||||||
| 					timing_.cycles_per_line = 65; | 					timing_.cycles_per_line = 65; | ||||||
| 					timing_.line_counter_increment_offset = 65 - 33;	// TODO: this is a bit of a hack; separate vertical and horizontal counting | 					timing_.line_counter_increment_offset = 65 - 33;	// TODO: this is a bit of a hack; separate vertical and horizontal counting | ||||||
| 					timing_.lines_per_progressive_field = 261; | 					timing_.lines_per_progressive_field = 261; | ||||||
| @@ -129,16 +145,15 @@ template <class T> class MOS6560 { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); | 			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); | ||||||
| 			crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); |  | ||||||
|  |  | ||||||
| //			switch(output_mode) { | 			switch(output_mode) { | ||||||
| //				case OutputMode::PAL: | 				case OutputMode::PAL: | ||||||
| //					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f)); | 					crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.05f, 0.9f, 0.9f)); | ||||||
| //				break; | 				break; | ||||||
| //				case OutputMode::NTSC: | 				case OutputMode::NTSC: | ||||||
| //					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f)); | 					crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); | ||||||
| //				break; | 				break; | ||||||
| //			} | 			} | ||||||
|  |  | ||||||
| 			for(int c = 0; c < 16; c++) { | 			for(int c = 0; c < 16; c++) { | ||||||
| 				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); | 				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); | ||||||
| @@ -325,7 +340,10 @@ template <class T> class MOS6560 { | |||||||
| 		/*! | 		/*! | ||||||
| 			Causes the 6560 to flush as much pending CRT and speaker communications as possible. | 			Causes the 6560 to flush as much pending CRT and speaker communications as possible. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline void flush() { update_audio(); speaker_->flush(); } | 		inline void flush() { | ||||||
|  | 			update_audio(); | ||||||
|  | 			audio_queue_.perform(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Writes to a 6560 register. | 			Writes to a 6560 register. | ||||||
| @@ -363,13 +381,13 @@ template <class T> class MOS6560 { | |||||||
| 				case 0xc: | 				case 0xc: | ||||||
| 				case 0xd: | 				case 0xd: | ||||||
| 					update_audio(); | 					update_audio(); | ||||||
| 					speaker_->set_control(address - 0xa, value); | 					audio_generator_.set_control(address - 0xa, value); | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 0xe: | 				case 0xe: | ||||||
| 					update_audio(); | 					update_audio(); | ||||||
| 					registers_.auxiliary_colour = colours_[value >> 4]; | 					registers_.auxiliary_colour = colours_[value >> 4]; | ||||||
| 					speaker_->set_volume(value & 0xf); | 					audio_generator_.set_volume(value & 0xf); | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 0xf: { | 				case 0xf: { | ||||||
| @@ -405,12 +423,15 @@ template <class T> class MOS6560 { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		AudioGenerator audio_generator_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_; | ||||||
|  |  | ||||||
| 		std::shared_ptr<Speaker> speaker_; |  | ||||||
| 		Cycles cycles_since_speaker_update_; | 		Cycles cycles_since_speaker_update_; | ||||||
| 		void update_audio() { | 		void update_audio() { | ||||||
| 			speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); | 			speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// register state | 		// register state | ||||||
| @@ -432,7 +453,7 @@ template <class T> class MOS6560 { | |||||||
| 		unsigned int cycles_in_state_; | 		unsigned int cycles_in_state_; | ||||||
|  |  | ||||||
| 		// counters that cover an entire field | 		// counters that cover an entire field | ||||||
| 		int horizontal_counter_, vertical_counter_, full_frame_counter_; | 		int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_; | ||||||
|  |  | ||||||
| 		// latches dictating start and length of drawing | 		// latches dictating start and length of drawing | ||||||
| 		bool vertical_drawing_latch_, horizontal_drawing_latch_; | 		bool vertical_drawing_latch_, horizontal_drawing_latch_; | ||||||
| @@ -447,7 +468,7 @@ template <class T> class MOS6560 { | |||||||
| 		// data latched from the bus | 		// data latched from the bus | ||||||
| 		uint8_t character_code_, character_colour_, character_value_; | 		uint8_t character_code_, character_colour_, character_value_; | ||||||
|  |  | ||||||
| 		bool is_odd_frame_, is_odd_line_; | 		bool is_odd_frame_ = false, is_odd_line_ = false; | ||||||
|  |  | ||||||
| 		// lookup table from 6560 colour index to appropriate PAL/NTSC value | 		// lookup table from 6560 colour index to appropriate PAL/NTSC value | ||||||
| 		uint16_t colours_[16]; | 		uint16_t colours_[16]; | ||||||
|   | |||||||
| @@ -76,8 +76,8 @@ template <class T> class i8255 { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void update_outputs() { | 		void update_outputs() { | ||||||
| 			port_handler_.set_value(0, outputs_[0]); | 			if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]); | ||||||
| 			port_handler_.set_value(1, outputs_[1]); | 			if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]); | ||||||
| 			port_handler_.set_value(2, outputs_[2]); | 			port_handler_.set_value(2, outputs_[2]); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -210,7 +210,7 @@ uint8_t i8272::get_register(int address) { | |||||||
| 	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\ | 	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\ | ||||||
| 	\ | 	\ | ||||||
| 	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\ | 	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\ | ||||||
| 	CONCAT(header_found, __LINE__):	0;\ | 	CONCAT(header_found, __LINE__):	(void)0;\ | ||||||
|  |  | ||||||
| #define FIND_DATA()	\ | #define FIND_DATA()	\ | ||||||
| 	set_data_mode(DataMode::Scanning);	\ | 	set_data_mode(DataMode::Scanning);	\ | ||||||
| @@ -291,7 +291,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 			WAIT_FOR_EVENT(Event8272::CommandByte) | 			WAIT_FOR_EVENT(Event8272::CommandByte) | ||||||
| 			SetBusy(); | 			SetBusy(); | ||||||
|  |  | ||||||
| 			static const size_t required_lengths[32] = { | 			static const std::size_t required_lengths[32] = { | ||||||
| 				0,	0,	9,	3,	2,	9,	9,	2, | 				0,	0,	9,	3,	2,	9,	9,	2, | ||||||
| 				1,	9,	2,	0,	9,	6,	0,	3, | 				1,	9,	2,	0,	9,	6,	0,	3, | ||||||
| 				0,	9,	0,	0,	0,	0,	0,	0, | 				0,	9,	0,	0,	0,	0,	0,	0, | ||||||
| @@ -563,7 +563,6 @@ void i8272::posit_event(int event_type) { | |||||||
| 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | ||||||
| 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | ||||||
| 			index_hole_limit_ = 2; | 			index_hole_limit_ = 2; | ||||||
| 		read_id_find_next_sector: |  | ||||||
| 			FIND_HEADER(); | 			FIND_HEADER(); | ||||||
| 			if(!index_hole_limit_) { | 			if(!index_hole_limit_) { | ||||||
| 				SetMissingAddressMark(); | 				SetMissingAddressMark(); | ||||||
| @@ -833,7 +832,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// last thing in it will be returned first. | 	// last thing in it will be returned first. | ||||||
| 	post_result: | 	post_result: | ||||||
| 			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); | 			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); | ||||||
| 			for(size_t c = 0; c < result_stack_.size(); c++) { | 			for(std::size_t c = 0; c < result_stack_.size(); c++) { | ||||||
| 				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); | 				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); | ||||||
| 			} | 			} | ||||||
| 			printf("\n"); | 			printf("\n"); | ||||||
|   | |||||||
							
								
								
									
										712
									
								
								Components/9918/9918.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										712
									
								
								Components/9918/9918.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,712 @@ | |||||||
|  | // | ||||||
|  | //  9918.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "9918.hpp" | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | using namespace TI; | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) { | ||||||
|  | 	uint32_t result = 0; | ||||||
|  | 	uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result); | ||||||
|  | 	result_ptr[0] = r; | ||||||
|  | 	result_ptr[1] = g; | ||||||
|  | 	result_ptr[2] = b; | ||||||
|  | 	result_ptr[3] = 0; | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const uint32_t palette[16] = { | ||||||
|  | 	palette_pack(0, 0, 0), | ||||||
|  | 	palette_pack(0, 0, 0), | ||||||
|  | 	palette_pack(33, 200, 66), | ||||||
|  | 	palette_pack(94, 220, 120), | ||||||
|  |  | ||||||
|  | 	palette_pack(84, 85, 237), | ||||||
|  | 	palette_pack(125, 118, 252), | ||||||
|  | 	palette_pack(212, 82, 77), | ||||||
|  | 	palette_pack(66, 235, 245), | ||||||
|  |  | ||||||
|  | 	palette_pack(252, 85, 84), | ||||||
|  | 	palette_pack(255, 121, 120), | ||||||
|  | 	palette_pack(212, 193, 84), | ||||||
|  | 	palette_pack(230, 206, 128), | ||||||
|  |  | ||||||
|  | 	palette_pack(33, 176, 59), | ||||||
|  | 	palette_pack(201, 91, 186), | ||||||
|  | 	palette_pack(204, 204, 204), | ||||||
|  | 	palette_pack(255, 255, 255) | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const uint8_t StatusInterrupt = 0x80; | ||||||
|  | const uint8_t StatusFifthSprite = 0x40; | ||||||
|  |  | ||||||
|  | const int StatusSpriteCollisionShift = 5; | ||||||
|  | const uint8_t StatusSpriteCollision = 0x20; | ||||||
|  |  | ||||||
|  | struct ReverseTable { | ||||||
|  | 	std::uint8_t map[256]; | ||||||
|  |  | ||||||
|  | 	ReverseTable() { | ||||||
|  | 		for(int c = 0; c < 256; ++c) { | ||||||
|  | 			map[c] = static_cast<uint8_t>( | ||||||
|  | 				((c & 0x80) >> 7) | | ||||||
|  | 				((c & 0x40) >> 5) | | ||||||
|  | 				((c & 0x20) >> 3) | | ||||||
|  | 				((c & 0x10) >> 1) | | ||||||
|  | 				((c & 0x08) << 1) | | ||||||
|  | 				((c & 0x04) << 3) | | ||||||
|  | 				((c & 0x02) << 5) | | ||||||
|  | 				((c & 0x01) << 7) | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } reverse_table; | ||||||
|  |  | ||||||
|  | // Bits are reversed in the internal mode value; they're stored | ||||||
|  | // in the order M1 M2 M3. Hence the definitions below. | ||||||
|  | enum ScreenMode { | ||||||
|  | 	Text = 4, | ||||||
|  | 	MultiColour = 2, | ||||||
|  | 	ColouredText = 0, | ||||||
|  | 	Graphics = 1 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TMS9918Base::TMS9918Base() : | ||||||
|  | 	// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole | ||||||
|  | 	// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. | ||||||
|  | 	crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {} | ||||||
|  |  | ||||||
|  | TMS9918::TMS9918(Personality p) { | ||||||
|  | 	// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed | ||||||
|  | 	// into whether there's a more natural form. | ||||||
|  | 	crt_->set_rgb_sampling_function( | ||||||
|  | 		"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" | ||||||
|  | 		"{" | ||||||
|  | 			"return texture(sampler, coordinate).rgb / vec3(255.0);" | ||||||
|  | 		"}"); | ||||||
|  | 	crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); | ||||||
|  | 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | ||||||
|  | 	crt_->set_input_gamma(2.8f); | ||||||
|  |  | ||||||
|  | 	// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement | ||||||
|  | 	// intended to produce the correct relationship between the hard edges between pixels and | ||||||
|  | 	// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS | ||||||
|  | 	// colour burst generator because I've yet to find any. | ||||||
|  | 	crt_->set_immediate_default_phase(0.85f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Outputs::CRT::CRT *TMS9918::get_crt() { | ||||||
|  | 	return crt_.get(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918Base::test_sprite(int sprite_number, int screen_row) { | ||||||
|  | 	if(!(status_ & StatusFifthSprite)) { | ||||||
|  | 		status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number); | ||||||
|  | 	} | ||||||
|  | 	if(sprites_stopped_) | ||||||
|  | 		return; | ||||||
|  |  | ||||||
|  | 	const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)]; | ||||||
|  | 	// A sprite Y of 208 means "don't scan the list any further". | ||||||
|  | 	if(sprite_position == 208) { | ||||||
|  | 		sprites_stopped_ = true; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const int sprite_row = (screen_row - sprite_position)&255; | ||||||
|  | 	if(sprite_row < 0 || sprite_row >= sprite_height_) return; | ||||||
|  |  | ||||||
|  | 	const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot; | ||||||
|  | 	if(active_sprite_slot == 4) { | ||||||
|  | 		status_ |= StatusFifthSprite; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot]; | ||||||
|  | 	sprite.index = sprite_number; | ||||||
|  | 	sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0); | ||||||
|  | 	sprite_sets_[active_sprite_set_].active_sprite_slot++; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918Base::get_sprite_contents(int field, int cycles_left, int screen_row) { | ||||||
|  | 	int sprite_id = field / 6; | ||||||
|  | 	field %= 6; | ||||||
|  |  | ||||||
|  | 	while(true) { | ||||||
|  | 		const int cycles_in_sprite = std::min(cycles_left, 6 - field); | ||||||
|  | 		cycles_left -= cycles_in_sprite; | ||||||
|  | 		const int final_field = cycles_in_sprite + field; | ||||||
|  |  | ||||||
|  | 		assert(sprite_id < 4); | ||||||
|  | 		SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id]; | ||||||
|  |  | ||||||
|  | 		if(field < 4) { | ||||||
|  | 			std::memcpy( | ||||||
|  | 				&sprite.info[field], | ||||||
|  | 				&ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field], | ||||||
|  | 				static_cast<size_t>(std::min(4, final_field) - field)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		field = std::min(4, final_field); | ||||||
|  | 		const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0); | ||||||
|  | 		const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?) | ||||||
|  | 		while(field < final_field) { | ||||||
|  | 			sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)]; | ||||||
|  | 			field++; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(!cycles_left) return; | ||||||
|  | 		field = 0; | ||||||
|  | 		sprite_id++; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918::run_for(const HalfCycles cycles) { | ||||||
|  | 	// As specific as I've been able to get: | ||||||
|  | 	// Scanline time is always 228 cycles. | ||||||
|  | 	// PAL output is 313 lines total. NTSC output is 262 lines total. | ||||||
|  | 	// Interrupt is signalled upon entering the lower border. | ||||||
|  |  | ||||||
|  | 	// Keep a count of cycles separate from internal counts to avoid | ||||||
|  | 	// potential errors mapping back and forth. | ||||||
|  | 	half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2); | ||||||
|  |  | ||||||
|  | 	// Convert 456 clocked half cycles per line to 342 internal cycles per line; | ||||||
|  | 	// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised | ||||||
|  | 	// for this part. So multiply by three quarters. | ||||||
|  | 	int int_cycles = (cycles.as_int() * 3) + cycles_error_; | ||||||
|  | 	cycles_error_ = int_cycles & 3; | ||||||
|  | 	int_cycles >>= 2; | ||||||
|  | 	if(!int_cycles) return; | ||||||
|  |  | ||||||
|  | 	while(int_cycles) { | ||||||
|  | 		// Determine how much time has passed in the remainder of this line, and proceed. | ||||||
|  | 		int cycles_left = std::min(342 - column_, int_cycles); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// ------------------------------------ | ||||||
|  | 		// Potentially perform a memory access. | ||||||
|  | 		// ------------------------------------ | ||||||
|  | 		if(queued_access_ != MemoryAccess::None) { | ||||||
|  | 			int time_until_access_slot = 0; | ||||||
|  | 			switch(line_mode_) { | ||||||
|  | 				case LineMode::Refresh: | ||||||
|  | 					if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1; | ||||||
|  | 					else time_until_access_slot = 3 - ((column_ - 53)&3); | ||||||
|  | 					// i.e. 53 -> 3, 52 -> 2, 51 -> 1, 50 -> 0, etc | ||||||
|  | 				break; | ||||||
|  | 				case LineMode::Text: | ||||||
|  | 					if(column_ < 59 || column_ >= 299) time_until_access_slot = column_&1; | ||||||
|  | 					else time_until_access_slot = 5 - ((column_ + 3)%6); | ||||||
|  | 					// i.e. 59 -> 3, 60 -> 2, 61 -> 1, etc | ||||||
|  | 				break; | ||||||
|  | 				case LineMode::Character: | ||||||
|  | 					if(column_ < 9) time_until_access_slot = column_&1; | ||||||
|  | 					else if(column_ < 30) time_until_access_slot = 30 - column_; | ||||||
|  | 					else if(column_ < 37) time_until_access_slot = column_&1; | ||||||
|  | 					else if(column_ < 311) time_until_access_slot = 31 - ((column_ + 7)&31); | ||||||
|  | 					// i.e. 53 -> 3, 54 -> 2, 55 -> 1, 56 -> 0, 57 -> 31, etc | ||||||
|  | 					else if(column_ < 313) time_until_access_slot = column_&1; | ||||||
|  | 					else time_until_access_slot = 342 - column_; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(cycles_left >= time_until_access_slot) { | ||||||
|  | 				if(queued_access_ == MemoryAccess::Write) { | ||||||
|  | 					ram_[ram_pointer_ & 16383] = read_ahead_buffer_; | ||||||
|  | 				} else { | ||||||
|  | 					read_ahead_buffer_ = ram_[ram_pointer_ & 16383]; | ||||||
|  | 				} | ||||||
|  | 				ram_pointer_++; | ||||||
|  | 				queued_access_ = MemoryAccess::None; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		column_ += cycles_left;		// column_ is now the column that has been reached in this line. | ||||||
|  | 		int_cycles -= cycles_left;	// Count down duration to run for. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// ------------------------------ | ||||||
|  | 		// Perform video memory accesses. | ||||||
|  | 		// ------------------------------ | ||||||
|  | 		if(((row_ < 192) || (row_ == frame_lines_-1)) && !blank_screen_) { | ||||||
|  | 			const int sprite_row = (row_ < 192) ? row_ : -1; | ||||||
|  | 			const int access_slot = column_ >> 1;	// There are only 171 available memory accesses per line. | ||||||
|  | 			switch(line_mode_) { | ||||||
|  | 				default: break; | ||||||
|  |  | ||||||
|  | 				case LineMode::Text: | ||||||
|  | 					access_pointer_ = std::min(30, access_slot); | ||||||
|  | 					if(access_pointer_ >= 30 && access_pointer_ < 150) { | ||||||
|  | 						const int row_base = pattern_name_address_ + (row_ >> 3) * 40; | ||||||
|  | 						const int end = std::min(150, access_slot); | ||||||
|  |  | ||||||
|  | 						// Pattern names are collected every third window starting from window 30. | ||||||
|  | 						const int pattern_names_start = (access_pointer_ - 30 + 2) / 3; | ||||||
|  | 						const int pattern_names_end = (end - 30 + 2) / 3; | ||||||
|  | 						std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start)); | ||||||
|  |  | ||||||
|  | 						// Patterns are collected every third window starting from window 32. | ||||||
|  | 						const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3; | ||||||
|  | 						const int pattern_buffer_end = (end - 32 + 2) / 3; | ||||||
|  | 						for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { | ||||||
|  | 							pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)]; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case LineMode::Character: | ||||||
|  | 					// Four access windows: no collection. | ||||||
|  | 					if(access_pointer_ < 5) | ||||||
|  | 						access_pointer_ = std::min(5, access_slot); | ||||||
|  |  | ||||||
|  | 					// Then ten access windows are filled with collection of sprite 3 and 4 details. | ||||||
|  | 					if(access_pointer_ >= 5 && access_pointer_ < 15) { | ||||||
|  | 						int end = std::min(15, access_slot); | ||||||
|  | 						get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, sprite_row - 1); | ||||||
|  | 						access_pointer_ = std::min(15, access_slot); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Four more access windows: no collection. | ||||||
|  | 					if(access_pointer_ >= 15 && access_pointer_ < 19) { | ||||||
|  | 						access_pointer_ = std::min(19, access_slot); | ||||||
|  |  | ||||||
|  | 						// Start new sprite set if this is location 19. | ||||||
|  | 						if(access_pointer_ == 19) { | ||||||
|  | 							active_sprite_set_ ^= 1; | ||||||
|  | 							sprite_sets_[active_sprite_set_].active_sprite_slot = 0; | ||||||
|  | 							sprites_stopped_ = false; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Then eight access windows fetch the y position for the first eight sprites. | ||||||
|  | 					while(access_pointer_ < 27 && access_pointer_ < access_slot) { | ||||||
|  | 						test_sprite(access_pointer_ - 19, sprite_row); | ||||||
|  | 						access_pointer_++; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// The next 128 access slots are video and sprite collection interleaved. | ||||||
|  | 					if(access_pointer_ >= 27 && access_pointer_ < 155) { | ||||||
|  | 						int end = std::min(155, access_slot); | ||||||
|  |  | ||||||
|  | 						int row_base = pattern_name_address_; | ||||||
|  | 						int pattern_base = pattern_generator_table_address_; | ||||||
|  | 						int colour_base = colour_table_address_; | ||||||
|  | 						if(screen_mode_ == ScreenMode::Graphics) { | ||||||
|  | 							// If this is high resolution mode, allow the row number to affect the pattern and colour addresses. | ||||||
|  | 							pattern_base &= 0x2000 | ((row_ & 0xc0) << 5); | ||||||
|  | 							colour_base &= 0x2000 | ((row_ & 0xc0) << 5); | ||||||
|  | 						} | ||||||
|  | 						row_base += (row_ << 2)&~31; | ||||||
|  |  | ||||||
|  | 						// Pattern names are collected every fourth window starting from window 27. | ||||||
|  | 						const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2; | ||||||
|  | 						const int pattern_names_end = (end - 27 + 3) >> 2; | ||||||
|  | 						std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start)); | ||||||
|  |  | ||||||
|  | 						// Colours are collected every fourth window starting from window 29. | ||||||
|  | 						const int colours_start = (access_pointer_ - 29 + 3) >> 2; | ||||||
|  | 						const int colours_end = (end - 29 + 3) >> 2; | ||||||
|  | 						if(screen_mode_ != 1) { | ||||||
|  | 							for(int column = colours_start; column < colours_end; ++column) { | ||||||
|  | 								colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)]; | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							for(int column = colours_start; column < colours_end; ++column) { | ||||||
|  | 								colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)]; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// Patterns are collected ever fourth window starting from window 30. | ||||||
|  | 						const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2; | ||||||
|  | 						const int pattern_buffer_end = (end - 30 + 3) >> 2; | ||||||
|  |  | ||||||
|  | 						// Multicolour mode uss a different function of row to pick bytes | ||||||
|  | 						const int row = (screen_mode_ != 2) ? (row_ & 7) : ((row_ >> 2) & 7); | ||||||
|  | 						for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { | ||||||
|  | 							pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row]; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// Sprite slots occur in three quarters of ever fourth window starting from window 28. | ||||||
|  | 						const int sprite_start = (access_pointer_ - 28 + 3) >> 2; | ||||||
|  | 						const int sprite_end = (end - 28 + 3) >> 2; | ||||||
|  | 						for(int column = sprite_start; column < sprite_end; ++column) { | ||||||
|  | 							if(column&3) { | ||||||
|  | 								test_sprite(7 + column - (column >> 2), sprite_row); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						access_pointer_ = std::min(155, access_slot); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Two access windows: no collection. | ||||||
|  | 					if(access_pointer_ < 157) | ||||||
|  | 						access_pointer_ = std::min(157, access_slot); | ||||||
|  |  | ||||||
|  | 					// Fourteen access windows: collect initial sprite information. | ||||||
|  | 					if(access_pointer_ >= 157 && access_pointer_ < 171) { | ||||||
|  | 						int end = std::min(171, access_slot); | ||||||
|  | 						get_sprite_contents(access_pointer_ - 157, end - access_pointer_, sprite_row); | ||||||
|  | 						access_pointer_ = std::min(171, access_slot); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// -------------------------- | ||||||
|  | 		// End video memory accesses. | ||||||
|  | 		// -------------------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// -------------------- | ||||||
|  | 		// Output video stream. | ||||||
|  | 		// -------------------- | ||||||
|  | 		if(row_	< 192 && !blank_screen_) { | ||||||
|  | 			// ---------------------- | ||||||
|  | 			// Output horizontal sync | ||||||
|  | 			// ---------------------- | ||||||
|  | 			if(!output_column_ && column_ >= 26) { | ||||||
|  | 				crt_->output_sync(13 * 4); | ||||||
|  | 				crt_->output_default_colour_burst(13 * 4); | ||||||
|  | 				output_column_ = 26; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// ------------------- | ||||||
|  | 			// Output left border. | ||||||
|  | 			// ------------------- | ||||||
|  | 			if(output_column_ >= 26) { | ||||||
|  | 				int pixels_end = std::min(first_pixel_column_, column_); | ||||||
|  | 				if(output_column_ < pixels_end) { | ||||||
|  | 					output_border(pixels_end - output_column_); | ||||||
|  | 					output_column_ = pixels_end; | ||||||
|  |  | ||||||
|  | 					// Grab a pointer for drawing pixels to, if the moment has arrived. | ||||||
|  | 					if(pixels_end == first_pixel_column_) { | ||||||
|  | 						pixel_base_ = pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_))); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// -------------- | ||||||
|  | 			// Output pixels. | ||||||
|  | 			// -------------- | ||||||
|  | 			if(output_column_ >= first_pixel_column_) { | ||||||
|  | 				int pixels_end = std::min(first_right_border_column_, column_); | ||||||
|  |  | ||||||
|  | 				if(output_column_ < pixels_end) { | ||||||
|  | 					switch(line_mode_) { | ||||||
|  | 						default: break; | ||||||
|  |  | ||||||
|  | 						case LineMode::Text: { | ||||||
|  | 							const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; | ||||||
|  |  | ||||||
|  | 							const int shift = (output_column_ - first_pixel_column_) % 6; | ||||||
|  | 							int byte_column = (output_column_ - first_pixel_column_) / 6; | ||||||
|  | 							int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; | ||||||
|  | 							int pixels_left = pixels_end - output_column_; | ||||||
|  | 							int length = std::min(pixels_left, 6 - shift); | ||||||
|  | 							while(true) { | ||||||
|  | 								pixels_left -= length; | ||||||
|  | 								for(int c = 0; c < length; ++c) { | ||||||
|  | 									pixel_target_[c] = colours[pattern&0x01]; | ||||||
|  | 									pattern >>= 1; | ||||||
|  | 								} | ||||||
|  | 								pixel_target_ += length; | ||||||
|  |  | ||||||
|  | 								if(!pixels_left) break; | ||||||
|  | 								length = std::min(6, pixels_left); | ||||||
|  | 								byte_column++; | ||||||
|  | 								pattern = reverse_table.map[pattern_buffer_[byte_column]]; | ||||||
|  | 							} | ||||||
|  | 							output_column_ = pixels_end; | ||||||
|  | 						} break; | ||||||
|  |  | ||||||
|  | 						case LineMode::Character: { | ||||||
|  | 							// If this is the start of the visible area, seed sprite shifter positions. | ||||||
|  | 							SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; | ||||||
|  | 							if(output_column_ == first_pixel_column_) { | ||||||
|  | 								int c = sprite_set.active_sprite_slot; | ||||||
|  | 								while(c--) { | ||||||
|  | 									SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | ||||||
|  | 									sprite.shift_position = -sprite.info[1]; | ||||||
|  | 									if(sprite.info[3] & 0x80) { | ||||||
|  | 										sprite.shift_position += 32; | ||||||
|  | 										if(sprite.shift_position > 0 && !sprites_magnified_) | ||||||
|  | 											sprite.shift_position *= 2; | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							// Paint the background tiles. | ||||||
|  | 							const int pixels_left = pixels_end - output_column_; | ||||||
|  | 							if(screen_mode_ == ScreenMode::MultiColour) { | ||||||
|  | 								int pixel_location = output_column_ - first_pixel_column_; | ||||||
|  | 								for(int c = 0; c < pixels_left; ++c) { | ||||||
|  | 									pixel_target_[c] = palette[ | ||||||
|  | 										(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15 | ||||||
|  | 									]; | ||||||
|  | 								} | ||||||
|  | 								pixel_target_ += pixels_left; | ||||||
|  | 							} else { | ||||||
|  | 								const int shift = (output_column_ - first_pixel_column_) & 7; | ||||||
|  | 								int byte_column = (output_column_ - first_pixel_column_) >> 3; | ||||||
|  |  | ||||||
|  | 								int length = std::min(pixels_left, 8 - shift); | ||||||
|  |  | ||||||
|  | 								int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; | ||||||
|  | 								uint8_t colour = colour_buffer_[byte_column]; | ||||||
|  | 								uint32_t colours[2] = { | ||||||
|  | 									palette[(colour & 15) ? (colour & 15) : background_colour_], | ||||||
|  | 									palette[(colour >> 4) ? (colour >> 4) : background_colour_] | ||||||
|  | 								}; | ||||||
|  |  | ||||||
|  | 								int background_pixels_left = pixels_left; | ||||||
|  | 								while(true) { | ||||||
|  | 									background_pixels_left -= length; | ||||||
|  | 									for(int c = 0; c < length; ++c) { | ||||||
|  | 										pixel_target_[c] = colours[pattern&0x01]; | ||||||
|  | 										pattern >>= 1; | ||||||
|  | 									} | ||||||
|  | 									pixel_target_ += length; | ||||||
|  |  | ||||||
|  | 									if(!background_pixels_left) break; | ||||||
|  | 									length = std::min(8, background_pixels_left); | ||||||
|  | 									byte_column++; | ||||||
|  |  | ||||||
|  | 									pattern = reverse_table.map[pattern_buffer_[byte_column]]; | ||||||
|  | 									colour = colour_buffer_[byte_column]; | ||||||
|  | 									colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; | ||||||
|  | 									colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							// Paint sprites and check for collisions. | ||||||
|  | 							if(sprite_set.active_sprite_slot) { | ||||||
|  | 								int sprite_pixels_left = pixels_left; | ||||||
|  | 								const int shift_advance = sprites_magnified_ ? 1 : 2; | ||||||
|  |  | ||||||
|  | 								const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | ||||||
|  | 								const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | ||||||
|  |  | ||||||
|  | 								while(sprite_pixels_left--) { | ||||||
|  | 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | ||||||
|  | 									int sprite_mask = 0; | ||||||
|  | 									int c = sprite_set.active_sprite_slot; | ||||||
|  | 									while(c--) { | ||||||
|  | 										SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | ||||||
|  |  | ||||||
|  | 										if(sprite.shift_position < 0) { | ||||||
|  | 											sprite.shift_position++; | ||||||
|  | 											continue; | ||||||
|  | 										} else if(sprite.shift_position < 32) { | ||||||
|  | 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | ||||||
|  | 											mask = (mask >> 7) & 1; | ||||||
|  | 											status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; | ||||||
|  | 											sprite_mask |= mask; | ||||||
|  | 											sprite.shift_position += shift_advance; | ||||||
|  |  | ||||||
|  | 											mask &= colour_masks[sprite.info[3]&15]; | ||||||
|  | 											sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | ||||||
|  | 										} | ||||||
|  | 									} | ||||||
|  |  | ||||||
|  | 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | ||||||
|  | 									output_column_++; | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							output_column_ = pixels_end; | ||||||
|  | 						} break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(output_column_ == first_right_border_column_) { | ||||||
|  | 						crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4); | ||||||
|  | 						pixel_target_ = nullptr; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// -------------------- | ||||||
|  | 			// Output right border. | ||||||
|  | 			// -------------------- | ||||||
|  | 			if(output_column_ >= first_right_border_column_) { | ||||||
|  | 				output_border(column_ - output_column_); | ||||||
|  | 				output_column_ = column_; | ||||||
|  | 			} | ||||||
|  | 		} else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) { | ||||||
|  | 			// Vertical sync. | ||||||
|  | 			if(column_ == 342) { | ||||||
|  | 				crt_->output_sync(342 * 4); | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			// Blank. | ||||||
|  | 			if(!output_column_ && column_ >= 26) { | ||||||
|  | 				crt_->output_sync(13 * 4); | ||||||
|  | 				crt_->output_default_colour_burst(13 * 4); | ||||||
|  | 				output_column_ = 26; | ||||||
|  | 			} | ||||||
|  | 			if(output_column_ >= 26) { | ||||||
|  | 				output_border(column_ - output_column_); | ||||||
|  | 				output_column_ = column_; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// ----------------- | ||||||
|  | 		// End video stream. | ||||||
|  | 		// ----------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// ----------------------------------- | ||||||
|  | 		// Prepare for next line, potentially. | ||||||
|  | 		// ----------------------------------- | ||||||
|  | 		if(column_ == 342) { | ||||||
|  | 			access_pointer_ = column_ = output_column_ = 0; | ||||||
|  | 			row_ = (row_ + 1) % frame_lines_; | ||||||
|  | 			if(row_ == 192) status_ |= StatusInterrupt; | ||||||
|  |  | ||||||
|  | 			screen_mode_ = next_screen_mode_; | ||||||
|  | 			blank_screen_ = next_blank_screen_; | ||||||
|  | 			switch(screen_mode_) { | ||||||
|  | 				case ScreenMode::Text: | ||||||
|  | 					line_mode_ = LineMode::Text; | ||||||
|  | 					first_pixel_column_ = 69; | ||||||
|  | 					first_right_border_column_ = 309; | ||||||
|  | 				break; | ||||||
|  | 				default: | ||||||
|  | 					line_mode_ = LineMode::Character; | ||||||
|  | 					first_pixel_column_ = 63; | ||||||
|  | 					first_right_border_column_ = 319; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			if(blank_screen_ || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918Base::output_border(int cycles) { | ||||||
|  | 	pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1)); | ||||||
|  | 	if(pixel_target_) *pixel_target_ = palette[background_colour_]; | ||||||
|  | 	crt_->output_level(static_cast<unsigned int>(cycles) * 4); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918::set_register(int address, uint8_t value) { | ||||||
|  | 	// Writes to address 0 are writes to the video RAM. Store | ||||||
|  | 	// the value and return. | ||||||
|  | 	if(!(address & 1)) { | ||||||
|  | 		write_phase_ = false; | ||||||
|  |  | ||||||
|  | 		// Enqueue the write to occur at the next available slot. | ||||||
|  | 		read_ahead_buffer_ = value; | ||||||
|  | 		queued_access_ = MemoryAccess::Write; | ||||||
|  |  | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Writes to address 1 are performed in pairs; if this is the | ||||||
|  | 	// low byte of a value, store it and wait for the high byte. | ||||||
|  | 	if(!write_phase_) { | ||||||
|  | 		low_write_ = value; | ||||||
|  | 		write_phase_ = true; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	write_phase_ = false; | ||||||
|  | 	if(value & 0x80) { | ||||||
|  | 		// This is a write to a register. | ||||||
|  | 		switch(value & 7) { | ||||||
|  | 			case 0: | ||||||
|  | 				next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 1: | ||||||
|  | 				next_blank_screen_ = !(low_write_ & 0x40); | ||||||
|  | 				generate_interrupts_ = !!(low_write_ & 0x20); | ||||||
|  | 				next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 2); | ||||||
|  | 				sprites_16x16_ = !!(low_write_ & 0x02); | ||||||
|  | 				sprites_magnified_ = !!(low_write_ & 0x01); | ||||||
|  |  | ||||||
|  | 				sprite_height_ = 8; | ||||||
|  | 				if(sprites_16x16_) sprite_height_ <<= 1; | ||||||
|  | 				if(sprites_magnified_) sprite_height_ <<= 1; | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 2: | ||||||
|  | 				pattern_name_address_ = static_cast<uint16_t>((low_write_ & 0xf) << 10); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 3: | ||||||
|  | 				colour_table_address_ = static_cast<uint16_t>(low_write_ << 6); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 4: | ||||||
|  | 				pattern_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 5: | ||||||
|  | 				sprite_attribute_table_address_ = static_cast<uint16_t>((low_write_ & 0x7f) << 7); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 6: | ||||||
|  | 				sprite_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 7: | ||||||
|  | 				text_colour_ = low_write_ >> 4; | ||||||
|  | 				background_colour_ = low_write_ & 0xf; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// This is a write to the RAM pointer. | ||||||
|  | 		ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8)); | ||||||
|  | 		if(!(value & 0x40)) { | ||||||
|  | 			// Officially a 'read' set, so perform lookahead. | ||||||
|  | 			queued_access_ = MemoryAccess::Read; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t TMS9918::get_register(int address) { | ||||||
|  | 	write_phase_ = false; | ||||||
|  |  | ||||||
|  | 	// Reads from address 0 read video RAM, via the read-ahead buffer. | ||||||
|  | 	if(!(address & 1)) { | ||||||
|  | 		// Enqueue the write to occur at the next available slot. | ||||||
|  | 		uint8_t result = read_ahead_buffer_; | ||||||
|  | 		queued_access_ = MemoryAccess::Read; | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Reads from address 1 get the status register. | ||||||
|  | 	uint8_t result = status_; | ||||||
|  | 	status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  HalfCycles TMS9918::get_time_until_interrupt() { | ||||||
|  | 	if(!generate_interrupts_) return HalfCycles(-1); | ||||||
|  | 	if(get_interrupt_line()) return HalfCycles(0); | ||||||
|  |  | ||||||
|  | 	const int half_cycles_per_frame = frame_lines_ * 228 * 2; | ||||||
|  | 	int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; | ||||||
|  | 	return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TMS9918::get_interrupt_line() { | ||||||
|  | 	return (status_ & StatusInterrupt) && generate_interrupts_; | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								Components/9918/9918.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								Components/9918/9918.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | // | ||||||
|  | //  9918.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef TMS9918_hpp | ||||||
|  | #define TMS9918_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
|  | #include "Implementation/9918Base.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace TI { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the | ||||||
|  | 	vessel for emulation of sufficiently close derivatives, such as the Master System VDP. | ||||||
|  |  | ||||||
|  | 	The TMS9918 and descendants are video display generators that own their own RAM, making it | ||||||
|  | 	accessible through an implicitly-timed register interface, and (depending on model) can generate | ||||||
|  | 	PAL and NTSC component and composite video. | ||||||
|  |  | ||||||
|  | 	These chips have only one non-on-demand interaction with the outside world: an interrupt line. | ||||||
|  | 	See get_time_until_interrupt and get_interrupt_line for asynchronous operation options. | ||||||
|  | */ | ||||||
|  | class TMS9918: public TMS9918Base { | ||||||
|  | 	public: | ||||||
|  | 		enum Personality { | ||||||
|  | 			TMS9918A,	// includes the 9928 and 9929; set TV standard and output device as desired. | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Constructs an instance of the drive controller that behaves according to personality @c p. | ||||||
|  | 			@param p The type of controller to emulate. | ||||||
|  | 		*/ | ||||||
|  | 		TMS9918(Personality p); | ||||||
|  |  | ||||||
|  | 		enum TVStandard { | ||||||
|  | 			/*! i.e. 50Hz output at around 312.5 lines/field */ | ||||||
|  | 			PAL, | ||||||
|  | 			/*! i.e. 60Hz output at around 262.5 lines/field */ | ||||||
|  | 			NTSC | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */ | ||||||
|  | 		void set_tv_standard(TVStandard standard); | ||||||
|  |  | ||||||
|  | 		/*! Provides the CRT this TMS is connected to. */ | ||||||
|  | 		Outputs::CRT::CRT *get_crt(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code | ||||||
|  | 			that the input clock rate is 3579545 Hz — the NTSC colour clock rate. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(const HalfCycles cycles); | ||||||
|  |  | ||||||
|  | 		/*! Sets a register value. */ | ||||||
|  | 		void set_register(int address, uint8_t value); | ||||||
|  |  | ||||||
|  | 		/*! Gets a register value. */ | ||||||
|  | 		uint8_t get_register(int address); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Returns the amount of time until get_interrupt_line would next return true if | ||||||
|  | 			there are no interceding calls to set_register or get_register. | ||||||
|  |  | ||||||
|  | 			If get_interrupt_line is true now, returns zero. If get_interrupt_line would | ||||||
|  | 			never return true, returns -1. | ||||||
|  | 		*/ | ||||||
|  | 		HalfCycles get_time_until_interrupt(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns @c true if the interrupt line is currently active; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool get_interrupt_line(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* TMS9918_hpp */ | ||||||
							
								
								
									
										101
									
								
								Components/9918/Implementation/9918Base.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								Components/9918/Implementation/9918Base.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | // | ||||||
|  | //  9918Base.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 14/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef TMS9918Base_hpp | ||||||
|  | #define TMS9918Base_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
|  | namespace TI { | ||||||
|  |  | ||||||
|  | class TMS9918Base { | ||||||
|  | 	protected: | ||||||
|  | 		TMS9918Base(); | ||||||
|  |  | ||||||
|  | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
|  | 		uint8_t ram_[16384]; | ||||||
|  |  | ||||||
|  | 		uint16_t ram_pointer_ = 0; | ||||||
|  | 		uint8_t read_ahead_buffer_ = 0; | ||||||
|  | 		enum class MemoryAccess { | ||||||
|  | 			Read, Write, None | ||||||
|  | 		} queued_access_ = MemoryAccess::None; | ||||||
|  |  | ||||||
|  | 		uint8_t status_ = 0; | ||||||
|  |  | ||||||
|  | 		bool write_phase_ = false; | ||||||
|  | 		uint8_t low_write_ = 0; | ||||||
|  |  | ||||||
|  | 		// The various register flags. | ||||||
|  | 		int next_screen_mode_ = 0, screen_mode_ = 0; | ||||||
|  | 		bool next_blank_screen_ = true, blank_screen_ = true; | ||||||
|  | 		bool sprites_16x16_ = false; | ||||||
|  | 		bool sprites_magnified_ = false; | ||||||
|  | 		bool generate_interrupts_ = false; | ||||||
|  | 		int sprite_height_ = 8; | ||||||
|  | 		uint16_t pattern_name_address_ = 0; | ||||||
|  | 		uint16_t colour_table_address_ = 0; | ||||||
|  | 		uint16_t pattern_generator_table_address_ = 0; | ||||||
|  | 		uint16_t sprite_attribute_table_address_ = 0; | ||||||
|  | 		uint16_t sprite_generator_table_address_ = 0; | ||||||
|  |  | ||||||
|  | 		uint8_t text_colour_ = 0; | ||||||
|  | 		uint8_t background_colour_ = 0; | ||||||
|  |  | ||||||
|  | 		HalfCycles half_cycles_into_frame_; | ||||||
|  | 		int column_ = 0, row_ = 0, output_column_ = 0; | ||||||
|  | 		int cycles_error_ = 0; | ||||||
|  | 		uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr; | ||||||
|  |  | ||||||
|  | 		void output_border(int cycles); | ||||||
|  |  | ||||||
|  | 		// Vertical timing details. | ||||||
|  | 		int frame_lines_ = 262; | ||||||
|  | 		int first_vsync_line_ = 227; | ||||||
|  |  | ||||||
|  | 		// Horizontal selections. | ||||||
|  | 		enum class LineMode { | ||||||
|  | 			Text = 0, | ||||||
|  | 			Character = 1, | ||||||
|  | 			Refresh = 2 | ||||||
|  | 		} line_mode_ = LineMode::Text; | ||||||
|  | 		int first_pixel_column_, first_right_border_column_; | ||||||
|  |  | ||||||
|  | 		uint8_t pattern_names_[40]; | ||||||
|  | 		uint8_t pattern_buffer_[40]; | ||||||
|  | 		uint8_t colour_buffer_[40]; | ||||||
|  |  | ||||||
|  | 		struct SpriteSet { | ||||||
|  | 			struct ActiveSprite { | ||||||
|  | 				int index = 0; | ||||||
|  | 				int row = 0; | ||||||
|  |  | ||||||
|  | 				uint8_t info[4]; | ||||||
|  | 				uint8_t image[2]; | ||||||
|  |  | ||||||
|  | 				int shift_position = 0; | ||||||
|  | 			} active_sprites[4]; | ||||||
|  | 			int active_sprite_slot = 0; | ||||||
|  | 		} sprite_sets_[2]; | ||||||
|  | 		int active_sprite_set_ = 0; | ||||||
|  | 		bool sprites_stopped_ = false; | ||||||
|  |  | ||||||
|  | 		int access_pointer_ = 0; | ||||||
|  |  | ||||||
|  | 		inline void test_sprite(int sprite_number, int screen_row); | ||||||
|  | 		inline void get_sprite_contents(int start, int cycles, int screen_row); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* TMS9918Base_hpp */ | ||||||
| @@ -8,18 +8,11 @@ | |||||||
|  |  | ||||||
| #include "AY38910.hpp" | #include "AY38910.hpp" | ||||||
|  |  | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
| using namespace GI::AY38910; | using namespace GI::AY38910; | ||||||
|  |  | ||||||
| AY38910::AY38910() : | AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { | ||||||
| 		selected_register_(0), |  | ||||||
| 		tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0}, |  | ||||||
| 		noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0), |  | ||||||
| 		envelope_divider_(0), envelope_period_(0), envelope_position_(0), |  | ||||||
| 		master_divider_(0), |  | ||||||
| 		output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |  | ||||||
| 		port_handler_(nullptr) { |  | ||||||
| 	output_registers_[8] = output_registers_[9] = output_registers_[10] = 0; |  | ||||||
|  |  | ||||||
| 	// set up envelope lookup tables | 	// set up envelope lookup tables | ||||||
| 	for(int c = 0; c < 16; c++) { | 	for(int c = 0; c < 16; c++) { | ||||||
| 		for(int p = 0; p < 32; p++) { | 		for(int p = 0; p < 32; p++) { | ||||||
| @@ -63,21 +56,22 @@ AY38910::AY38910() : | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	set_sample_volume_range(0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AY38910::set_sample_volume_range(std::int16_t range) { | ||||||
| 	// set up volume lookup table | 	// set up volume lookup table | ||||||
| 	float max_volume = 8192; | 	const float max_volume = static_cast<float>(range) / 3.0f;	// As there are three channels. | ||||||
| 	float root_two = sqrtf(2.0f); | 	const float root_two = sqrtf(2.0f); | ||||||
| 	for(int v = 0; v < 16; v++) { | 	for(int v = 0; v < 16; v++) { | ||||||
| 		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); | 		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); | ||||||
| 	} | 	} | ||||||
| 	volumes_[0] = 0; | 	volumes_[0] = 0; | ||||||
|  | 	evaluate_output_volume(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::set_clock_rate(double clock_rate) { | void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
| 	set_input_rate(static_cast<float>(clock_rate)); | 	std::size_t c = 0; | ||||||
| } |  | ||||||
|  |  | ||||||
| void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { |  | ||||||
| 	unsigned int c = 0; |  | ||||||
| 	while((master_divider_&7) && c < number_of_samples) { | 	while((master_divider_&7) && c < number_of_samples) { | ||||||
| 		target[c] = output_volume_; | 		target[c] = output_volume_; | ||||||
| 		master_divider_++; | 		master_divider_++; | ||||||
| @@ -167,7 +161,12 @@ void AY38910::evaluate_output_volume() { | |||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Register manipulation | bool AY38910::is_zero_level() { | ||||||
|  | 	// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero. | ||||||
|  | 	return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Register manipulation | ||||||
|  |  | ||||||
| void AY38910::select_register(uint8_t r) { | void AY38910::select_register(uint8_t r) { | ||||||
| 	selected_register_ = r; | 	selected_register_ = r; | ||||||
| @@ -178,7 +177,7 @@ void AY38910::set_register_value(uint8_t value) { | |||||||
| 	registers_[selected_register_] = value; | 	registers_[selected_register_] = value; | ||||||
| 	if(selected_register_ < 14) { | 	if(selected_register_ < 14) { | ||||||
| 		int selected_register = selected_register_; | 		int selected_register = selected_register_; | ||||||
| 		enqueue([=] () { | 		task_queue_.defer([=] () { | ||||||
| 			uint8_t masked_value = value; | 			uint8_t masked_value = value; | ||||||
| 			switch(selected_register) { | 			switch(selected_register) { | ||||||
| 				case 0: case 2: case 4: | 				case 0: case 2: case 4: | ||||||
| @@ -233,13 +232,13 @@ uint8_t AY38910::get_register_value() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Port handling | // MARK: - Port handling | ||||||
|  |  | ||||||
| uint8_t AY38910::get_port_output(bool port_b) { | uint8_t AY38910::get_port_output(bool port_b) { | ||||||
| 	return registers_[port_b ? 15 : 14]; | 	return registers_[port_b ? 15 : 14]; | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Bus handling | // MARK: - Bus handling | ||||||
|  |  | ||||||
| void AY38910::set_port_handler(PortHandler *handler) { | void AY38910::set_port_handler(PortHandler *handler) { | ||||||
| 	port_handler_ = handler; | 	port_handler_ = handler; | ||||||
|   | |||||||
| @@ -9,7 +9,8 @@ | |||||||
| #ifndef AY_3_8910_hpp | #ifndef AY_3_8910_hpp | ||||||
| #define AY_3_8910_hpp | #define AY_3_8910_hpp | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker.hpp" | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
| namespace GI { | namespace GI { | ||||||
| namespace AY38910 { | namespace AY38910 { | ||||||
| @@ -55,13 +56,10 @@ enum ControlLines { | |||||||
| 	noise generator and a volume envelope generator, which also provides two bidirectional | 	noise generator and a volume envelope generator, which also provides two bidirectional | ||||||
| 	interface ports. | 	interface ports. | ||||||
| */ | */ | ||||||
| class AY38910: public ::Outputs::Filter<AY38910> { | class AY38910: public ::Outputs::Speaker::SampleSource { | ||||||
| 	public: | 	public: | ||||||
| 		/// Creates a new AY38910. | 		/// Creates a new AY38910. | ||||||
| 		AY38910(); | 		AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||||
|  |  | ||||||
| 		/// Sets the clock rate at which this AY38910 will be run. |  | ||||||
| 		void set_clock_rate(double clock_rate); |  | ||||||
|  |  | ||||||
| 		/// Sets the value the AY would read from its data lines if it were not outputting. | 		/// Sets the value the AY would read from its data lines if it were not outputting. | ||||||
| 		void set_data_input(uint8_t r); | 		void set_data_input(uint8_t r); | ||||||
| @@ -86,27 +84,32 @@ class AY38910: public ::Outputs::Filter<AY38910> { | |||||||
| 		void set_port_handler(PortHandler *); | 		void set_port_handler(PortHandler *); | ||||||
|  |  | ||||||
| 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | ||||||
| 		void get_samples(unsigned int number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
|  | 		bool is_zero_level(); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		int selected_register_; | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
| 		uint8_t registers_[16], output_registers_[16]; |  | ||||||
|  | 		int selected_register_ = 0; | ||||||
|  | 		uint8_t registers_[16]; | ||||||
|  | 		uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||||||
| 		uint8_t port_inputs_[2]; | 		uint8_t port_inputs_[2]; | ||||||
|  |  | ||||||
| 		int master_divider_; | 		int master_divider_ = 0; | ||||||
|  |  | ||||||
| 		int tone_periods_[3]; | 		int tone_periods_[3] = {0, 0, 0}; | ||||||
| 		int tone_counters_[3]; | 		int tone_counters_[3] = {0, 0, 0}; | ||||||
| 		int tone_outputs_[3]; | 		int tone_outputs_[3] = {0, 0, 0}; | ||||||
|  |  | ||||||
| 		int noise_period_; | 		int noise_period_ = 0; | ||||||
| 		int noise_counter_; | 		int noise_counter_ = 0; | ||||||
| 		int noise_shift_register_; | 		int noise_shift_register_ = 0xffff; | ||||||
| 		int noise_output_; | 		int noise_output_ = 0; | ||||||
|  |  | ||||||
| 		int envelope_period_; | 		int envelope_period_ = 0; | ||||||
| 		int envelope_divider_; | 		int envelope_divider_ = 0; | ||||||
| 		int envelope_position_; | 		int envelope_position_ = 0; | ||||||
| 		int envelope_shapes_[16][32]; | 		int envelope_shapes_[16][32]; | ||||||
| 		int envelope_overflow_masks_[16]; | 		int envelope_overflow_masks_[16]; | ||||||
|  |  | ||||||
| @@ -129,7 +132,7 @@ class AY38910: public ::Outputs::Filter<AY38910> { | |||||||
| 		inline void evaluate_output_volume(); | 		inline void evaluate_output_volume(); | ||||||
|  |  | ||||||
| 		inline void update_bus(); | 		inline void update_bus(); | ||||||
| 		PortHandler *port_handler_; | 		PortHandler *port_handler_ = nullptr; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										115
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | |||||||
|  | // | ||||||
|  | //  KonamiSCC.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 06/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "KonamiSCC.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | using namespace Konami; | ||||||
|  |  | ||||||
|  | SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) : | ||||||
|  | 	task_queue_(task_queue) {} | ||||||
|  |  | ||||||
|  | bool SCC::is_zero_level() { | ||||||
|  | 	return !(channel_enable_ & 0x1f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { | ||||||
|  | 	if(is_zero_level()) { | ||||||
|  | 		std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::size_t c = 0; | ||||||
|  | 	while((master_divider_&7) && c < number_of_samples) { | ||||||
|  | 		target[c] = transient_output_level_; | ||||||
|  | 		master_divider_++; | ||||||
|  | 		c++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	while(c < number_of_samples) { | ||||||
|  | 		for(int channel = 0; channel < 5; ++channel) { | ||||||
|  | 			if(channels_[channel].tone_counter) channels_[channel].tone_counter--; | ||||||
|  | 			else { | ||||||
|  | 				channels_[channel].offset = (channels_[channel].offset + 1) & 0x1f; | ||||||
|  | 				channels_[channel].tone_counter = channels_[channel].period; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 		for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { | ||||||
|  | 			target[c] = transient_output_level_; | ||||||
|  | 			c++; | ||||||
|  | 			master_divider_++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SCC::write(uint16_t address, uint8_t value) { | ||||||
|  | 	address &= 0xff; | ||||||
|  | 	if(address < 0x80) ram_[address] = value; | ||||||
|  |  | ||||||
|  | 	task_queue_.defer([=] { | ||||||
|  | 		// Check for a write into waveform memory. | ||||||
|  | 		if(address < 0x80) { | ||||||
|  | 			waves_[address >> 5].samples[address & 0x1f] = value; | ||||||
|  | 		} else switch(address) { | ||||||
|  | 			default: break; | ||||||
|  |  | ||||||
|  | 			case 0x80: case 0x82: case 0x84: case 0x86: case 0x88: { | ||||||
|  | 				int channel = (address - 0x80) >> 1; | ||||||
|  | 				channels_[channel].period = (channels_[channel].period & ~0xff) | value; | ||||||
|  | 			} break; | ||||||
|  |  | ||||||
|  | 			case 0x81: case 0x83: case 0x85: case 0x87: case 0x89: { | ||||||
|  | 				int channel = (address - 0x80) >> 1; | ||||||
|  | 				channels_[channel].period = (channels_[channel].period & 0xff) | ((value & 0xf) << 8); | ||||||
|  | 			} break; | ||||||
|  |  | ||||||
|  | 			case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: | ||||||
|  | 				channels_[address - 0x8a].amplitude = value & 0xf; | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0x8f: | ||||||
|  | 				channel_enable_ = value; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		evaluate_output_volume(); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SCC::evaluate_output_volume() { | ||||||
|  | 	transient_output_level_ = | ||||||
|  | 		static_cast<int16_t>( | ||||||
|  | 			(( | ||||||
|  | 				(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 | ||||||
|  | 			) * master_volume_) / (255*15*5) | ||||||
|  | 			// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5. | ||||||
|  | 		); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SCC::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	master_volume_ = range; | ||||||
|  | 	evaluate_output_volume(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t SCC::read(uint16_t address) { | ||||||
|  | 	address &= 0xff; | ||||||
|  | 	if(address < 0x80) { | ||||||
|  | 		return ram_[address]; | ||||||
|  | 	} | ||||||
|  | 	return 0xff; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										74
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | // | ||||||
|  | //  KonamiSCC.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 06/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef KonamiSCC_hpp | ||||||
|  | #define KonamiSCC_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
|  | namespace Konami { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides an emulation of Konami's Sound Creative Chip ('SCC'). | ||||||
|  |  | ||||||
|  | 	The SCC is a primitive wavetable synthesis chip, offering 32-sample tables, | ||||||
|  | 	and five channels of output. The original SCC uses the same wave for channels | ||||||
|  | 	four and five, the SCC+ supports different waves for the two channels. | ||||||
|  | */ | ||||||
|  | class SCC: public ::Outputs::Speaker::SampleSource { | ||||||
|  | 	public: | ||||||
|  | 		/// Creates a new SCC. | ||||||
|  | 		SCC(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||||
|  |  | ||||||
|  | 		/// As per ::SampleSource; provides a broadphase test for silence. | ||||||
|  | 		bool is_zero_level(); | ||||||
|  |  | ||||||
|  | 		/// As per ::SampleSource; provides audio output. | ||||||
|  | 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
|  | 		/// Writes to the SCC. | ||||||
|  | 		void write(uint16_t address, uint8_t value); | ||||||
|  |  | ||||||
|  | 		/// Reads from the SCC. | ||||||
|  | 		uint8_t read(uint16_t address); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|  |  | ||||||
|  | 		// State from here on down is accessed ony from the audio thread. | ||||||
|  | 		int master_divider_ = 0; | ||||||
|  | 		std::int16_t master_volume_ = 0; | ||||||
|  | 		int16_t transient_output_level_ = 0; | ||||||
|  |  | ||||||
|  | 		struct Channel { | ||||||
|  | 			int period = 0; | ||||||
|  | 			int amplitude = 0; | ||||||
|  |  | ||||||
|  | 			int tone_counter = 0; | ||||||
|  | 			int offset = 0; | ||||||
|  | 		} channels_[5]; | ||||||
|  |  | ||||||
|  | 		struct Wavetable { | ||||||
|  | 			std::uint8_t samples[32]; | ||||||
|  | 		} waves_[4]; | ||||||
|  |  | ||||||
|  | 		std::uint8_t channel_enable_ = 0; | ||||||
|  | 		std::uint8_t test_register_ = 0; | ||||||
|  |  | ||||||
|  | 		void evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 		// This keeps a copy of wave memory that is accessed from the | ||||||
|  | 		// main emulation thread. | ||||||
|  | 		std::uint8_t ram_[128]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* KonamiSCC_hpp */ | ||||||
							
								
								
									
										161
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | |||||||
|  | // | ||||||
|  | //  SN76489.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 26/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "SN76489.hpp" | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
|  | using namespace TI; | ||||||
|  |  | ||||||
|  | SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { | ||||||
|  | 	set_sample_volume_range(0); | ||||||
|  |  | ||||||
|  | 	switch(personality) { | ||||||
|  | 		case Personality::SN76494: | ||||||
|  | 			master_divider_period_ = 2; | ||||||
|  | 			shifter_is_16bit_ = false; | ||||||
|  | 		break; | ||||||
|  | 		case Personality::SN76489: | ||||||
|  | 			master_divider_period_ = 16; | ||||||
|  | 			shifter_is_16bit_ = false; | ||||||
|  | 		break; | ||||||
|  | 		case Personality::SMS: | ||||||
|  | 			master_divider_period_ = 16; | ||||||
|  | 			shifter_is_16bit_ = true; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	assert((master_divider_period_ % additional_divider) == 0); | ||||||
|  | 	assert(additional_divider < master_divider_period_); | ||||||
|  | 	master_divider_period_ /= additional_divider; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SN76489::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	// Build a volume table. | ||||||
|  | 	double multiplier = pow(10.0, -0.1); | ||||||
|  | 	double volume = static_cast<float>(range) / 4.0f;	// As there are four channels. | ||||||
|  | 	for(int c = 0; c < 16; ++c) { | ||||||
|  | 		volumes_[c] = (int)round(volume); | ||||||
|  | 		volume *= multiplier; | ||||||
|  | 	} | ||||||
|  | 	volumes_[15] = 0; | ||||||
|  | 	evaluate_output_volume(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SN76489::set_register(uint8_t value) { | ||||||
|  | 	task_queue_.defer([value, this] () { | ||||||
|  | 		if(value & 0x80) { | ||||||
|  | 			active_register_ = value; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const int channel = (active_register_ >> 5)&3; | ||||||
|  | 		if(active_register_ & 0x10) { | ||||||
|  | 			// latch for volume | ||||||
|  | 			channels_[channel].volume = value & 0xf; | ||||||
|  | 			evaluate_output_volume(); | ||||||
|  | 		} else { | ||||||
|  | 			// latch for tone/data | ||||||
|  | 			if(channel < 3) { | ||||||
|  | 				if(value & 0x80) { | ||||||
|  | 					channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf); | ||||||
|  | 				} else { | ||||||
|  | 					channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4)); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				// writes to the noise register always reset the shifter | ||||||
|  | 				noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000; | ||||||
|  |  | ||||||
|  | 				if(value & 4) { | ||||||
|  | 					noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15; | ||||||
|  | 				} else { | ||||||
|  | 					noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3)); | ||||||
|  | 				// Special case: if these bits are both set, the noise channel should track channel 2, | ||||||
|  | 				// which is marked with a divider of 0xffff. | ||||||
|  | 				if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SN76489::is_zero_level() { | ||||||
|  | 	return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SN76489::evaluate_output_volume() { | ||||||
|  | 	output_volume_ = static_cast<int16_t>( | ||||||
|  | 		channels_[0].level * volumes_[channels_[0].volume] + | ||||||
|  | 		channels_[1].level * volumes_[channels_[1].volume] + | ||||||
|  | 		channels_[2].level * volumes_[channels_[2].volume] + | ||||||
|  | 		channels_[3].level * volumes_[channels_[3].volume] | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { | ||||||
|  | 	std::size_t c = 0; | ||||||
|  | 	while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) { | ||||||
|  | 		target[c] = output_volume_; | ||||||
|  | 		master_divider_++; | ||||||
|  | 		c++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	while(c < number_of_samples) { | ||||||
|  | 		bool did_flip = false; | ||||||
|  |  | ||||||
|  | #define step_channel(x, s) \ | ||||||
|  | 		if(channels_[x].counter) channels_[x].counter--;\ | ||||||
|  | 		else {\ | ||||||
|  | 			channels_[x].level ^= 1;\ | ||||||
|  | 			channels_[x].counter = channels_[x].divider;\ | ||||||
|  | 			s;\ | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		step_channel(0, /**/); | ||||||
|  | 		step_channel(1, /**/); | ||||||
|  | 		step_channel(2, did_flip = true); | ||||||
|  |  | ||||||
|  | #undef step_channel | ||||||
|  |  | ||||||
|  | 		if(channels_[3].divider != 0xffff) { | ||||||
|  | 			if(channels_[3].counter) channels_[3].counter--; | ||||||
|  | 			else { | ||||||
|  | 				did_flip = true; | ||||||
|  | 				channels_[3].counter = channels_[3].divider; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(did_flip) { | ||||||
|  | 			channels_[3].level = noise_shifter_ & 1; | ||||||
|  | 			int new_bit = channels_[3].level; | ||||||
|  | 			switch(noise_mode_) { | ||||||
|  | 				default: break; | ||||||
|  | 				case Noise15: | ||||||
|  | 					new_bit ^= (noise_shifter_ >> 1); | ||||||
|  | 				break; | ||||||
|  | 				case Noise16: | ||||||
|  | 					new_bit ^= (noise_shifter_ >> 3); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			noise_shifter_ >>= 1; | ||||||
|  | 			noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 		for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) { | ||||||
|  | 			target[c] = output_volume_; | ||||||
|  | 			c++; | ||||||
|  | 			master_divider_++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	master_divider_ &= (master_divider_period_ - 1); | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | // | ||||||
|  | //  SN76489.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 26/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef SN76489_hpp | ||||||
|  | #define SN76489_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
|  | namespace TI { | ||||||
|  |  | ||||||
|  | class SN76489: public Outputs::Speaker::SampleSource { | ||||||
|  | 	public: | ||||||
|  | 		enum class Personality { | ||||||
|  | 			SN76489, | ||||||
|  | 			SN76494, | ||||||
|  | 			SMS | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Creates a new SN76489. | ||||||
|  | 		SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1); | ||||||
|  |  | ||||||
|  | 		/// Writes a new value to the SN76489. | ||||||
|  | 		void set_register(uint8_t value); | ||||||
|  |  | ||||||
|  | 		// As per SampleSource. | ||||||
|  | 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||||
|  | 		bool is_zero_level(); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int master_divider_ = 0; | ||||||
|  | 		int master_divider_period_ = 16; | ||||||
|  | 		int16_t output_volume_ = 0; | ||||||
|  | 		void evaluate_output_volume(); | ||||||
|  | 		int volumes_[16]; | ||||||
|  |  | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|  |  | ||||||
|  | 		struct ToneChannel { | ||||||
|  | 			// Programmatically-set state; updated by the processor. | ||||||
|  | 			uint16_t divider = 0; | ||||||
|  | 			uint8_t volume = 0xf; | ||||||
|  |  | ||||||
|  | 			// Active state; self-evolving as a function of time. | ||||||
|  | 			uint16_t counter = 0; | ||||||
|  | 			int level = 0; | ||||||
|  | 		} channels_[4]; | ||||||
|  | 		enum { | ||||||
|  | 			Periodic15, | ||||||
|  | 			Periodic16, | ||||||
|  | 			Noise15, | ||||||
|  | 			Noise16 | ||||||
|  | 		} noise_mode_ = Periodic15; | ||||||
|  | 		uint16_t noise_shifter_ = 0; | ||||||
|  | 		int active_register_ = 0; | ||||||
|  |  | ||||||
|  | 		bool shifter_is_16bit_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* SN76489_hpp */ | ||||||
| @@ -45,6 +45,7 @@ AsyncTaskQueue::AsyncTaskQueue() | |||||||
|  |  | ||||||
| AsyncTaskQueue::~AsyncTaskQueue() { | AsyncTaskQueue::~AsyncTaskQueue() { | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
|  | 	flush(); | ||||||
| 	dispatch_release(serial_dispatch_queue_); | 	dispatch_release(serial_dispatch_queue_); | ||||||
| 	serial_dispatch_queue_ = nullptr; | 	serial_dispatch_queue_ = nullptr; | ||||||
| #else | #else | ||||||
| @@ -79,3 +80,26 @@ void AsyncTaskQueue::flush() { | |||||||
| 	flush_condition->wait(lock); | 	flush_condition->wait(lock); | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
|  | DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { | ||||||
|  | 	perform(); | ||||||
|  | 	flush(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { | ||||||
|  | 	if(!deferred_tasks_) { | ||||||
|  | 		deferred_tasks_.reset(new std::list<std::function<void(void)>>); | ||||||
|  | 	} | ||||||
|  | 	deferred_tasks_->push_back(function); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DeferringAsyncTaskQueue::perform() { | ||||||
|  | 	if(!deferred_tasks_) return; | ||||||
|  | 	std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_; | ||||||
|  | 	deferred_tasks_.reset(); | ||||||
|  | 	enqueue([deferred_tasks] { | ||||||
|  | 		for(auto &function : *deferred_tasks) { | ||||||
|  | 			function(); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,10 +9,12 @@ | |||||||
| #ifndef AsyncTaskQueue_hpp | #ifndef AsyncTaskQueue_hpp | ||||||
| #define AsyncTaskQueue_hpp | #define AsyncTaskQueue_hpp | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <functional> | ||||||
|  | #include <list> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <thread> | #include <thread> | ||||||
| #include <list> |  | ||||||
| #include <condition_variable> |  | ||||||
|  |  | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
| #include <dispatch/dispatch.h> | #include <dispatch/dispatch.h> | ||||||
| @@ -28,7 +30,7 @@ namespace Concurrency { | |||||||
| class AsyncTaskQueue { | class AsyncTaskQueue { | ||||||
| 	public: | 	public: | ||||||
| 		AsyncTaskQueue(); | 		AsyncTaskQueue(); | ||||||
| 		~AsyncTaskQueue(); | 		virtual ~AsyncTaskQueue(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Adds @c function to the queue. | 			Adds @c function to the queue. | ||||||
| @@ -57,6 +59,39 @@ class AsyncTaskQueue { | |||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A deferring async task queue is one that accepts a list of functions to be performed but defers | ||||||
|  | 	any action until told to perform. It performs them by enquing a single asynchronous task that will | ||||||
|  | 	perform the deferred tasks in order. | ||||||
|  |  | ||||||
|  | 	It therefore offers similar semantics to an asynchronous task queue, but allows for management of | ||||||
|  | 	synchronisation costs, since neither defer nor perform make any effort to be thread safe. | ||||||
|  | */ | ||||||
|  | class DeferringAsyncTaskQueue: public AsyncTaskQueue { | ||||||
|  | 	public: | ||||||
|  | 		~DeferringAsyncTaskQueue(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Adds a function to the deferral list. | ||||||
|  |  | ||||||
|  | 			This is not thread safe; it should be serialised with other calls to itself and to perform. | ||||||
|  | 		*/ | ||||||
|  | 		void defer(std::function<void(void)> function); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Enqueues a function that will perform all currently deferred functions, in the | ||||||
|  | 			order that they were deferred. | ||||||
|  |  | ||||||
|  | 			This is not thread safe; it should be serialised with other calls to itself and to defer. | ||||||
|  | 		*/ | ||||||
|  | 		void perform(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// TODO: this is a shared_ptr because of the issues capturing moveables in C++11; | ||||||
|  | 		// switch to a unique_ptr if/when adapting to C++14 | ||||||
|  | 		std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* Concurrency_hpp */ | #endif /* Concurrency_hpp */ | ||||||
|   | |||||||
| @@ -12,6 +12,16 @@ | |||||||
|  |  | ||||||
| using namespace Concurrency; | using namespace Concurrency; | ||||||
|  |  | ||||||
|  | BestEffortUpdater::BestEffortUpdater() { | ||||||
|  | 	// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means. | ||||||
|  | 	update_is_ongoing_.clear(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BestEffortUpdater::~BestEffortUpdater() { | ||||||
|  | 	// Don't allow further deconstruction until the task queue is stopped. | ||||||
|  | 	flush(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BestEffortUpdater::update() { | void BestEffortUpdater::update() { | ||||||
| 	// Perform an update only if one is not currently ongoing. | 	// Perform an update only if one is not currently ongoing. | ||||||
| 	if(!update_is_ongoing_.test_and_set()) { | 	if(!update_is_ongoing_.test_and_set()) { | ||||||
| @@ -26,13 +36,11 @@ void BestEffortUpdater::update() { | |||||||
| 				// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate | 				// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate | ||||||
| 				// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum — | 				// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum — | ||||||
| 				// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments. | 				// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments. | ||||||
| 				const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count(); | 				const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count(); | ||||||
| 				if(duration > 0) { | 				if(integer_duration > 0) { | ||||||
| 					double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_; |  | ||||||
| 					error_ = fmod(cycles, 1.0); |  | ||||||
|  |  | ||||||
| 					if(delegate_) { | 					if(delegate_) { | ||||||
| 						delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_); | 						const double duration = static_cast<double>(integer_duration) / 1e9; | ||||||
|  | 						delegate_->update(this, duration, has_skipped_); | ||||||
| 					} | 					} | ||||||
| 					has_skipped_ = false; | 					has_skipped_ = false; | ||||||
| 				} | 				} | ||||||
| @@ -60,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) { | |||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BestEffortUpdater::set_clock_rate(const double clock_rate) { |  | ||||||
| 	async_task_queue_.enqueue([this, clock_rate]() { |  | ||||||
| 		this->clock_rate_ = clock_rate; |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ | |||||||
| #include <chrono> | #include <chrono> | ||||||
|  |  | ||||||
| #include "AsyncTaskQueue.hpp" | #include "AsyncTaskQueue.hpp" | ||||||
|  | #include "../ClockReceiver/TimeTypes.hpp" | ||||||
|  |  | ||||||
| namespace Concurrency { | namespace Concurrency { | ||||||
|  |  | ||||||
| @@ -25,17 +26,17 @@ namespace Concurrency { | |||||||
| */ | */ | ||||||
| class BestEffortUpdater { | class BestEffortUpdater { | ||||||
| 	public: | 	public: | ||||||
|  | 		BestEffortUpdater(); | ||||||
|  | 		~BestEffortUpdater(); | ||||||
|  |  | ||||||
| 		/// A delegate receives timing cues. | 		/// A delegate receives timing cues. | ||||||
| 		struct Delegate { | 		struct Delegate { | ||||||
| 			virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0; | 			virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0; | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		/// Sets the current delegate. | 		/// Sets the current delegate. | ||||||
| 		void set_delegate(Delegate *); | 		void set_delegate(Delegate *); | ||||||
|  |  | ||||||
| 		/// Sets the clock rate of the delegate. |  | ||||||
| 		void set_clock_rate(double clock_rate); |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time. | 			If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time. | ||||||
| 			The call is asynchronous; this method will return immediately. | 			The call is asynchronous; this method will return immediately. | ||||||
| @@ -51,11 +52,9 @@ class BestEffortUpdater { | |||||||
|  |  | ||||||
| 		std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_; | 		std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_; | ||||||
| 		bool has_previous_time_point_ = false; | 		bool has_previous_time_point_ = false; | ||||||
| 		double error_ = 0.0; |  | ||||||
| 		bool has_skipped_ = false; | 		bool has_skipped_ = false; | ||||||
|  |  | ||||||
| 		Delegate *delegate_ = nullptr; | 		Delegate *delegate_ = nullptr; | ||||||
| 		double clock_rate_ = 1.0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								Configurable/Configurable.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Configurable/Configurable.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  Configurable.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 18/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Configurable.hpp" | ||||||
|  |  | ||||||
|  | using namespace Configurable; | ||||||
|  |  | ||||||
|  | ListSelection *BooleanSelection::list_selection() { | ||||||
|  | 	return new ListSelection(value ? "yes" : "no"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ListSelection *ListSelection::list_selection() { | ||||||
|  | 	return new ListSelection(value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BooleanSelection *ListSelection::boolean_selection() { | ||||||
|  | 	return new BooleanSelection(value != "no" && value != "n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BooleanSelection *BooleanSelection::boolean_selection() { | ||||||
|  | 	return new BooleanSelection(value); | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								Configurable/Configurable.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								Configurable/Configurable.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | // | ||||||
|  | //  Configurable.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 17/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Configurable_h | ||||||
|  | #define Configurable_h | ||||||
|  |  | ||||||
|  | #include <map> | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Configurable { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	The Option class hierarchy provides a way for components, machines, etc, to provide a named | ||||||
|  | 	list of typed options to which they can respond. | ||||||
|  | */ | ||||||
|  | struct Option { | ||||||
|  | 	std::string long_name; | ||||||
|  | 	std::string short_name; | ||||||
|  | 	virtual ~Option() {} | ||||||
|  |  | ||||||
|  | 	Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {} | ||||||
|  |  | ||||||
|  | 	virtual bool operator==(const Option &rhs) { | ||||||
|  | 		return long_name == rhs.long_name && short_name == rhs.short_name; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct BooleanOption: public Option { | ||||||
|  | 	BooleanOption(const std::string &long_name, const std::string &short_name) : Option(long_name, short_name) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct ListOption: public Option { | ||||||
|  | 	std::vector<std::string> options; | ||||||
|  | 	ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {} | ||||||
|  |  | ||||||
|  | 	virtual bool operator==(const Option &rhs) { | ||||||
|  | 		const ListOption *list_rhs = dynamic_cast<const ListOption *>(&rhs); | ||||||
|  | 		if(!list_rhs) return false; | ||||||
|  | 		return long_name == rhs.long_name && short_name == rhs.short_name && options == list_rhs->options; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct BooleanSelection; | ||||||
|  | struct ListSelection; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Selections are responses to Options. | ||||||
|  | */ | ||||||
|  | struct Selection { | ||||||
|  | 	virtual ~Selection() {} | ||||||
|  | 	virtual ListSelection *list_selection() = 0; | ||||||
|  | 	virtual BooleanSelection *boolean_selection() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct BooleanSelection: public Selection { | ||||||
|  | 	bool value; | ||||||
|  |  | ||||||
|  | 	ListSelection *list_selection(); | ||||||
|  | 	BooleanSelection *boolean_selection(); | ||||||
|  | 	BooleanSelection(bool value) : value(value) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct ListSelection: public Selection { | ||||||
|  | 	std::string value; | ||||||
|  | 	 | ||||||
|  | 	ListSelection *list_selection(); | ||||||
|  | 	BooleanSelection *boolean_selection(); | ||||||
|  | 	ListSelection(const std::string value) : value(value) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | using SelectionSet = std::map<std::string, std::unique_ptr<Selection>>; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A Configuratble provides the options that it responds to and allows selections to be set. | ||||||
|  | */ | ||||||
|  | struct Device { | ||||||
|  | 	virtual std::vector<std::unique_ptr<Option>> get_options() = 0; | ||||||
|  | 	virtual void set_selections(const SelectionSet &selection_by_option) = 0; | ||||||
|  | 	virtual SelectionSet get_accurate_selections() = 0; | ||||||
|  | 	virtual SelectionSet get_user_friendly_selections() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template <typename T> T *selection(const Configurable::SelectionSet &selections_by_option, const std::string &name) { | ||||||
|  | 	auto selection = selections_by_option.find(name); | ||||||
|  | 	if(selection == selections_by_option.end()) return nullptr; | ||||||
|  | 	return dynamic_cast<T *>(selection->second.get()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Configurable_h */ | ||||||
							
								
								
									
										93
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | // | ||||||
|  | //  StandardOptions.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StandardOptions.hpp" | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends a Boolean selection of @c selection for option @c name to @c selection_set. | ||||||
|  | */ | ||||||
|  | void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) { | ||||||
|  | 	selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found. | ||||||
|  | */ | ||||||
|  | bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) { | ||||||
|  | 	auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload"); | ||||||
|  | 	if(!quickload) return false; | ||||||
|  | 	result = quickload->value; | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Standard option list builder | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) { | ||||||
|  | 	std::vector<std::unique_ptr<Configurable::Option>> options; | ||||||
|  | 	if(mask & QuickLoadTape)				options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload")); | ||||||
|  | 	if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) { | ||||||
|  | 		std::vector<std::string> display_options; | ||||||
|  | 		if(mask & DisplayComposite)	display_options.emplace_back("composite"); | ||||||
|  | 		if(mask & DisplaySVideo) 	display_options.emplace_back("svideo"); | ||||||
|  | 		if(mask & DisplayRGB) 		display_options.emplace_back("rgb"); | ||||||
|  | 		options.emplace_back(new Configurable::ListOption("Display", "display", display_options)); | ||||||
|  | 	} | ||||||
|  | 	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); | ||||||
|  | 	return options; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Selection appenders | ||||||
|  | void Configurable::append_quick_load_tape_selection(Configurable::SelectionSet &selection_set, bool selection) { | ||||||
|  | 	append_bool(selection_set, "quickload", selection); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection) { | ||||||
|  | 	append_bool(selection_set, "autotapemotor", selection); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) { | ||||||
|  | 	std::string string_selection; | ||||||
|  | 	switch(selection) { | ||||||
|  | 		default: | ||||||
|  | 		case Display::RGB:			string_selection = "rgb";		break; | ||||||
|  | 		case Display::SVideo:		string_selection = "svideo";	break; | ||||||
|  | 		case Display::Composite:	string_selection = "composite";	break; | ||||||
|  | 	} | ||||||
|  | 	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Selection parsers | ||||||
|  | bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) { | ||||||
|  | 	return get_bool(selections_by_option, "quickload", result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Configurable::get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result) { | ||||||
|  | 	return get_bool(selections_by_option, "autotapemotor", result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Configurable::get_display(const Configurable::SelectionSet &selections_by_option, Configurable::Display &result) { | ||||||
|  | 	auto display = Configurable::selection<Configurable::ListSelection>(selections_by_option, "display"); | ||||||
|  | 	if(display) { | ||||||
|  | 		if(display->value == "rgb") { | ||||||
|  | 			result = Configurable::Display::RGB; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		if(display->value == "svideo") { | ||||||
|  | 			result = Configurable::Display::SVideo; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		if(display->value == "composite") { | ||||||
|  | 			result = Configurable::Display::Composite; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | // | ||||||
|  | //  StandardOptions.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StandardOptions_hpp | ||||||
|  | #define StandardOptions_hpp | ||||||
|  |  | ||||||
|  | #include "Configurable.hpp" | ||||||
|  |  | ||||||
|  | namespace Configurable { | ||||||
|  |  | ||||||
|  | enum StandardOptions { | ||||||
|  | 	DisplayRGB					= (1 << 0), | ||||||
|  | 	DisplaySVideo				= (1 << 1), | ||||||
|  | 	DisplayComposite			= (1 << 2), | ||||||
|  | 	QuickLoadTape				= (1 << 3), | ||||||
|  | 	AutomaticTapeMotorControl	= (1 << 4) | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class Display { | ||||||
|  | 	RGB, | ||||||
|  | 	SVideo, | ||||||
|  | 	Composite | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	@returns An option list comprised of the standard names for all the options indicated by @c mask. | ||||||
|  | */ | ||||||
|  | std::vector<std::unique_ptr<Option>> standard_options(StandardOptions mask); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends to @c selection_set a selection of @c selection for QuickLoadTape. | ||||||
|  | */ | ||||||
|  | void append_quick_load_tape_selection(SelectionSet &selection_set, bool selection); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends to @c selection_set a selection of @c selection for AutomaticTapeMotorControl. | ||||||
|  | */ | ||||||
|  | void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends to @c selection_set a selection of @c selection for DisplayRGBComposite. | ||||||
|  | */ | ||||||
|  | void append_display_selection(SelectionSet &selection_set, Display selection); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts to discern a QuickLoadTape selection from @c selections_by_option. | ||||||
|  |   | ||||||
|  | 	@param selections_by_option The user selections. | ||||||
|  | 	@param result The location to which the selection will be stored if found. | ||||||
|  | 	@returns @c true if a selection is found; @c false otherwise. | ||||||
|  | */ | ||||||
|  | bool get_quick_load_tape(const SelectionSet &selections_by_option, bool &result); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts to discern an AutomaticTapeMotorControl selection from @c selections_by_option. | ||||||
|  |   | ||||||
|  | 	@param selections_by_option The user selections. | ||||||
|  | 	@param result The location to which the selection will be stored if found. | ||||||
|  | 	@returns @c true if a selection is found; @c false otherwise. | ||||||
|  | */ | ||||||
|  | bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts to discern a display RGB/composite selection from @c selections_by_option. | ||||||
|  |   | ||||||
|  | 	@param selections_by_option The user selections. | ||||||
|  | 	@param result The location to which the selection will be stored if found. | ||||||
|  | 	@returns @c true if a selection is found; @c false otherwise. | ||||||
|  | */ | ||||||
|  | bool get_display(const SelectionSet &selections_by_option, Display &result); | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* StandardOptions_hpp */ | ||||||
| @@ -21,18 +21,45 @@ class Joystick { | |||||||
| 	public: | 	public: | ||||||
| 		virtual ~Joystick() {} | 		virtual ~Joystick() {} | ||||||
|  |  | ||||||
| 		enum class DigitalInput { | 		struct DigitalInput { | ||||||
| 			Up, Down, Left, Right, Fire | 			enum Type { | ||||||
|  | 				Up, Down, Left, Right, Fire, | ||||||
|  | 				Key | ||||||
|  | 			} type; | ||||||
|  | 			union { | ||||||
|  | 				struct { | ||||||
|  | 					int index; | ||||||
|  | 				} control; | ||||||
|  | 				struct { | ||||||
|  | 					wchar_t symbol; | ||||||
|  | 				} key; | ||||||
|  | 			} info; | ||||||
|  |  | ||||||
|  | 			DigitalInput(Type type, int index = 0) : type(type) { | ||||||
|  | 				info.control.index = index; | ||||||
|  | 			} | ||||||
|  | 			DigitalInput(wchar_t symbol) : type(Key) { | ||||||
|  | 				info.key.symbol = symbol; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			bool operator == (const DigitalInput &rhs) { | ||||||
|  | 				if(rhs.type != type) return false; | ||||||
|  | 				if(rhs.type == Key) { | ||||||
|  | 					return rhs.info.key.symbol == info.key.symbol; | ||||||
|  | 				} else { | ||||||
|  | 					return rhs.info.control.index == info.control.index; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | 		virtual std::vector<DigitalInput> get_inputs() = 0; | ||||||
|  |  | ||||||
| 		// Host interface. | 		// Host interface. | ||||||
| 		virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0; | 		virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0; | ||||||
| 		virtual void reset_all_inputs() { | 		virtual void reset_all_inputs() { | ||||||
| 			set_digital_input(DigitalInput::Up, false); | 			for(const auto &input: get_inputs()) { | ||||||
| 			set_digital_input(DigitalInput::Down, false); | 				set_digital_input(input, false); | ||||||
| 			set_digital_input(DigitalInput::Left, false); | 			} | ||||||
| 			set_digital_input(DigitalInput::Right, false); |  | ||||||
| 			set_digital_input(DigitalInput::Fire, false); |  | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ using namespace Inputs; | |||||||
| Keyboard::Keyboard() {} | Keyboard::Keyboard() {} | ||||||
|  |  | ||||||
| void Keyboard::set_key_pressed(Key key, bool is_pressed) { | void Keyboard::set_key_pressed(Key key, bool is_pressed) { | ||||||
| 	size_t key_offset = static_cast<size_t>(key); | 	std::size_t key_offset = static_cast<std::size_t>(key); | ||||||
| 	if(key_offset >= key_states_.size()) { | 	if(key_offset >= key_states_.size()) { | ||||||
| 		key_states_.resize(key_offset+1, false); | 		key_states_.resize(key_offset+1, false); | ||||||
| 	} | 	} | ||||||
| @@ -32,7 +32,7 @@ void Keyboard::set_delegate(Delegate *delegate) { | |||||||
| } | } | ||||||
|  |  | ||||||
| bool Keyboard::get_key_state(Key key) { | bool Keyboard::get_key_state(Key key) { | ||||||
| 	size_t key_offset = static_cast<size_t>(key); | 	std::size_t key_offset = static_cast<std::size_t>(key); | ||||||
| 	if(key_offset >= key_states_.size()) return false; | 	if(key_offset >= key_states_.size()) return false; | ||||||
| 	return key_states_[key_offset]; | 	return key_states_[key_offset]; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -20,12 +20,29 @@ | |||||||
| #include "../Utility/MemoryFuzzer.hpp" | #include "../Utility/MemoryFuzzer.hpp" | ||||||
| #include "../Utility/Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/AmstradCPC/Target.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | enum ROMType: int { | ||||||
|  | 	OS464 = 0,	BASIC464, | ||||||
|  | 	OS664,		BASIC664, | ||||||
|  | 	OS6128,		BASIC6128, | ||||||
|  | 	AMSDOS | ||||||
|  | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | ||||||
| 	is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period | 	is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period | ||||||
| @@ -105,14 +122,12 @@ class InterruptTimer { | |||||||
| class AYDeferrer { | class AYDeferrer { | ||||||
| 	public: | 	public: | ||||||
| 		/// Constructs a new AY instance and sets its clock rate. | 		/// Constructs a new AY instance and sets its clock rate. | ||||||
| 		inline void setup_output() { | 		AYDeferrer() : ay_(audio_queue_), speaker_(ay_) { | ||||||
| 			ay_.reset(new GI::AY38910::AY38910); | 			speaker_.set_input_rate(1000000); | ||||||
| 			ay_->set_input_rate(1000000); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Destructs the AY. | 		~AYDeferrer() { | ||||||
| 		inline void close_output() { | 			audio_queue_.flush(); | ||||||
| 			ay_.reset(); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Adds @c half_cycles half cycles to the amount of time that has passed. | 		/// Adds @c half_cycles half cycles to the amount of time that has passed. | ||||||
| @@ -122,26 +137,28 @@ class AYDeferrer { | |||||||
|  |  | ||||||
| 		/// Enqueues an update-to-now into the AY's deferred queue. | 		/// Enqueues an update-to-now into the AY's deferred queue. | ||||||
| 		inline void update() { | 		inline void update() { | ||||||
| 			ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4))); | 			speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4))); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Issues a request to the AY to perform all processing up to the current time. | 		/// Issues a request to the AY to perform all processing up to the current time. | ||||||
| 		inline void flush() { | 		inline void flush() { | ||||||
| 			ay_->flush(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the speaker the AY is using for output. | 		/// @returns the speaker the AY is using for output. | ||||||
| 		std::shared_ptr<Outputs::Speaker> get_speaker() { | 		Outputs::Speaker::Speaker *get_speaker() { | ||||||
| 			return ay_; | 			return &speaker_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the AY itself. | 		/// @returns the AY itself. | ||||||
| 		GI::AY38910::AY38910 *ay() { | 		GI::AY38910::AY38910 &ay() { | ||||||
| 			return ay_.get(); | 			return ay_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::shared_ptr<GI::AY38910::AY38910> ay_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		GI::AY38910::AY38910 ay_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_; | ||||||
| 		HalfCycles cycles_since_update_; | 		HalfCycles cycles_since_update_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -300,7 +317,7 @@ class CRTCBusHandler { | |||||||
| 					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" | 					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" | ||||||
| 				"}"); | 				"}"); | ||||||
| 			crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); | 			crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); | ||||||
| 			crt_->set_output_device(Outputs::CRT::Monitor); | 			crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Destructs the CRT. | 		/// Destructs the CRT. | ||||||
| @@ -309,8 +326,8 @@ class CRTCBusHandler { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the CRT. | 		/// @returns the CRT. | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt() { | 		Outputs::CRT::CRT *get_crt() { | ||||||
| 			return crt_; | 			return crt_.get(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| @@ -493,7 +510,7 @@ class CRTCBusHandler { | |||||||
| 		bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false; | 		bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false; | ||||||
| 		int cycles_into_hsync_ = 0; | 		int cycles_into_hsync_ = 0; | ||||||
|  |  | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
| 		uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; | 		uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; | ||||||
|  |  | ||||||
| 		uint8_t *ram_ = nullptr; | 		uint8_t *ram_ = nullptr; | ||||||
| @@ -602,9 +619,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 			const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc, | 			const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc, | ||||||
| 			AYDeferrer &ay, | 			AYDeferrer &ay, | ||||||
| 			Storage::Tape::BinaryTapePlayer &tape_player) : | 			Storage::Tape::BinaryTapePlayer &tape_player) : | ||||||
| 				key_state_(key_state), |  | ||||||
| 				crtc_(crtc), |  | ||||||
| 				ay_(ay), | 				ay_(ay), | ||||||
|  | 				crtc_(crtc), | ||||||
|  | 				key_state_(key_state), | ||||||
| 				tape_player_(tape_player) {} | 				tape_player_(tape_player) {} | ||||||
|  |  | ||||||
| 		/// The i8255 will call this to set a new output value of @c value for @c port. | 		/// The i8255 will call this to set a new output value of @c value for @c port. | ||||||
| @@ -613,7 +630,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 				case 0: | 				case 0: | ||||||
| 					// Port A is connected to the AY's data bus. | 					// Port A is connected to the AY's data bus. | ||||||
| 					ay_.update(); | 					ay_.update(); | ||||||
| 					ay_.ay()->set_data_input(value); | 					ay_.ay().set_data_input(value); | ||||||
| 				break; | 				break; | ||||||
| 				case 1: | 				case 1: | ||||||
| 					// Port B is an input only. So output goes nowehere. | 					// Port B is an input only. So output goes nowehere. | ||||||
| @@ -629,7 +646,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 					tape_player_.set_tape_output((value & 0x20) ? true : false); | 					tape_player_.set_tape_output((value & 0x20) ? true : false); | ||||||
|  |  | ||||||
| 					// Bits 6 and 7 set BDIR and BC1 for the AY. | 					// Bits 6 and 7 set BDIR and BC1 for the AY. | ||||||
| 					ay_.ay()->set_control_lines( | 					ay_.ay().set_control_lines( | ||||||
| 						(GI::AY38910::ControlLines)( | 						(GI::AY38910::ControlLines)( | ||||||
| 							((value & 0x80) ? GI::AY38910::BDIR : 0) | | 							((value & 0x80) ? GI::AY38910::BDIR : 0) | | ||||||
| 							((value & 0x40) ? GI::AY38910::BC1 : 0) | | 							((value & 0x40) ? GI::AY38910::BC1 : 0) | | ||||||
| @@ -642,7 +659,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 		/// The i8255 will call this to obtain a new input for @c port. | 		/// The i8255 will call this to obtain a new input for @c port. | ||||||
| 		uint8_t get_value(int port) { | 		uint8_t get_value(int port) { | ||||||
| 			switch(port) { | 			switch(port) { | ||||||
| 				case 0: return ay_.ay()->get_data_output();	// Port A is wired to the AY | 				case 0: return ay_.ay().get_data_output();	// Port A is wired to the AY | ||||||
| 				case 1:	return | 				case 1:	return | ||||||
| 					(crtc_.get_bus_state().vsync ? 0x01 : 0x00) |	// Bit 0 returns CRTC vsync. | 					(crtc_.get_bus_state().vsync ? 0x01 : 0x00) |	// Bit 0 returns CRTC vsync. | ||||||
| 					(tape_player_.get_input() ? 0x80 : 0x00) |		// Bit 7 returns cassette input. | 					(tape_player_.get_input() ? 0x80 : 0x00) |		// Bit 7 returns cassette input. | ||||||
| @@ -658,8 +675,8 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		AYDeferrer &ay_; | 		AYDeferrer &ay_; | ||||||
| 		KeyboardState &key_state_; |  | ||||||
| 		const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_; | 		const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_; | ||||||
|  | 		KeyboardState &key_state_; | ||||||
| 		Storage::Tape::BinaryTapePlayer &tape_player_; | 		Storage::Tape::BinaryTapePlayer &tape_player_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -667,6 +684,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 	The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. | 	The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. | ||||||
| */ | */ | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public Sleeper::SleepObserver, | 	public Sleeper::SleepObserver, | ||||||
| @@ -674,12 +694,13 @@ class ConcreteMachine: | |||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine() : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			crtc_counter_(HalfCycles(4)),	// This starts the CRTC exactly out of phase with the CPU's memory accesses |  | ||||||
| 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), |  | ||||||
| 			crtc_bus_handler_(ram_, interrupt_timer_), | 			crtc_bus_handler_(ram_, interrupt_timer_), | ||||||
| 			i8255_(i8255_port_handler_), | 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), | ||||||
| 			i8255_port_handler_(key_state_, crtc_, ay_, tape_player_), | 			i8255_port_handler_(key_state_, crtc_, ay_, tape_player_), | ||||||
| 			tape_player_(8000000) { | 			i8255_(i8255_port_handler_), | ||||||
|  | 			tape_player_(8000000), | ||||||
|  | 			crtc_counter_(HalfCycles(4))	// This starts the CRTC exactly out of phase with the CPU's memory accesses | ||||||
|  | 		{ | ||||||
| 			// primary clock is 4Mhz | 			// primary clock is 4Mhz | ||||||
| 			set_clock_rate(4000000); | 			set_clock_rate(4000000); | ||||||
|  |  | ||||||
| @@ -692,6 +713,8 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			tape_player_.set_sleep_observer(this); | 			tape_player_.set_sleep_observer(this); | ||||||
| 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | ||||||
|  |  | ||||||
|  | 			ay_.ay().set_port_handler(&key_state_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The entry point for performing a partial Z80 machine cycle. | 		/// The entry point for performing a partial Z80 machine cycle. | ||||||
| @@ -834,23 +857,20 @@ class ConcreteMachine: | |||||||
| 		/// A CRTMachine function; indicates that outputs should be created now. | 		/// A CRTMachine function; indicates that outputs should be created now. | ||||||
| 		void setup_output(float aspect_ratio) override final { | 		void setup_output(float aspect_ratio) override final { | ||||||
| 			crtc_bus_handler_.setup_output(aspect_ratio); | 			crtc_bus_handler_.setup_output(aspect_ratio); | ||||||
| 			ay_.setup_output(); |  | ||||||
| 			ay_.ay()->set_port_handler(&key_state_); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// A CRTMachine function; indicates that outputs should be destroyed now. | 		/// A CRTMachine function; indicates that outputs should be destroyed now. | ||||||
| 		void close_output() override final { | 		void close_output() override final { | ||||||
| 			crtc_bus_handler_.close_output(); | 			crtc_bus_handler_.close_output(); | ||||||
| 			ay_.close_output(); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the CRT in use. | 		/// @returns the CRT in use. | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt() override final { | 		Outputs::CRT::CRT *get_crt() override final { | ||||||
| 			return crtc_bus_handler_.get_crt(); | 			return crtc_bus_handler_.get_crt(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the speaker in use. | 		/// @returns the speaker in use. | ||||||
| 		std::shared_ptr<Outputs::Speaker> get_speaker() override final { | 		Outputs::Speaker::Speaker *get_speaker() override final { | ||||||
| 			return ay_.get_speaker(); | 			return ay_.get_speaker(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -860,19 +880,21 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override final  { | 		void configure_as_target(const Analyser::Static::Target *target) override final  { | ||||||
| 			switch(target.amstradcpc.model) { | 			auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target); | ||||||
| 				case StaticAnalyser::AmstradCPCModel::CPC464: |  | ||||||
|  | 			switch(cpc_target->model) { | ||||||
|  | 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: | ||||||
| 					rom_model_ = ROMType::OS464; | 					rom_model_ = ROMType::OS464; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
| 					has_fdc_ = false; | 					has_fdc_ = false; | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::AmstradCPCModel::CPC664: | 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: | ||||||
| 					rom_model_ = ROMType::OS664; | 					rom_model_ = ROMType::OS664; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
| 					has_fdc_ = true; | 					has_fdc_ = true; | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::AmstradCPCModel::CPC6128: | 				case Analyser::Static::AmstradCPC::Target::Model::CPC6128: | ||||||
| 					rom_model_ = ROMType::OS6128; | 					rom_model_ = ROMType::OS6128; | ||||||
| 					has_128k_ = true; | 					has_128k_ = true; | ||||||
| 					has_fdc_ = true; | 					has_fdc_ = true; | ||||||
| @@ -894,14 +916,14 @@ class ConcreteMachine: | |||||||
| 			read_pointers_[3] = roms_[upper_rom_].data(); | 			read_pointers_[3] = roms_[upper_rom_].data(); | ||||||
|  |  | ||||||
| 			// Type whatever is required. | 			// Type whatever is required. | ||||||
| 			if(target.loadingCommand.length()) { | 			if(target->loading_command.length()) { | ||||||
| 				set_typer_for_string(target.loadingCommand.c_str()); | 				type_string(target->loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			// If there are any tapes supplied, use the first of them. | 			// If there are any tapes supplied, use the first of them. | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_player_.set_tape(media.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| @@ -918,9 +940,25 @@ class ConcreteMachine: | |||||||
| 			return !media.tapes.empty() || (!media.disks.empty() && has_fdc_); | 			return !media.tapes.empty() || (!media.disks.empty() && has_fdc_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// See header; provides the system ROMs. | 		// Obtains the system ROMs. | ||||||
| 		void set_rom(ROMType type, std::vector<uint8_t> data) override final { | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
| 			roms_[static_cast<int>(type)] = data; | 			auto roms = roms_with_names( | ||||||
|  | 				"AmstradCPC", | ||||||
|  | 				{ | ||||||
|  | 					"os464.rom",	"basic464.rom", | ||||||
|  | 					"os664.rom",	"basic664.rom", | ||||||
|  | 					"os6128.rom",	"basic6128.rom", | ||||||
|  | 					"amsdos.rom" | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
|  | 				auto &data = roms[index]; | ||||||
|  | 				if(!data) return false; | ||||||
|  | 				roms_[static_cast<int>(index)] = std::move(*data); | ||||||
|  | 				roms_[static_cast<int>(index)].resize(16384); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return true; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_component_is_sleeping(void *component, bool is_sleeping) override final { | 		void set_component_is_sleeping(void *component, bool is_sleeping) override final { | ||||||
| @@ -928,11 +966,11 @@ class ConcreteMachine: | |||||||
| 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| #pragma mark - Keyboard | // MARK: - Keyboard | ||||||
|  |  | ||||||
| 		void set_typer_for_string(const char *string) override final { | 		void type_string(const std::string &string) override final { | ||||||
| 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | ||||||
| 			Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		HalfCycles get_typer_delay() override final { | 		HalfCycles get_typer_delay() override final { | ||||||
| @@ -953,8 +991,8 @@ class ConcreteMachine: | |||||||
| 			key_state_.clear_all_keys(); | 			key_state_.clear_all_keys(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		KeyboardMapper &get_keyboard_mapper() override { | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
| 			return keyboard_mapper_; | 			return &keyboard_mapper_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user