mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 15:16:10 +00:00 
			
		
		
		
	Compare commits
	
		
			1511 Commits
		
	
	
		
			2024-09-08
			...
			Tube
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					82a5d5116a | ||
| 
						 | 
					62a6797ef3 | ||
| 
						 | 
					bb66033682 | ||
| 
						 | 
					d4aa0799a9 | ||
| 
						 | 
					fba2d37714 | ||
| 
						 | 
					e891697f88 | ||
| 
						 | 
					ee6ac3b4a9 | ||
| 
						 | 
					c8130f9d6f | ||
| 
						 | 
					8abd837c8b | ||
| 
						 | 
					02ad080bb8 | ||
| 
						 | 
					5887e3e580 | ||
| 
						 | 
					1994b2dc9f | ||
| 
						 | 
					0017bd6d0f | ||
| 
						 | 
					15f30995b1 | ||
| 
						 | 
					37ca0e4f81 | ||
| 
						 | 
					e400aa200c | ||
| 
						 | 
					e168298aa0 | ||
| 
						 | 
					c4afbf8f2e | ||
| 
						 | 
					112aff9887 | ||
| 
						 | 
					a8207ded4f | ||
| 
						 | 
					bafef023a5 | ||
| 
						 | 
					9b39eebc2d | ||
| 
						 | 
					02fdcd6ece | ||
| 
						 | 
					49601e0a78 | ||
| 
						 | 
					cf10abff5b | ||
| 
						 | 
					e75c27cb66 | ||
| 
						 | 
					d27f0e3633 | ||
| 
						 | 
					e19bd0d517 | ||
| 
						 | 
					3427120b3f | ||
| 
						 | 
					ecc623cd6c | ||
| 
						 | 
					3ef615f508 | ||
| 
						 | 
					1c4c3a6cae | ||
| 
						 | 
					fd7142f6a1 | ||
| 
						 | 
					b30fda3c36 | ||
| 
						 | 
					7e43b40415 | ||
| 
						 | 
					53501a9443 | ||
| 
						 | 
					c5dc65fc61 | ||
| 
						 | 
					b389889e1f | ||
| 
						 | 
					02a29056b7 | ||
| 
						 | 
					5e789b4b0c | ||
| 
						 | 
					9ff09a45be | ||
| 
						 | 
					fc02a3d34b | ||
| 
						 | 
					48f983c040 | ||
| 
						 | 
					dd25d387fe | ||
| 
						 | 
					80a503f317 | ||
| 
						 | 
					5aa9168dd6 | ||
| 
						 | 
					b3f01fe314 | ||
| 
						 | 
					e688d87c22 | ||
| 
						 | 
					332fb2f384 | ||
| 
						 | 
					5332bcd6b4 | ||
| 
						 | 
					55c59e6164 | ||
| 
						 | 
					80bfa1859f | ||
| 
						 | 
					58e1880773 | ||
| 
						 | 
					c8c4c99f09 | ||
| 
						 | 
					350f424055 | ||
| 
						 | 
					bc5b7a6725 | ||
| 
						 | 
					7b4e71e6dd | ||
| 
						 | 
					a32202ab72 | ||
| 
						 | 
					193c027c8b | ||
| 
						 | 
					3dd07b6ac1 | ||
| 
						 | 
					12d912b627 | ||
| 
						 | 
					f847a72696 | ||
| 
						 | 
					967c3f6dba | ||
| 
						 | 
					6cf87e3aa9 | ||
| 
						 | 
					cbc6477431 | ||
| 
						 | 
					45d0f101a7 | ||
| 
						 | 
					cba96aee37 | ||
| 
						 | 
					17325834b5 | ||
| 
						 | 
					3673144a44 | ||
| 
						 | 
					4df49d9f18 | ||
| 
						 | 
					8b04608d68 | ||
| 
						 | 
					2bac276870 | ||
| 
						 | 
					213f9850e7 | ||
| 
						 | 
					378bffbf84 | ||
| 
						 | 
					c291d5313d | ||
| 
						 | 
					1f6f665639 | ||
| 
						 | 
					e81233c586 | ||
| 
						 | 
					b946029394 | ||
| 
						 | 
					6095936354 | ||
| 
						 | 
					fe79a1231d | ||
| 
						 | 
					76a5872d17 | ||
| 
						 | 
					0d72c75e15 | ||
| 
						 | 
					2e0e89c494 | ||
| 
						 | 
					7d6b7a5874 | ||
| 
						 | 
					48f8ddf53a | ||
| 
						 | 
					9aae07b737 | ||
| 
						 | 
					d7abdc8017 | ||
| 
						 | 
					cb81156835 | ||
| 
						 | 
					1fd8d94e2e | ||
| 
						 | 
					e4fe127444 | ||
| 
						 | 
					aeabd5f113 | ||
| 
						 | 
					58f7d4065c | ||
| 
						 | 
					60f25a3ba4 | ||
| 
						 | 
					d267571dc6 | ||
| 
						 | 
					3c34aa6696 | ||
| 
						 | 
					5dc00a2092 | ||
| 
						 | 
					b20d489bf0 | ||
| 
						 | 
					df39870587 | ||
| 
						 | 
					f742eab4be | ||
| 
						 | 
					e9c8c61dcf | ||
| 
						 | 
					e5f09002e9 | ||
| 
						 | 
					d42f005e17 | ||
| 
						 | 
					24e060abee | ||
| 
						 | 
					8b6d763442 | ||
| 
						 | 
					e239745f63 | ||
| 
						 | 
					cfef2b4e19 | ||
| 
						 | 
					cf93c39881 | ||
| 
						 | 
					5d223bce4c | ||
| 
						 | 
					b454ebc1c9 | ||
| 
						 | 
					7cf9910cae | ||
| 
						 | 
					79ab1d8cb1 | ||
| 
						 | 
					7cd20f5d12 | ||
| 
						 | 
					5396d751e1 | ||
| 
						 | 
					d23e715650 | ||
| 
						 | 
					0791bce338 | ||
| 
						 | 
					2bcb74072a | ||
| 
						 | 
					c5f2f17f33 | ||
| 
						 | 
					62a8bf4261 | ||
| 
						 | 
					ebda18b44e | ||
| 
						 | 
					a8f41b9017 | ||
| 
						 | 
					410c19a7da | ||
| 
						 | 
					a346e2e04b | ||
| 
						 | 
					2d114b6677 | ||
| 
						 | 
					02e74ca1f4 | ||
| 
						 | 
					69122cdec4 | ||
| 
						 | 
					d730168631 | ||
| 
						 | 
					2f210ebe3b | ||
| 
						 | 
					693b53baa2 | ||
| 
						 | 
					77554879a5 | ||
| 
						 | 
					45363922b5 | ||
| 
						 | 
					0463c1ceda | ||
| 
						 | 
					b35a55a658 | ||
| 
						 | 
					4da68c9fa8 | ||
| 
						 | 
					72f133f31b | ||
| 
						 | 
					af4a8f6d9c | ||
| 
						 | 
					b5899a2e42 | ||
| 
						 | 
					4ee8f8564e | ||
| 
						 | 
					ff08c03bc5 | ||
| 
						 | 
					95dd430b0d | ||
| 
						 | 
					20eb8b1442 | ||
| 
						 | 
					2d6a0b3ed0 | ||
| 
						 | 
					80f0ce78e0 | ||
| 
						 | 
					fde0e2434e | ||
| 
						 | 
					fe2da7fd95 | ||
| 
						 | 
					25e783ff2f | ||
| 
						 | 
					2eb94f1b66 | ||
| 
						 | 
					2cdf6ac8f9 | ||
| 
						 | 
					309c58a93d | ||
| 
						 | 
					700bd0ddd4 | ||
| 
						 | 
					bd5a2f240d | ||
| 
						 | 
					73054d971c | ||
| 
						 | 
					8c7f2491d7 | ||
| 
						 | 
					24fcbea6f2 | ||
| 
						 | 
					fddc9c8c48 | ||
| 
						 | 
					294893b7da | ||
| 
						 | 
					564542420b | ||
| 
						 | 
					3f7e3e6d75 | ||
| 
						 | 
					6521d7d02b | ||
| 
						 | 
					ad162a4e4a | ||
| 
						 | 
					676b1f6fdc | ||
| 
						 | 
					406ef4e16c | ||
| 
						 | 
					217976350b | ||
| 
						 | 
					e8f860d6fe | ||
| 
						 | 
					859e6e2396 | ||
| 
						 | 
					51186e615f | ||
| 
						 | 
					bd8287fda3 | ||
| 
						 | 
					287ff99bbc | ||
| 
						 | 
					0bbfcedabb | ||
| 
						 | 
					812e1e637d | ||
| 
						 | 
					f20fd38940 | ||
| 
						 | 
					b4cfabc005 | ||
| 
						 | 
					c49e160501 | ||
| 
						 | 
					a0a24902d5 | ||
| 
						 | 
					1047bc8a80 | ||
| 
						 | 
					0eed49c4cb | ||
| 
						 | 
					e7f09e2ece | ||
| 
						 | 
					89678f1ea7 | ||
| 
						 | 
					e43ec7d549 | ||
| 
						 | 
					95395132f0 | ||
| 
						 | 
					89293d8481 | ||
| 
						 | 
					e6de24557f | ||
| 
						 | 
					66d76dc36a | ||
| 
						 | 
					06629def62 | ||
| 
						 | 
					97aeb5e930 | ||
| 
						 | 
					bf45b6e20b | ||
| 
						 | 
					6ad41326b0 | ||
| 
						 | 
					2bbca3c169 | ||
| 
						 | 
					ae903b0712 | ||
| 
						 | 
					a2a7f82716 | ||
| 
						 | 
					00456c891a | ||
| 
						 | 
					afd5faaab1 | ||
| 
						 | 
					bb33cf0f8d | ||
| 
						 | 
					edc510572a | ||
| 
						 | 
					bc6cffa95c | ||
| 
						 | 
					48ed2912b0 | ||
| 
						 | 
					a8af262c41 | ||
| 
						 | 
					dcf49933bc | ||
| 
						 | 
					9c014001da | ||
| 
						 | 
					4f410088dd | ||
| 
						 | 
					e1c1b66dc5 | ||
| 
						 | 
					23c3a1fa99 | ||
| 
						 | 
					ef6e1b2f74 | ||
| 
						 | 
					e130ae0a8a | ||
| 
						 | 
					1a1e3281e4 | ||
| 
						 | 
					a4e55c9362 | ||
| 
						 | 
					0b4c51eebd | ||
| 
						 | 
					1107f0d9a3 | ||
| 
						 | 
					775819432b | ||
| 
						 | 
					a71a60937f | ||
| 
						 | 
					5e661fe96b | ||
| 
						 | 
					a9f5b17fcb | ||
| 
						 | 
					b0c2b55fc9 | ||
| 
						 | 
					925832aac5 | ||
| 
						 | 
					994131e2ea | ||
| 
						 | 
					f8d27d0ae0 | ||
| 
						 | 
					fc50af0e17 | ||
| 
						 | 
					087d3535f6 | ||
| 
						 | 
					e9d310962f | ||
| 
						 | 
					0f9c89d259 | ||
| 
						 | 
					258c37685b | ||
| 
						 | 
					56f092a0c3 | ||
| 
						 | 
					6c3048ffbf | ||
| 
						 | 
					c58eba61de | ||
| 
						 | 
					8a54773f1b | ||
| 
						 | 
					2c483e7b97 | ||
| 
						 | 
					1027e9ffdc | ||
| 
						 | 
					85d6957e03 | ||
| 
						 | 
					c3609b66a9 | ||
| 
						 | 
					605f4a92d7 | ||
| 
						 | 
					d395e2bc75 | ||
| 
						 | 
					e6ccdc5a97 | ||
| 
						 | 
					a68c7aa45f | ||
| 
						 | 
					66e959ab65 | ||
| 
						 | 
					d68b172a40 | ||
| 
						 | 
					d3ee778265 | ||
| 
						 | 
					da96df7df7 | ||
| 
						 | 
					4ea82581ec | ||
| 
						 | 
					4473d3400e | ||
| 
						 | 
					2f1f843e48 | ||
| 
						 | 
					53a3d9042e | ||
| 
						 | 
					6eb32f98b2 | ||
| 
						 | 
					0fad97ed48 | ||
| 
						 | 
					27246247a2 | ||
| 
						 | 
					cbc96e2223 | ||
| 
						 | 
					8fdf32cde8 | ||
| 
						 | 
					03a94e59e2 | ||
| 
						 | 
					2c0610fef8 | ||
| 
						 | 
					60b3c51085 | ||
| 
						 | 
					d7b5a45417 | ||
| 
						 | 
					e11060bde8 | ||
| 
						 | 
					4653de9161 | ||
| 
						 | 
					1926ad9215 | ||
| 
						 | 
					33d047c703 | ||
| 
						 | 
					fadda00246 | ||
| 
						 | 
					a3fed788d8 | ||
| 
						 | 
					dde31e8687 | ||
| 
						 | 
					190fb009bc | ||
| 
						 | 
					62574d04c6 | ||
| 
						 | 
					2496257bcf | ||
| 
						 | 
					ab73b4de6b | ||
| 
						 | 
					6c1c32baca | ||
| 
						 | 
					239cc15c8f | ||
| 
						 | 
					6b437c3907 | ||
| 
						 | 
					4756f63169 | ||
| 
						 | 
					7229acb34f | ||
| 
						 | 
					43cb91760a | ||
| 
						 | 
					7bb4d052d1 | ||
| 
						 | 
					5885bdf0f8 | ||
| 
						 | 
					05042b1859 | ||
| 
						 | 
					3ca2f72184 | ||
| 
						 | 
					b076450b73 | ||
| 
						 | 
					7a90662c06 | ||
| 
						 | 
					eb31aaeb7d | ||
| 
						 | 
					ebfb215246 | ||
| 
						 | 
					eb97e4e518 | ||
| 
						 | 
					61d3e65c05 | ||
| 
						 | 
					0ac5681d13 | ||
| 
						 | 
					1e27c5759b | ||
| 
						 | 
					5e71aedc99 | ||
| 
						 | 
					d0d8c2316b | ||
| 
						 | 
					8f0a5b2191 | ||
| 
						 | 
					242b180862 | ||
| 
						 | 
					feb4766d7b | ||
| 
						 | 
					a224eea077 | ||
| 
						 | 
					ecefbc23ae | ||
| 
						 | 
					e5e0cbfc53 | ||
| 
						 | 
					38781c9395 | ||
| 
						 | 
					e70d72b614 | ||
| 
						 | 
					fcf648bbb2 | ||
| 
						 | 
					a8325b6bce | ||
| 
						 | 
					22554a9ba4 | ||
| 
						 | 
					02a10ef651 | ||
| 
						 | 
					e3ca44f3ca | ||
| 
						 | 
					a9abc0dd5f | ||
| 
						 | 
					cbcc7c718e | ||
| 
						 | 
					4377c79068 | ||
| 
						 | 
					514993bc2e | ||
| 
						 | 
					c182176134 | ||
| 
						 | 
					abd1f10395 | ||
| 
						 | 
					f279bebc1a | ||
| 
						 | 
					4e3fa5a6ff | ||
| 
						 | 
					01d355a247 | ||
| 
						 | 
					009f71a186 | ||
| 
						 | 
					de5c311d84 | ||
| 
						 | 
					eefe34f99e | ||
| 
						 | 
					82e3c870b3 | ||
| 
						 | 
					d44a1d9761 | ||
| 
						 | 
					e6fd54c14b | ||
| 
						 | 
					ccb8e90110 | ||
| 
						 | 
					a4e66f291a | ||
| 
						 | 
					3ab3f34bef | ||
| 
						 | 
					99da8c4424 | ||
| 
						 | 
					9a1bf1cf74 | ||
| 
						 | 
					2256e99157 | ||
| 
						 | 
					3a5f8b4987 | ||
| 
						 | 
					6de5fcc980 | ||
| 
						 | 
					a454d0d4b7 | ||
| 
						 | 
					67339754e3 | ||
| 
						 | 
					7316fe00ee | ||
| 
						 | 
					feb4a7021c | ||
| 
						 | 
					5e84b671b6 | ||
| 
						 | 
					bcefef62f9 | ||
| 
						 | 
					cc953dda34 | ||
| 
						 | 
					f9e5b0f0c7 | ||
| 
						 | 
					578654411e | ||
| 
						 | 
					247e92cfa2 | ||
| 
						 | 
					66f605de0f | ||
| 
						 | 
					709b0efc9b | ||
| 
						 | 
					622679f4c2 | ||
| 
						 | 
					fdeb421513 | ||
| 
						 | 
					8fe25cde8d | ||
| 
						 | 
					fbd71451f1 | ||
| 
						 | 
					0d91ce8e6a | ||
| 
						 | 
					d71796c88a | ||
| 
						 | 
					277748c8f5 | ||
| 
						 | 
					8c1358ace9 | ||
| 
						 | 
					1254916058 | ||
| 
						 | 
					f228bee4b8 | ||
| 
						 | 
					8094477b09 | ||
| 
						 | 
					32a5bf76cd | ||
| 
						 | 
					0375d000e6 | ||
| 
						 | 
					141d43d3e5 | ||
| 
						 | 
					823f7b1d2e | ||
| 
						 | 
					6579f011d0 | ||
| 
						 | 
					93f768af9b | ||
| 
						 | 
					f8c11bf217 | ||
| 
						 | 
					26ccd930c3 | ||
| 
						 | 
					82211c7312 | ||
| 
						 | 
					2015c154fe | ||
| 
						 | 
					ef17d116a8 | ||
| 
						 | 
					46fddc44bf | ||
| 
						 | 
					0214a77cd7 | ||
| 
						 | 
					425ed658f1 | ||
| 
						 | 
					a53adb561e | ||
| 
						 | 
					3c3c55090a | ||
| 
						 | 
					ebc04c6520 | ||
| 
						 | 
					8b0e8f5b13 | ||
| 
						 | 
					16132a007e | ||
| 
						 | 
					b6e41ceea7 | ||
| 
						 | 
					7015e46227 | ||
| 
						 | 
					cce2607c80 | ||
| 
						 | 
					9dd2ec8bda | ||
| 
						 | 
					068726e0ab | ||
| 
						 | 
					89e86ad9bd | ||
| 
						 | 
					2e49bc2044 | ||
| 
						 | 
					174c8dafbf | ||
| 
						 | 
					90a96293de | ||
| 
						 | 
					84877c4fec | ||
| 
						 | 
					a7cceb5fa9 | ||
| 
						 | 
					ca6359a597 | ||
| 
						 | 
					b7c3667be1 | ||
| 
						 | 
					b6dea59db3 | ||
| 
						 | 
					aa51f13743 | ||
| 
						 | 
					f34ec03ff0 | ||
| 
						 | 
					1363be59b7 | ||
| 
						 | 
					622c24ef24 | ||
| 
						 | 
					539b0e49d4 | ||
| 
						 | 
					0c42976312 | ||
| 
						 | 
					3f6b3a4fa0 | ||
| 
						 | 
					67e1773495 | ||
| 
						 | 
					a199b64aa0 | ||
| 
						 | 
					ebf09aceb2 | ||
| 
						 | 
					ca226e4295 | ||
| 
						 | 
					9261939f62 | ||
| 
						 | 
					0349931953 | ||
| 
						 | 
					d612a385d2 | ||
| 
						 | 
					ed4f299d55 | ||
| 
						 | 
					7cef789d41 | ||
| 
						 | 
					66bfb86d42 | ||
| 
						 | 
					c4a5bc12ef | ||
| 
						 | 
					557631f6ba | ||
| 
						 | 
					362ffaff7f | ||
| 
						 | 
					fb5ef200fb | ||
| 
						 | 
					5e78ac3af5 | ||
| 
						 | 
					719a090b34 | ||
| 
						 | 
					3af85da6e0 | ||
| 
						 | 
					8fd62aa525 | ||
| 
						 | 
					40747f51bd | ||
| 
						 | 
					f3cef6bd73 | ||
| 
						 | 
					eef0ee8180 | ||
| 
						 | 
					503e974375 | ||
| 
						 | 
					c959f2fee5 | ||
| 
						 | 
					7d5e434cba | ||
| 
						 | 
					2720bcdf18 | ||
| 
						 | 
					c513b7262b | ||
| 
						 | 
					57a795df96 | ||
| 
						 | 
					6bdd9e4543 | ||
| 
						 | 
					ede3def37f | ||
| 
						 | 
					87d9022280 | ||
| 
						 | 
					ff0ba7d48b | ||
| 
						 | 
					b49c47425f | ||
| 
						 | 
					3916ba1a42 | ||
| 
						 | 
					0b3d22b97c | ||
| 
						 | 
					9b8b0f2023 | ||
| 
						 | 
					06e0d17be0 | ||
| 
						 | 
					239c485f3c | ||
| 
						 | 
					5e5fdda0ca | ||
| 
						 | 
					4b2dddf3c6 | ||
| 
						 | 
					c99ec745ca | ||
| 
						 | 
					1ec2e455ec | ||
| 
						 | 
					69304737c6 | ||
| 
						 | 
					fe91670127 | ||
| 
						 | 
					7a59f94f3d | ||
| 
						 | 
					4efe3a333d | ||
| 
						 | 
					421bf28582 | ||
| 
						 | 
					4c49ffe3d1 | ||
| 
						 | 
					26b1ef247b | ||
| 
						 | 
					3aafba707a | ||
| 
						 | 
					ae774e88fa | ||
| 
						 | 
					ff56dd53cf | ||
| 
						 | 
					12f063c178 | ||
| 
						 | 
					888148d282 | ||
| 
						 | 
					7bba0b82ef | ||
| 
						 | 
					41196a862d | ||
| 
						 | 
					a99ed0e557 | ||
| 
						 | 
					654981fb03 | ||
| 
						 | 
					c473c36d46 | ||
| 
						 | 
					e863e61af8 | ||
| 
						 | 
					efb486dd1b | ||
| 
						 | 
					25b15fcdd1 | ||
| 
						 | 
					1106fbb5ef | ||
| 
						 | 
					9e1a29f0ff | ||
| 
						 | 
					b3c057f911 | ||
| 
						 | 
					1c33e9ead9 | ||
| 
						 | 
					d78f35b940 | ||
| 
						 | 
					506236a5ed | ||
| 
						 | 
					18b32dbba3 | ||
| 
						 | 
					26e40564dc | ||
| 
						 | 
					b6e8421a0a | ||
| 
						 | 
					03c5a3b325 | ||
| 
						 | 
					a1f33d3fc6 | ||
| 
						 | 
					b8fca7db80 | ||
| 
						 | 
					6ea70cd245 | ||
| 
						 | 
					683fea675e | ||
| 
						 | 
					811a010a60 | ||
| 
						 | 
					019526332d | ||
| 
						 | 
					1e90387198 | ||
| 
						 | 
					84d6bb47ea | ||
| 
						 | 
					512179d92a | ||
| 
						 | 
					04344a3723 | ||
| 
						 | 
					d032207473 | ||
| 
						 | 
					b33dc2779d | ||
| 
						 | 
					28699a1af5 | ||
| 
						 | 
					ff3fe135a3 | ||
| 
						 | 
					ff69709926 | ||
| 
						 | 
					2162822cec | ||
| 
						 | 
					34330baaa0 | ||
| 
						 | 
					28d8aab7e5 | ||
| 
						 | 
					6a91c89126 | ||
| 
						 | 
					c350f6fe5e | ||
| 
						 | 
					493bf0a666 | ||
| 
						 | 
					0305203e61 | ||
| 
						 | 
					71d7b1dfad | ||
| 
						 | 
					69748a1f1b | ||
| 
						 | 
					39c2db38b7 | ||
| 
						 | 
					f499de3622 | ||
| 
						 | 
					e8a16a8fce | ||
| 
						 | 
					81f2952c97 | ||
| 
						 | 
					dcf9de1a01 | ||
| 
						 | 
					95f57f4eeb | ||
| 
						 | 
					7a794b8b6e | ||
| 
						 | 
					cbaf841f13 | ||
| 
						 | 
					6fdca9e89c | ||
| 
						 | 
					8c3b3d98f6 | ||
| 
						 | 
					6dd2abfb61 | ||
| 
						 | 
					32defde397 | ||
| 
						 | 
					b1969c9528 | ||
| 
						 | 
					b6d5af81b5 | ||
| 
						 | 
					0358853d5a | ||
| 
						 | 
					eb60f63223 | ||
| 
						 | 
					fdbb06436d | ||
| 
						 | 
					bceaa073cd | ||
| 
						 | 
					ccace48d5a | ||
| 
						 | 
					193203bbf7 | ||
| 
						 | 
					d0cc4e1557 | ||
| 
						 | 
					d52ae8c662 | ||
| 
						 | 
					6afd40cb39 | ||
| 
						 | 
					6713baf86b | ||
| 
						 | 
					365145e7c0 | ||
| 
						 | 
					0413af79e9 | ||
| 
						 | 
					868c498e28 | ||
| 
						 | 
					c5fbbe8a69 | ||
| 
						 | 
					b698850ce8 | ||
| 
						 | 
					0d1fe03369 | ||
| 
						 | 
					98b900e886 | ||
| 
						 | 
					8210da9e34 | ||
| 
						 | 
					fac3d99f64 | ||
| 
						 | 
					d3c216374f | ||
| 
						 | 
					105272630e | ||
| 
						 | 
					53cc8ecaf0 | ||
| 
						 | 
					fb8e8b4b3a | ||
| 
						 | 
					035713b4d3 | ||
| 
						 | 
					54b7dc56b5 | ||
| 
						 | 
					7fd39f44d0 | ||
| 
						 | 
					691292501a | ||
| 
						 | 
					a58158ae08 | ||
| 
						 | 
					ef09b971fa | ||
| 
						 | 
					e07dee380d | ||
| 
						 | 
					125bc5baa6 | ||
| 
						 | 
					995444b11b | ||
| 
						 | 
					8f2384dbfc | ||
| 
						 | 
					0cdd1c23ce | ||
| 
						 | 
					4765a39759 | ||
| 
						 | 
					7f4047772c | ||
| 
						 | 
					45c4ca6bec | ||
| 
						 | 
					4a573a5aae | ||
| 
						 | 
					5125ff6a8c | ||
| 
						 | 
					482d3301ce | ||
| 
						 | 
					cdeec8ac47 | ||
| 
						 | 
					3cef12b53b | ||
| 
						 | 
					dd098a16a8 | ||
| 
						 | 
					61a175e84a | ||
| 
						 | 
					a5bcd38fe8 | ||
| 
						 | 
					cad42beef4 | ||
| 
						 | 
					5a57958639 | ||
| 
						 | 
					260336c1e5 | ||
| 
						 | 
					889cb9c78f | ||
| 
						 | 
					b90e8f5af3 | ||
| 
						 | 
					12361d2854 | ||
| 
						 | 
					d307ddfa8e | ||
| 
						 | 
					96fd0b7892 | ||
| 
						 | 
					6f1db15d7c | ||
| 
						 | 
					1854296ee8 | ||
| 
						 | 
					515cc5f326 | ||
| 
						 | 
					091be7eafe | ||
| 
						 | 
					27a19ea417 | ||
| 
						 | 
					9a5e9af67c | ||
| 
						 | 
					3a493f2428 | ||
| 
						 | 
					ca6e34f4b4 | ||
| 
						 | 
					e1e68312c4 | ||
| 
						 | 
					c7ff2cece4 | ||
| 
						 | 
					8e6f4fa36f | ||
| 
						 | 
					4d302da9fa | ||
| 
						 | 
					e0917dc734 | ||
| 
						 | 
					26f82e8143 | ||
| 
						 | 
					6518f08bc7 | ||
| 
						 | 
					8599123b30 | ||
| 
						 | 
					f934a1aa10 | ||
| 
						 | 
					81be5f809f | ||
| 
						 | 
					8bb94804d4 | ||
| 
						 | 
					c3f64e85ce | ||
| 
						 | 
					53057aff5d | ||
| 
						 | 
					8cad5ac7e9 | ||
| 
						 | 
					b4f643f3dd | ||
| 
						 | 
					2d659289e8 | ||
| 
						 | 
					9883640b63 | ||
| 
						 | 
					bc3a0c3c91 | ||
| 
						 | 
					4e822347a5 | ||
| 
						 | 
					d5650da8c0 | ||
| 
						 | 
					d3c77523c3 | ||
| 
						 | 
					787a5ce568 | ||
| 
						 | 
					91dfd405d1 | ||
| 
						 | 
					08c615c493 | ||
| 
						 | 
					5483102276 | ||
| 
						 | 
					32686d898b | ||
| 
						 | 
					4b50c8d96c | ||
| 
						 | 
					c1780ee26b | ||
| 
						 | 
					46b2db00bc | ||
| 
						 | 
					7042e457ab | ||
| 
						 | 
					faeec1701f | ||
| 
						 | 
					2a75a1f9f2 | ||
| 
						 | 
					8c65dccc53 | ||
| 
						 | 
					de7c3ba92f | ||
| 
						 | 
					ac204aadd2 | ||
| 
						 | 
					46fc0d677f | ||
| 
						 | 
					2d4f4b0036 | ||
| 
						 | 
					c1de00bf9d | ||
| 
						 | 
					4639e1c47c | ||
| 
						 | 
					5d2c156bc9 | ||
| 
						 | 
					357f98f015 | ||
| 
						 | 
					62f23ac27c | ||
| 
						 | 
					0936646ef9 | ||
| 
						 | 
					856c12f46f | ||
| 
						 | 
					7489783837 | ||
| 
						 | 
					a4f0a4c310 | ||
| 
						 | 
					bb1ef114f1 | ||
| 
						 | 
					f1610b6407 | ||
| 
						 | 
					5e48a4c724 | ||
| 
						 | 
					d825c03372 | ||
| 
						 | 
					d177549dd6 | ||
| 
						 | 
					9d1543401f | ||
| 
						 | 
					094eb7e252 | ||
| 
						 | 
					95e6726468 | ||
| 
						 | 
					8fded8f210 | ||
| 
						 | 
					19c4940abd | ||
| 
						 | 
					7b1f6b3c53 | ||
| 
						 | 
					43042c3737 | ||
| 
						 | 
					30b50b8a1b | ||
| 
						 | 
					095be3072b | ||
| 
						 | 
					91831200d6 | ||
| 
						 | 
					8295d4511b | ||
| 
						 | 
					df589d9588 | ||
| 
						 | 
					b826e1c661 | ||
| 
						 | 
					6727e2fe73 | ||
| 
						 | 
					ecdcee8d4e | ||
| 
						 | 
					8b4a4369c1 | ||
| 
						 | 
					eeb06de916 | ||
| 
						 | 
					5018d7d577 | ||
| 
						 | 
					1ca279d99d | ||
| 
						 | 
					8a149a188c | ||
| 
						 | 
					076334bc4e | ||
| 
						 | 
					e6b45c978c | ||
| 
						 | 
					a07615445f | ||
| 
						 | 
					41d30c2835 | ||
| 
						 | 
					71f1635e23 | ||
| 
						 | 
					57df6d9bf7 | ||
| 
						 | 
					fd670d5175 | ||
| 
						 | 
					39d4c315c8 | ||
| 
						 | 
					6487086354 | ||
| 
						 | 
					7d6e24b8ed | ||
| 
						 | 
					4922073300 | ||
| 
						 | 
					778a02324e | ||
| 
						 | 
					8e89aa97a0 | ||
| 
						 | 
					dfd521e938 | ||
| 
						 | 
					d47332adf5 | ||
| 
						 | 
					14e7ba8fab | ||
| 
						 | 
					e68a356fd0 | ||
| 
						 | 
					6e77b8659c | ||
| 
						 | 
					00fad7e424 | ||
| 
						 | 
					0a65248bf7 | ||
| 
						 | 
					9cff26b163 | ||
| 
						 | 
					9309d8c3f2 | ||
| 
						 | 
					07e96c10d2 | ||
| 
						 | 
					d95abc99d9 | ||
| 
						 | 
					b83c2615de | ||
| 
						 | 
					78a2b27e65 | ||
| 
						 | 
					bae594e34c | ||
| 
						 | 
					4ded6fceea | ||
| 
						 | 
					0e498829f7 | ||
| 
						 | 
					ddd090d581 | ||
| 
						 | 
					4cd979e5fb | ||
| 
						 | 
					2f7a6bb242 | ||
| 
						 | 
					3b3b2e61b0 | ||
| 
						 | 
					ab4fde9bd7 | ||
| 
						 | 
					a9996f0b81 | ||
| 
						 | 
					246d34e072 | ||
| 
						 | 
					d35efe3f32 | ||
| 
						 | 
					ebd1a6b47c | ||
| 
						 | 
					83980678a0 | ||
| 
						 | 
					201393f87d | ||
| 
						 | 
					055eb3873e | ||
| 
						 | 
					dc94d58148 | ||
| 
						 | 
					0adaec1665 | ||
| 
						 | 
					4ee30dc36f | ||
| 
						 | 
					54ff2fa01f | ||
| 
						 | 
					03c70b49ba | ||
| 
						 | 
					4b2d8e13d1 | ||
| 
						 | 
					a0c50f0521 | ||
| 
						 | 
					b15a865c88 | ||
| 
						 | 
					8e5bbbbc71 | ||
| 
						 | 
					615ebaf711 | ||
| 
						 | 
					0882d2b7ce | ||
| 
						 | 
					900195efac | ||
| 
						 | 
					b58b962ccf | ||
| 
						 | 
					5255499445 | ||
| 
						 | 
					d9a2be4250 | ||
| 
						 | 
					256e14a8a6 | ||
| 
						 | 
					1ab26d4a2f | ||
| 
						 | 
					91b2c751af | ||
| 
						 | 
					edf7617d1e | ||
| 
						 | 
					32666d304f | ||
| 
						 | 
					b65f7b4a6a | ||
| 
						 | 
					7c4df23c1c | ||
| 
						 | 
					a8e60163e1 | ||
| 
						 | 
					02ec1b5da6 | ||
| 
						 | 
					a9a6aba862 | ||
| 
						 | 
					03c6a60f68 | ||
| 
						 | 
					8ab688687e | ||
| 
						 | 
					bdec32722e | ||
| 
						 | 
					ad50e5c754 | ||
| 
						 | 
					9c48e44e9e | ||
| 
						 | 
					76284eb462 | ||
| 
						 | 
					0745c5128a | ||
| 
						 | 
					01fbe2d3de | ||
| 
						 | 
					9e14c22259 | ||
| 
						 | 
					dff0111cd5 | ||
| 
						 | 
					e7452b0ea1 | ||
| 
						 | 
					61a0f892c4 | ||
| 
						 | 
					4ceab01ed4 | ||
| 
						 | 
					9908969eea | ||
| 
						 | 
					19a78ef1ac | ||
| 
						 | 
					4785c1ae84 | ||
| 
						 | 
					ef03841efa | ||
| 
						 | 
					4747a70ce7 | ||
| 
						 | 
					cd986cc2dc | ||
| 
						 | 
					c29d5ca4a8 | ||
| 
						 | 
					56b49011d6 | ||
| 
						 | 
					48c55211e6 | ||
| 
						 | 
					72f68f3b0b | ||
| 
						 | 
					7b6dddc994 | ||
| 
						 | 
					51fbe4e8c5 | ||
| 
						 | 
					c148d9ee6c | ||
| 
						 | 
					9dfe59a104 | ||
| 
						 | 
					b6aae65afd | ||
| 
						 | 
					9fed93a771 | ||
| 
						 | 
					cdfb68f261 | ||
| 
						 | 
					46450bd080 | ||
| 
						 | 
					9a25d601f1 | ||
| 
						 | 
					fe0834ecda | ||
| 
						 | 
					846f745e2c | ||
| 
						 | 
					30d40e6f9b | ||
| 
						 | 
					f7501b10f7 | ||
| 
						 | 
					379c513f8a | ||
| 
						 | 
					5a6d77e958 | ||
| 
						 | 
					6646039ffe | ||
| 
						 | 
					5e0994270f | ||
| 
						 | 
					44fc801720 | ||
| 
						 | 
					405c61f53d | ||
| 
						 | 
					c40acb9406 | ||
| 
						 | 
					7778d2a47e | ||
| 
						 | 
					96afb245a5 | ||
| 
						 | 
					cf0677c30b | ||
| 
						 | 
					667614d9de | ||
| 
						 | 
					652ede57b3 | ||
| 
						 | 
					09a34f880e | ||
| 
						 | 
					a9f9be330d | ||
| 
						 | 
					39568d2464 | ||
| 
						 | 
					10e07a9966 | ||
| 
						 | 
					fe00a69136 | ||
| 
						 | 
					9d0c2cd67f | ||
| 
						 | 
					b5aab442f8 | ||
| 
						 | 
					7c010bd1ef | ||
| 
						 | 
					1bf898405f | ||
| 
						 | 
					c490166b35 | ||
| 
						 | 
					e6862364ed | ||
| 
						 | 
					cf20c84edd | ||
| 
						 | 
					88e776ad5b | ||
| 
						 | 
					e79a60f5cd | ||
| 
						 | 
					fd4a91ba72 | ||
| 
						 | 
					5705ece2a3 | ||
| 
						 | 
					c723f20f39 | ||
| 
						 | 
					0f7447d539 | ||
| 
						 | 
					1a08944854 | ||
| 
						 | 
					7b0b06f6df | ||
| 
						 | 
					d2ad227a24 | ||
| 
						 | 
					71d7982d14 | ||
| 
						 | 
					a94dcc12ef | ||
| 
						 | 
					b7bfcfa1e3 | ||
| 
						 | 
					416ae0ca04 | ||
| 
						 | 
					8d66cd4874 | ||
| 
						 | 
					a701ba8030 | ||
| 
						 | 
					0160908522 | ||
| 
						 | 
					cdd0d6d127 | ||
| 
						 | 
					65ee745d6e | ||
| 
						 | 
					4141dfc353 | ||
| 
						 | 
					fb6cd105c3 | ||
| 
						 | 
					6ff9168146 | ||
| 
						 | 
					7b5e08aab6 | ||
| 
						 | 
					c7dd4526c1 | ||
| 
						 | 
					066036ccdd | ||
| 
						 | 
					7c164453a5 | ||
| 
						 | 
					8b31cfeafb | ||
| 
						 | 
					2ddaf0afa3 | ||
| 
						 | 
					a8a97b4606 | ||
| 
						 | 
					a55b63a210 | ||
| 
						 | 
					2e4f7cd667 | ||
| 
						 | 
					bf257a8d9e | ||
| 
						 | 
					c4e66f7a35 | ||
| 
						 | 
					efff433aa0 | ||
| 
						 | 
					ee60e36a16 | ||
| 
						 | 
					841fc3cfaf | ||
| 
						 | 
					2a44caea6c | ||
| 
						 | 
					0f661928ae | ||
| 
						 | 
					0961e5cc2e | ||
| 
						 | 
					df621a8205 | ||
| 
						 | 
					bfa416ca99 | ||
| 
						 | 
					8041b87317 | ||
| 
						 | 
					b3000f6350 | ||
| 
						 | 
					947baab269 | ||
| 
						 | 
					a41ea90ca7 | ||
| 
						 | 
					8b3f0d8fd6 | ||
| 
						 | 
					bd9740a9a4 | ||
| 
						 | 
					3f735e44f1 | ||
| 
						 | 
					9e5235fd30 | ||
| 
						 | 
					159f3cb780 | ||
| 
						 | 
					61469f8e09 | ||
| 
						 | 
					71343b5131 | ||
| 
						 | 
					82caee6d7d | ||
| 
						 | 
					275e75980c | ||
| 
						 | 
					2572da872a | ||
| 
						 | 
					6934618589 | ||
| 
						 | 
					01fd07c372 | ||
| 
						 | 
					02f9cf0318 | ||
| 
						 | 
					6bc586025a | ||
| 
						 | 
					0d34960d60 | ||
| 
						 | 
					99b94a31ea | ||
| 
						 | 
					b0d4bcd26c | ||
| 
						 | 
					248ea52e06 | ||
| 
						 | 
					9b51fe3db4 | ||
| 
						 | 
					5f95696815 | ||
| 
						 | 
					8c9df5556d | ||
| 
						 | 
					32495f47b3 | ||
| 
						 | 
					23bc561524 | ||
| 
						 | 
					fa2cc0f62e | ||
| 
						 | 
					a686a167cc | ||
| 
						 | 
					8a2468a4fb | ||
| 
						 | 
					4cc21a2c20 | ||
| 
						 | 
					5350e41da1 | ||
| 
						 | 
					e07e6b6954 | ||
| 
						 | 
					0a60e38d82 | ||
| 
						 | 
					f53b40e127 | ||
| 
						 | 
					4df51a00ed | ||
| 
						 | 
					3981c4d101 | ||
| 
						 | 
					fc3e8f7cef | ||
| 
						 | 
					f4d67ec5e6 | ||
| 
						 | 
					59aafa6c1e | ||
| 
						 | 
					75da46dac5 | ||
| 
						 | 
					1f1d380e26 | ||
| 
						 | 
					35b3e425be | ||
| 
						 | 
					b4535c489d | ||
| 
						 | 
					f6bb502e87 | ||
| 
						 | 
					6cf825d3d8 | ||
| 
						 | 
					f766841fad | ||
| 
						 | 
					10e4e7f6c6 | ||
| 
						 | 
					1277e56435 | ||
| 
						 | 
					4089532f81 | ||
| 
						 | 
					9790b4d2e9 | ||
| 
						 | 
					ad37c0d2ac | ||
| 
						 | 
					b7a1fd4f8f | ||
| 
						 | 
					be5362e393 | ||
| 
						 | 
					4977c9bc4c | ||
| 
						 | 
					e13dbc03da | ||
| 
						 | 
					16fec0679b | ||
| 
						 | 
					55361b8552 | ||
| 
						 | 
					cc67993621 | ||
| 
						 | 
					49ba4998d6 | ||
| 
						 | 
					03eb381b3b | ||
| 
						 | 
					e5161faa43 | ||
| 
						 | 
					de78fb7a1c | ||
| 
						 | 
					a9ceb5e21a | ||
| 
						 | 
					e62b41f615 | ||
| 
						 | 
					592e339b70 | ||
| 
						 | 
					84a9138df7 | ||
| 
						 | 
					6db7c4a8eb | ||
| 
						 | 
					213bd09a9c | ||
| 
						 | 
					8eb246cdec | ||
| 
						 | 
					caacf8e373 | ||
| 
						 | 
					c53d42a578 | ||
| 
						 | 
					cfc5ef4a3c | ||
| 
						 | 
					e78c1bbec9 | ||
| 
						 | 
					b1582d33c0 | ||
| 
						 | 
					5abcf28a0e | ||
| 
						 | 
					4cd57856ce | ||
| 
						 | 
					a826fd5c0e | ||
| 
						 | 
					7de23ec2aa | ||
| 
						 | 
					d7d2957319 | ||
| 
						 | 
					fbd81b9930 | ||
| 
						 | 
					dacb52403a | ||
| 
						 | 
					e008a02b99 | ||
| 
						 | 
					9363453720 | ||
| 
						 | 
					9c70615fd1 | ||
| 
						 | 
					1c78c65816 | ||
| 
						 | 
					2a9a68ca53 | ||
| 
						 | 
					fb16baab99 | ||
| 
						 | 
					54f509c210 | ||
| 
						 | 
					5be8e5eff3 | ||
| 
						 | 
					29b9f129f6 | ||
| 
						 | 
					f41629daae | ||
| 
						 | 
					feea6023f4 | ||
| 
						 | 
					262d8cd0d9 | ||
| 
						 | 
					fbbec04f8c | ||
| 
						 | 
					3e4eaee96b | ||
| 
						 | 
					5f99a2240d | ||
| 
						 | 
					5937387e94 | ||
| 
						 | 
					b3099d8e71 | ||
| 
						 | 
					7721f74200 | ||
| 
						 | 
					fa58cc05f3 | ||
| 
						 | 
					c61a9e47b2 | ||
| 
						 | 
					148ee266ed | ||
| 
						 | 
					8ccec81cc6 | ||
| 
						 | 
					668901f71d | ||
| 
						 | 
					ad6ad144a5 | ||
| 
						 | 
					d5997a30b2 | ||
| 
						 | 
					ecc7501377 | ||
| 
						 | 
					45262a1a46 | ||
| 
						 | 
					3c04e08df2 | ||
| 
						 | 
					7c7675179e | ||
| 
						 | 
					88ed49a833 | ||
| 
						 | 
					0c88e62815 | ||
| 
						 | 
					88d34012c4 | ||
| 
						 | 
					3be8de6fb0 | ||
| 
						 | 
					804fbf5d5f | ||
| 
						 | 
					1a68dcbc14 | ||
| 
						 | 
					a9a72a767d | ||
| 
						 | 
					afc3a8d373 | ||
| 
						 | 
					da00e6588c | ||
| 
						 | 
					d6376d0ddf | ||
| 
						 | 
					1cca711560 | ||
| 
						 | 
					552f9196af | ||
| 
						 | 
					c6fa72cd83 | ||
| 
						 | 
					42edc46887 | ||
| 
						 | 
					ec7e343673 | ||
| 
						 | 
					69d4d8acb0 | ||
| 
						 | 
					a7eab8df22 | ||
| 
						 | 
					4247da9118 | ||
| 
						 | 
					cfba8aeb89 | ||
| 
						 | 
					db26a26926 | ||
| 
						 | 
					1551fbeb1f | ||
| 
						 | 
					d5c53ca624 | ||
| 
						 | 
					b34702e370 | ||
| 
						 | 
					8b1543d9c9 | ||
| 
						 | 
					e264375a97 | ||
| 
						 | 
					fd31d07f41 | ||
| 
						 | 
					fac15f5539 | ||
| 
						 | 
					6ad88101f1 | ||
| 
						 | 
					2768b66d10 | ||
| 
						 | 
					d10164be26 | ||
| 
						 | 
					ce7ff13bbe | ||
| 
						 | 
					c1d2c159f3 | ||
| 
						 | 
					d3dbdb153c | ||
| 
						 | 
					e7218c0321 | ||
| 
						 | 
					48d8fdb875 | ||
| 
						 | 
					b387ca921a | ||
| 
						 | 
					53c1a322ed | ||
| 
						 | 
					0c502fc9cf | ||
| 
						 | 
					5d1e3b6c93 | ||
| 
						 | 
					7fc16fa2c8 | ||
| 
						 | 
					fe6a88c5df | ||
| 
						 | 
					7b14df82e0 | ||
| 
						 | 
					966b41313d | ||
| 
						 | 
					91f1c3322c | ||
| 
						 | 
					0b276c5a76 | ||
| 
						 | 
					b654c2e170 | ||
| 
						 | 
					32c88da6c4 | ||
| 
						 | 
					3d19d0816b | ||
| 
						 | 
					15da707324 | ||
| 
						 | 
					387e8b04f9 | ||
| 
						 | 
					4ca47be8a8 | ||
| 
						 | 
					f9d9dc68b7 | ||
| 
						 | 
					14a8f4511c | ||
| 
						 | 
					97d237eed0 | ||
| 
						 | 
					20d0406a24 | ||
| 
						 | 
					73e843abd3 | ||
| 
						 | 
					8d956da65b | ||
| 
						 | 
					9b9cdfe937 | ||
| 
						 | 
					3dcba9362c | ||
| 
						 | 
					2d3a3ada57 | ||
| 
						 | 
					143d1d5e35 | ||
| 
						 | 
					13b32b269c | ||
| 
						 | 
					592cea6a27 | ||
| 
						 | 
					e76ca304e6 | ||
| 
						 | 
					d77d8df1ac | ||
| 
						 | 
					4f35d5dabd | ||
| 
						 | 
					e927feb2d6 | ||
| 
						 | 
					98f20c57c1 | ||
| 
						 | 
					7a88d31fd9 | ||
| 
						 | 
					96326411bf | ||
| 
						 | 
					4e882e7d4d | ||
| 
						 | 
					bff10c1714 | ||
| 
						 | 
					cee2484108 | ||
| 
						 | 
					93078abe87 | ||
| 
						 | 
					8caa1a9664 | ||
| 
						 | 
					d7b46ee03c | ||
| 
						 | 
					e07b3da983 | ||
| 
						 | 
					0f166cee48 | ||
| 
						 | 
					08df42d05b | ||
| 
						 | 
					2c165c3873 | ||
| 
						 | 
					9135402d9e | ||
| 
						 | 
					53135ec2c0 | ||
| 
						 | 
					4eb8c8dea9 | ||
| 
						 | 
					f318bec53c | ||
| 
						 | 
					7d84d6909e | ||
| 
						 | 
					9224645473 | ||
| 
						 | 
					6717771f9a | ||
| 
						 | 
					99e0902b74 | ||
| 
						 | 
					c7f2805b05 | ||
| 
						 | 
					6e1909647b | ||
| 
						 | 
					0c7db56e15 | ||
| 
						 | 
					3287ca449e | ||
| 
						 | 
					53a8f65ecc | ||
| 
						 | 
					113035b374 | ||
| 
						 | 
					c6e64837c3 | ||
| 
						 | 
					82419e6df1 | ||
| 
						 | 
					faa76ee017 | ||
| 
						 | 
					ffdefb4106 | ||
| 
						 | 
					ba7b1c47b9 | ||
| 
						 | 
					342b8105c4 | ||
| 
						 | 
					367c2b568a | ||
| 
						 | 
					0eef2c0d04 | ||
| 
						 | 
					c9a065107b | ||
| 
						 | 
					cacacc00f6 | ||
| 
						 | 
					1b94cfc72c | ||
| 
						 | 
					89fd41124f | ||
| 
						 | 
					4e3b0ae3c1 | ||
| 
						 | 
					9df6d535e2 | ||
| 
						 | 
					d545cce276 | ||
| 
						 | 
					71b481d3be | ||
| 
						 | 
					2710acaae6 | ||
| 
						 | 
					d79135ea01 | ||
| 
						 | 
					1464011f6f | ||
| 
						 | 
					409c8a6859 | ||
| 
						 | 
					805ce36592 | ||
| 
						 | 
					07fa56c53d | ||
| 
						 | 
					28fca80023 | ||
| 
						 | 
					08c0ee9ca8 | ||
| 
						 | 
					2878ab1578 | ||
| 
						 | 
					16f850cbcc | ||
| 
						 | 
					b9177e50d3 | ||
| 
						 | 
					c3258551d7 | ||
| 
						 | 
					ea0799c546 | ||
| 
						 | 
					4843b7f7b8 | ||
| 
						 | 
					61694c625e | ||
| 
						 | 
					64b4b99d48 | ||
| 
						 | 
					dad966b551 | ||
| 
						 | 
					e46f503641 | ||
| 
						 | 
					fc4a1e6ace | ||
| 
						 | 
					02b9a20c11 | ||
| 
						 | 
					1db4e4caed | ||
| 
						 | 
					991e176c85 | ||
| 
						 | 
					4a41a6bb85 | ||
| 
						 | 
					6229e1049b | ||
| 
						 | 
					3cfd6ed109 | ||
| 
						 | 
					ad70180edd | ||
| 
						 | 
					efd4a83bd2 | ||
| 
						 | 
					e13b4ab7c9 | ||
| 
						 | 
					fee89cbaad | ||
| 
						 | 
					2bdcba437c | ||
| 
						 | 
					2c2216afae | ||
| 
						 | 
					0823fc32fe | ||
| 
						 | 
					091d19caf5 | ||
| 
						 | 
					00a7381f08 | ||
| 
						 | 
					d494d1e3ee | ||
| 
						 | 
					b1c331a1df | ||
| 
						 | 
					b1a4fd085b | ||
| 
						 | 
					511ab2afe9 | ||
| 
						 | 
					30387ad654 | ||
| 
						 | 
					5d528ee1f3 | ||
| 
						 | 
					96bb4d50ba | ||
| 
						 | 
					45f850adae | ||
| 
						 | 
					09341ddbe9 | ||
| 
						 | 
					eab4274737 | ||
| 
						 | 
					49fec1bc10 | ||
| 
						 | 
					de164469c4 | ||
| 
						 | 
					f595871cd9 | ||
| 
						 | 
					ff86cbd48e | ||
| 
						 | 
					47bd4dade5 | ||
| 
						 | 
					ff8180920f | ||
| 
						 | 
					d4f08f0006 | ||
| 
						 | 
					1db756063b | ||
| 
						 | 
					b44ea31bbf | ||
| 
						 | 
					ddccb946ff | ||
| 
						 | 
					1f6f30ae9e | ||
| 
						 | 
					4b19a3f4ed | ||
| 
						 | 
					c39d0ce2f7 | ||
| 
						 | 
					cbdf1a941c | ||
| 
						 | 
					a340635de5 | ||
| 
						 | 
					d62362db1a | ||
| 
						 | 
					a0aba69306 | ||
| 
						 | 
					765683cd34 | ||
| 
						 | 
					93ddf4f0ba | ||
| 
						 | 
					8dcccf11bf | ||
| 
						 | 
					43353ce892 | ||
| 
						 | 
					a698fea078 | ||
| 
						 | 
					390d9b0fe1 | ||
| 
						 | 
					fd3ff05b17 | ||
| 
						 | 
					a5e4c9dd7b | ||
| 
						 | 
					37f07dcc5a | ||
| 
						 | 
					75bae9a59c | ||
| 
						 | 
					1684d88a3b | ||
| 
						 | 
					50acbb70da | ||
| 
						 | 
					4de1025468 | ||
| 
						 | 
					c2506dd115 | ||
| 
						 | 
					f8c9aa8e6c | ||
| 
						 | 
					e19dc1f067 | ||
| 
						 | 
					84776bced1 | ||
| 
						 | 
					9162c86e21 | ||
| 
						 | 
					88ffcbc62b | ||
| 
						 | 
					6aff0b74cd | ||
| 
						 | 
					79671890c5 | ||
| 
						 | 
					edd4ed307f | ||
| 
						 | 
					f786f8a970 | ||
| 
						 | 
					a1d10adaa3 | ||
| 
						 | 
					7f480e8e56 | ||
| 
						 | 
					94b972aaf4 | ||
| 
						 | 
					9470775292 | ||
| 
						 | 
					93eb63d930 | ||
| 
						 | 
					ea81096a43 | ||
| 
						 | 
					594045b4e7 | ||
| 
						 | 
					1449330ed3 | ||
| 
						 | 
					07493a6b18 | ||
| 
						 | 
					0310db5f24 | ||
| 
						 | 
					ed6d5a7d38 | ||
| 
						 | 
					ca7c1bc631 | ||
| 
						 | 
					259070c658 | ||
| 
						 | 
					e1a7dd9b24 | ||
| 
						 | 
					e5945fbb3d | ||
| 
						 | 
					247f636988 | ||
| 
						 | 
					c58a2ee624 | ||
| 
						 | 
					35a1b44c21 | ||
| 
						 | 
					fd09f06507 | ||
| 
						 | 
					d66b501a99 | ||
| 
						 | 
					349f6766ac | ||
| 
						 | 
					cd55ed1514 | ||
| 
						 | 
					14d4c8accc | ||
| 
						 | 
					37bca96bde | ||
| 
						 | 
					b0e6ae58c4 | ||
| 
						 | 
					0375e47359 | ||
| 
						 | 
					8450ad2856 | ||
| 
						 | 
					015a1fbd53 | ||
| 
						 | 
					318b61b4d7 | ||
| 
						 | 
					60640517ca | ||
| 
						 | 
					051e38f034 | ||
| 
						 | 
					300054b9f7 | ||
| 
						 | 
					4f9e1e3a6b | ||
| 
						 | 
					856bc27bf1 | ||
| 
						 | 
					408b774b42 | ||
| 
						 | 
					f82ef5aad4 | ||
| 
						 | 
					a92df41eb5 | ||
| 
						 | 
					605990929d | ||
| 
						 | 
					925ca28659 | ||
| 
						 | 
					41e3fa7aa7 | ||
| 
						 | 
					5fa27448f8 | ||
| 
						 | 
					c3428bdaed | ||
| 
						 | 
					0539de9c4e | ||
| 
						 | 
					eed87164b1 | ||
| 
						 | 
					0fe726c503 | ||
| 
						 | 
					f7f2113f1c | ||
| 
						 | 
					49d931f5cc | ||
| 
						 | 
					4dbd63de08 | ||
| 
						 | 
					3a53c349a0 | ||
| 
						 | 
					a9fe5d5c87 | ||
| 
						 | 
					9cecccf5da | ||
| 
						 | 
					6cb3bbaa2d | ||
| 
						 | 
					0ff6a0bb53 | ||
| 
						 | 
					8ba57dec03 | ||
| 
						 | 
					d749c305ed | ||
| 
						 | 
					f46ac2d0ed | ||
| 
						 | 
					d7b7152315 | ||
| 
						 | 
					da1d52033b | ||
| 
						 | 
					01ddc24c02 | ||
| 
						 | 
					53a3e88d16 | ||
| 
						 | 
					bc8d1cc384 | ||
| 
						 | 
					ed2ba63a5f | ||
| 
						 | 
					8a2c009653 | ||
| 
						 | 
					55690a4c7f | ||
| 
						 | 
					161ad4b143 | ||
| 
						 | 
					d701990df4 | ||
| 
						 | 
					95a2d1013c | ||
| 
						 | 
					5d4f3c0b3e | ||
| 
						 | 
					f8e4023307 | ||
| 
						 | 
					609aba7c73 | ||
| 
						 | 
					bc7ab0eba1 | ||
| 
						 | 
					56f271c8ad | ||
| 
						 | 
					11190cff1d | ||
| 
						 | 
					348a593dc1 | ||
| 
						 | 
					b67bb50f4a | ||
| 
						 | 
					1174f651ab | ||
| 
						 | 
					dad9777c3e | ||
| 
						 | 
					4f6285a8e7 | ||
| 
						 | 
					20cecf4702 | ||
| 
						 | 
					5763eabff4 | ||
| 
						 | 
					0fc753949d | ||
| 
						 | 
					083c1b7ca7 | ||
| 
						 | 
					8f6b1b11e5 | ||
| 
						 | 
					53b7d19c10 | ||
| 
						 | 
					b0b4f5e51a | ||
| 
						 | 
					b7414aa59c | ||
| 
						 | 
					f449045118 | ||
| 
						 | 
					1e9ddada37 | ||
| 
						 | 
					55d59a1854 | ||
| 
						 | 
					beb9f38514 | ||
| 
						 | 
					00b1865fc8 | ||
| 
						 | 
					0f545608c4 | ||
| 
						 | 
					bde2047184 | ||
| 
						 | 
					0a22d8fb9e | ||
| 
						 | 
					1bef37d504 | ||
| 
						 | 
					7f5d290b66 | ||
| 
						 | 
					9461e6f285 | ||
| 
						 | 
					3f59a03f29 | ||
| 
						 | 
					062b581b55 | ||
| 
						 | 
					58d3fdc1c2 | ||
| 
						 | 
					2f546842a7 | ||
| 
						 | 
					f089a85908 | ||
| 
						 | 
					a6e453a452 | ||
| 
						 | 
					3adf3dc547 | ||
| 
						 | 
					1d0ea96ae9 | ||
| 
						 | 
					a4cb17a1cb | ||
| 
						 | 
					733da3161b | ||
| 
						 | 
					1b1a0f553d | ||
| 
						 | 
					972619c1fe | ||
| 
						 | 
					61086d5360 | ||
| 
						 | 
					37513d726c | ||
| 
						 | 
					6510bee327 | ||
| 
						 | 
					cd36f3f096 | ||
| 
						 | 
					407a6f5e31 | ||
| 
						 | 
					2b28df280e | ||
| 
						 | 
					eb763ed82c | ||
| 
						 | 
					755f53cce0 | ||
| 
						 | 
					c190ab40b0 | ||
| 
						 | 
					a3ad82de42 | ||
| 
						 | 
					0f6cd6904d | ||
| 
						 | 
					79c89af6ea | ||
| 
						 | 
					56f10a9a52 | ||
| 
						 | 
					c679e2c067 | ||
| 
						 | 
					0677987320 | ||
| 
						 | 
					5fb6e6780c | ||
| 
						 | 
					58ef91a7b1 | ||
| 
						 | 
					e1ae65b6d1 | ||
| 
						 | 
					65307186dc | ||
| 
						 | 
					6fa29c204b | ||
| 
						 | 
					8219beeb1a | ||
| 
						 | 
					ace7e24dfb | ||
| 
						 | 
					828c2a6883 | ||
| 
						 | 
					f195dc313d | ||
| 
						 | 
					5b8a005f41 | ||
| 
						 | 
					9feb75e645 | ||
| 
						 | 
					7f8e90bd29 | ||
| 
						 | 
					2fd34b649d | ||
| 
						 | 
					104054ed1a | ||
| 
						 | 
					457b28c22c | ||
| 
						 | 
					095c8dcd0c | ||
| 
						 | 
					8463e9ed94 | ||
| 
						 | 
					b6278c6144 | ||
| 
						 | 
					1c1e1eee47 | ||
| 
						 | 
					b37ed9ec60 | ||
| 
						 | 
					45f3ef6920 | ||
| 
						 | 
					2cd6c4238b | ||
| 
						 | 
					f6ed0b33eb | ||
| 
						 | 
					c4f4ca3f90 | ||
| 
						 | 
					9a6780616b | ||
| 
						 | 
					db4eca0a42 | ||
| 
						 | 
					6d674edb48 | ||
| 
						 | 
					c0469a044b | ||
| 
						 | 
					b9b64eba9a | ||
| 
						 | 
					2d74387a00 | ||
| 
						 | 
					f66b6fc20c | ||
| 
						 | 
					f0711a9fbc | ||
| 
						 | 
					83a8c7215a | ||
| 
						 | 
					a86f966cb4 | ||
| 
						 | 
					74db978b81 | ||
| 
						 | 
					1300546a52 | ||
| 
						 | 
					3aeb0bba71 | ||
| 
						 | 
					03d3efa323 | ||
| 
						 | 
					f9c220bee0 | ||
| 
						 | 
					114c2e2636 | ||
| 
						 | 
					75a0e622ad | ||
| 
						 | 
					8e2de4ee30 | ||
| 
						 | 
					b1602261cf | ||
| 
						 | 
					e5ed11f8ec | ||
| 
						 | 
					c1ecfd289e | ||
| 
						 | 
					c5cca15b4e | ||
| 
						 | 
					fa978315e6 | ||
| 
						 | 
					c5bffc38f4 | ||
| 
						 | 
					88b5f6b148 | ||
| 
						 | 
					fc04742151 | ||
| 
						 | 
					c618d18d46 | ||
| 
						 | 
					33bc7c00df | ||
| 
						 | 
					1ed550d7f9 | ||
| 
						 | 
					18b87f2c80 | ||
| 
						 | 
					fad503ca80 | ||
| 
						 | 
					37ec3e4605 | ||
| 
						 | 
					70e3d23f26 | ||
| 
						 | 
					6ebf415a52 | ||
| 
						 | 
					5c31104d0e | ||
| 
						 | 
					aed8b65e2b | ||
| 
						 | 
					906e8aa2b2 | ||
| 
						 | 
					d0703f95af | ||
| 
						 | 
					538b00797d | ||
| 
						 | 
					985c555518 | ||
| 
						 | 
					5ef26a25ee | ||
| 
						 | 
					3db0e30d12 | ||
| 
						 | 
					a666cabae9 | ||
| 
						 | 
					2e8d9018ef | ||
| 
						 | 
					ae49505e67 | ||
| 
						 | 
					09bd5503b4 | ||
| 
						 | 
					dbe733524c | ||
| 
						 | 
					1625f5c0f9 | ||
| 
						 | 
					7cd49d07f2 | ||
| 
						 | 
					a542345456 | ||
| 
						 | 
					0b98f21443 | ||
| 
						 | 
					c42e231e99 | ||
| 
						 | 
					ab653af4b3 | ||
| 
						 | 
					8653f572c8 | ||
| 
						 | 
					39b431fb19 | ||
| 
						 | 
					e158c5bc30 | ||
| 
						 | 
					d0fdfda4cb | ||
| 
						 | 
					668a5ca041 | ||
| 
						 | 
					5e3947b8bc | ||
| 
						 | 
					233a627c9f | ||
| 
						 | 
					6197486d49 | ||
| 
						 | 
					60856b974b | ||
| 
						 | 
					d1eca5dc21 | ||
| 
						 | 
					24b281d625 | ||
| 
						 | 
					b0d1dee38b | ||
| 
						 | 
					0751c51803 | ||
| 
						 | 
					0005229c1e | ||
| 
						 | 
					33c2353107 | ||
| 
						 | 
					cb98297bb5 | ||
| 
						 | 
					0e663e1da8 | ||
| 
						 | 
					918a8c5f8b | ||
| 
						 | 
					34938e8c62 | ||
| 
						 | 
					da6efe52ff | ||
| 
						 | 
					570f1caa8f | ||
| 
						 | 
					6f638805f7 | ||
| 
						 | 
					c92b0dc886 | ||
| 
						 | 
					b4b216de84 | ||
| 
						 | 
					80f5d7c735 | ||
| 
						 | 
					01aeb46664 | ||
| 
						 | 
					2cfd2ff624 | ||
| 
						 | 
					ff12bbbdb6 | ||
| 
						 | 
					e19fe5d0e2 | ||
| 
						 | 
					9670d5f4de | ||
| 
						 | 
					863b09c39b | ||
| 
						 | 
					6b90de539e | ||
| 
						 | 
					4bb53ed6ba | ||
| 
						 | 
					e6523f3ec1 | ||
| 
						 | 
					c8ad8c79bd | ||
| 
						 | 
					b08cc9cb49 | ||
| 
						 | 
					4f93dc0adf | ||
| 
						 | 
					096f48be33 | ||
| 
						 | 
					81398d58a2 | ||
| 
						 | 
					7466da5651 | ||
| 
						 | 
					c8fdde4c5e | ||
| 
						 | 
					15583e7975 | ||
| 
						 | 
					5acdf39566 | ||
| 
						 | 
					2240ada5db | ||
| 
						 | 
					9d5c10d440 | ||
| 
						 | 
					709f350d60 | ||
| 
						 | 
					3d7e016b42 | ||
| 
						 | 
					b76104d145 | ||
| 
						 | 
					702b6d6567 | ||
| 
						 | 
					3e93004db6 | ||
| 
						 | 
					589903c43c | ||
| 
						 | 
					f41b54de21 | ||
| 
						 | 
					700b848f26 | ||
| 
						 | 
					a1f6e93e22 | ||
| 
						 | 
					1628af2ffc | ||
| 
						 | 
					1b5d446635 | ||
| 
						 | 
					83a9ef772a | ||
| 
						 | 
					1d9c3fb827 | ||
| 
						 | 
					ff92bdb324 | ||
| 
						 | 
					363ad7342a | ||
| 
						 | 
					663acd3810 | ||
| 
						 | 
					c2fc26089e | ||
| 
						 | 
					58b464bdfc | ||
| 
						 | 
					ed766c74e6 | ||
| 
						 | 
					1d07b8238c | ||
| 
						 | 
					41c6ed7c5a | ||
| 
						 | 
					f7750af3d0 | ||
| 
						 | 
					8854ffddee | ||
| 
						 | 
					a487619578 | ||
| 
						 | 
					0eab6146fc | ||
| 
						 | 
					389ba95e5a | ||
| 
						 | 
					84d178c0ca | ||
| 
						 | 
					aed8f8efa8 | ||
| 
						 | 
					38325741de | ||
| 
						 | 
					891d5c2066 | ||
| 
						 | 
					6b7edac6e4 | ||
| 
						 | 
					ab2a576e1b | ||
| 
						 | 
					5a3e4dd47b | ||
| 
						 | 
					064c4b4312 | ||
| 
						 | 
					cbde504057 | ||
| 
						 | 
					949cfcfa69 | ||
| 
						 | 
					06a005321e | ||
| 
						 | 
					d3587f595f | ||
| 
						 | 
					b0158ed7ce | ||
| 
						 | 
					e5f4300e54 | ||
| 
						 | 
					7cc6f8604e | ||
| 
						 | 
					657960e7d0 | ||
| 
						 | 
					aecd7f9283 | ||
| 
						 | 
					6f1b30cd24 | ||
| 
						 | 
					a6ba549b67 | ||
| 
						 | 
					84ea04f61d | ||
| 
						 | 
					0d52cf5f97 | ||
| 
						 | 
					b15a083a15 | ||
| 
						 | 
					a128247ef5 | ||
| 
						 | 
					590bd934c0 | ||
| 
						 | 
					e9826d2e7e | ||
| 
						 | 
					58ed63cd18 | ||
| 
						 | 
					fd1bd3032f | ||
| 
						 | 
					d7206096ea | ||
| 
						 | 
					f43e594eca | ||
| 
						 | 
					ea4fe5e809 | ||
| 
						 | 
					9fcb634510 | ||
| 
						 | 
					c14a4515ce | ||
| 
						 | 
					8e71180cd2 | ||
| 
						 | 
					08f98aa32f | ||
| 
						 | 
					e8aa9b9eb2 | ||
| 
						 | 
					19f815eeff | ||
| 
						 | 
					a508f7a463 | ||
| 
						 | 
					e58d3ee060 | ||
| 
						 | 
					268842681a | ||
| 
						 | 
					9b357a9fbf | ||
| 
						 | 
					6f80018b6e | ||
| 
						 | 
					7a1153be65 | ||
| 
						 | 
					85d4c24aba | ||
| 
						 | 
					48c0ae8fe4 | ||
| 
						 | 
					e835b2c68c | ||
| 
						 | 
					10d20f5d09 | ||
| 
						 | 
					88b31ea940 | ||
| 
						 | 
					9cb28d23a3 | ||
| 
						 | 
					ce5aae3f7d | ||
| 
						 | 
					e7f0eb6746 | ||
| 
						 | 
					65a118d1f3 | ||
| 
						 | 
					3d2eefc7e7 | ||
| 
						 | 
					f804c32eee | ||
| 
						 | 
					b89ecadc3a | ||
| 
						 | 
					6d4ff0b89a | ||
| 
						 | 
					598003ea39 | ||
| 
						 | 
					6ef63790a9 | ||
| 
						 | 
					3ffd986a1c | ||
| 
						 | 
					0b5cd4c665 | ||
| 
						 | 
					0371b0507a | ||
| 
						 | 
					9fa71231c4 | ||
| 
						 | 
					32beafc12d | ||
| 
						 | 
					09e2ef334b | ||
| 
						 | 
					c0ce62ed2b | ||
| 
						 | 
					d3ed485e7a | ||
| 
						 | 
					31c878b654 | ||
| 
						 | 
					3a0f4a0bfc | ||
| 
						 | 
					8b88d1294d | ||
| 
						 | 
					43fcf46d69 | ||
| 
						 | 
					394fe0f1f1 | ||
| 
						 | 
					872921f635 | ||
| 
						 | 
					7248470950 | ||
| 
						 | 
					9d87296316 | ||
| 
						 | 
					23c67f7e38 | ||
| 
						 | 
					bd98d95dbf | ||
| 
						 | 
					3addb8d72b | ||
| 
						 | 
					5545906063 | ||
| 
						 | 
					36edfe9715 | ||
| 
						 | 
					030b54a2b6 | ||
| 
						 | 
					f332613922 | ||
| 
						 | 
					088bc14b11 | ||
| 
						 | 
					86fa8da8c5 | ||
| 
						 | 
					abfc73299e | ||
| 
						 | 
					bd97fd5973 | ||
| 
						 | 
					f00e7c4a80 | ||
| 
						 | 
					49c811b5f5 | ||
| 
						 | 
					b6c21e071d | ||
| 
						 | 
					e68129e47e | ||
| 
						 | 
					72d7917415 | ||
| 
						 | 
					53837fe132 | ||
| 
						 | 
					08d094c786 | ||
| 
						 | 
					a1634ab496 | ||
| 
						 | 
					d35165bd8e | ||
| 
						 | 
					b701ce9721 | ||
| 
						 | 
					f3e18da416 | ||
| 
						 | 
					131ab00304 | ||
| 
						 | 
					26d7d58a5f | ||
| 
						 | 
					02f92a7818 | ||
| 
						 | 
					b6fff521e4 | ||
| 
						 | 
					23f1308231 | ||
| 
						 | 
					947e890c59 | ||
| 
						 | 
					0f1714de7c | ||
| 
						 | 
					a7d2b0f63b | ||
| 
						 | 
					9c550a8154 | ||
| 
						 | 
					49012a21c8 | ||
| 
						 | 
					f136151064 | ||
| 
						 | 
					4838728521 | ||
| 
						 | 
					95fac5dc13 | ||
| 
						 | 
					ac1f7884b5 | ||
| 
						 | 
					ab411512d4 | ||
| 
						 | 
					704495ff42 | ||
| 
						 | 
					9acc80260f | ||
| 
						 | 
					7759fb7e68 | ||
| 
						 | 
					0d71724598 | ||
| 
						 | 
					ae436f7a51 | ||
| 
						 | 
					43ac20cbd2 | ||
| 
						 | 
					2d90868f5c | ||
| 
						 | 
					60987ae4a7 | ||
| 
						 | 
					65c1d99120 | ||
| 
						 | 
					35acf88847 | ||
| 
						 | 
					45549b5fcd | ||
| 
						 | 
					2eb9fb6a08 | ||
| 
						 | 
					0d0e1083e6 | ||
| 
						 | 
					e650f3772a | ||
| 
						 | 
					e5ff4c65b7 | ||
| 
						 | 
					276809f76a | ||
| 
						 | 
					5e3840c5f1 | ||
| 
						 | 
					6eace2a3ef | ||
| 
						 | 
					7817b23857 | ||
| 
						 | 
					432854aeb5 | ||
| 
						 | 
					433c8f9c3c | ||
| 
						 | 
					ea25dbfd1e | ||
| 
						 | 
					10f8318e79 | ||
| 
						 | 
					17ff0c4f65 | ||
| 
						 | 
					9abd653fb9 | ||
| 
						 | 
					ff6753fcdf | ||
| 
						 | 
					a65551f652 | ||
| 
						 | 
					f0d807a0fe | ||
| 
						 | 
					dfcdbe5b6a | ||
| 
						 | 
					53e73238fd | ||
| 
						 | 
					581454db69 | ||
| 
						 | 
					63d501b629 | ||
| 
						 | 
					60bd877ed9 | ||
| 
						 | 
					44574465c5 | ||
| 
						 | 
					2b7382a014 | ||
| 
						 | 
					584b6df40d | ||
| 
						 | 
					e55f61deb2 | ||
| 
						 | 
					a6c6a1c6da | 
							
								
								
									
										58
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,18 +1,26 @@
 | 
			
		||||
name: Build
 | 
			
		||||
on: [pull_request]
 | 
			
		||||
jobs:
 | 
			
		||||
 | 
			
		||||
  build-mac-xcodebuild:
 | 
			
		||||
    name: Mac UI / xcodebuild / ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [macos-12, macos-13, macos-14]
 | 
			
		||||
        os: [macos-latest] #[macos-13, macos-14, macos-15]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout
 | 
			
		||||
      uses: actions/checkout@v4
 | 
			
		||||
    - name: Install Xcode
 | 
			
		||||
      uses: maxim-lobanov/setup-xcode@v1
 | 
			
		||||
      with:
 | 
			
		||||
        xcode-version: latest-stable
 | 
			
		||||
    - name: Make
 | 
			
		||||
      working-directory: OSBindings/Mac
 | 
			
		||||
      run: xcodebuild CODE_SIGN_IDENTITY=-
 | 
			
		||||
      run: |
 | 
			
		||||
        xcodebuild -downloadComponent MetalToolchain
 | 
			
		||||
        xcodebuild CODE_SIGN_IDENTITY=-
 | 
			
		||||
 | 
			
		||||
  build-sdl-cmake:
 | 
			
		||||
    name: SDL UI / cmake / ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
@@ -31,6 +39,7 @@ jobs:
 | 
			
		||||
            sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
 | 
			
		||||
            ;;
 | 
			
		||||
          macOS)
 | 
			
		||||
            brew uninstall cmake
 | 
			
		||||
            brew install cmake sdl2
 | 
			
		||||
            ;;
 | 
			
		||||
        esac
 | 
			
		||||
@@ -49,11 +58,12 @@ jobs:
 | 
			
		||||
        esac
 | 
			
		||||
        cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
 | 
			
		||||
        cmake --build build -v -j"$jobs"
 | 
			
		||||
 | 
			
		||||
  build-sdl-scons:
 | 
			
		||||
    name: SDL UI / scons / ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [macos-14, ubuntu-latest]
 | 
			
		||||
        os: [macos-latest, ubuntu-latest]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout
 | 
			
		||||
@@ -85,3 +95,45 @@ jobs:
 | 
			
		||||
            jobs=1
 | 
			
		||||
        esac
 | 
			
		||||
        scons -j"$jobs"
 | 
			
		||||
 | 
			
		||||
  build-qt5:
 | 
			
		||||
    name: Qt 5 / ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [ubuntu-latest]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout
 | 
			
		||||
      uses: actions/checkout@v4
 | 
			
		||||
    - name: Install dependencies
 | 
			
		||||
      uses: jurplel/install-qt-action@v3
 | 
			
		||||
      with:
 | 
			
		||||
        version: '5.15.2'
 | 
			
		||||
        archives: 'qtbase qtmultimedia qtx11extras icu'
 | 
			
		||||
    - name: Make
 | 
			
		||||
      working-directory: OSBindings/Qt
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        qmake -o Makefile clksignal.pro
 | 
			
		||||
        make
 | 
			
		||||
 | 
			
		||||
#  build-qt6:
 | 
			
		||||
#    name: Qt 6 / ${{ matrix.os }}
 | 
			
		||||
#    strategy:
 | 
			
		||||
#      matrix:
 | 
			
		||||
#        os: [ubuntu-latest]
 | 
			
		||||
#    runs-on: ${{ matrix.os }}
 | 
			
		||||
#    steps:
 | 
			
		||||
#    - name: Checkout
 | 
			
		||||
#      uses: actions/checkout@v4
 | 
			
		||||
#    - name: Install dependencies
 | 
			
		||||
#      uses: jurplel/install-qt-action@v4
 | 
			
		||||
#      with:
 | 
			
		||||
#        version: '6.8'
 | 
			
		||||
#        archives: qtbase
 | 
			
		||||
#    - name: Make
 | 
			
		||||
#      working-directory: OSBindings/Qt
 | 
			
		||||
#      shell: bash
 | 
			
		||||
#      run: |
 | 
			
		||||
#        qmake -o Makefile clksignal.pro
 | 
			
		||||
#        make
 | 
			
		||||
 
 | 
			
		||||
@@ -22,38 +22,38 @@ namespace Activity {
 | 
			
		||||
	and/or to show or unshow status indicators.
 | 
			
		||||
*/
 | 
			
		||||
class Observer {
 | 
			
		||||
	public:
 | 
			
		||||
		virtual ~Observer() = default;
 | 
			
		||||
public:
 | 
			
		||||
	virtual ~Observer() = default;
 | 
			
		||||
 | 
			
		||||
		/// Provides hints as to the sort of information presented on an LED.
 | 
			
		||||
		enum LEDPresentation: uint8_t {
 | 
			
		||||
			/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
 | 
			
		||||
			/// If this flag is absent then the LED describes an ephemeral state, such as media access.
 | 
			
		||||
			Persistent = (1 << 0),
 | 
			
		||||
		};
 | 
			
		||||
	/// Provides hints as to the sort of information presented on an LED.
 | 
			
		||||
	enum LEDPresentation: uint8_t {
 | 
			
		||||
		/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
 | 
			
		||||
		/// If this flag is absent then the LED describes an ephemeral state, such as media access.
 | 
			
		||||
		Persistent = (1 << 0),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
		/// Announces to the receiver that there is an LED of name @c name.
 | 
			
		||||
		virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
 | 
			
		||||
	/// Announces to the receiver that there is an LED of name @c name.
 | 
			
		||||
	virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
 | 
			
		||||
 | 
			
		||||
		/// Announces to the receiver that there is a drive of name @c name.
 | 
			
		||||
		///
 | 
			
		||||
		/// If a drive has the same name as an LED, that LED goes with this drive.
 | 
			
		||||
		virtual void register_drive([[maybe_unused]] const std::string &name) {}
 | 
			
		||||
	/// Announces to the receiver that there is a drive of name @c name.
 | 
			
		||||
	///
 | 
			
		||||
	/// If a drive has the same name as an LED, that LED goes with this drive.
 | 
			
		||||
	virtual void register_drive([[maybe_unused]] const std::string &name) {}
 | 
			
		||||
 | 
			
		||||
		/// Informs the receiver of the new state of the LED with name @c name.
 | 
			
		||||
		virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
 | 
			
		||||
	/// Informs the receiver of the new state of the LED with name @c name.
 | 
			
		||||
	virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
 | 
			
		||||
 | 
			
		||||
		enum class DriveEvent {
 | 
			
		||||
			StepNormal,
 | 
			
		||||
			StepBelowZero,
 | 
			
		||||
			StepBeyondMaximum
 | 
			
		||||
		};
 | 
			
		||||
	enum class DriveEvent {
 | 
			
		||||
		StepNormal,
 | 
			
		||||
		StepBelowZero,
 | 
			
		||||
		StepBeyondMaximum
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
		/// Informs the receiver that the named event just occurred for the drive with name @c name.
 | 
			
		||||
		virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
 | 
			
		||||
	/// Informs the receiver that the named event just occurred for the drive with name @c name.
 | 
			
		||||
	virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
 | 
			
		||||
 | 
			
		||||
		/// Informs the receiver of the motor-on status of the drive with name @c name.
 | 
			
		||||
		virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
 | 
			
		||||
	/// Informs the receiver of the motor-on status of the drive with name @c name.
 | 
			
		||||
	virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@
 | 
			
		||||
namespace Activity {
 | 
			
		||||
 | 
			
		||||
class Source {
 | 
			
		||||
	public:
 | 
			
		||||
		virtual void set_activity_observer(Observer *observer) = 0;
 | 
			
		||||
public:
 | 
			
		||||
	virtual void set_activity_observer(Observer *) = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
float ConfidenceCounter::get_confidence() {
 | 
			
		||||
float ConfidenceCounter::confidence() const {
 | 
			
		||||
	return float(hits_) / float(hits_ + misses_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,25 +18,25 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	The initial value of the confidence counter is 0.5.
 | 
			
		||||
*/
 | 
			
		||||
class ConfidenceCounter: public ConfidenceSource {
 | 
			
		||||
	public:
 | 
			
		||||
		/*! @returns The computed probability, based on the history of events. */
 | 
			
		||||
		float get_confidence() final;
 | 
			
		||||
public:
 | 
			
		||||
	/*! @returns The computed probability, based on the history of events. */
 | 
			
		||||
	float confidence() const final;
 | 
			
		||||
 | 
			
		||||
		/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
 | 
			
		||||
		void add_hit();
 | 
			
		||||
	/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
 | 
			
		||||
	void add_hit();
 | 
			
		||||
 | 
			
		||||
		/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
 | 
			
		||||
		void add_miss();
 | 
			
		||||
	/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
 | 
			
		||||
	void add_miss();
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Records an event that could be correct but isn't necessarily so; which can push probability
 | 
			
		||||
			down towards 0.5, but will never push it upwards.
 | 
			
		||||
		*/
 | 
			
		||||
		void add_equivocal();
 | 
			
		||||
	/*!
 | 
			
		||||
		Records an event that could be correct but isn't necessarily so; which can push probability
 | 
			
		||||
		down towards 0.5, but will never push it upwards.
 | 
			
		||||
	*/
 | 
			
		||||
	void add_equivocal();
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		int hits_ = 1;
 | 
			
		||||
		int misses_ = 1;
 | 
			
		||||
private:
 | 
			
		||||
	int hits_ = 1;
 | 
			
		||||
	int misses_ = 1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	program is handed to an Atari 2600 then its confidence should grow towards 1.0.
 | 
			
		||||
*/
 | 
			
		||||
struct ConfidenceSource {
 | 
			
		||||
	virtual float get_confidence() = 0;
 | 
			
		||||
	virtual float confidence() const = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,16 +13,19 @@
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
 | 
			
		||||
ConfidenceSummary::ConfidenceSummary(
 | 
			
		||||
	const std::vector<ConfidenceSource *> &sources,
 | 
			
		||||
	const std::vector<float> &weights
 | 
			
		||||
) :
 | 
			
		||||
	sources_(sources), weights_(weights) {
 | 
			
		||||
	assert(weights.size() == sources.size());
 | 
			
		||||
	weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ConfidenceSummary::get_confidence() {
 | 
			
		||||
float ConfidenceSummary::confidence() const {
 | 
			
		||||
	float result = 0.0f;
 | 
			
		||||
	for(std::size_t index = 0; index < sources_.size(); ++index) {
 | 
			
		||||
		result += sources_[index]->get_confidence() * weights_[index];
 | 
			
		||||
		result += sources_[index]->confidence() * weights_[index];
 | 
			
		||||
	}
 | 
			
		||||
	return result / weight_sum_;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,24 +18,24 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	Summaries a collection of confidence sources by calculating their weighted sum.
 | 
			
		||||
*/
 | 
			
		||||
class ConfidenceSummary: public ConfidenceSource {
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Instantiates a summary that will produce the weighted sum of
 | 
			
		||||
			@c sources, each using the corresponding entry of @c weights.
 | 
			
		||||
public:
 | 
			
		||||
	/*!
 | 
			
		||||
		Instantiates a summary that will produce the weighted sum of
 | 
			
		||||
		@c sources, each using the corresponding entry of @c weights.
 | 
			
		||||
 | 
			
		||||
			Requires that @c sources and @c weights are of the same length.
 | 
			
		||||
		*/
 | 
			
		||||
		ConfidenceSummary(
 | 
			
		||||
			const std::vector<ConfidenceSource *> &sources,
 | 
			
		||||
			const std::vector<float> &weights);
 | 
			
		||||
		Requires that @c sources and @c weights are of the same length.
 | 
			
		||||
	*/
 | 
			
		||||
	ConfidenceSummary(
 | 
			
		||||
		const std::vector<ConfidenceSource *> &sources,
 | 
			
		||||
		const std::vector<float> &weights);
 | 
			
		||||
 | 
			
		||||
		/*! @returns The weighted sum of all sources. */
 | 
			
		||||
		float get_confidence() final;
 | 
			
		||||
	/*! @returns The weighted sum of all sources. */
 | 
			
		||||
	float confidence() const final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<ConfidenceSource *> sources_;
 | 
			
		||||
		const std::vector<float> weights_;
 | 
			
		||||
		float weight_sum_;
 | 
			
		||||
private:
 | 
			
		||||
	const std::vector<ConfidenceSource *> sources_;
 | 
			
		||||
	const std::vector<float> weights_;
 | 
			
		||||
	float weight_sum_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,90 +15,90 @@ using namespace Analyser::Dynamic;
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
class MultiStruct: public Reflection::Struct {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
 | 
			
		||||
			for(auto device: devices) {
 | 
			
		||||
				options_.emplace_back(device->get_options());
 | 
			
		||||
public:
 | 
			
		||||
	MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
 | 
			
		||||
		for(auto device: devices) {
 | 
			
		||||
			options_.emplace_back(device->get_options());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void apply() {
 | 
			
		||||
		auto options = options_.begin();
 | 
			
		||||
		for(auto device: devices_) {
 | 
			
		||||
			device->set_options(*options);
 | 
			
		||||
			++options;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::vector<std::string> all_keys() const final {
 | 
			
		||||
		std::set<std::string> keys;
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			const auto new_keys = options->all_keys();
 | 
			
		||||
			keys.insert(new_keys.begin(), new_keys.end());
 | 
			
		||||
		}
 | 
			
		||||
		return std::vector<std::string>(keys.begin(), keys.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::vector<std::string> values_for(const std::string &name) const final {
 | 
			
		||||
		std::set<std::string> values;
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			const auto new_values = options->values_for(name);
 | 
			
		||||
			values.insert(new_values.begin(), new_values.end());
 | 
			
		||||
		}
 | 
			
		||||
		return std::vector<std::string>(values.begin(), values.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const std::type_info *type_of(const std::string &name) const final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto info = options->type_of(name);
 | 
			
		||||
			if(info) return info;
 | 
			
		||||
		}
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size_t count_of(const std::string &name) const final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto info = options->type_of(name);
 | 
			
		||||
			if(info) return options->count_of(name);
 | 
			
		||||
		}
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const void *get(const std::string &name) const final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto value = options->get(name);
 | 
			
		||||
			if(value) return value;
 | 
			
		||||
		}
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void *get(const std::string &name) final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto value = options->get(name);
 | 
			
		||||
			if(value) return value;
 | 
			
		||||
		}
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set(const std::string &name, const void *value, const size_t offset) final {
 | 
			
		||||
		const auto safe_type = type_of(name);
 | 
			
		||||
		if(!safe_type) return;
 | 
			
		||||
 | 
			
		||||
		// Set this property only where the child's type is the same as that
 | 
			
		||||
		// which was returned from here for type_of.
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			const auto type = options->type_of(name);
 | 
			
		||||
			if(!type) continue;
 | 
			
		||||
 | 
			
		||||
			if(*type == *safe_type) {
 | 
			
		||||
				options->set(name, value, offset);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void apply() {
 | 
			
		||||
			auto options = options_.begin();
 | 
			
		||||
			for(auto device: devices_) {
 | 
			
		||||
				device->set_options(*options);
 | 
			
		||||
				++options;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::vector<std::string> all_keys() const final {
 | 
			
		||||
			std::set<std::string> keys;
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				const auto new_keys = options->all_keys();
 | 
			
		||||
				keys.insert(new_keys.begin(), new_keys.end());
 | 
			
		||||
			}
 | 
			
		||||
			return std::vector<std::string>(keys.begin(), keys.end());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::vector<std::string> values_for(const std::string &name) const final {
 | 
			
		||||
			std::set<std::string> values;
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				const auto new_values = options->values_for(name);
 | 
			
		||||
				values.insert(new_values.begin(), new_values.end());
 | 
			
		||||
			}
 | 
			
		||||
			return std::vector<std::string>(values.begin(), values.end());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const std::type_info *type_of(const std::string &name) const final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto info = options->type_of(name);
 | 
			
		||||
				if(info) return info;
 | 
			
		||||
			}
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		size_t count_of(const std::string &name) const final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto info = options->type_of(name);
 | 
			
		||||
				if(info) return options->count_of(name);
 | 
			
		||||
			}
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const void *get(const std::string &name) const final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto value = options->get(name);
 | 
			
		||||
				if(value) return value;
 | 
			
		||||
			}
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void *get(const std::string &name) final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto value = options->get(name);
 | 
			
		||||
				if(value) return value;
 | 
			
		||||
			}
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void set(const std::string &name, const void *value, size_t offset) final {
 | 
			
		||||
			const auto safe_type = type_of(name);
 | 
			
		||||
			if(!safe_type) return;
 | 
			
		||||
 | 
			
		||||
			// Set this property only where the child's type is the same as that
 | 
			
		||||
			// which was returned from here for type_of.
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				const auto type = options->type_of(name);
 | 
			
		||||
				if(!type) continue;
 | 
			
		||||
 | 
			
		||||
				if(*type == *safe_type) {
 | 
			
		||||
					options->set(name, value, offset);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<Configurable::Device *> &devices_;
 | 
			
		||||
		std::vector<std::unique_ptr<Reflection::Struct>> options_;
 | 
			
		||||
private:
 | 
			
		||||
	const std::vector<Configurable::Device *> &devices_;
 | 
			
		||||
	std::vector<std::unique_ptr<Reflection::Struct>> options_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -115,6 +115,6 @@ void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &s
 | 
			
		||||
	options->apply();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
 | 
			
		||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() const {
 | 
			
		||||
	return std::make_unique<MultiStruct>(devices_);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Configurable/Configurable.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Configurable/Configurable.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
@@ -23,15 +23,15 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
class MultiConfigurable: public Configurable::Device {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
public:
 | 
			
		||||
	MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
 | 
			
		||||
		// Below is the standard Configurable::Device interface; see there for documentation.
 | 
			
		||||
		void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
 | 
			
		||||
		std::unique_ptr<Reflection::Struct> get_options() final;
 | 
			
		||||
	// Below is the standard Configurable::Device interface; see there for documentation.
 | 
			
		||||
	void set_options(const std::unique_ptr<Reflection::Struct> &) final;
 | 
			
		||||
	std::unique_ptr<Reflection::Struct> get_options() const final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<Configurable::Device *> devices_;
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<Configurable::Device *> devices_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,52 +15,52 @@ using namespace Analyser::Dynamic;
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
class MultiJoystick: public Inputs::Joystick {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
 | 
			
		||||
			for(const auto &machine: machines) {
 | 
			
		||||
				const auto &joysticks = machine->get_joysticks();
 | 
			
		||||
				if(joysticks.size() >= index) {
 | 
			
		||||
					joysticks_.push_back(joysticks[index].get());
 | 
			
		||||
				}
 | 
			
		||||
public:
 | 
			
		||||
	MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, const std::size_t index) {
 | 
			
		||||
		for(const auto &machine: machines) {
 | 
			
		||||
			const auto &joysticks = machine->get_joysticks();
 | 
			
		||||
			if(joysticks.size() > index) {
 | 
			
		||||
				joysticks_.push_back(joysticks[index].get());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		const std::vector<Input> &get_inputs() final {
 | 
			
		||||
			if(inputs.empty()) {
 | 
			
		||||
				for(const auto &joystick: joysticks_) {
 | 
			
		||||
					std::vector<Input> joystick_inputs = joystick->get_inputs();
 | 
			
		||||
					for(const auto &input: joystick_inputs) {
 | 
			
		||||
						if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
 | 
			
		||||
							inputs.push_back(input);
 | 
			
		||||
						}
 | 
			
		||||
	const std::vector<Input> &get_inputs() final {
 | 
			
		||||
		if(inputs.empty()) {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				std::vector<Input> joystick_inputs = joystick->get_inputs();
 | 
			
		||||
				for(const auto &input: joystick_inputs) {
 | 
			
		||||
					if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
 | 
			
		||||
						inputs.push_back(input);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return inputs;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void set_input(const Input &digital_input, bool is_active) final {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				joystick->set_input(digital_input, is_active);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return inputs;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void set_input(const Input &digital_input, float value) final {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				joystick->set_input(digital_input, value);
 | 
			
		||||
			}
 | 
			
		||||
	void set_input(const Input &digital_input, const bool is_active) final {
 | 
			
		||||
		for(const auto &joystick: joysticks_) {
 | 
			
		||||
			joystick->set_input(digital_input, is_active);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void reset_all_inputs() final {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				joystick->reset_all_inputs();
 | 
			
		||||
			}
 | 
			
		||||
	void set_input(const Input &digital_input, const float value) final {
 | 
			
		||||
		for(const auto &joystick: joysticks_) {
 | 
			
		||||
			joystick->set_input(digital_input, value);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<Input> inputs;
 | 
			
		||||
		std::vector<Inputs::Joystick *> joysticks_;
 | 
			
		||||
	void reset_all_inputs() final {
 | 
			
		||||
		for(const auto &joystick: joysticks_) {
 | 
			
		||||
			joystick->reset_all_inputs();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<Input> inputs;
 | 
			
		||||
	std::vector<Inputs::Joystick *> joysticks_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
@@ -22,14 +22,14 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
public:
 | 
			
		||||
	MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
 | 
			
		||||
		// Below is the standard JoystickMachine::Machine interface; see there for documentation.
 | 
			
		||||
		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
 | 
			
		||||
	// Below is the standard JoystickMachine::Machine interface; see there for documentation.
 | 
			
		||||
	const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ void MultiKeyboardMachine::clear_all_keys() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
 | 
			
		||||
void MultiKeyboardMachine::set_key_state(const uint16_t key, const bool is_pressed) {
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		machine->set_key_state(key, is_pressed);
 | 
			
		||||
	}
 | 
			
		||||
@@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MultiKeyboardMachine::can_type(char c) const {
 | 
			
		||||
bool MultiKeyboardMachine::can_type(const char c) const {
 | 
			
		||||
	bool can_type = true;
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		can_type &= machine->can_type(c);
 | 
			
		||||
@@ -51,12 +51,20 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
 | 
			
		||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
 | 
			
		||||
	: machines_(machines) {
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
 | 
			
		||||
		observed_keys_.insert(
 | 
			
		||||
			machine->get_keyboard().observed_keys().begin(),
 | 
			
		||||
			machine->get_keyboard().observed_keys().end()
 | 
			
		||||
		);
 | 
			
		||||
		is_exclusive_ |= machine->get_keyboard().is_exclusive();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
 | 
			
		||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(
 | 
			
		||||
	const Key key,
 | 
			
		||||
	const char value,
 | 
			
		||||
	const bool is_pressed,
 | 
			
		||||
	const bool is_repeat
 | 
			
		||||
) {
 | 
			
		||||
	bool was_consumed = false;
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Machines/KeyboardMachine.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Machines/KeyboardMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
@@ -23,34 +23,34 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<MachineTypes::KeyboardMachine *> machines_;
 | 
			
		||||
 | 
			
		||||
		class MultiKeyboard: public Inputs::Keyboard {
 | 
			
		||||
			public:
 | 
			
		||||
				MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
 | 
			
		||||
 | 
			
		||||
				bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
 | 
			
		||||
				void reset_all_keys() final;
 | 
			
		||||
				const std::set<Key> &observed_keys() const final;
 | 
			
		||||
				bool is_exclusive() const final;
 | 
			
		||||
 | 
			
		||||
			private:
 | 
			
		||||
				const std::vector<MachineTypes::KeyboardMachine *> &machines_;
 | 
			
		||||
				std::set<Key> observed_keys_;
 | 
			
		||||
				bool is_exclusive_ = false;
 | 
			
		||||
		};
 | 
			
		||||
		std::unique_ptr<MultiKeyboard> keyboard_;
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<MachineTypes::KeyboardMachine *> machines_;
 | 
			
		||||
 | 
			
		||||
	class MultiKeyboard: public Inputs::Keyboard {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
		MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
 | 
			
		||||
 | 
			
		||||
		// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
 | 
			
		||||
		void clear_all_keys() final;
 | 
			
		||||
		void set_key_state(uint16_t key, bool is_pressed) final;
 | 
			
		||||
		void type_string(const std::string &) final;
 | 
			
		||||
		bool can_type(char c) const final;
 | 
			
		||||
		Inputs::Keyboard &get_keyboard() final;
 | 
			
		||||
		bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
 | 
			
		||||
		void reset_all_keys() final;
 | 
			
		||||
		const std::set<Key> &observed_keys() const final;
 | 
			
		||||
		bool is_exclusive() const final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<MachineTypes::KeyboardMachine *> &machines_;
 | 
			
		||||
		std::set<Key> observed_keys_;
 | 
			
		||||
		bool is_exclusive_ = false;
 | 
			
		||||
	};
 | 
			
		||||
	std::unique_ptr<MultiKeyboard> keyboard_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
 | 
			
		||||
	void clear_all_keys() final;
 | 
			
		||||
	void set_key_state(uint16_t key, bool is_pressed) final;
 | 
			
		||||
	void type_string(const std::string &) final;
 | 
			
		||||
	bool can_type(char c) const final;
 | 
			
		||||
	Inputs::Keyboard &get_keyboard() final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "MultiMediaTarget.hpp"
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
@@ -18,9 +19,38 @@ MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
 | 
			
		||||
	// TODO: copy media afresh for each target machine; media
 | 
			
		||||
	// generally has mutable state.
 | 
			
		||||
 | 
			
		||||
	bool inserted = false;
 | 
			
		||||
	for(const auto &target : targets_) {
 | 
			
		||||
		inserted |= target->insert_media(media);
 | 
			
		||||
	}
 | 
			
		||||
	return inserted;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MultiMediaChangeObserver::MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
			
		||||
	for(const auto &machine: machines) {
 | 
			
		||||
		auto media_change_observer = machine->media_change_observer();
 | 
			
		||||
		if(media_change_observer) targets_.push_back(media_change_observer);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
using ChangeEffect = MachineTypes::MediaChangeObserver::ChangeEffect;
 | 
			
		||||
 | 
			
		||||
ChangeEffect MultiMediaChangeObserver::effect_for_file_did_change(const std::string &name) const {
 | 
			
		||||
	if(targets_.empty()) {
 | 
			
		||||
		return ChangeEffect::None;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::unordered_set<ChangeEffect> effects;
 | 
			
		||||
	for(const auto &target: targets_) {
 | 
			
		||||
		effects.insert(target->effect_for_file_did_change(name));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No agreement => restart.
 | 
			
		||||
	if(effects.size() > 1) {
 | 
			
		||||
		return ChangeEffect::RestartMachine;
 | 
			
		||||
	}
 | 
			
		||||
	return *effects.begin();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../../Machines/MediaTarget.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Machines/MediaTarget.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
@@ -23,14 +23,25 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
struct MultiMediaTarget: public MachineTypes::MediaTarget {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
public:
 | 
			
		||||
	MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
 | 
			
		||||
		// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
			
		||||
		bool insert_media(const Analyser::Static::Media &media) final;
 | 
			
		||||
	// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
			
		||||
	bool insert_media(const Analyser::Static::Media &) final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<MachineTypes::MediaTarget *> targets_;
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<MachineTypes::MediaTarget *> targets_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MultiMediaChangeObserver: public MachineTypes::MediaChangeObserver {
 | 
			
		||||
public:
 | 
			
		||||
	MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
			
		||||
	ChangeEffect effect_for_file_did_change(const std::string &) const final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<MachineTypes::MediaChangeObserver *> targets_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ template <typename MachineType>
 | 
			
		||||
void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &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::size_t outstanding_machines;
 | 
			
		||||
	std::condition_variable condition;
 | 
			
		||||
	std::mutex mutex;
 | 
			
		||||
	{
 | 
			
		||||
@@ -33,7 +33,7 @@ void MultiInterface<MachineType>::perform_parallel(const std::function<void(Mach
 | 
			
		||||
				if(machine) function(machine);
 | 
			
		||||
 | 
			
		||||
				std::lock_guard lock(mutex);
 | 
			
		||||
				outstanding_machines--;
 | 
			
		||||
				--outstanding_machines;
 | 
			
		||||
				condition.notify_all();
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
@@ -53,7 +53,7 @@ void MultiInterface<MachineType>::perform_serial(const std::function<void(Machin
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MARK: - MultiScanProducer
 | 
			
		||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
			
		||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
 | 
			
		||||
	scan_target_ = scan_target;
 | 
			
		||||
 | 
			
		||||
	std::lock_guard machines_lock(machines_mutex_);
 | 
			
		||||
@@ -80,7 +80,12 @@ void MultiScanProducer::did_change_machine_order() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MARK: - MultiAudioProducer
 | 
			
		||||
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
 | 
			
		||||
MultiAudioProducer::MultiAudioProducer(
 | 
			
		||||
	const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
 | 
			
		||||
	std::recursive_mutex &machines_mutex
 | 
			
		||||
) :
 | 
			
		||||
	MultiInterface(machines, machines_mutex)
 | 
			
		||||
{
 | 
			
		||||
	speaker_ = MultiSpeaker::create(machines);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -96,10 +101,10 @@ void MultiAudioProducer::did_change_machine_order() {
 | 
			
		||||
 | 
			
		||||
// MARK: - MultiTimedMachine
 | 
			
		||||
 | 
			
		||||
void MultiTimedMachine::run_for(Time::Seconds duration) {
 | 
			
		||||
void MultiTimedMachine::run_for(const Time::Seconds duration) {
 | 
			
		||||
	perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
 | 
			
		||||
		if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if(delegate_) delegate_->did_run_machines(this);
 | 
			
		||||
	if(delegate_) delegate_->did_run_machines(*this);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
 | 
			
		||||
#include "../../../../Machines/MachineTypes.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Concurrency/AsyncTaskQueue.hpp"
 | 
			
		||||
#include "Machines/MachineTypes.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include "MultiSpeaker.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -21,88 +21,91 @@
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
 | 
			
		||||
template <typename MachineType> class MultiInterface {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
 | 
			
		||||
			machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
 | 
			
		||||
public:
 | 
			
		||||
	MultiInterface(
 | 
			
		||||
		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
 | 
			
		||||
		std::recursive_mutex &machines_mutex
 | 
			
		||||
	) :
 | 
			
		||||
		machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		/*!
 | 
			
		||||
			Performs a parallel for operation across all machines, performing the supplied
 | 
			
		||||
			function on each and returning only once all applications have completed.
 | 
			
		||||
protected:
 | 
			
		||||
	/*!
 | 
			
		||||
		Performs a parallel for operation across all machines, performing the supplied
 | 
			
		||||
		function on each and returning only once all applications have completed.
 | 
			
		||||
 | 
			
		||||
			No guarantees are extended as to which thread operations will occur on.
 | 
			
		||||
		*/
 | 
			
		||||
		void perform_parallel(const std::function<void(MachineType *)> &);
 | 
			
		||||
		No guarantees are extended as to which thread operations will occur on.
 | 
			
		||||
	*/
 | 
			
		||||
	void perform_parallel(const std::function<void(MachineType *)> &);
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Performs a serial for operation across all machines, performing the supplied
 | 
			
		||||
			function on each on the calling thread.
 | 
			
		||||
		*/
 | 
			
		||||
		void perform_serial(const std::function<void(MachineType *)> &);
 | 
			
		||||
	/*!
 | 
			
		||||
		Performs a serial for operation across all machines, performing the supplied
 | 
			
		||||
		function on each on the calling thread.
 | 
			
		||||
	*/
 | 
			
		||||
	void perform_serial(const std::function<void(MachineType *)> &);
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
			
		||||
		std::recursive_mutex &machines_mutex_;
 | 
			
		||||
protected:
 | 
			
		||||
	const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
			
		||||
	std::recursive_mutex &machines_mutex_;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
 | 
			
		||||
	public:
 | 
			
		||||
		using MultiInterface::MultiInterface;
 | 
			
		||||
public:
 | 
			
		||||
	using MultiInterface::MultiInterface;
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Provides a mechanism by which a delegate can be informed each time a call to run_for has
 | 
			
		||||
			been received.
 | 
			
		||||
		*/
 | 
			
		||||
		struct Delegate {
 | 
			
		||||
			virtual void did_run_machines(MultiTimedMachine *) = 0;
 | 
			
		||||
		};
 | 
			
		||||
		/// Sets @c delegate as the receiver of delegate messages.
 | 
			
		||||
		void set_delegate(Delegate *delegate) {
 | 
			
		||||
			delegate_ = delegate;
 | 
			
		||||
		}
 | 
			
		||||
	/*!
 | 
			
		||||
		Provides a mechanism by which a delegate can be informed each time a call to run_for has
 | 
			
		||||
		been received.
 | 
			
		||||
	*/
 | 
			
		||||
	struct Delegate {
 | 
			
		||||
		virtual void did_run_machines(MultiTimedMachine &) = 0;
 | 
			
		||||
	};
 | 
			
		||||
	/// Sets @c delegate as the receiver of delegate messages.
 | 
			
		||||
	void set_delegate(Delegate *const delegate) {
 | 
			
		||||
		delegate_ = delegate;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void run_for(Time::Seconds duration) final;
 | 
			
		||||
	void run_for(Time::Seconds duration) final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void run_for(const Cycles) final {}
 | 
			
		||||
		Delegate *delegate_ = nullptr;
 | 
			
		||||
private:
 | 
			
		||||
	void run_for(Cycles) final {}
 | 
			
		||||
	Delegate *delegate_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
 | 
			
		||||
	public:
 | 
			
		||||
		using MultiInterface::MultiInterface;
 | 
			
		||||
public:
 | 
			
		||||
	using MultiInterface::MultiInterface;
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Informs the MultiScanProducer that the order of machines has changed; it
 | 
			
		||||
			uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
 | 
			
		||||
			are necessary to bridge the gap between one machine and the next.
 | 
			
		||||
		*/
 | 
			
		||||
		void did_change_machine_order();
 | 
			
		||||
	/*!
 | 
			
		||||
		Informs the MultiScanProducer that the order of machines has changed; it
 | 
			
		||||
		uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
 | 
			
		||||
		are necessary to bridge the gap between one machine and the next.
 | 
			
		||||
	*/
 | 
			
		||||
	void did_change_machine_order();
 | 
			
		||||
 | 
			
		||||
		void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
 | 
			
		||||
		Outputs::Display::ScanStatus get_scan_status() const final;
 | 
			
		||||
	void set_scan_target(Outputs::Display::ScanTarget *) final;
 | 
			
		||||
	Outputs::Display::ScanStatus get_scan_status() const final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		Outputs::Display::ScanTarget *scan_target_ = nullptr;
 | 
			
		||||
private:
 | 
			
		||||
	Outputs::Display::ScanTarget *scan_target_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
 | 
			
		||||
public:
 | 
			
		||||
	MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &, std::recursive_mutex &);
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Informs the MultiAudio that the order of machines has changed; it
 | 
			
		||||
			uses this as an opportunity to switch speaker delegates as appropriate.
 | 
			
		||||
		*/
 | 
			
		||||
		void did_change_machine_order();
 | 
			
		||||
	/*!
 | 
			
		||||
		Informs the MultiAudio that the order of machines has changed; it
 | 
			
		||||
		uses this as an opportunity to switch speaker delegates as appropriate.
 | 
			
		||||
	*/
 | 
			
		||||
	void did_change_machine_order();
 | 
			
		||||
 | 
			
		||||
		Outputs::Speaker::Speaker *get_speaker() final;
 | 
			
		||||
	Outputs::Speaker::Speaker *get_speaker() final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		MultiSpeaker *speaker_ = nullptr;
 | 
			
		||||
private:
 | 
			
		||||
	MultiSpeaker *speaker_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speak
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
 | 
			
		||||
float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const float maximum) {
 | 
			
		||||
	float ideal = 0.0f;
 | 
			
		||||
	for(const auto &speaker: speakers_) {
 | 
			
		||||
		ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
 | 
			
		||||
@@ -37,7 +37,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
 | 
			
		||||
	return ideal / float(speakers_.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
 | 
			
		||||
void MultiSpeaker::set_computed_output_rate(const float cycles_per_second, const int buffer_size, const bool stereo) {
 | 
			
		||||
	stereo_output_ = stereo;
 | 
			
		||||
	for(const auto &speaker: speakers_) {
 | 
			
		||||
		speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
 | 
			
		||||
@@ -54,39 +54,39 @@ bool MultiSpeaker::get_is_stereo() {
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::set_output_volume(float volume) {
 | 
			
		||||
void MultiSpeaker::set_output_volume(const float volume) {
 | 
			
		||||
	for(const auto &speaker: speakers_) {
 | 
			
		||||
		speaker->set_output_volume(volume);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
 | 
			
		||||
void MultiSpeaker::speaker_did_complete_samples(Speaker &speaker, const std::vector<int16_t> &buffer) {
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order_relaxed);
 | 
			
		||||
	if(!delegate) return;
 | 
			
		||||
	{
 | 
			
		||||
		std::lock_guard lock_guard(front_speaker_mutex_);
 | 
			
		||||
		if(speaker != front_speaker_) return;
 | 
			
		||||
		if(&speaker != front_speaker_) return;
 | 
			
		||||
	}
 | 
			
		||||
	did_complete_samples(this, buffer, stereo_output_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
 | 
			
		||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker &speaker) {
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order_relaxed);
 | 
			
		||||
	if(!delegate) return;
 | 
			
		||||
	{
 | 
			
		||||
		std::lock_guard lock_guard(front_speaker_mutex_);
 | 
			
		||||
		if(speaker != front_speaker_) return;
 | 
			
		||||
		if(&speaker != front_speaker_) return;
 | 
			
		||||
	}
 | 
			
		||||
	delegate->speaker_did_change_input_clock(this);
 | 
			
		||||
	delegate->speaker_did_change_input_clock(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
 | 
			
		||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
 | 
			
		||||
	{
 | 
			
		||||
		std::lock_guard lock_guard(front_speaker_mutex_);
 | 
			
		||||
		front_speaker_ = machine->audio_producer()->get_speaker();
 | 
			
		||||
	}
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order_relaxed);
 | 
			
		||||
	if(delegate) {
 | 
			
		||||
		delegate->speaker_did_change_input_clock(this);
 | 
			
		||||
		delegate->speaker_did_change_input_clock(*this);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Outputs/Speaker/Speaker.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Outputs/Speaker/Speaker.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
@@ -25,32 +25,32 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	abreast of the current frontmost machine.
 | 
			
		||||
*/
 | 
			
		||||
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Provides a construction mechanism that may return nullptr, in the case that all included
 | 
			
		||||
			machines return nullptr as their speaker.
 | 
			
		||||
		*/
 | 
			
		||||
		static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
public:
 | 
			
		||||
	/*!
 | 
			
		||||
		Provides a construction mechanism that may return nullptr, in the case that all included
 | 
			
		||||
		machines return nullptr as their speaker.
 | 
			
		||||
	*/
 | 
			
		||||
	static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
 | 
			
		||||
		/// This class requires the caller to nominate changes in the frontmost machine.
 | 
			
		||||
		void set_new_front_machine(::Machine::DynamicMachine *machine);
 | 
			
		||||
	/// This class requires the caller to nominate changes in the frontmost machine.
 | 
			
		||||
	void set_new_front_machine(::Machine::DynamicMachine *);
 | 
			
		||||
 | 
			
		||||
		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
 | 
			
		||||
		float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
 | 
			
		||||
		void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
 | 
			
		||||
		bool get_is_stereo() override;
 | 
			
		||||
		void set_output_volume(float) override;
 | 
			
		||||
	// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
 | 
			
		||||
	float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
 | 
			
		||||
	void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
 | 
			
		||||
	bool get_is_stereo() override;
 | 
			
		||||
	void set_output_volume(float) override;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
 | 
			
		||||
		void speaker_did_change_input_clock(Speaker *speaker) final;
 | 
			
		||||
		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
 | 
			
		||||
private:
 | 
			
		||||
	void speaker_did_complete_samples(Speaker &, const std::vector<int16_t> &buffer) final;
 | 
			
		||||
	void speaker_did_change_input_clock(Speaker &) final;
 | 
			
		||||
	MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
 | 
			
		||||
 | 
			
		||||
		std::vector<Outputs::Speaker::Speaker *> speakers_;
 | 
			
		||||
		Outputs::Speaker::Speaker *front_speaker_ = nullptr;
 | 
			
		||||
		std::mutex front_speaker_mutex_;
 | 
			
		||||
	std::vector<Outputs::Speaker::Speaker *> speakers_;
 | 
			
		||||
	Outputs::Speaker::Speaker *front_speaker_ = nullptr;
 | 
			
		||||
	std::mutex front_speaker_mutex_;
 | 
			
		||||
 | 
			
		||||
		bool stereo_output_ = false;
 | 
			
		||||
	bool stereo_output_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,14 +7,12 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "MultiMachine.hpp"
 | 
			
		||||
#include "../../../Outputs/Log.hpp"
 | 
			
		||||
#include "Outputs/Log.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
Log::Logger<Log::Source::MultiMachine> logger;
 | 
			
		||||
 | 
			
		||||
using Logger = Log::Logger<Log::Source::MultiMachine>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
@@ -27,7 +25,9 @@ MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machin
 | 
			
		||||
	audio_producer_(machines_, machines_mutex_),
 | 
			
		||||
	joystick_machine_(machines_),
 | 
			
		||||
	keyboard_machine_(machines_),
 | 
			
		||||
	media_target_(machines_) {
 | 
			
		||||
	media_target_(machines_),
 | 
			
		||||
	media_change_observer_(machines_)
 | 
			
		||||
{
 | 
			
		||||
	timed_machine_.set_delegate(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -35,13 +35,13 @@ Activity::Source *MultiMachine::activity_source() {
 | 
			
		||||
	return nullptr; // TODO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define Provider(type, name, member)	\
 | 
			
		||||
	type *MultiMachine::name() {	\
 | 
			
		||||
		if(has_picked_) {	\
 | 
			
		||||
#define Provider(type, name, member)			\
 | 
			
		||||
	type *MultiMachine::name() {				\
 | 
			
		||||
		if(has_picked_) {						\
 | 
			
		||||
			return machines_.front()->name();	\
 | 
			
		||||
		} else {	\
 | 
			
		||||
			return &member;	\
 | 
			
		||||
		}	\
 | 
			
		||||
		} else {								\
 | 
			
		||||
			return &member;						\
 | 
			
		||||
		}										\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
Provider(Configurable::Device, configurable_device, configurable_)
 | 
			
		||||
@@ -51,6 +51,7 @@ Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
 | 
			
		||||
Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
 | 
			
		||||
Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
 | 
			
		||||
Provider(MachineTypes::MediaTarget, media_target, media_target_)
 | 
			
		||||
Provider(MachineTypes::MediaChangeObserver, media_change_observer, media_change_observer_)
 | 
			
		||||
 | 
			
		||||
MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
 | 
			
		||||
	// TODO.
 | 
			
		||||
@@ -65,11 +66,11 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
 | 
			
		||||
		(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiMachine::did_run_machines(MultiTimedMachine *) {
 | 
			
		||||
void MultiMachine::did_run_machines(MultiTimedMachine &) {
 | 
			
		||||
	std::lock_guard machines_lock(machines_mutex_);
 | 
			
		||||
 | 
			
		||||
	if constexpr (logger.enabled) {
 | 
			
		||||
		auto line = logger.info();
 | 
			
		||||
	if constexpr (Logger::InfoEnabled) {
 | 
			
		||||
		auto line = Logger::info();
 | 
			
		||||
		for(const auto &machine: machines_) {
 | 
			
		||||
			auto timed_machine = machine->timed_machine();
 | 
			
		||||
			line.append("%0.4f %s; ", timed_machine->get_confidence(), timed_machine->debug_type().c_str());
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,14 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Implementation/MultiConfigurable.hpp"
 | 
			
		||||
#include "Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Implementation/MultiJoystickMachine.hpp"
 | 
			
		||||
#include "Implementation/MultiKeyboardMachine.hpp"
 | 
			
		||||
#include "Implementation/MultiMediaTarget.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiMediaTarget.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
@@ -38,44 +38,46 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	the others in the set, that machine stops running.
 | 
			
		||||
*/
 | 
			
		||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
 | 
			
		||||
			requesting this class as a proxy.
 | 
			
		||||
public:
 | 
			
		||||
	/*!
 | 
			
		||||
		Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
 | 
			
		||||
		requesting this class as a proxy.
 | 
			
		||||
 | 
			
		||||
			@returns @c true if the multimachine would discard all but the first machine in this list;
 | 
			
		||||
				@c false otherwise.
 | 
			
		||||
		*/
 | 
			
		||||
		static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
 | 
			
		||||
		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
 | 
			
		||||
		@returns @c true if the multimachine would discard all but the first machine in this list;
 | 
			
		||||
			@c false otherwise.
 | 
			
		||||
	*/
 | 
			
		||||
	static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &);
 | 
			
		||||
	MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&);
 | 
			
		||||
 | 
			
		||||
		Activity::Source *activity_source() final;
 | 
			
		||||
		Configurable::Device *configurable_device() final;
 | 
			
		||||
		MachineTypes::TimedMachine *timed_machine() final;
 | 
			
		||||
		MachineTypes::ScanProducer *scan_producer() final;
 | 
			
		||||
		MachineTypes::AudioProducer *audio_producer() final;
 | 
			
		||||
		MachineTypes::JoystickMachine *joystick_machine() final;
 | 
			
		||||
		MachineTypes::KeyboardMachine *keyboard_machine() final;
 | 
			
		||||
		MachineTypes::MouseMachine *mouse_machine() final;
 | 
			
		||||
		MachineTypes::MediaTarget *media_target() final;
 | 
			
		||||
		void *raw_pointer() final;
 | 
			
		||||
	Activity::Source *activity_source() final;
 | 
			
		||||
	Configurable::Device *configurable_device() final;
 | 
			
		||||
	MachineTypes::TimedMachine *timed_machine() final;
 | 
			
		||||
	MachineTypes::ScanProducer *scan_producer() final;
 | 
			
		||||
	MachineTypes::AudioProducer *audio_producer() final;
 | 
			
		||||
	MachineTypes::JoystickMachine *joystick_machine() final;
 | 
			
		||||
	MachineTypes::KeyboardMachine *keyboard_machine() final;
 | 
			
		||||
	MachineTypes::MouseMachine *mouse_machine() final;
 | 
			
		||||
	MachineTypes::MediaTarget *media_target() final;
 | 
			
		||||
	MachineTypes::MediaChangeObserver *media_change_observer() final;
 | 
			
		||||
	void *raw_pointer() final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void did_run_machines(MultiTimedMachine *) final;
 | 
			
		||||
private:
 | 
			
		||||
	void did_run_machines(MultiTimedMachine &) final;
 | 
			
		||||
 | 
			
		||||
		std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
			
		||||
		std::recursive_mutex machines_mutex_;
 | 
			
		||||
	std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
			
		||||
	std::recursive_mutex machines_mutex_;
 | 
			
		||||
 | 
			
		||||
		MultiConfigurable configurable_;
 | 
			
		||||
		MultiTimedMachine timed_machine_;
 | 
			
		||||
		MultiScanProducer scan_producer_;
 | 
			
		||||
		MultiAudioProducer audio_producer_;
 | 
			
		||||
		MultiJoystickMachine joystick_machine_;
 | 
			
		||||
		MultiKeyboardMachine keyboard_machine_;
 | 
			
		||||
		MultiMediaTarget media_target_;
 | 
			
		||||
	MultiConfigurable configurable_;
 | 
			
		||||
	MultiTimedMachine timed_machine_;
 | 
			
		||||
	MultiScanProducer scan_producer_;
 | 
			
		||||
	MultiAudioProducer audio_producer_;
 | 
			
		||||
	MultiJoystickMachine joystick_machine_;
 | 
			
		||||
	MultiKeyboardMachine keyboard_machine_;
 | 
			
		||||
	MultiMediaTarget media_target_;
 | 
			
		||||
	MultiMediaChangeObserver media_change_observer_;
 | 
			
		||||
 | 
			
		||||
		void pick_first();
 | 
			
		||||
		bool has_picked_ = false;
 | 
			
		||||
	void pick_first();
 | 
			
		||||
	bool has_picked_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ enum class Machine {
 | 
			
		||||
	AtariST,
 | 
			
		||||
	Amiga,
 | 
			
		||||
	Archimedes,
 | 
			
		||||
	BBCMicro,
 | 
			
		||||
	ColecoVision,
 | 
			
		||||
	Electron,
 | 
			
		||||
	Enterprise,
 | 
			
		||||
@@ -25,6 +26,7 @@ enum class Machine {
 | 
			
		||||
	MasterSystem,
 | 
			
		||||
	MSX,
 | 
			
		||||
	Oric,
 | 
			
		||||
	Plus4,
 | 
			
		||||
	PCCompatible,
 | 
			
		||||
	Vic20,
 | 
			
		||||
	ZX8081,
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../Numeric/CRC.hpp"
 | 
			
		||||
#include "Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "Numeric/CRC.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
@@ -49,27 +49,39 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
 | 
			
		||||
		char name[10];
 | 
			
		||||
		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
 | 
			
		||||
		new_file.name = name;
 | 
			
		||||
		new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
 | 
			
		||||
		new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
 | 
			
		||||
		new_file.load_address = uint32_t(
 | 
			
		||||
			details->samples[0][file_offset] |
 | 
			
		||||
			(details->samples[0][file_offset+1] << 8) |
 | 
			
		||||
			((details->samples[0][file_offset+6]&0x0c) << 14)
 | 
			
		||||
		);
 | 
			
		||||
		new_file.execution_address = uint32_t(
 | 
			
		||||
			details->samples[0][file_offset+2] |
 | 
			
		||||
			(details->samples[0][file_offset+3] << 8) |
 | 
			
		||||
			((details->samples[0][file_offset+6]&0xc0) << 10)
 | 
			
		||||
		);
 | 
			
		||||
		if(names->samples[0][file_offset + 7] & 0x80) {
 | 
			
		||||
			// File is locked; it may not be altered or deleted.
 | 
			
		||||
			new_file.flags |= File::Flags::Locked;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
 | 
			
		||||
		auto data_length = long(
 | 
			
		||||
			details->samples[0][file_offset+4] |
 | 
			
		||||
			(details->samples[0][file_offset+5] << 8) |
 | 
			
		||||
			((details->samples[0][file_offset+6]&0x30) << 12)
 | 
			
		||||
		);
 | 
			
		||||
		int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
 | 
			
		||||
		new_file.data.reserve(size_t(data_length));
 | 
			
		||||
 | 
			
		||||
		if(start_sector < 2) continue;
 | 
			
		||||
		while(data_length > 0) {
 | 
			
		||||
			uint8_t sector = uint8_t(start_sector % 10);
 | 
			
		||||
			uint8_t track = uint8_t(start_sector / 10);
 | 
			
		||||
			start_sector++;
 | 
			
		||||
			const uint8_t sector = uint8_t(start_sector % 10);
 | 
			
		||||
			const uint8_t track = uint8_t(start_sector / 10);
 | 
			
		||||
			++start_sector;
 | 
			
		||||
 | 
			
		||||
			const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
 | 
			
		||||
			if(!next_sector) break;
 | 
			
		||||
 | 
			
		||||
			long length_from_sector = std::min(data_length, 256l);
 | 
			
		||||
			const long length_from_sector = std::min(data_length, 256l);
 | 
			
		||||
			new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
 | 
			
		||||
			data_length -= length_from_sector;
 | 
			
		||||
		}
 | 
			
		||||
@@ -133,7 +145,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Parse the root directory, at least.
 | 
			
		||||
	for(std::size_t file_offset = 0x005; file_offset < (catalogue->has_large_sectors ? 0x7d7 : 0x4cb); file_offset += 0x1a) {
 | 
			
		||||
	const std::size_t directory_extent = catalogue->has_large_sectors ? 0x7d7 : 0x4cb;
 | 
			
		||||
	for(std::size_t file_offset = 0x005; file_offset < directory_extent; file_offset += 0x1a) {
 | 
			
		||||
		// Obtain the name, which will be at most ten characters long, and will
 | 
			
		||||
		// be terminated by either a NULL character or a \r.
 | 
			
		||||
		char name[11]{};
 | 
			
		||||
@@ -190,11 +203,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
 | 
			
		||||
 | 
			
		||||
		new_file.data.reserve(size);
 | 
			
		||||
		while(new_file.data.size() < size) {
 | 
			
		||||
			const Storage::Encodings::MFM::Sector *const sector = parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
 | 
			
		||||
			const Storage::Encodings::MFM::Sector *const sector =
 | 
			
		||||
				parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
 | 
			
		||||
			if(!sector) break;
 | 
			
		||||
 | 
			
		||||
			const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
 | 
			
		||||
			new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
 | 
			
		||||
			new_file.data.insert(
 | 
			
		||||
				new_file.data.end(),
 | 
			
		||||
				sector->samples[0].begin(),
 | 
			
		||||
				sector->samples[0].begin() + ssize_t(length_from_sector)
 | 
			
		||||
			);
 | 
			
		||||
			++start_sector;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "Storage/Disk/Disk.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
 | 
			
		||||
@@ -27,7 +27,7 @@ struct Catalogue {
 | 
			
		||||
	} bootOption;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
 | 
			
		||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,28 +12,45 @@
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Numeric/StringSimilarity.hpp"
 | 
			
		||||
#include "Numeric/StringSimilarity.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Acorn;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
bool is_basic(const File &file) {
 | 
			
		||||
	std::size_t pointer = 0;
 | 
			
		||||
	const uint8_t *const data = file.data.data();
 | 
			
		||||
	const std::size_t data_size = file.data.size();
 | 
			
		||||
	while(true) {
 | 
			
		||||
		if(pointer >= data_size-1 || data[pointer] != 0x0d) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		if((data[pointer+1]&0x7f) == 0x7f) break;
 | 
			
		||||
		pointer += data[pointer+3];
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
		AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
		const auto &segments = cartridge->get_segments();
 | 
			
		||||
 | 
			
		||||
		// only one mapped item is allowed
 | 
			
		||||
		// Only one mapped item is allowed.
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// which must be 8 or 16 kb in size
 | 
			
		||||
		// Cartridges must be 8 or 16 kb in size.
 | 
			
		||||
		const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
			
		||||
		if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
 | 
			
		||||
 | 
			
		||||
		// is a copyright string present?
 | 
			
		||||
		// Check copyright string.
 | 
			
		||||
		const uint8_t copyright_offset = segment.data[7];
 | 
			
		||||
		if(
 | 
			
		||||
			segment.data[copyright_offset] != 0x00 ||
 | 
			
		||||
@@ -42,16 +59,16 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
			segment.data[copyright_offset+3] != 0x29
 | 
			
		||||
		) continue;
 | 
			
		||||
 | 
			
		||||
		// is the language entry point valid?
 | 
			
		||||
		// Check language entry point.
 | 
			
		||||
		if(!(
 | 
			
		||||
			(segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) ||
 | 
			
		||||
			(segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0)
 | 
			
		||||
			)) continue;
 | 
			
		||||
 | 
			
		||||
		// is the service entry point valid?
 | 
			
		||||
		// Check service entry point.
 | 
			
		||||
		if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue;
 | 
			
		||||
 | 
			
		||||
		// probability of a random binary blob that isn't an Acorn ROM proceeding to here:
 | 
			
		||||
		// Probability of a random binary blob that isn't an Acorn ROM proceeding to here:
 | 
			
		||||
		//		1/(2^32) *
 | 
			
		||||
		//		( ((2^24)-1)/(2^24)*(1/4)		+		1/(2^24)	) *
 | 
			
		||||
		//		1/4
 | 
			
		||||
@@ -62,45 +79,38 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
	return acorn_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
 | 
			
		||||
	auto target8bit = std::make_unique<ElectronTarget>();
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	auto targetElectron = std::make_unique<ElectronTarget>();
 | 
			
		||||
	auto targetBBC = std::make_unique<BBCMicroTarget>();
 | 
			
		||||
	auto targetArchimedes = std::make_unique<ArchimedesTarget>();
 | 
			
		||||
	int bbc_hits = 0;
 | 
			
		||||
	int electron_hits = 0;
 | 
			
		||||
	bool format_prefers_bbc = false;
 | 
			
		||||
 | 
			
		||||
	// Copy appropriate cartridges to the 8-bit target.
 | 
			
		||||
	target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
			
		||||
	// Copy appropriate cartridges to the 8-bit targets.
 | 
			
		||||
	targetElectron->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
			
		||||
	targetBBC->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
			
		||||
 | 
			
		||||
	// If there are any tapes, attempt to get data from the first.
 | 
			
		||||
	// If there are tapes, attempt to get data from the first.
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
 | 
			
		||||
		std::vector<File> files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
		auto serialiser = tape->serialiser();
 | 
			
		||||
		std::vector<File> files = GetFiles(*serialiser);
 | 
			
		||||
 | 
			
		||||
		// continue if there are any files
 | 
			
		||||
		// Continue only if there are any files.
 | 
			
		||||
		if(!files.empty()) {
 | 
			
		||||
			bool is_basic = true;
 | 
			
		||||
 | 
			
		||||
			// If a file is execute-only, that means *RUN.
 | 
			
		||||
			if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
 | 
			
		||||
 | 
			
		||||
			// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
 | 
			
		||||
			// so that's also justification to *RUN
 | 
			
		||||
			std::size_t pointer = 0;
 | 
			
		||||
			uint8_t *const data = &files.front().data[0];
 | 
			
		||||
			const std::size_t data_size = files.front().data.size();
 | 
			
		||||
			while(1) {
 | 
			
		||||
				if(pointer >= data_size-1 || data[pointer] != 13) {
 | 
			
		||||
					is_basic = false;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
				if((data[pointer+1]&0x7f) == 0x7f) break;
 | 
			
		||||
				pointer += data[pointer+3];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Inspect first file. If it's protected or doesn't look like BASIC
 | 
			
		||||
			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | 
			
		||||
			target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
 | 
			
		||||
			targetElectron->loading_command =
 | 
			
		||||
				(files.front().flags & File::Flags::ExecuteOnly) || !is_basic(files.front()) ? "*RUN\n" : "CHAIN\"\"\n";
 | 
			
		||||
			targetElectron->media.tapes = media.tapes;
 | 
			
		||||
 | 
			
		||||
			target8bit->media.tapes = media.tapes;
 | 
			
		||||
			// TODO: my BBC Micro doesn't yet support tapes; evaluate here in the future.
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -115,25 +125,50 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
 | 
			
		||||
		// 8-bit options: DFS and Hugo-style ADFS.
 | 
			
		||||
		if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
 | 
			
		||||
			// Accept the disk and determine whether DFS or ADFS ROMs are implied.
 | 
			
		||||
			// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
 | 
			
		||||
			target8bit->media.disks = media.disks;
 | 
			
		||||
			target8bit->has_dfs = bool(dfs_catalogue);
 | 
			
		||||
			target8bit->has_pres_adfs = bool(adfs_catalogue);
 | 
			
		||||
 | 
			
		||||
			// Electron: use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
 | 
			
		||||
			targetElectron->media.disks = media.disks;
 | 
			
		||||
			targetElectron->has_dfs = bool(dfs_catalogue);
 | 
			
		||||
			targetElectron->has_pres_adfs = bool(adfs_catalogue);
 | 
			
		||||
 | 
			
		||||
			// BBC: only the 1770 DFS is currently supported, so use that.
 | 
			
		||||
			targetBBC->media.disks = media.disks;
 | 
			
		||||
			targetBBC->has_1770dfs = bool(dfs_catalogue);
 | 
			
		||||
			targetBBC->has_adfs = bool(adfs_catalogue);
 | 
			
		||||
 | 
			
		||||
			// Check whether a simple shift+break will do for loading this disk.
 | 
			
		||||
			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
			
		||||
			const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
			
		||||
			if(bootOption != Catalogue::BootOption::None) {
 | 
			
		||||
				target8bit->should_shift_restart = true;
 | 
			
		||||
				targetBBC->should_shift_restart = targetElectron->should_shift_restart = true;
 | 
			
		||||
			} else {
 | 
			
		||||
				target8bit->loading_command = "*CAT\n";
 | 
			
		||||
				// Otherwise: if there's only one BASIC program then chain it.
 | 
			
		||||
				// Failing that, do a *CAT to be communicative.
 | 
			
		||||
 | 
			
		||||
				const File *sole_basic_file = nullptr;
 | 
			
		||||
				for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
 | 
			
		||||
					if(is_basic(file)) {
 | 
			
		||||
						if(!sole_basic_file) {
 | 
			
		||||
							sole_basic_file = &file;
 | 
			
		||||
						} else {
 | 
			
		||||
							sole_basic_file = nullptr;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				targetBBC->loading_command = targetElectron->loading_command =
 | 
			
		||||
					sole_basic_file ? "CHAIN \"" + sole_basic_file->name + "\"\n" : "*CAT\n";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Check whether adding the AP6 ROM is justified.
 | 
			
		||||
			// For now this is an incredibly dense text search;
 | 
			
		||||
			// if any of the commands that aren't usually present
 | 
			
		||||
			// on a stock Electron are here, add the AP6 ROM and
 | 
			
		||||
			// some sideways RAM such that the SR commands are useful.
 | 
			
		||||
			// Add a slight preference for the BBC over the Electron, all else being equal, if this is a DFS floppy.
 | 
			
		||||
			format_prefers_bbc = bool(dfs_catalogue);
 | 
			
		||||
 | 
			
		||||
			for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
 | 
			
		||||
				// Electron: check whether adding the AP6 ROM is justified.
 | 
			
		||||
				// For now this is an incredibly dense text search;
 | 
			
		||||
				// if any of the commands that aren't usually present
 | 
			
		||||
				// on a stock Electron are here, add the AP6 ROM and
 | 
			
		||||
				// some sideways RAM such that the SR commands are useful.
 | 
			
		||||
				for(const auto &command: {
 | 
			
		||||
					"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
 | 
			
		||||
					"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
 | 
			
		||||
@@ -141,10 +176,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
 | 
			
		||||
					"VERIFY", "ZERO"
 | 
			
		||||
				}) {
 | 
			
		||||
					if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
 | 
			
		||||
						target8bit->has_ap6_rom = true;
 | 
			
		||||
						target8bit->has_sideways_ram = true;
 | 
			
		||||
						targetElectron->has_ap6_rom = true;
 | 
			
		||||
						targetElectron->has_sideways_ram = true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Look for any 'BBC indicators', i.e. direct access to BBC-specific hardware.
 | 
			
		||||
				// Also currently a dense search.
 | 
			
		||||
 | 
			
		||||
				const auto hits = [&](const std::initializer_list<uint16_t> collection) {
 | 
			
		||||
					int hits = 0;
 | 
			
		||||
					for(const auto address: collection) {
 | 
			
		||||
						const uint8_t sta_address[3] = {
 | 
			
		||||
							0x8d, uint8_t(address & 0xff), uint8_t(address >> 8)
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						if(std::search(
 | 
			
		||||
							file.data.begin(), file.data.end(),
 | 
			
		||||
							std::begin(sta_address), std::end(sta_address)
 | 
			
		||||
						) != file.data.end()) {
 | 
			
		||||
							++hits;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						// I think I'll want std::ranges::contains_subrange if/when building for C++23.
 | 
			
		||||
					}
 | 
			
		||||
					return hits;
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				bbc_hits += hits({
 | 
			
		||||
					// The video control registers.
 | 
			
		||||
					0xfe20, 0xfe21,
 | 
			
		||||
 | 
			
		||||
					// The system VIA.
 | 
			
		||||
					0xfe40, 0xfe41, 0xfe42, 0xfe43, 0xfe44, 0xfe45, 0xfe46, 0xfe47,
 | 
			
		||||
					0xfe48, 0xfe49, 0xfe4a, 0xfe4b, 0xfe4c, 0xfe4d, 0xfe4e, 0xfe4f,
 | 
			
		||||
 | 
			
		||||
					// The user VIA.
 | 
			
		||||
					0xfe60, 0xfe61, 0xfe62, 0xfe63, 0xfe64, 0xfe65, 0xfe66, 0xfe67,
 | 
			
		||||
					0xfe68, 0xfe69, 0xfe6a, 0xfe6b, 0xfe6c, 0xfe6d, 0xfe6e, 0xfe6f,
 | 
			
		||||
				});
 | 
			
		||||
				// BASIC for "MODE7".
 | 
			
		||||
				static constexpr uint8_t mode7[] = {0xeb, 0x37};
 | 
			
		||||
				bbc_hits += std::search(
 | 
			
		||||
							file.data.begin(), file.data.end(),
 | 
			
		||||
							std::begin(mode7), std::end(mode7)
 | 
			
		||||
						) != file.data.end();
 | 
			
		||||
 | 
			
		||||
				electron_hits += hits({
 | 
			
		||||
					// ULA addresses that aren't also the BBC's CRTC.
 | 
			
		||||
					0xfe03, 0xfe04, 0xfe05,
 | 
			
		||||
					0xfe06, 0xfe07, 0xfe08,
 | 
			
		||||
					0xfe09, 0xfe0a, 0xfe0b,
 | 
			
		||||
					0xfe0c, 0xfe0d, 0xfe0e,
 | 
			
		||||
					0xfe0f,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		} else if(adfs_catalogue) {
 | 
			
		||||
			// Archimedes options, implicitly: ADFS, non-Hugo.
 | 
			
		||||
@@ -159,8 +244,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
 | 
			
		||||
				// Take whatever else comes with a preference for things that don't
 | 
			
		||||
				// have 'boot' or 'read' in them (the latter of which will tend to be
 | 
			
		||||
				// read_me or read_this or similar).
 | 
			
		||||
				constexpr char read[] = "read";
 | 
			
		||||
				constexpr char boot[] = "boot";
 | 
			
		||||
				static constexpr char read[] = "read";
 | 
			
		||||
				static constexpr char boot[] = "boot";
 | 
			
		||||
				const auto has = [&](const char *begin, const char *end) {
 | 
			
		||||
					return  std::search(
 | 
			
		||||
						file.name.begin(), file.name.end(),
 | 
			
		||||
@@ -189,28 +274,38 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
 | 
			
		||||
	// Enable the Acorn ADFS if a mass-storage device is attached;
 | 
			
		||||
	// unlike the Pres ADFS it retains SCSI logic.
 | 
			
		||||
	if(!media.mass_storage_devices.empty()) {
 | 
			
		||||
		target8bit->has_pres_adfs = false;	// To override a floppy selection, if one was made.
 | 
			
		||||
		target8bit->has_acorn_adfs = true;
 | 
			
		||||
		targetElectron->has_pres_adfs = false;	// To override a floppy selection, if one was made.
 | 
			
		||||
		targetElectron->has_acorn_adfs = true;
 | 
			
		||||
 | 
			
		||||
		// Assume some sort of later-era Acorn work is likely to happen;
 | 
			
		||||
		// so ensure *TYPE, etc are present.
 | 
			
		||||
		target8bit->has_ap6_rom = true;
 | 
			
		||||
		target8bit->has_sideways_ram = true;
 | 
			
		||||
		targetElectron->has_ap6_rom = true;
 | 
			
		||||
		targetElectron->has_sideways_ram = true;
 | 
			
		||||
 | 
			
		||||
		target8bit->media.mass_storage_devices = media.mass_storage_devices;
 | 
			
		||||
		targetElectron->media.mass_storage_devices = media.mass_storage_devices;
 | 
			
		||||
 | 
			
		||||
		// Check for a boot option.
 | 
			
		||||
		const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
 | 
			
		||||
		const auto sector = targetElectron->media.mass_storage_devices.front()->get_block(1);
 | 
			
		||||
		if(sector[0xfd]) {
 | 
			
		||||
			target8bit->should_shift_restart = true;
 | 
			
		||||
			targetElectron->should_shift_restart = true;
 | 
			
		||||
		} else {
 | 
			
		||||
			target8bit->loading_command = "*CAT\n";
 | 
			
		||||
			targetElectron->loading_command = "*CAT\n";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
	if(!target8bit->media.empty()) {
 | 
			
		||||
		targets.push_back(std::move(target8bit));
 | 
			
		||||
	if(!targetElectron->media.empty() && !targetBBC->media.empty()) {
 | 
			
		||||
		if(bbc_hits > electron_hits || (bbc_hits == electron_hits && format_prefers_bbc)) {
 | 
			
		||||
			targets.push_back(std::move(targetBBC));
 | 
			
		||||
		} else {
 | 
			
		||||
			targets.push_back(std::move(targetElectron));
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if(!targetElectron->media.empty()) {
 | 
			
		||||
			targets.push_back(std::move(targetElectron));
 | 
			
		||||
		} else if(!targetBBC->media.empty()) {
 | 
			
		||||
			targets.push_back(std::move(targetBBC));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if(!targetArchimedes->media.empty()) {
 | 
			
		||||
		targets.push_back(std::move(targetArchimedes));
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,70 +10,68 @@
 | 
			
		||||
 | 
			
		||||
#include <deque>
 | 
			
		||||
 | 
			
		||||
#include "../../../Numeric/CRC.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
 | 
			
		||||
#include "Numeric/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(
 | 
			
		||||
	Storage::Tape::TapeSerialiser &serialiser,
 | 
			
		||||
	Storage::Tape::Acorn::Parser &parser
 | 
			
		||||
) {
 | 
			
		||||
	auto new_chunk = std::make_unique<File::Chunk>();
 | 
			
		||||
	int shift_register = 0;
 | 
			
		||||
 | 
			
		||||
	// TODO: move this into the parser
 | 
			
		||||
	const auto shift = [&] {
 | 
			
		||||
		shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9);
 | 
			
		||||
	const auto find = [&](int target) {
 | 
			
		||||
		while(!serialiser.is_at_end() && (shift_register != target)) {
 | 
			
		||||
			shift_register = (shift_register >> 1) | (parser.get_next_bit(serialiser) << 9);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// find next area of high tone
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x3ff)) {
 | 
			
		||||
		shift();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// find next 0x2a (swallowing stop bit)
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x254)) {
 | 
			
		||||
		shift();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Find first sync byte that follows high tone.
 | 
			
		||||
	find(0x3ff);
 | 
			
		||||
	find(0x254);	// i.e. 0x2a wrapped in a 1 start bit and a 0 stop bit.
 | 
			
		||||
	parser.reset_crc();
 | 
			
		||||
	parser.reset_error_flag();
 | 
			
		||||
 | 
			
		||||
	// read out name
 | 
			
		||||
	char name[11];
 | 
			
		||||
	// Read name.
 | 
			
		||||
	char name[11]{};
 | 
			
		||||
	std::size_t name_ptr = 0;
 | 
			
		||||
	while(!tape->is_at_end() && name_ptr < sizeof(name)) {
 | 
			
		||||
		name[name_ptr] = char(parser.get_next_byte(tape));
 | 
			
		||||
	while(!serialiser.is_at_end() && name_ptr < sizeof(name)) {
 | 
			
		||||
		name[name_ptr] = char(parser.get_next_byte(serialiser));
 | 
			
		||||
		if(!name[name_ptr]) break;
 | 
			
		||||
		++name_ptr;
 | 
			
		||||
	}
 | 
			
		||||
	name[sizeof(name)-1] = '\0';
 | 
			
		||||
	new_chunk->name = name;
 | 
			
		||||
 | 
			
		||||
	// addresses
 | 
			
		||||
	new_chunk->load_address = uint32_t(parser.get_next_word(tape));
 | 
			
		||||
	new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
 | 
			
		||||
	new_chunk->block_number = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
	new_chunk->block_length = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
	new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
 | 
			
		||||
	new_chunk->next_address = uint32_t(parser.get_next_word(tape));
 | 
			
		||||
	// Read rest of header fields.
 | 
			
		||||
	new_chunk->load_address = uint32_t(parser.get_next_word(serialiser));
 | 
			
		||||
	new_chunk->execution_address = uint32_t(parser.get_next_word(serialiser));
 | 
			
		||||
	new_chunk->block_number = uint16_t(parser.get_next_short(serialiser));
 | 
			
		||||
	new_chunk->block_length = uint16_t(parser.get_next_short(serialiser));
 | 
			
		||||
	new_chunk->block_flag = uint8_t(parser.get_next_byte(serialiser));
 | 
			
		||||
	new_chunk->next_address = uint32_t(parser.get_next_word(serialiser));
 | 
			
		||||
 | 
			
		||||
	uint16_t calculated_header_crc = parser.get_crc();
 | 
			
		||||
	uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
	stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
 | 
			
		||||
	new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
 | 
			
		||||
	const auto matched_crc = [&]() {
 | 
			
		||||
		const uint16_t calculated_crc = parser.get_crc();
 | 
			
		||||
		uint16_t stored_crc = uint16_t(parser.get_next_short(serialiser));
 | 
			
		||||
		stored_crc = uint16_t((stored_crc >> 8) | (stored_crc << 8));
 | 
			
		||||
		return stored_crc == calculated_crc;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	new_chunk->header_crc_matched = matched_crc();
 | 
			
		||||
 | 
			
		||||
	if(!new_chunk->header_crc_matched) return nullptr;
 | 
			
		||||
 | 
			
		||||
	parser.reset_crc();
 | 
			
		||||
	new_chunk->data.reserve(new_chunk->block_length);
 | 
			
		||||
	for(int c = 0; c < new_chunk->block_length; c++) {
 | 
			
		||||
		new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Bit 6 of the block flag means 'empty block'; allow it to override declared block length.
 | 
			
		||||
	if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
 | 
			
		||||
		uint16_t calculated_data_crc = parser.get_crc();
 | 
			
		||||
		uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
		stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
 | 
			
		||||
		new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
 | 
			
		||||
		parser.reset_crc();
 | 
			
		||||
		new_chunk->data.reserve(new_chunk->block_length);
 | 
			
		||||
		for(int c = 0; c < new_chunk->block_length; c++) {
 | 
			
		||||
			new_chunk->data.push_back(uint8_t(parser.get_next_byte(serialiser)));
 | 
			
		||||
		}
 | 
			
		||||
		new_chunk->data_crc_matched = matched_crc();
 | 
			
		||||
	} else {
 | 
			
		||||
		new_chunk->data_crc_matched = true;
 | 
			
		||||
	}
 | 
			
		||||
@@ -82,67 +80,62 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
 | 
			
		||||
	// find next chunk with a block number of 0
 | 
			
		||||
	while(chunks.size() && chunks.front().block_number) {
 | 
			
		||||
	// Find next chunk with a block number of 0.
 | 
			
		||||
	while(!chunks.empty() && chunks.front().block_number) {
 | 
			
		||||
		chunks.pop_front();
 | 
			
		||||
	}
 | 
			
		||||
	if(chunks.empty()) return nullptr;
 | 
			
		||||
 | 
			
		||||
	if(!chunks.size()) return nullptr;
 | 
			
		||||
 | 
			
		||||
	// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
 | 
			
		||||
	// Accumulate sequential blocks until end-of-file bit is set.
 | 
			
		||||
	auto file = std::make_unique<File>();
 | 
			
		||||
 | 
			
		||||
	uint16_t block_number = 0;
 | 
			
		||||
 | 
			
		||||
	while(chunks.size()) {
 | 
			
		||||
	while(!chunks.empty()) {
 | 
			
		||||
		if(chunks.front().block_number != block_number) return nullptr;
 | 
			
		||||
 | 
			
		||||
		bool was_last = chunks.front().block_flag & 0x80;
 | 
			
		||||
		const bool was_last = chunks.front().block_flag & 0x80;
 | 
			
		||||
		file->chunks.push_back(chunks.front());
 | 
			
		||||
		chunks.pop_front();
 | 
			
		||||
		block_number++;
 | 
			
		||||
		++block_number;
 | 
			
		||||
 | 
			
		||||
		if(was_last) break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// accumulate total data, copy flags appropriately
 | 
			
		||||
	// Grab metadata flags.
 | 
			
		||||
	file->name = file->chunks.front().name;
 | 
			
		||||
	file->load_address = file->chunks.front().load_address;
 | 
			
		||||
	file->execution_address = file->chunks.front().execution_address;
 | 
			
		||||
	// I think the final chunk's flags are the ones that count; TODO: check.
 | 
			
		||||
	if(file->chunks.back().block_flag & 0x01) {
 | 
			
		||||
		// File is locked, which in more generalised terms means it is
 | 
			
		||||
		// for execution only.
 | 
			
		||||
		// File is locked i.e. for execution only.
 | 
			
		||||
		file->flags |= File::Flags::ExecuteOnly;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// copy all data into a single big block
 | 
			
		||||
	for(File::Chunk chunk : file->chunks) {
 | 
			
		||||
	// Copy data into a single big block.
 | 
			
		||||
	file->data.reserve(file->chunks.size() * 256);
 | 
			
		||||
	for(auto &chunk : file->chunks) {
 | 
			
		||||
		file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return file;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
std::vector<File> Analyser::Static::Acorn::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
	Storage::Tape::Acorn::Parser parser;
 | 
			
		||||
 | 
			
		||||
	// populate chunk list
 | 
			
		||||
	// Read all chunks.
 | 
			
		||||
	std::deque<File::Chunk> chunk_list;
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
		const std::unique_ptr<File::Chunk> chunk = GetNextChunk(serialiser, parser);
 | 
			
		||||
		if(chunk) {
 | 
			
		||||
			chunk_list.push_back(*chunk);
 | 
			
		||||
			chunk_list.push_back(std::move(*chunk));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// decompose into file list
 | 
			
		||||
	// Convert to files.
 | 
			
		||||
	std::vector<File> file_list;
 | 
			
		||||
 | 
			
		||||
	while(chunk_list.size()) {
 | 
			
		||||
		std::unique_ptr<File> next_file = GetNextFile(chunk_list);
 | 
			
		||||
	while(!chunk_list.empty()) {
 | 
			
		||||
		const std::unique_ptr<File> next_file = GetNextFile(chunk_list);
 | 
			
		||||
		if(next_file) {
 | 
			
		||||
			file_list.push_back(*next_file);
 | 
			
		||||
			file_list.push_back(std::move(*next_file));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,13 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
@@ -23,14 +24,40 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str
 | 
			
		||||
	bool should_shift_restart = false;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	ElectronTarget() : Analyser::Static::Target(Machine::Electron) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(has_pres_adfs);
 | 
			
		||||
			DeclareField(has_acorn_adfs);
 | 
			
		||||
			DeclareField(has_dfs);
 | 
			
		||||
			DeclareField(has_ap6_rom);
 | 
			
		||||
			DeclareField(has_sideways_ram);
 | 
			
		||||
		}
 | 
			
		||||
	ElectronTarget() : Analyser::Static::Target(Machine::Electron) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<ElectronTarget>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_pres_adfs);
 | 
			
		||||
		DeclareField(has_acorn_adfs);
 | 
			
		||||
		DeclareField(has_dfs);
 | 
			
		||||
		DeclareField(has_ap6_rom);
 | 
			
		||||
		DeclareField(has_sideways_ram);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<BBCMicroTarget> {
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
	bool should_shift_restart = false;
 | 
			
		||||
 | 
			
		||||
	bool has_1770dfs = false;
 | 
			
		||||
	bool has_adfs = false;
 | 
			
		||||
	bool has_sideways_ram = true;
 | 
			
		||||
 | 
			
		||||
	ReflectableEnum(TubeProcessor, None, WDC65C02);
 | 
			
		||||
	TubeProcessor tube_processor = TubeProcessor::None;
 | 
			
		||||
 | 
			
		||||
	BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<BBCMicroTarget>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_1770dfs);
 | 
			
		||||
		DeclareField(has_adfs);
 | 
			
		||||
		DeclareField(has_sideways_ram);
 | 
			
		||||
		AnnounceEnum(TubeProcessor);
 | 
			
		||||
		DeclareField(tube_processor);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -38,6 +65,10 @@ struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::S
 | 
			
		||||
	std::string main_program;
 | 
			
		||||
 | 
			
		||||
	ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<ArchimedesTarget>;
 | 
			
		||||
	void declare_fields() {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,14 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
	if(media.disks.empty() && !is_confident) return {};
 | 
			
		||||
 | 
			
		||||
	// As there is at least one usable media image, wave it through.
 | 
			
		||||
	Analyser::Static::TargetList targets;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Amiga {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Amiga {
 | 
			
		||||
 | 
			
		||||
@@ -28,13 +28,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
 | 
			
		||||
	FastRAM fast_ram = FastRAM::EightMegabytes;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Amiga) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(fast_ram);
 | 
			
		||||
			DeclareField(chip_ram);
 | 
			
		||||
			AnnounceEnum(FastRAM);
 | 
			
		||||
			AnnounceEnum(ChipRAM);
 | 
			
		||||
		}
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Amiga) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(fast_ram);
 | 
			
		||||
		DeclareField(chip_ram);
 | 
			
		||||
		AnnounceEnum(FastRAM);
 | 
			
		||||
		AnnounceEnum(ChipRAM);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,17 +8,26 @@
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
#include "Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
std::string rtrimmed(const std::string &input) {
 | 
			
		||||
	auto trimmed = input;
 | 
			
		||||
	trimmed.erase(std::find_if(trimmed.rbegin(), trimmed.rend(), [](const char ch) {
 | 
			
		||||
		return !std::isspace(ch);
 | 
			
		||||
	}).base(), trimmed.end());
 | 
			
		||||
	return trimmed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool strcmp_insensitive(const char *a, const char *b) {
 | 
			
		||||
	if(std::strlen(a) != std::strlen(b)) return false;
 | 
			
		||||
	while(*a) {
 | 
			
		||||
@@ -63,8 +72,8 @@ std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
 | 
			
		||||
 | 
			
		||||
void InspectCatalogue(
 | 
			
		||||
	const Storage::Disk::CPM::Catalogue &catalogue,
 | 
			
		||||
	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
			
		||||
 | 
			
		||||
	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
 | 
			
		||||
) {
 | 
			
		||||
	std::vector<const Storage::Disk::CPM::File *> candidate_files;
 | 
			
		||||
	candidate_files.reserve(catalogue.files.size());
 | 
			
		||||
	for(const auto &file : catalogue.files) {
 | 
			
		||||
@@ -104,61 +113,91 @@ void InspectCatalogue(
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
 | 
			
		||||
	// that AMSDOS allows to be omitted, pick that one.
 | 
			
		||||
	int basic_files = 0;
 | 
			
		||||
	int implicit_suffixed_files = 0;
 | 
			
		||||
	const auto run_name = [&]() -> std::optional<std::string> {
 | 
			
		||||
		// Collect:
 | 
			
		||||
		//
 | 
			
		||||
		//	1. a set of all files that can be run without specifying an extension plus their appearance counts;
 | 
			
		||||
		//	2. a set of all BASIC file names.
 | 
			
		||||
		std::unordered_map<std::string, int> candidates;
 | 
			
		||||
		std::unordered_set<std::string> basic_names;
 | 
			
		||||
		for(std::size_t c = 0; c < candidate_files.size(); c++) {
 | 
			
		||||
			// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
 | 
			
		||||
			if(
 | 
			
		||||
				(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ") ||
 | 
			
		||||
				!is_implied_extension(candidate_files[c]->type)
 | 
			
		||||
			) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	std::size_t last_basic_file = 0;
 | 
			
		||||
	std::size_t last_implicit_suffixed_file = 0;
 | 
			
		||||
 | 
			
		||||
	for(std::size_t c = 0; c < candidate_files.size(); c++) {
 | 
			
		||||
		// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
 | 
			
		||||
		if(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ")
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		// Check for whether this is [potentially] BASIC.
 | 
			
		||||
		if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
 | 
			
		||||
			basic_files++;
 | 
			
		||||
			last_basic_file = c;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check suffix for emptiness.
 | 
			
		||||
		if(is_implied_extension(candidate_files[c]->type)) {
 | 
			
		||||
			implicit_suffixed_files++;
 | 
			
		||||
			last_implicit_suffixed_file = c;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if(basic_files == 1 || implicit_suffixed_files == 1) {
 | 
			
		||||
		std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
 | 
			
		||||
		target->loading_command = RunCommandFor(*candidate_files[selected_file]);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// One more guess: if only one remaining candidate file has a different name than the others,
 | 
			
		||||
	// assume it is intended to stand out.
 | 
			
		||||
	std::map<std::string, int> name_counts;
 | 
			
		||||
	std::map<std::string, std::size_t> indices_by_name;
 | 
			
		||||
	std::size_t index = 0;
 | 
			
		||||
	for(const auto &file : candidate_files) {
 | 
			
		||||
		name_counts[file->name]++;
 | 
			
		||||
		indices_by_name[file->name] = index;
 | 
			
		||||
		index++;
 | 
			
		||||
	}
 | 
			
		||||
	if(name_counts.size() == 2) {
 | 
			
		||||
		for(const auto &pair : name_counts) {
 | 
			
		||||
			if(pair.second == 1) {
 | 
			
		||||
				target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
 | 
			
		||||
				return;
 | 
			
		||||
			++candidates[candidate_files[c]->name];
 | 
			
		||||
			if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
 | 
			
		||||
				basic_names.insert(candidate_files[c]->name);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Desperation.
 | 
			
		||||
	target->loading_command = "cat\n";
 | 
			
		||||
		// Only one candidate total.
 | 
			
		||||
		if(candidates.size() == 1) {
 | 
			
		||||
			return candidates.begin()->first;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Only one BASIC candidate.
 | 
			
		||||
		if(basic_names.size() == 1) {
 | 
			
		||||
			return *basic_names.begin();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Exactly two candidate names, but only one is a unique name.
 | 
			
		||||
		if(candidates.size() == 2) {
 | 
			
		||||
			const auto item1 = candidates.begin();
 | 
			
		||||
			const auto item2 = std::next(item1);
 | 
			
		||||
 | 
			
		||||
			if(item1->second == 1 && item2->second != 1) {
 | 
			
		||||
				return item1->first;
 | 
			
		||||
			}
 | 
			
		||||
			if(item2->second == 1 && item1->second != 1) {
 | 
			
		||||
				return item2->first;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Remove from candidates anything that is just a suffixed version of
 | 
			
		||||
		// another name, as long as the other name is three or more characters.
 | 
			
		||||
		std::vector<std::string> to_remove;
 | 
			
		||||
		for(const auto &lhs: candidates) {
 | 
			
		||||
			const auto trimmed = rtrimmed(lhs.first);
 | 
			
		||||
			if(trimmed.size() < 3) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for(const auto &rhs: candidates) {
 | 
			
		||||
				if(lhs.first == rhs.first) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(rhs.first.find(trimmed) == 0) {
 | 
			
		||||
					to_remove.push_back(rhs.first);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for(const auto &candidate: to_remove) {
 | 
			
		||||
			candidates.erase(candidate);
 | 
			
		||||
		}
 | 
			
		||||
		if(candidates.size() == 1) {
 | 
			
		||||
			return candidates.begin()->first;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {};
 | 
			
		||||
	} ();
 | 
			
		||||
 | 
			
		||||
	if(run_name.has_value()) {
 | 
			
		||||
		target->loading_command = "run\"" + rtrimmed(*run_name) + "\n";
 | 
			
		||||
	} else {
 | 
			
		||||
		target->loading_command = "cat\n";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
			
		||||
bool CheckBootSector(
 | 
			
		||||
	const std::shared_ptr<Storage::Disk::Disk> &disk,
 | 
			
		||||
	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
 | 
			
		||||
) {
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
 | 
			
		||||
	if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
 | 
			
		||||
@@ -182,7 +221,7 @@ bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
	// Limited sophistication here; look for a CPC-style file header, that is
 | 
			
		||||
	// any Spectrum-esque block with a synchronisation character of 0x2c.
 | 
			
		||||
	//
 | 
			
		||||
@@ -191,7 +230,7 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	Parser parser(Parser::MachineType::AmstradCPC);
 | 
			
		||||
 | 
			
		||||
	while(true) {
 | 
			
		||||
		const auto block = parser.find_block(tape);
 | 
			
		||||
		const auto block = parser.find_block(serialiser);
 | 
			
		||||
		if(!block) break;
 | 
			
		||||
 | 
			
		||||
		if(block->type == 0x2c) {
 | 
			
		||||
@@ -204,7 +243,12 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
 | 
			
		||||
} // namespace
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
@@ -214,7 +258,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		bool has_cpc_tape = false;
 | 
			
		||||
		for(auto &tape: media.tapes) {
 | 
			
		||||
			has_cpc_tape |= IsAmstradTape(tape);
 | 
			
		||||
			const auto serialiser = tape->serialiser();
 | 
			
		||||
			has_cpc_tape |= IsAmstradTape(*serialiser);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(has_cpc_tape) {
 | 
			
		||||
@@ -233,7 +278,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
 | 
			
		||||
 | 
			
		||||
		for(auto &disk: media.disks) {
 | 
			
		||||
			// Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk.
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue =
 | 
			
		||||
				Storage::Disk::CPM::GetCatalogue(disk, data_format, false);
 | 
			
		||||
			if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) {
 | 
			
		||||
				InspectCatalogue(*data_catalogue, target);
 | 
			
		||||
				target->media.disks.push_back(disk);
 | 
			
		||||
@@ -247,7 +293,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Failing that check for a system catalogue.
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue =
 | 
			
		||||
				Storage::Disk::CPM::GetCatalogue(disk, system_format, false);
 | 
			
		||||
			if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) {
 | 
			
		||||
				InspectCatalogue(*system_catalogue, target);
 | 
			
		||||
				target->media.disks.push_back(disk);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AmstradCPC {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AmstradCPC {
 | 
			
		||||
@@ -26,13 +26,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	// This is used internally for testing; it therefore isn't exposed reflectively.
 | 
			
		||||
	bool catch_ssm_codes = false;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AmstradCPC) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			DeclareField(crtc_type);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
			AnnounceEnum(CRTCType);
 | 
			
		||||
		}
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AmstradCPC) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(crtc_type);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(CRTCType);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,12 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->media = media;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleII {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleII {
 | 
			
		||||
 | 
			
		||||
@@ -36,21 +36,23 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	SCSIController scsi_controller = SCSIController::None;
 | 
			
		||||
	bool has_mockingboard = true;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleII) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			DeclareField(disk_controller);
 | 
			
		||||
			DeclareField(scsi_controller);
 | 
			
		||||
			DeclareField(has_mockingboard);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleII) {}
 | 
			
		||||
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
			AnnounceEnum(DiskController);
 | 
			
		||||
			AnnounceEnum(SCSIController);
 | 
			
		||||
		}
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(disk_controller);
 | 
			
		||||
		DeclareField(scsi_controller);
 | 
			
		||||
		DeclareField(has_mockingboard);
 | 
			
		||||
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(DiskController);
 | 
			
		||||
		AnnounceEnum(SCSIController);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr bool is_iie(Target::Model model) {
 | 
			
		||||
constexpr bool is_iie(const Target::Model model) {
 | 
			
		||||
	return model == Target::Model::IIe || model == Target::Model::EnhancedIIe;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,12 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->media = media;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleIIgs {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleIIgs {
 | 
			
		||||
 | 
			
		||||
@@ -29,13 +29,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	Model model = Model::ROM01;
 | 
			
		||||
	MemoryModel memory_model = MemoryModel::EightMB;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleIIgs) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			DeclareField(memory_model);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
			AnnounceEnum(MemoryModel);
 | 
			
		||||
		}
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleIIgs) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(memory_model);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(MemoryModel);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
#include "Analyser/Static/Disassembler/6502.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Atari2600;
 | 
			
		||||
using Target = Analyser::Static::Atari2600::Target;
 | 
			
		||||
@@ -33,11 +33,13 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
 | 
			
		||||
	// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 
			
		||||
	// large amounts of memory.
 | 
			
		||||
	bool has_wide_area_store = false;
 | 
			
		||||
	for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
 | 
			
		||||
	for(const auto &entry : high_location_disassembly.instructions_by_address) {
 | 
			
		||||
		using Instruction = Analyser::Static::MOS6502::Instruction;
 | 
			
		||||
		if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
 | 
			
		||||
			has_wide_area_store |=
 | 
			
		||||
				entry.second.addressing_mode == Instruction::Indirect ||
 | 
			
		||||
				entry.second.addressing_mode == Instruction::IndexedIndirectX ||
 | 
			
		||||
				entry.second.addressing_mode == Instruction::IndirectIndexedY;
 | 
			
		||||
 | 
			
		||||
			if(has_wide_area_store) break;
 | 
			
		||||
		}
 | 
			
		||||
@@ -50,13 +52,21 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
 | 
			
		||||
	if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
static void DeterminePagingFor8kCartridge(
 | 
			
		||||
	Target &target,
 | 
			
		||||
	const Storage::Cartridge::Cartridge::Segment &segment,
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly
 | 
			
		||||
) {
 | 
			
		||||
	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | 
			
		||||
	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | 
			
		||||
	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
 | 
			
		||||
	if(
 | 
			
		||||
		segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
 | 
			
		||||
		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
 | 
			
		||||
		segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 &&
 | 
			
		||||
		segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
 | 
			
		||||
		(
 | 
			
		||||
			segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 ||
 | 
			
		||||
			segment.data[8190] != 0x00 || segment.data[8188] != 0x00
 | 
			
		||||
		) &&
 | 
			
		||||
		segment.data[0] == 0x78
 | 
			
		||||
	) {
 | 
			
		||||
		target.paging_model = Target::PagingModel::ActivisionStack;
 | 
			
		||||
@@ -88,7 +98,11 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
 | 
			
		||||
	else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
static void DeterminePagingFor16kCartridge(
 | 
			
		||||
	Target &target,
 | 
			
		||||
	const Storage::Cartridge::Cartridge::Segment &,
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly
 | 
			
		||||
) {
 | 
			
		||||
	// Make an assumption that this is the Atari paging model.
 | 
			
		||||
	target.paging_model = Target::PagingModel::Atari16k;
 | 
			
		||||
 | 
			
		||||
@@ -108,7 +122,11 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri
 | 
			
		||||
	if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
static void DeterminePagingFor64kCartridge(
 | 
			
		||||
	Target &target,
 | 
			
		||||
	const Storage::Cartridge::Cartridge::Segment &,
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly
 | 
			
		||||
) {
 | 
			
		||||
	// Make an assumption that this is a Tigervision if there is a write to 3F.
 | 
			
		||||
	target.paging_model =
 | 
			
		||||
		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
 | 
			
		||||
@@ -121,8 +139,12 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
 | 
			
		||||
	const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
 | 
			
		||||
	const auto word = [](const uint8_t low, const uint8_t high) {
 | 
			
		||||
		return uint16_t(low | (high << 8));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const auto entry_address = word(segment.data[segment.data.size() - 4], segment.data[segment.data.size() - 3]);
 | 
			
		||||
	const auto break_address = word(segment.data[segment.data.size() - 2], segment.data[segment.data.size() - 1]);
 | 
			
		||||
 | 
			
		||||
	std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
 | 
			
		||||
		if(!(address & 0x1000)) return size_t(-1);
 | 
			
		||||
@@ -130,27 +152,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
 | 
			
		||||
	Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
			
		||||
	const auto disassembly =
 | 
			
		||||
		Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
			
		||||
 | 
			
		||||
	switch(segment.data.size()) {
 | 
			
		||||
		case 8192:
 | 
			
		||||
			DeterminePagingFor8kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		case 10495:
 | 
			
		||||
			target.paging_model = Target::PagingModel::Pitfall2;
 | 
			
		||||
		break;
 | 
			
		||||
		case 12288:
 | 
			
		||||
			target.paging_model = Target::PagingModel::CBSRamPlus;
 | 
			
		||||
		break;
 | 
			
		||||
		case 16384:
 | 
			
		||||
			DeterminePagingFor16kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		case 32768:
 | 
			
		||||
			target.paging_model = Target::PagingModel::Atari32k;
 | 
			
		||||
		break;
 | 
			
		||||
		case 65536:
 | 
			
		||||
			DeterminePagingFor64kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		case 8192:		DeterminePagingFor8kCartridge(target, segment, disassembly);	break;
 | 
			
		||||
		case 10495:		target.paging_model = Target::PagingModel::Pitfall2;			break;
 | 
			
		||||
		case 12288:		target.paging_model = Target::PagingModel::CBSRamPlus;			break;
 | 
			
		||||
		case 16384:		DeterminePagingFor16kCartridge(target, segment, disassembly);	break;
 | 
			
		||||
		case 32768:		target.paging_model = Target::PagingModel::Atari32k;			break;
 | 
			
		||||
		case 65536:		DeterminePagingFor64kCartridge(target, segment, disassembly);	break;
 | 
			
		||||
		default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
@@ -177,7 +188,12 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// TODO: sanity checking; is this image really for an Atari 2600?
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Atari2600 {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Atari2600 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,12 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AtariST {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AtariST {
 | 
			
		||||
 | 
			
		||||
@@ -20,11 +20,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
		FourMegabytes);
 | 
			
		||||
	MemorySize memory_size = MemorySize::OneMegabyte;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AtariST) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(memory_size);
 | 
			
		||||
			AnnounceEnum(MemorySize);
 | 
			
		||||
		}
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AtariST) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(memory_size);
 | 
			
		||||
		AnnounceEnum(MemorySize);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
		ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
@@ -52,11 +52,20 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
	return coleco_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	const bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
	auto target = std::make_unique<Target>(Machine::ColecoVision);
 | 
			
		||||
	target->confidence = 1.0f - 1.0f / 32768.0f;
 | 
			
		||||
	target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
 | 
			
		||||
	if(is_confident) {
 | 
			
		||||
		target->media = media;
 | 
			
		||||
	} else {
 | 
			
		||||
		target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
 | 
			
		||||
	}
 | 
			
		||||
	if(!target->media.empty())
 | 
			
		||||
		targets.push_back(std::move(target));
 | 
			
		||||
	return targets;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Coleco {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,166 +7,168 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
			
		||||
#include "../../../Storage/Data/Commodore.hpp"
 | 
			
		||||
#include "Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
			
		||||
#include "Storage/Data/Commodore.hpp"
 | 
			
		||||
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
			
		||||
	public:
 | 
			
		||||
		CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
 | 
			
		||||
			emplace_drive(4000000, 300, 2);
 | 
			
		||||
			set_drive(1);
 | 
			
		||||
			get_drive().set_motor_on(true);
 | 
			
		||||
		}
 | 
			
		||||
public:
 | 
			
		||||
	CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
 | 
			
		||||
		emplace_drive(4000000, 300, 2);
 | 
			
		||||
		set_drive(1);
 | 
			
		||||
		get_drive().set_motor_on(true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		struct Sector {
 | 
			
		||||
			uint8_t sector, track;
 | 
			
		||||
			std::array<uint8_t, 256> data;
 | 
			
		||||
			bool header_checksum_matched;
 | 
			
		||||
			bool data_checksum_matched;
 | 
			
		||||
		};
 | 
			
		||||
	struct Sector {
 | 
			
		||||
		uint8_t sector, track;
 | 
			
		||||
		std::array<uint8_t, 256> data;
 | 
			
		||||
		bool header_checksum_matched;
 | 
			
		||||
		bool data_checksum_matched;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Attempts to read the sector located at @c track and @c sector.
 | 
			
		||||
	/*!
 | 
			
		||||
		Attempts to read the sector located at @c track and @c sector.
 | 
			
		||||
 | 
			
		||||
			@returns a sector if one was found; @c nullptr otherwise.
 | 
			
		||||
		*/
 | 
			
		||||
		std::shared_ptr<Sector> sector(uint8_t track, uint8_t sector) {
 | 
			
		||||
			int difference = int(track) - int(track_);
 | 
			
		||||
			track_ = track;
 | 
			
		||||
		@returns a sector if one was found; @c nullptr otherwise.
 | 
			
		||||
	*/
 | 
			
		||||
	const Sector *sector(const uint8_t track, const uint8_t sector) {
 | 
			
		||||
		int difference = int(track) - int(track_);
 | 
			
		||||
		track_ = track;
 | 
			
		||||
 | 
			
		||||
			if(difference) {
 | 
			
		||||
				int direction = difference < 0 ? -1 : 1;
 | 
			
		||||
				difference *= direction;
 | 
			
		||||
		if(difference) {
 | 
			
		||||
			const int direction = difference < 0 ? -1 : 1;
 | 
			
		||||
			difference *= direction;
 | 
			
		||||
 | 
			
		||||
				for(int c = 0; c < difference; c++) {
 | 
			
		||||
					get_drive().step(Storage::Disk::HeadPosition(direction));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				unsigned int zone = 3;
 | 
			
		||||
				if(track >= 18) zone = 2;
 | 
			
		||||
				else if(track >= 25) zone = 1;
 | 
			
		||||
				else if(track >= 31) zone = 0;
 | 
			
		||||
				set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
 | 
			
		||||
			for(int c = 0; c < difference; c++) {
 | 
			
		||||
				get_drive().step(Storage::Disk::HeadPosition(direction));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return get_sector(sector);
 | 
			
		||||
			unsigned int zone = 3;
 | 
			
		||||
			if(track >= 18) zone = 2;
 | 
			
		||||
			else if(track >= 25) zone = 1;
 | 
			
		||||
			else if(track >= 31) zone = 0;
 | 
			
		||||
			set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
			get_drive().set_disk(disk);
 | 
			
		||||
		return get_sector(sector);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
		get_drive().set_disk(disk);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	unsigned int shift_register_;
 | 
			
		||||
	int index_count_;
 | 
			
		||||
	int bit_count_;
 | 
			
		||||
	uint8_t track_;
 | 
			
		||||
	std::unordered_map<uint16_t, std::unique_ptr<Sector>> sector_cache_;
 | 
			
		||||
 | 
			
		||||
	void process_input_bit(const int value) override {
 | 
			
		||||
		shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
 | 
			
		||||
		bit_count_++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsigned int proceed_to_next_block(const int max_index_count) {
 | 
			
		||||
		// find GCR lead-in
 | 
			
		||||
		proceed_to_shift_value(0x3ff);
 | 
			
		||||
		if(shift_register_ != 0x3ff) return 0xff;
 | 
			
		||||
 | 
			
		||||
		// find end of lead-in
 | 
			
		||||
		while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
 | 
			
		||||
			run_for(Cycles(1));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		unsigned int shift_register_;
 | 
			
		||||
		int index_count_;
 | 
			
		||||
		int bit_count_;
 | 
			
		||||
		uint8_t track_;
 | 
			
		||||
		std::shared_ptr<Sector> sector_cache_[65536];
 | 
			
		||||
 | 
			
		||||
		void process_input_bit(int value) {
 | 
			
		||||
			shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
 | 
			
		||||
			bit_count_++;
 | 
			
		||||
		// continue for a further nine bits
 | 
			
		||||
		bit_count_ = 0;
 | 
			
		||||
		while(bit_count_ < 9 && index_count_ < max_index_count) {
 | 
			
		||||
			run_for(Cycles(1));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		unsigned int proceed_to_next_block(int max_index_count) {
 | 
			
		||||
			// find GCR lead-in
 | 
			
		||||
			proceed_to_shift_value(0x3ff);
 | 
			
		||||
			if(shift_register_ != 0x3ff) return 0xff;
 | 
			
		||||
		return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// find end of lead-in
 | 
			
		||||
			while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
	unsigned int get_next_byte() {
 | 
			
		||||
		bit_count_ = 0;
 | 
			
		||||
		while(bit_count_ < 10) run_for(Cycles(1));
 | 
			
		||||
		return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// continue for a further nine bits
 | 
			
		||||
			bit_count_ = 0;
 | 
			
		||||
			while(bit_count_ < 9 && index_count_ < max_index_count) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
	void proceed_to_shift_value(const unsigned int shift_value) {
 | 
			
		||||
		const int max_index_count = index_count_ + 2;
 | 
			
		||||
		while(shift_register_ != shift_value && index_count_ < max_index_count) {
 | 
			
		||||
			run_for(Cycles(1));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		unsigned int get_next_byte() {
 | 
			
		||||
			bit_count_ = 0;
 | 
			
		||||
			while(bit_count_ < 10) run_for(Cycles(1));
 | 
			
		||||
			return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
	void process_index_hole() override {
 | 
			
		||||
		index_count_++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const Sector *get_sector(const uint8_t sector) {
 | 
			
		||||
		const uint16_t sector_address = uint16_t((track_ << 8) | sector);
 | 
			
		||||
		auto existing = sector_cache_.find(sector_address);
 | 
			
		||||
		if(existing != sector_cache_.end()) return existing->second.get();
 | 
			
		||||
 | 
			
		||||
		const auto first_sector = get_next_sector();
 | 
			
		||||
		if(!first_sector) return first_sector;
 | 
			
		||||
		if(first_sector->sector == sector) return first_sector;
 | 
			
		||||
 | 
			
		||||
		while(true) {
 | 
			
		||||
			const auto next_sector = get_next_sector();
 | 
			
		||||
			if(next_sector->sector == first_sector->sector) return nullptr;
 | 
			
		||||
			if(next_sector->sector == sector) return next_sector;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void proceed_to_shift_value(unsigned int shift_value) {
 | 
			
		||||
			const int max_index_count = index_count_ + 2;
 | 
			
		||||
			while(shift_register_ != shift_value && index_count_ < max_index_count) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void process_index_hole() {
 | 
			
		||||
			index_count_++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::shared_ptr<Sector> get_sector(uint8_t sector) {
 | 
			
		||||
			const uint16_t sector_address = uint16_t((track_ << 8) | sector);
 | 
			
		||||
			if(sector_cache_[sector_address]) return sector_cache_[sector_address];
 | 
			
		||||
 | 
			
		||||
			const std::shared_ptr<Sector> first_sector = get_next_sector();
 | 
			
		||||
			if(!first_sector) return first_sector;
 | 
			
		||||
			if(first_sector->sector == sector) return first_sector;
 | 
			
		||||
	const Sector *get_next_sector() {
 | 
			
		||||
		auto sector = std::make_unique<Sector>();
 | 
			
		||||
		const int max_index_count = index_count_ + 2;
 | 
			
		||||
 | 
			
		||||
		while(index_count_ < max_index_count) {
 | 
			
		||||
			// look for a sector header
 | 
			
		||||
			while(1) {
 | 
			
		||||
				const std::shared_ptr<Sector> next_sector = get_next_sector();
 | 
			
		||||
				if(next_sector->sector == first_sector->sector) return nullptr;
 | 
			
		||||
				if(next_sector->sector == sector) return next_sector;
 | 
			
		||||
				if(proceed_to_next_block(max_index_count) == 0x08) break;
 | 
			
		||||
				if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// get sector details, skip if this looks malformed
 | 
			
		||||
			uint8_t checksum = uint8_t(get_next_byte());
 | 
			
		||||
			sector->sector = uint8_t(get_next_byte());
 | 
			
		||||
			sector->track = uint8_t(get_next_byte());
 | 
			
		||||
			uint8_t disk_id[2];
 | 
			
		||||
			disk_id[0] = uint8_t(get_next_byte());
 | 
			
		||||
			disk_id[1] = uint8_t(get_next_byte());
 | 
			
		||||
			if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
 | 
			
		||||
 | 
			
		||||
			// look for the following data
 | 
			
		||||
			while(1) {
 | 
			
		||||
				if(proceed_to_next_block(max_index_count) == 0x07) break;
 | 
			
		||||
				if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			checksum = 0;
 | 
			
		||||
			for(std::size_t c = 0; c < 256; c++) {
 | 
			
		||||
				sector->data[c] = uint8_t(get_next_byte());
 | 
			
		||||
				checksum ^= sector->data[c];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(checksum == get_next_byte()) {
 | 
			
		||||
				uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
 | 
			
		||||
				auto pair = sector_cache_.emplace(sector_address, std::move(sector));
 | 
			
		||||
				return pair.first->second.get();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::shared_ptr<Sector> get_next_sector() {
 | 
			
		||||
			auto sector = std::make_shared<Sector>();
 | 
			
		||||
			const int max_index_count = index_count_ + 2;
 | 
			
		||||
 | 
			
		||||
			while(index_count_ < max_index_count) {
 | 
			
		||||
				// look for a sector header
 | 
			
		||||
				while(1) {
 | 
			
		||||
					if(proceed_to_next_block(max_index_count) == 0x08) break;
 | 
			
		||||
					if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// get sector details, skip if this looks malformed
 | 
			
		||||
				uint8_t checksum = uint8_t(get_next_byte());
 | 
			
		||||
				sector->sector = uint8_t(get_next_byte());
 | 
			
		||||
				sector->track = uint8_t(get_next_byte());
 | 
			
		||||
				uint8_t disk_id[2];
 | 
			
		||||
				disk_id[0] = uint8_t(get_next_byte());
 | 
			
		||||
				disk_id[1] = uint8_t(get_next_byte());
 | 
			
		||||
				if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
 | 
			
		||||
 | 
			
		||||
				// look for the following data
 | 
			
		||||
				while(1) {
 | 
			
		||||
					if(proceed_to_next_block(max_index_count) == 0x07) break;
 | 
			
		||||
					if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				checksum = 0;
 | 
			
		||||
				for(std::size_t c = 0; c < 256; c++) {
 | 
			
		||||
					sector->data[c] = uint8_t(get_next_byte());
 | 
			
		||||
					checksum ^= sector->data[c];
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(checksum == get_next_byte()) {
 | 
			
		||||
					uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
 | 
			
		||||
					sector_cache_[sector_address] = sector;
 | 
			
		||||
					return sector;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
@@ -174,20 +176,26 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
			
		||||
	CommodoreGCRParser parser;
 | 
			
		||||
	parser.set_disk(disk);
 | 
			
		||||
 | 
			
		||||
	// find any sector whatsoever to establish the current track
 | 
			
		||||
	std::shared_ptr<CommodoreGCRParser::Sector> sector;
 | 
			
		||||
 | 
			
		||||
	// assemble directory
 | 
			
		||||
	// Assemble directory.
 | 
			
		||||
	std::vector<uint8_t> directory;
 | 
			
		||||
	uint8_t next_track = 18;
 | 
			
		||||
	uint8_t next_sector = 1;
 | 
			
		||||
	while(1) {
 | 
			
		||||
		sector = parser.sector(next_track, next_sector);
 | 
			
		||||
	directory.reserve(20 * 1024);	// Probably more than plenty.
 | 
			
		||||
	std::set<std::pair<uint8_t, uint8_t>> visited;
 | 
			
		||||
	while(true) {
 | 
			
		||||
		// Don't be fooled by disks that are encoded with a looping directory.
 | 
			
		||||
		const auto key = std::make_pair(next_track, next_sector);
 | 
			
		||||
		if(visited.find(key) != visited.end()) break;
 | 
			
		||||
		visited.insert(key);
 | 
			
		||||
 | 
			
		||||
		// Append sector to directory and follow next link.
 | 
			
		||||
		const auto sector = parser.sector(next_track, next_sector);
 | 
			
		||||
		if(!sector) break;
 | 
			
		||||
		directory.insert(directory.end(), sector->data.begin(), sector->data.end());
 | 
			
		||||
		next_track = sector->data[0];
 | 
			
		||||
		next_sector = sector->data[1];
 | 
			
		||||
 | 
			
		||||
		// Check for end-of-directory.
 | 
			
		||||
		if(!next_track) break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -216,24 +224,36 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
			
		||||
		}
 | 
			
		||||
		new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
 | 
			
		||||
 | 
			
		||||
		std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
 | 
			
		||||
		new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
			
		||||
		const std::size_t number_of_sectors =
 | 
			
		||||
			size_t(directory[header_pointer + 0x1e]) +
 | 
			
		||||
			(size_t(directory[header_pointer + 0x1f]) << 8);
 | 
			
		||||
		if(number_of_sectors) {
 | 
			
		||||
			new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
			
		||||
 | 
			
		||||
		bool is_first_sector = true;
 | 
			
		||||
		while(next_track) {
 | 
			
		||||
			sector = parser.sector(next_track, next_sector);
 | 
			
		||||
			if(!sector) break;
 | 
			
		||||
			bool is_first_sector = true;
 | 
			
		||||
			while(next_track) {
 | 
			
		||||
				const auto sector = parser.sector(next_track, next_sector);
 | 
			
		||||
				if(!sector) break;
 | 
			
		||||
 | 
			
		||||
			next_track = sector->data[0];
 | 
			
		||||
			next_sector = sector->data[1];
 | 
			
		||||
				next_track = sector->data[0];
 | 
			
		||||
				next_sector = sector->data[1];
 | 
			
		||||
 | 
			
		||||
			if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
 | 
			
		||||
			if(next_track)
 | 
			
		||||
				new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
 | 
			
		||||
			else
 | 
			
		||||
				new_file.data.insert(new_file.data.end(), sector->data.begin() + 2, sector->data.begin() + next_sector);
 | 
			
		||||
				if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
 | 
			
		||||
				if(next_track)
 | 
			
		||||
					new_file.data.insert(
 | 
			
		||||
						new_file.data.end(),
 | 
			
		||||
						sector->data.begin() + (is_first_sector ? 4 : 2),
 | 
			
		||||
						sector->data.end()
 | 
			
		||||
					);
 | 
			
		||||
				else
 | 
			
		||||
					new_file.data.insert(
 | 
			
		||||
						new_file.data.end(),
 | 
			
		||||
						sector->data.begin() + 2,
 | 
			
		||||
						sector->data.begin() + next_sector
 | 
			
		||||
					);
 | 
			
		||||
 | 
			
		||||
			is_first_sector = false;
 | 
			
		||||
				is_first_sector = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(!next_track) files.push_back(new_file);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,13 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//  File.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 10/09/2016.
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
 | 
			
		||||
bool Analyser::Static::Commodore::File::is_basic() {
 | 
			
		||||
	// BASIC files are always relocatable (?)
 | 
			
		||||
	if(type != File::RelocatableProgram) return false;
 | 
			
		||||
 | 
			
		||||
	uint16_t line_address = starting_address;
 | 
			
		||||
	int line_number = -1;
 | 
			
		||||
 | 
			
		||||
	// decide whether this is a BASIC file based on the proposition that:
 | 
			
		||||
	//	(1) they're always relocatable; and
 | 
			
		||||
	//	(2) they have a per-line structure of:
 | 
			
		||||
	//		[4 bytes: address of start of next line]
 | 
			
		||||
	//		[4 bytes: this line number]
 | 
			
		||||
	//		... null-terminated code ...
 | 
			
		||||
	//	(with a next line address of 0000 indicating end of program)
 | 
			
		||||
	while(1) {
 | 
			
		||||
		if(size_t(line_address - starting_address) >= data.size() + 2) break;
 | 
			
		||||
 | 
			
		||||
		uint16_t next_line_address = data[line_address - starting_address];
 | 
			
		||||
		next_line_address |= data[line_address - starting_address + 1] << 8;
 | 
			
		||||
 | 
			
		||||
		if(!next_line_address) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		if(next_line_address < line_address + 5) break;
 | 
			
		||||
 | 
			
		||||
		if(size_t(line_address - starting_address) >= data.size() + 5) break;
 | 
			
		||||
		uint16_t next_line_number = data[line_address - starting_address + 2];
 | 
			
		||||
		next_line_number |= data[line_address - starting_address + 3] << 8;
 | 
			
		||||
 | 
			
		||||
		if(next_line_number <= line_number) break;
 | 
			
		||||
 | 
			
		||||
		line_number = uint16_t(next_line_number);
 | 
			
		||||
		line_address = next_line_address;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
@@ -29,8 +29,6 @@ struct File {
 | 
			
		||||
		Relative
 | 
			
		||||
	} type;
 | 
			
		||||
	std::vector<uint8_t> data;
 | 
			
		||||
 | 
			
		||||
	bool is_basic();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,26 +12,33 @@
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
			
		||||
#include "../../../Outputs/Log.hpp"
 | 
			
		||||
#include "Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
			
		||||
#include "Outputs/Log.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/Disassembler/6502.hpp"
 | 
			
		||||
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
		Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
		const auto &segments = cartridge->get_segments();
 | 
			
		||||
 | 
			
		||||
		// only one mapped item is allowed
 | 
			
		||||
		// Only one mapped item is allowed ...
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// which must be 16 kb in size
 | 
			
		||||
		// ... which must be 16 kb in size.
 | 
			
		||||
		Storage::Cartridge::Cartridge::Segment segment = segments.front();
 | 
			
		||||
		if(segment.start_address != 0xa000) continue;
 | 
			
		||||
		if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
 | 
			
		||||
@@ -39,126 +46,312 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
		vic20_cartridges.push_back(cartridge);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: other machines?
 | 
			
		||||
 | 
			
		||||
	return vic20_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
struct BASICAnalysis {
 | 
			
		||||
	enum class Version {
 | 
			
		||||
		NotBASIC,
 | 
			
		||||
		BASIC2,
 | 
			
		||||
		BASIC4,
 | 
			
		||||
		BASIC3_5,
 | 
			
		||||
	} minimum_version = Version::NotBASIC;
 | 
			
		||||
	std::vector<uint16_t> machine_code_addresses;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->machine = Machine::Vic20;	// TODO: machine estimation
 | 
			
		||||
	target->confidence = 0.5; // TODO: a proper estimation
 | 
			
		||||
std::optional<BASICAnalysis> analyse(const File &file) {
 | 
			
		||||
	BASICAnalysis analysis;
 | 
			
		||||
 | 
			
		||||
	switch(file.type) {
 | 
			
		||||
		// For 'program' types, proceed with analysis below.
 | 
			
		||||
		case File::RelocatableProgram:
 | 
			
		||||
		case File::NonRelocatableProgram:
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		// For sequential and relative data stop right now.
 | 
			
		||||
		case File::DataSequence:
 | 
			
		||||
		case File::Relative:
 | 
			
		||||
		return std::nullopt;
 | 
			
		||||
 | 
			
		||||
		// For user data, try decoding from the starting point.
 | 
			
		||||
		case File::User:
 | 
			
		||||
			analysis.machine_code_addresses.push_back(file.starting_address);
 | 
			
		||||
		return analysis;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Don't form an opinion if file is empty.
 | 
			
		||||
	if(file.data.empty()) {
 | 
			
		||||
		return std::nullopt;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint16_t line_address = file.starting_address;
 | 
			
		||||
//	int previous_line_number = -1;
 | 
			
		||||
 | 
			
		||||
	const auto byte = [&](uint16_t address) {
 | 
			
		||||
		return file.data[address - file.starting_address];
 | 
			
		||||
	};
 | 
			
		||||
	const auto word = [&](uint16_t address) {
 | 
			
		||||
		return uint16_t(byte(address) | byte(address + 1) << 8);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// BASIC programs have a per-line structure of:
 | 
			
		||||
	//		[2 bytes: address of start of next line]
 | 
			
		||||
	//		[2 bytes: this line number]
 | 
			
		||||
	//		... null-terminated code ...
 | 
			
		||||
	//	(with a next line address of 0000 indicating end of program)
 | 
			
		||||
	//
 | 
			
		||||
	// If a SYS is encountered that jumps into the BASIC program then treat that as
 | 
			
		||||
	// a machine code entry point.
 | 
			
		||||
 | 
			
		||||
	std::unordered_set<uint16_t> visited_lines;
 | 
			
		||||
	while(true) {
 | 
			
		||||
		// Analysis has failed if there isn't at least one complete BASIC line from here.
 | 
			
		||||
		// Fall back on guessing the start address as a machine code entrypoint.
 | 
			
		||||
		if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) {
 | 
			
		||||
			analysis.machine_code_addresses.push_back(file.starting_address);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto next_line_address = word(line_address);
 | 
			
		||||
//		const auto line_number = word(line_address + 2);
 | 
			
		||||
 | 
			
		||||
		uint16_t code = line_address + 4;
 | 
			
		||||
		const auto next = [&]() -> uint8_t {
 | 
			
		||||
			if(code >= file.starting_address + file.data.size()) {
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
			return byte(code++);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// TODO: sanity check on apparent line contents.
 | 
			
		||||
		// TODO: observe token set (and possibly parameters?) to guess BASIC version.
 | 
			
		||||
		while(true) {
 | 
			
		||||
			const auto token = next();
 | 
			
		||||
			if(!token || token == 0x8f) break;
 | 
			
		||||
 | 
			
		||||
			switch(token) {
 | 
			
		||||
				case 0x9e: {	// SYS; parse following ASCII argument.
 | 
			
		||||
					uint16_t address = 0;
 | 
			
		||||
					while(true) {
 | 
			
		||||
						const auto c = next();
 | 
			
		||||
						if(c < '0' || c > '9') {
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
						address = (address * 10) + (c - '0');
 | 
			
		||||
					};
 | 
			
		||||
					analysis.machine_code_addresses.push_back(address);
 | 
			
		||||
				} break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Exit if a formal end of the program has been declared or if, as some copy protections do,
 | 
			
		||||
		// the linked list of line contents has been made circular.
 | 
			
		||||
		visited_lines.insert(line_address);
 | 
			
		||||
		if(!next_line_address || visited_lines.find(next_line_address) != visited_lines.end()) {
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
//		previous_line_number = line_number;
 | 
			
		||||
		line_address = next_line_address;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return analysis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename TargetT>
 | 
			
		||||
void set_loading_command(TargetT &target) {
 | 
			
		||||
	if(target.media.disks.empty()) {
 | 
			
		||||
		target.loading_command = "LOAD\"\",1,1\nRUN\n";
 | 
			
		||||
	} else {
 | 
			
		||||
		target.loading_command = "LOAD\"*\",8,1\nRUN\n";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool obviously_uses_ted(const File &file) {
 | 
			
		||||
	const auto analysis = analyse(file);
 | 
			
		||||
	if(!analysis) return false;
 | 
			
		||||
 | 
			
		||||
	// Disassemble.
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
		file.data,
 | 
			
		||||
		Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
 | 
			
		||||
		analysis->machine_code_addresses
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// Check for interrupt status and paging touches.
 | 
			
		||||
	for(const auto address: {0xff3e, 0xff3f, 0xff09}) {
 | 
			
		||||
		for(const auto &collection: {
 | 
			
		||||
			disassembly.external_loads,
 | 
			
		||||
			disassembly.external_stores,
 | 
			
		||||
			disassembly.external_modifies
 | 
			
		||||
		}) {
 | 
			
		||||
			if(collection.find(uint16_t(address)) != collection.end()) {
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct FileAnalysis {
 | 
			
		||||
	int device = 0;
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	bool is_disk = false;
 | 
			
		||||
	Analyser::Static::Media media;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
	// strip out inappropriate cartridges
 | 
			
		||||
	target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
 | 
			
		||||
template <TargetPlatform::Type platform>
 | 
			
		||||
FileAnalysis analyse_files(const Analyser::Static::Media &media) {
 | 
			
		||||
	FileAnalysis analysis;
 | 
			
		||||
 | 
			
		||||
	// check disks
 | 
			
		||||
	// Find all valid Commodore files on disks.
 | 
			
		||||
	for(auto &disk : media.disks) {
 | 
			
		||||
		std::vector<File> disk_files = GetFiles(disk);
 | 
			
		||||
		if(!disk_files.empty()) {
 | 
			
		||||
			is_disk = true;
 | 
			
		||||
			files.insert(files.end(), disk_files.begin(), disk_files.end());
 | 
			
		||||
			target->media.disks.push_back(disk);
 | 
			
		||||
			if(!device) device = 8;
 | 
			
		||||
			analysis.is_disk = true;
 | 
			
		||||
			analysis.files.insert(
 | 
			
		||||
				analysis.files.end(),
 | 
			
		||||
				std::make_move_iterator(disk_files.begin()),
 | 
			
		||||
				std::make_move_iterator(disk_files.end())
 | 
			
		||||
			);
 | 
			
		||||
			analysis.media.disks.push_back(disk);
 | 
			
		||||
			if(!analysis.device) analysis.device = 8;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check tapes
 | 
			
		||||
	// Find all valid Commodore files on tapes.
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
		auto serialiser = tape->serialiser();
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(*serialiser, platform);
 | 
			
		||||
		if(!tape_files.empty()) {
 | 
			
		||||
			files.insert(files.end(), tape_files.begin(), tape_files.end());
 | 
			
		||||
			target->media.tapes.push_back(tape);
 | 
			
		||||
			if(!device) device = 1;
 | 
			
		||||
			analysis.files.insert(
 | 
			
		||||
				analysis.files.end(),
 | 
			
		||||
				std::make_move_iterator(tape_files.begin()),
 | 
			
		||||
				std::make_move_iterator(tape_files.end())
 | 
			
		||||
			);
 | 
			
		||||
			analysis.media.tapes.push_back(tape);
 | 
			
		||||
			if(!analysis.device) analysis.device = 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!files.empty()) {
 | 
			
		||||
		auto memory_model = Target::MemoryModel::Unexpanded;
 | 
			
		||||
		std::ostringstream string_stream;
 | 
			
		||||
		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
 | 
			
		||||
		if(files.front().is_basic()) {
 | 
			
		||||
			string_stream << "0";
 | 
			
		||||
		} else {
 | 
			
		||||
			string_stream << "1";
 | 
			
		||||
	return analysis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string loading_command(const FileAnalysis &file_analysis) {
 | 
			
		||||
	std::ostringstream string_stream;
 | 
			
		||||
	string_stream << "LOAD\"" << (file_analysis.is_disk ? "*" : "") << "\"," << file_analysis.device;
 | 
			
		||||
 | 
			
		||||
	const auto analysis = analyse(file_analysis.files[0]);
 | 
			
		||||
	if(analysis && !analysis->machine_code_addresses.empty()) {
 | 
			
		||||
		string_stream << ",1";
 | 
			
		||||
	}
 | 
			
		||||
	string_stream << "\nRUN\n";
 | 
			
		||||
	return string_stream.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::pair<TargetPlatform::IntType, std::optional<Vic20Target::MemoryModel>>
 | 
			
		||||
analyse_starting_address(uint16_t starting_address) {
 | 
			
		||||
	switch(starting_address) {
 | 
			
		||||
		case 0x1c01:
 | 
			
		||||
			// TODO: assume C128.
 | 
			
		||||
		default:
 | 
			
		||||
			Log::Logger<Log::Source::CommodoreStaticAnalyser>::error().append(
 | 
			
		||||
				"Unrecognised loading address for Commodore program: %04x", starting_address);
 | 
			
		||||
			[[fallthrough]];
 | 
			
		||||
		case 0x1001:
 | 
			
		||||
		return std::make_pair(TargetPlatform::Vic20 | TargetPlatform::Plus4, Vic20Target::MemoryModel::Unexpanded);
 | 
			
		||||
 | 
			
		||||
		case 0x1201:	return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::ThirtyTwoKB);
 | 
			
		||||
		case 0x0401:	return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::EightKB);
 | 
			
		||||
		case 0x0801:	return std::make_pair(TargetPlatform::C64, std::nullopt);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <TargetPlatform::IntType platform>
 | 
			
		||||
std::unique_ptr<Analyser::Static::Target> get_target(
 | 
			
		||||
	const Analyser::Static::Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Plus4>(
 | 
			
		||||
	const Analyser::Static::Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	auto target = std::make_unique<Plus4Target>();
 | 
			
		||||
	if(is_confident) {
 | 
			
		||||
		target->media = media;
 | 
			
		||||
		set_loading_command(*target);
 | 
			
		||||
	} else {
 | 
			
		||||
		const auto files = analyse_files<TargetPlatform::Plus4>(media);
 | 
			
		||||
		if(!files.files.empty()) {
 | 
			
		||||
			target->loading_command = loading_command(files);
 | 
			
		||||
		}
 | 
			
		||||
		string_stream << "\nRUN\n";
 | 
			
		||||
		target->loading_command = string_stream.str();
 | 
			
		||||
 | 
			
		||||
		// make a first guess based on loading address
 | 
			
		||||
		switch(files.front().starting_address) {
 | 
			
		||||
			default:
 | 
			
		||||
				Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append("Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
 | 
			
		||||
				[[fallthrough]];
 | 
			
		||||
			case 0x1001:
 | 
			
		||||
				memory_model = Target::MemoryModel::Unexpanded;
 | 
			
		||||
			break;
 | 
			
		||||
			case 0x1201:
 | 
			
		||||
				memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
			
		||||
			break;
 | 
			
		||||
			case 0x0401:
 | 
			
		||||
				memory_model = Target::MemoryModel::EightKB;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		target->set_memory_model(memory_model);
 | 
			
		||||
 | 
			
		||||
		// General approach: increase memory size conservatively such that the largest file found will fit.
 | 
			
		||||
//		for(File &file : files) {
 | 
			
		||||
//			std::size_t file_size = file.data.size();
 | 
			
		||||
//			bool is_basic = file.is_basic();
 | 
			
		||||
 | 
			
		||||
			/*if(is_basic)
 | 
			
		||||
			{
 | 
			
		||||
				// BASIC files may be relocated, so the only limit is size.
 | 
			
		||||
				//
 | 
			
		||||
				// An unexpanded machine has 3583 bytes free for BASIC;
 | 
			
		||||
				// a 3kb expanded machine has 6655 bytes free.
 | 
			
		||||
				if(file_size > 6655)
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
			
		||||
				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::EightKB;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{*/
 | 
			
		||||
//			if(!file.type == File::NonRelocatableProgram)
 | 
			
		||||
//			{
 | 
			
		||||
				// Non-BASIC files may be relocatable but, if so, by what logic?
 | 
			
		||||
				// Given that this is unknown, take starting address as literal
 | 
			
		||||
				// and check against memory windows.
 | 
			
		||||
				//
 | 
			
		||||
				// (ignoring colour memory...)
 | 
			
		||||
				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
 | 
			
		||||
				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
 | 
			
		||||
				// A 32kb expanded Vic has memory in the entire low 32kb.
 | 
			
		||||
//				uint16_t starting_address = file.starting_address;
 | 
			
		||||
 | 
			
		||||
				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
 | 
			
		||||
				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
 | 
			
		||||
//				if(starting_address + file_size > 0x2000)
 | 
			
		||||
//					target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
			
		||||
//				else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
 | 
			
		||||
//					target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
			
		||||
//			}
 | 
			
		||||
//		}
 | 
			
		||||
		target->media.disks = media.disks;
 | 
			
		||||
		target->media.tapes = media.tapes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Attach a 1541 if there are any disks here.
 | 
			
		||||
	target->has_c1541 = !target->media.disks.empty();
 | 
			
		||||
	return target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
 | 
			
		||||
	const Analyser::Static::Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	auto target = std::make_unique<Vic20Target>();
 | 
			
		||||
	const auto files = analyse_files<TargetPlatform::Vic20>(media);
 | 
			
		||||
	if(!files.files.empty()) {
 | 
			
		||||
		target->loading_command = loading_command(files);
 | 
			
		||||
 | 
			
		||||
		const auto model = analyse_starting_address(files.files[0].starting_address);
 | 
			
		||||
		if(model.second.has_value()) {
 | 
			
		||||
			target->set_memory_model(*model.second);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(is_confident) {
 | 
			
		||||
		target->media = media;
 | 
			
		||||
		set_loading_command(*target);
 | 
			
		||||
	} else {
 | 
			
		||||
		// Strip out inappropriate cartridges but retain all tapes and disks.
 | 
			
		||||
		target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
 | 
			
		||||
		target->media.disks = media.disks;
 | 
			
		||||
		target->media.tapes = media.tapes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for(const auto &file : files.files) {
 | 
			
		||||
		// The Vic-20 never has RAM after 0x8000.
 | 
			
		||||
		if(file.ending_address >= 0x8000) {
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(obviously_uses_ted(file)) {
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Inspect filename for configuration hints.
 | 
			
		||||
	if(!target->media.empty()) {
 | 
			
		||||
		// Inspect filename for configuration hints.
 | 
			
		||||
		using Region = Analyser::Static::Commodore::Vic20Target::Region;
 | 
			
		||||
 | 
			
		||||
		std::string lowercase_name = file_name;
 | 
			
		||||
		std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
 | 
			
		||||
 | 
			
		||||
		// Hint 1: 'ntsc' anywhere in the name implies America.
 | 
			
		||||
		if(lowercase_name.find("ntsc") != std::string::npos) {
 | 
			
		||||
			target->region = Analyser::Static::Commodore::Target::Region::American;
 | 
			
		||||
			target->region = Region::American;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Potential additional hints: check for TheC64 tags.
 | 
			
		||||
		// Potential additional hints: check for TheC64 tags; these are Vic-20 exclusive.
 | 
			
		||||
		auto final_underscore = lowercase_name.find_last_of('_');
 | 
			
		||||
		if(final_underscore != std::string::npos) {
 | 
			
		||||
			auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
 | 
			
		||||
@@ -180,10 +373,10 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
 | 
			
		||||
				target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
 | 
			
		||||
				target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
 | 
			
		||||
				if(!strcmp(next_tag, "tn")) {	// i.e. NTSC.
 | 
			
		||||
					target->region = Analyser::Static::Commodore::Target::Region::American;
 | 
			
		||||
					target->region = Region::American;
 | 
			
		||||
				}
 | 
			
		||||
				if(!strcmp(next_tag, "tp")) {	// i.e. PAL.
 | 
			
		||||
					target->region = Analyser::Static::Commodore::Target::Region::European;
 | 
			
		||||
					target->region = Region::European;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Unhandled:
 | 
			
		||||
@@ -194,11 +387,36 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
 | 
			
		||||
				//	RO:		this disk image should be treated as read-only.
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		// Attach a 1540 if there are any disks here.
 | 
			
		||||
		target->has_c1540 = !target->media.disks.empty();
 | 
			
		||||
	// Attach a 1540 if there are any disks here.
 | 
			
		||||
	target->has_c1540 = !target->media.disks.empty();
 | 
			
		||||
	return target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		destination.push_back(std::move(target));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType platforms,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
 | 
			
		||||
	if(platforms & TargetPlatform::Vic20) {
 | 
			
		||||
		auto vic20 = get_target<TargetPlatform::Vic20>(media, file_name, is_confident);
 | 
			
		||||
		if(vic20) {
 | 
			
		||||
			destination.push_back(std::move(vic20));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(platforms & TargetPlatform::Plus4) {
 | 
			
		||||
		auto plus4 = get_target<TargetPlatform::Plus4>(media, file_name, is_confident);
 | 
			
		||||
		if(plus4) {
 | 
			
		||||
			destination.push_back(std::move(plus4));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return destination;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,26 +7,27 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Commodore.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	Storage::Tape::Commodore::Parser parser;
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) {
 | 
			
		||||
	Storage::Tape::Commodore::Parser parser(type);
 | 
			
		||||
	std::vector<File> file_list;
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
 | 
			
		||||
	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(serialiser);
 | 
			
		||||
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
		if(!header) {
 | 
			
		||||
			header = parser.get_next_header(tape);
 | 
			
		||||
			header = parser.get_next_header(serialiser);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch(header->type) {
 | 
			
		||||
			case Storage::Tape::Commodore::Header::DataSequenceHeader: {
 | 
			
		||||
				File new_file;
 | 
			
		||||
				File &new_file = file_list.emplace_back();
 | 
			
		||||
				new_file.name = header->name;
 | 
			
		||||
				new_file.raw_name = header->raw_name;
 | 
			
		||||
				new_file.starting_address = header->starting_address;
 | 
			
		||||
@@ -34,38 +35,36 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
			
		||||
				new_file.type = File::DataSequence;
 | 
			
		||||
 | 
			
		||||
				new_file.data.swap(header->data);
 | 
			
		||||
				while(!tape->is_at_end()) {
 | 
			
		||||
					header = parser.get_next_header(tape);
 | 
			
		||||
				while(!serialiser.is_at_end()) {
 | 
			
		||||
					header = parser.get_next_header(serialiser);
 | 
			
		||||
					if(!header) continue;
 | 
			
		||||
					if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
 | 
			
		||||
					std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
 | 
			
		||||
					std::ranges::copy(header->data, std::back_inserter(new_file.data));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				file_list.push_back(new_file);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			case Storage::Tape::Commodore::Header::RelocatableProgram:
 | 
			
		||||
			case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
 | 
			
		||||
				std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
 | 
			
		||||
				std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(serialiser);
 | 
			
		||||
				if(data) {
 | 
			
		||||
					File new_file;
 | 
			
		||||
					File &new_file = file_list.emplace_back();
 | 
			
		||||
					new_file.name = header->name;
 | 
			
		||||
					new_file.raw_name = header->raw_name;
 | 
			
		||||
					new_file.starting_address = header->starting_address;
 | 
			
		||||
					new_file.ending_address = header->ending_address;
 | 
			
		||||
					new_file.data.swap(data->data);
 | 
			
		||||
					new_file.type = (header->type == Storage::Tape::Commodore::Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram;
 | 
			
		||||
 | 
			
		||||
					file_list.push_back(new_file);
 | 
			
		||||
					new_file.type =
 | 
			
		||||
						header->type == Storage::Tape::Commodore::Header::RelocatableProgram
 | 
			
		||||
							? File::RelocatableProgram : File::NonRelocatableProgram;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				header = parser.get_next_header(tape);
 | 
			
		||||
				header = parser.get_next_header(serialiser);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			default:
 | 
			
		||||
				header = parser.get_next_header(tape);
 | 
			
		||||
				header = parser.get_next_header(serialiser);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &, TargetPlatform::Type);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,28 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
struct Plus4Target: public Analyser::Static::Target, public Reflection::StructImpl<Plus4Target> {
 | 
			
		||||
	// TODO: region, etc.
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
	bool has_c1541 = false;
 | 
			
		||||
 | 
			
		||||
	Plus4Target() : Analyser::Static::Target(Machine::Plus4) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Plus4Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_c1541);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Vic20Target: public Analyser::Static::Target, public Reflection::StructImpl<Vic20Target> {
 | 
			
		||||
	enum class MemoryModel {
 | 
			
		||||
		Unexpanded,
 | 
			
		||||
		EightKB,
 | 
			
		||||
@@ -54,17 +68,19 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	bool has_c1540 = false;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Vic20) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(enabled_ram.bank0);
 | 
			
		||||
			DeclareField(enabled_ram.bank1);
 | 
			
		||||
			DeclareField(enabled_ram.bank2);
 | 
			
		||||
			DeclareField(enabled_ram.bank3);
 | 
			
		||||
			DeclareField(enabled_ram.bank5);
 | 
			
		||||
			DeclareField(region);
 | 
			
		||||
			DeclareField(has_c1540);
 | 
			
		||||
			AnnounceEnum(Region);
 | 
			
		||||
		}
 | 
			
		||||
	Vic20Target() : Analyser::Static::Target(Machine::Vic20) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Vic20Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(enabled_ram.bank0);
 | 
			
		||||
		DeclareField(enabled_ram.bank1);
 | 
			
		||||
		DeclareField(enabled_ram.bank2);
 | 
			
		||||
		DeclareField(enabled_ram.bank3);
 | 
			
		||||
		DeclareField(enabled_ram.bank5);
 | 
			
		||||
		DeclareField(region);
 | 
			
		||||
		DeclareField(has_c1540);
 | 
			
		||||
		AnnounceEnum(Region);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,12 @@ using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Dis
 | 
			
		||||
 | 
			
		||||
struct MOS6502Disassembler {
 | 
			
		||||
 | 
			
		||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
 | 
			
		||||
static void AddToDisassembly(
 | 
			
		||||
	PartialDisassembly &disassembly,
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	uint16_t entry_point
 | 
			
		||||
) {
 | 
			
		||||
	disassembly.disassembly.internal_calls.insert(entry_point);
 | 
			
		||||
	uint16_t address = entry_point;
 | 
			
		||||
	while(true) {
 | 
			
		||||
@@ -75,23 +80,25 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Decode operation.
 | 
			
		||||
#define RM_INSTRUCTION(base, op)	\
 | 
			
		||||
	case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19:	\
 | 
			
		||||
		instruction.operation = op;	\
 | 
			
		||||
#define RM_INSTRUCTION(base, op)									\
 | 
			
		||||
	case base+0x09: case base+0x05: case base+0x15: case base+0x01:	\
 | 
			
		||||
	case base+0x11: case base+0x0d: case base+0x1d: case base+0x19:	\
 | 
			
		||||
		instruction.operation = op;									\
 | 
			
		||||
	break;
 | 
			
		||||
 | 
			
		||||
#define URM_INSTRUCTION(base, op)	\
 | 
			
		||||
#define URM_INSTRUCTION(base, op)																					\
 | 
			
		||||
	case base+0x07: case base+0x17: case base+0x03: case base+0x13: case base+0x0f: case base+0x1f: case base+0x1b:	\
 | 
			
		||||
		instruction.operation = op;	\
 | 
			
		||||
		instruction.operation = op;																					\
 | 
			
		||||
	break;
 | 
			
		||||
 | 
			
		||||
#define M_INSTRUCTION(base, op)	\
 | 
			
		||||
#define M_INSTRUCTION(base, op)														\
 | 
			
		||||
	case base+0x0a: case base+0x06: case base+0x16: case base+0x0e: case base+0x1e:	\
 | 
			
		||||
		instruction.operation = op;	\
 | 
			
		||||
		instruction.operation = op;													\
 | 
			
		||||
	break;
 | 
			
		||||
 | 
			
		||||
#define IM_INSTRUCTION(base, op)	\
 | 
			
		||||
#define IM_INSTRUCTION(base, op)					\
 | 
			
		||||
	case base:	instruction.operation = op; break;
 | 
			
		||||
 | 
			
		||||
		switch(operation) {
 | 
			
		||||
			default:
 | 
			
		||||
				instruction.operation = Instruction::KIL;
 | 
			
		||||
@@ -259,7 +266,10 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
		disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
 | 
			
		||||
 | 
			
		||||
		// TODO: something wider-ranging than this
 | 
			
		||||
		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
 | 
			
		||||
		if(
 | 
			
		||||
			instruction.addressing_mode == Instruction::Absolute ||
 | 
			
		||||
			instruction.addressing_mode == Instruction::ZeroPage
 | 
			
		||||
		) {
 | 
			
		||||
			const size_t mapped_address = address_mapper(instruction.operand);
 | 
			
		||||
			const bool is_external = mapped_address >= memory.size();
 | 
			
		||||
 | 
			
		||||
@@ -272,20 +282,23 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
			
		||||
				case Instruction::ADC: case Instruction::SBC:
 | 
			
		||||
				case Instruction::LAS:
 | 
			
		||||
				case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads)
 | 
			
		||||
						.insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case Instruction::STY: case Instruction::STX: case Instruction::STA:
 | 
			
		||||
				case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
 | 
			
		||||
				case Instruction::TAS:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores)
 | 
			
		||||
						.insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
 | 
			
		||||
				case Instruction::DCP: case Instruction::ISC:
 | 
			
		||||
				case Instruction::INC: case Instruction::DEC:
 | 
			
		||||
				case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies)
 | 
			
		||||
						.insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -330,5 +343,10 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	std::vector<uint16_t> entry_points) {
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(
 | 
			
		||||
		memory,
 | 
			
		||||
		address_mapper,
 | 
			
		||||
		entry_points,
 | 
			
		||||
		false
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ namespace Analyser::Static::Disassembler {
 | 
			
		||||
	Provides an address mapper that relocates a chunk of memory so that it starts at
 | 
			
		||||
	address @c start_address.
 | 
			
		||||
*/
 | 
			
		||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
 | 
			
		||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(const T start_address) {
 | 
			
		||||
	return [start_address](T argument) {
 | 
			
		||||
		return size_t(argument - start_address);
 | 
			
		||||
	};
 | 
			
		||||
 
 | 
			
		||||
@@ -16,51 +16,55 @@ namespace {
 | 
			
		||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
 | 
			
		||||
 | 
			
		||||
class Accessor {
 | 
			
		||||
	public:
 | 
			
		||||
		Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
 | 
			
		||||
			memory_(memory), address_mapper_(address_mapper), address_(address) {}
 | 
			
		||||
public:
 | 
			
		||||
	Accessor(
 | 
			
		||||
		const std::vector<uint8_t> &memory,
 | 
			
		||||
		const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
		uint16_t address
 | 
			
		||||
	) :
 | 
			
		||||
		memory_(memory), address_mapper_(address_mapper), address_(address) {}
 | 
			
		||||
 | 
			
		||||
		uint8_t byte() {
 | 
			
		||||
			std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
			address_++;
 | 
			
		||||
			if(mapped_address >= memory_.size()) {
 | 
			
		||||
				overrun_ = true;
 | 
			
		||||
				return 0xff;
 | 
			
		||||
			}
 | 
			
		||||
			return memory_[mapped_address];
 | 
			
		||||
	uint8_t byte() {
 | 
			
		||||
		std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
		++address_;
 | 
			
		||||
		if(mapped_address >= memory_.size()) {
 | 
			
		||||
			overrun_ = true;
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		}
 | 
			
		||||
		return memory_[mapped_address];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		uint16_t word() {
 | 
			
		||||
			uint8_t low = byte();
 | 
			
		||||
			uint8_t high = byte();
 | 
			
		||||
			return uint16_t(low | (high << 8));
 | 
			
		||||
		}
 | 
			
		||||
	uint16_t word() {
 | 
			
		||||
		uint8_t low = byte();
 | 
			
		||||
		uint8_t high = byte();
 | 
			
		||||
		return uint16_t(low | (high << 8));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		bool overrun() {
 | 
			
		||||
			return overrun_;
 | 
			
		||||
		}
 | 
			
		||||
	bool overrun() const {
 | 
			
		||||
		return overrun_;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		bool at_end() {
 | 
			
		||||
			std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
			return mapped_address >= memory_.size();
 | 
			
		||||
		}
 | 
			
		||||
	bool at_end() const {
 | 
			
		||||
		std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
		return mapped_address >= memory_.size();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		uint16_t address() {
 | 
			
		||||
			return address_;
 | 
			
		||||
		}
 | 
			
		||||
	uint16_t address() const {
 | 
			
		||||
		return address_;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<uint8_t> &memory_;
 | 
			
		||||
		const std::function<std::size_t(uint16_t)> &address_mapper_;
 | 
			
		||||
		uint16_t address_;
 | 
			
		||||
		bool overrun_ = false;
 | 
			
		||||
private:
 | 
			
		||||
	const std::vector<uint8_t> &memory_;
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper_;
 | 
			
		||||
	uint16_t address_;
 | 
			
		||||
	bool overrun_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr uint8_t x(uint8_t v) { return v >> 6; }
 | 
			
		||||
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
 | 
			
		||||
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
 | 
			
		||||
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
 | 
			
		||||
constexpr uint8_t z(uint8_t v) { return v & 7; }
 | 
			
		||||
constexpr uint8_t x(const uint8_t v) { return v >> 6; }
 | 
			
		||||
constexpr uint8_t y(const uint8_t v) { return (v >> 3) & 7; }
 | 
			
		||||
constexpr uint8_t q(const uint8_t v) { return (v >> 3) & 1; }
 | 
			
		||||
constexpr uint8_t p(const uint8_t v) { return (v >> 4) & 3; }
 | 
			
		||||
constexpr uint8_t z(const uint8_t v) { return v & 7; }
 | 
			
		||||
 | 
			
		||||
Instruction::Condition condition_table[] = {
 | 
			
		||||
	Instruction::Condition::NZ,		Instruction::Condition::Z,
 | 
			
		||||
@@ -83,8 +87,12 @@ Instruction::Location register_pair_table2[] = {
 | 
			
		||||
	Instruction::Location::AF
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
	Instruction::Location register_table[] = {
 | 
			
		||||
Instruction::Location RegisterTableEntry(
 | 
			
		||||
	const int offset, Accessor &accessor,
 | 
			
		||||
	Instruction &instruction,
 | 
			
		||||
	const bool needs_indirect_offset
 | 
			
		||||
) {
 | 
			
		||||
	static constexpr Instruction::Location register_table[] = {
 | 
			
		||||
		Instruction::Location::B,	Instruction::Location::C,
 | 
			
		||||
		Instruction::Location::D,	Instruction::Location::E,
 | 
			
		||||
		Instruction::Location::H,	Instruction::Location::L,
 | 
			
		||||
@@ -92,7 +100,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
 | 
			
		||||
		Instruction::Location::A
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Instruction::Location location = register_table[offset];
 | 
			
		||||
	const Instruction::Location location = register_table[offset];
 | 
			
		||||
	if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
 | 
			
		||||
		instruction.offset = accessor.byte() - 128;
 | 
			
		||||
	}
 | 
			
		||||
@@ -100,7 +108,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
 | 
			
		||||
	return location;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Instruction::Operation alu_table[] = {
 | 
			
		||||
constexpr Instruction::Operation alu_table[] = {
 | 
			
		||||
	Instruction::Operation::ADD,
 | 
			
		||||
	Instruction::Operation::ADC,
 | 
			
		||||
	Instruction::Operation::SUB,
 | 
			
		||||
@@ -111,7 +119,7 @@ Instruction::Operation alu_table[] = {
 | 
			
		||||
	Instruction::Operation::CP
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Operation rotation_table[] = {
 | 
			
		||||
constexpr Instruction::Operation rotation_table[] = {
 | 
			
		||||
	Instruction::Operation::RLC,
 | 
			
		||||
	Instruction::Operation::RRC,
 | 
			
		||||
	Instruction::Operation::RL,
 | 
			
		||||
@@ -122,19 +130,32 @@ Instruction::Operation rotation_table[] = {
 | 
			
		||||
	Instruction::Operation::SRL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Operation block_table[][4] = {
 | 
			
		||||
	{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
 | 
			
		||||
	{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
 | 
			
		||||
	{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
 | 
			
		||||
	{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
 | 
			
		||||
constexpr Instruction::Operation block_table[][4] = {
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDI, Instruction::Operation::CPI,
 | 
			
		||||
		Instruction::Operation::INI, Instruction::Operation::OUTI
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDD, Instruction::Operation::CPD,
 | 
			
		||||
		Instruction::Operation::IND, Instruction::Operation::OUTD
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDIR, Instruction::Operation::CPIR,
 | 
			
		||||
		Instruction::Operation::INIR, Instruction::Operation::OTIR
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDDR, Instruction::Operation::CPDR,
 | 
			
		||||
		Instruction::Operation::INDR, Instruction::Operation::OTDR
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
 | 
			
		||||
	const uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
	if(!x(operation)) {
 | 
			
		||||
		instruction.operation = rotation_table[y(operation)];
 | 
			
		||||
		instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
		instruction.source = instruction.destination =
 | 
			
		||||
			RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
	} else {
 | 
			
		||||
		instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
		instruction.source = Instruction::Location::Operand;
 | 
			
		||||
@@ -148,7 +169,7 @@ void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
 | 
			
		||||
	const uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
	switch(x(operation)) {
 | 
			
		||||
@@ -170,7 +191,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
 | 
			
		||||
					if(y(operation) == 6) {
 | 
			
		||||
						instruction.destination = Instruction::Location::None;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 1:
 | 
			
		||||
@@ -179,7 +201,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
 | 
			
		||||
					if(y(operation) == 6) {
 | 
			
		||||
						instruction.source = Instruction::Location::None;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 2:
 | 
			
		||||
@@ -190,11 +213,13 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
 | 
			
		||||
				case 3:
 | 
			
		||||
					instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
					if(q(operation)) {
 | 
			
		||||
						instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.destination =
 | 
			
		||||
							RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = Instruction::Location::Operand_Indirect;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.destination = Instruction::Location::Operand_Indirect;
 | 
			
		||||
						instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source =
 | 
			
		||||
							RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
					instruction.operand = accessor.word();
 | 
			
		||||
				break;
 | 
			
		||||
@@ -202,7 +227,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
 | 
			
		||||
					instruction.operation = Instruction::Operation::NEG;
 | 
			
		||||
				break;
 | 
			
		||||
				case 5:
 | 
			
		||||
					instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
 | 
			
		||||
					instruction.operation =
 | 
			
		||||
						y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN;
 | 
			
		||||
				break;
 | 
			
		||||
				case 6:
 | 
			
		||||
					instruction.operation = Instruction::Operation::IM;
 | 
			
		||||
@@ -253,7 +279,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
	} hl_substitution = None;
 | 
			
		||||
 | 
			
		||||
	while(true) {
 | 
			
		||||
		uint8_t operation = accessor.byte();
 | 
			
		||||
		const uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
		switch(x(operation)) {
 | 
			
		||||
			case 0:
 | 
			
		||||
@@ -343,15 +369,18 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
					break;
 | 
			
		||||
					case 4:
 | 
			
		||||
						instruction.operation = Instruction::Operation::INC;
 | 
			
		||||
						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					break;
 | 
			
		||||
					case 5:
 | 
			
		||||
						instruction.operation = Instruction::Operation::DEC;
 | 
			
		||||
						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					break;
 | 
			
		||||
					case 6:
 | 
			
		||||
						instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = Instruction::Location::Operand;
 | 
			
		||||
						instruction.operand = accessor.byte();
 | 
			
		||||
					break;
 | 
			
		||||
@@ -374,8 +403,10 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
					instruction.operation = Instruction::Operation::HALT;
 | 
			
		||||
				} else {
 | 
			
		||||
					instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
					instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					instruction.source =
 | 
			
		||||
						RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					instruction.destination =
 | 
			
		||||
						RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
				}
 | 
			
		||||
			break;
 | 
			
		||||
			case 2:
 | 
			
		||||
@@ -517,10 +548,14 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
			instruction.destination == Instruction::Location::HL_Indirect) {
 | 
			
		||||
 | 
			
		||||
			if(instruction.source == Instruction::Location::HL_Indirect) {
 | 
			
		||||
				instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
				instruction.source =
 | 
			
		||||
					hl_substitution == IX ?
 | 
			
		||||
						Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
			}
 | 
			
		||||
			if(instruction.destination == Instruction::Location::HL_Indirect) {
 | 
			
		||||
				instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
				instruction.destination =
 | 
			
		||||
					hl_substitution == IX ?
 | 
			
		||||
						Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
			}
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
@@ -542,7 +577,12 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Z80Disassembler {
 | 
			
		||||
	static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
 | 
			
		||||
	static void AddToDisassembly(
 | 
			
		||||
		PartialDisassembly &disassembly,
 | 
			
		||||
		const std::vector<uint8_t> &memory,
 | 
			
		||||
		const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
		const uint16_t entry_point
 | 
			
		||||
	) {
 | 
			
		||||
		disassembly.disassembly.internal_calls.insert(entry_point);
 | 
			
		||||
		Accessor accessor(memory, address_mapper, entry_point);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,14 @@
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../AppleII/Target.hpp"
 | 
			
		||||
#include "../AppleIIgs/Target.hpp"
 | 
			
		||||
#include "../Oric/Target.hpp"
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
#include "../Disassembler/AddressMapper.hpp"
 | 
			
		||||
#include "Analyser/Static/AppleII/Target.hpp"
 | 
			
		||||
#include "Analyser/Static//AppleIIgs/Target.hpp"
 | 
			
		||||
#include "Analyser/Static//Oric/Target.hpp"
 | 
			
		||||
#include "Analyser/Static//Disassembler/6502.hpp"
 | 
			
		||||
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
 | 
			
		||||
#include "Storage/Disk/Track/TrackSerialiser.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
@@ -47,7 +47,12 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
@@ -55,14 +60,15 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
 | 
			
		||||
	// If the disk image is too large for a 5.25" disk, map this to the IIgs.
 | 
			
		||||
	if(disk->get_maximum_head_position() > Storage::Disk::HeadPosition(40)) {
 | 
			
		||||
	if(disk->maximum_head_position() > Storage::Disk::HeadPosition(40)) {
 | 
			
		||||
		targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIIgsTarget()));
 | 
			
		||||
		targets.back()->media = media;
 | 
			
		||||
		return targets;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Grab track 0, sector 0: the boot sector.
 | 
			
		||||
	const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
			
		||||
	const auto track_zero =
 | 
			
		||||
		disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
			
		||||
	const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
 | 
			
		||||
		Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
 | 
			
		||||
 | 
			
		||||
@@ -89,7 +95,8 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
 | 
			
		||||
	// If the boot sector looks like it's intended for the Oric, create an Oric.
 | 
			
		||||
	// Otherwise go with the Apple II.
 | 
			
		||||
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
		sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
 | 
			
		||||
 | 
			
		||||
	bool did_read_shift_register = false;
 | 
			
		||||
	bool is_oric = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::DiskII {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Parsers/FAT.hpp"
 | 
			
		||||
#include "Storage/Disk/Parsers/FAT.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +26,12 @@ bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +77,8 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi
 | 
			
		||||
 | 
			
		||||
			if(!has_exdos_ini) {
 | 
			
		||||
				if(did_pick_file) {
 | 
			
		||||
					target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
 | 
			
		||||
					target->loading_command =
 | 
			
		||||
						std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
 | 
			
		||||
				} else {
 | 
			
		||||
					target->loading_command = ":dir\n";
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Enterprise {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
@@ -30,20 +30,22 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	Speed speed = Speed::FourMHz;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Enterprise) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
			AnnounceEnum(EXOSVersion);
 | 
			
		||||
			AnnounceEnum(BASICVersion);
 | 
			
		||||
			AnnounceEnum(DOS);
 | 
			
		||||
			AnnounceEnum(Speed);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Enterprise) {}
 | 
			
		||||
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			DeclareField(exos_version);
 | 
			
		||||
			DeclareField(basic_version);
 | 
			
		||||
			DeclareField(dos);
 | 
			
		||||
			DeclareField(speed);
 | 
			
		||||
		}
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(EXOSVersion);
 | 
			
		||||
		AnnounceEnum(BASICVersion);
 | 
			
		||||
		AnnounceEnum(DOS);
 | 
			
		||||
		AnnounceEnum(Speed);
 | 
			
		||||
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(exos_version);
 | 
			
		||||
		DeclareField(basic_version);
 | 
			
		||||
		DeclareField(dos);
 | 
			
		||||
		DeclareField(speed);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,15 +8,20 @@
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../Enterprise/StaticAnalyser.hpp"
 | 
			
		||||
#include "../PCCompatible/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Enterprise/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/PCCompatible/StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Constants.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
 | 
			
		||||
#include "Storage/Disk/Track/TrackSerialiser.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/SegmentParser.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType platforms) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType platforms,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
@@ -34,12 +39,13 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
 | 
			
		||||
 | 
			
		||||
	// If the disk image is very small or large, map it to the PC. That's the only option old enough
 | 
			
		||||
	// to have used 5.25" media.
 | 
			
		||||
	if(disk->get_maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
 | 
			
		||||
		return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
 | 
			
		||||
	if(disk->maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
 | 
			
		||||
		return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Attempt to grab MFM track 0, sector 1: the boot sector.
 | 
			
		||||
	const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
			
		||||
	const auto track_zero =
 | 
			
		||||
		disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
			
		||||
	const auto sector_map = Storage::Encodings::MFM::sectors_from_segment(
 | 
			
		||||
			Storage::Disk::track_serialisation(
 | 
			
		||||
				*track_zero,
 | 
			
		||||
@@ -48,7 +54,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
 | 
			
		||||
 | 
			
		||||
	// If no sectors were found, assume this disk was either single density or high density, which both imply the PC.
 | 
			
		||||
	if(sector_map.empty() || sector_map.size() > 10) {
 | 
			
		||||
		return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
 | 
			
		||||
		return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
 | 
			
		||||
@@ -77,7 +83,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
 | 
			
		||||
		if(
 | 
			
		||||
			std::search(sample.begin(), sample.end(), string.begin(), string.end()) != sample.end()
 | 
			
		||||
		) {
 | 
			
		||||
			return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
 | 
			
		||||
			return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -96,5 +102,5 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
 | 
			
		||||
	// could redirect to an MSX2 with MSX-DOS2? Though it'd be nicer if I had a machine that was pure CP/M.
 | 
			
		||||
 | 
			
		||||
	// Being unable to prove that this is a PC disk, throw it to the Enterprise.
 | 
			
		||||
	return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms);
 | 
			
		||||
	return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms, false);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::FAT12 {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
#include "Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::MSX {
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +26,7 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
 | 
			
		||||
	};
 | 
			
		||||
	const Type type;
 | 
			
		||||
 | 
			
		||||
	Cartridge(const std::vector<Segment> &segments, Type type) :
 | 
			
		||||
	Cartridge(const std::vector<Segment> &segments, const Type type) :
 | 
			
		||||
		Storage::Cartridge::Cartridge(segments), type(type) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../Disassembler/Z80.hpp"
 | 
			
		||||
#include "../Disassembler/AddressMapper.hpp"
 | 
			
		||||
#include "Analyser/Static/Disassembler/Z80.hpp"
 | 
			
		||||
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
@@ -27,7 +27,8 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
 | 
			
		||||
	std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
 | 
			
		||||
	if(segment.data.size() & 0x1fff) {
 | 
			
		||||
		std::vector<uint8_t> truncated_data;
 | 
			
		||||
		std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
 | 
			
		||||
		const auto truncated_size =
 | 
			
		||||
			std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
 | 
			
		||||
		truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
 | 
			
		||||
		output_segments.emplace_back(start_address, truncated_data);
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -82,7 +83,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
 | 
			
		||||
		Storage::Cartridge::Cartridge::Segment segment = segments.front();
 | 
			
		||||
		const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
			
		||||
		const size_t data_size = segment.data.size();
 | 
			
		||||
		if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
 | 
			
		||||
 | 
			
		||||
@@ -101,7 +102,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
		// Reject cartridge if the ROM header wasn't found.
 | 
			
		||||
		if(!found_start) continue;
 | 
			
		||||
 | 
			
		||||
		uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
 | 
			
		||||
		const uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
 | 
			
		||||
		// TODO: check for a rational init address?
 | 
			
		||||
 | 
			
		||||
		// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
 | 
			
		||||
@@ -137,10 +138,12 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
 | 
			
		||||
		const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
 | 
			
		||||
		const auto ascii_8kb_total =
 | 
			
		||||
			address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
 | 
			
		||||
		const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
 | 
			
		||||
		const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
 | 
			
		||||
		const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
 | 
			
		||||
		const auto konami_with_scc_total =
 | 
			
		||||
			address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
 | 
			
		||||
 | 
			
		||||
		const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
 | 
			
		||||
 | 
			
		||||
@@ -182,7 +185,12 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
	return targets;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
 | 
			
		||||
	// Append targets for any cartridges that look correct.
 | 
			
		||||
@@ -194,7 +202,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
 | 
			
		||||
 | 
			
		||||
	// Check tapes for loadable files.
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		std::vector<File> files_on_tape = GetFiles(tape);
 | 
			
		||||
		const std::vector<File> files_on_tape = GetFiles(tape);
 | 
			
		||||
		if(!files_on_tape.empty()) {
 | 
			
		||||
			switch(files_on_tape.front().type) {
 | 
			
		||||
				case File::Type::ASCII:				target->loading_command = "RUN\"CAS:\r";		break;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::MSX {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/MSX.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/MSX.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::MSX;
 | 
			
		||||
 | 
			
		||||
@@ -29,12 +29,12 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
			
		||||
 | 
			
		||||
	Storage::Tape::BinaryTapePlayer tape_player(1000000);
 | 
			
		||||
	tape_player.set_motor_control(true);
 | 
			
		||||
	tape_player.set_tape(tape);
 | 
			
		||||
	tape_player.set_tape(tape, TargetPlatform::MSX);
 | 
			
		||||
 | 
			
		||||
	using Parser = Storage::Tape::MSX::Parser;
 | 
			
		||||
 | 
			
		||||
	// Get all recognisable files from the tape.
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
	while(!tape_player.is_at_end()) {
 | 
			
		||||
		// Try to locate and measure a header.
 | 
			
		||||
		std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player);
 | 
			
		||||
		if(!file_speed) continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
@@ -32,6 +32,6 @@ struct File {
 | 
			
		||||
	File();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::MSX {
 | 
			
		||||
@@ -33,15 +33,17 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
 | 
			
		||||
	);
 | 
			
		||||
	Region region = Region::USA;
 | 
			
		||||
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::MSX) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(has_disk_drive);
 | 
			
		||||
			DeclareField(has_msx_music);
 | 
			
		||||
			DeclareField(region);
 | 
			
		||||
			AnnounceEnum(Region);
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
		}
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::MSX) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_disk_drive);
 | 
			
		||||
		DeclareField(has_msx_music);
 | 
			
		||||
		DeclareField(region);
 | 
			
		||||
		AnnounceEnum(Region);
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,14 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
			
		||||
	if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
 | 
			
		||||
	if(media.disks.empty() && media.mass_storage_devices.empty() && !is_confident) return {};
 | 
			
		||||
 | 
			
		||||
	// As there is at least one usable media image, wave it through.
 | 
			
		||||
	Analyser::Static::TargetList targets;
 | 
			
		||||
@@ -24,7 +29,7 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media
 | 
			
		||||
	if(media.mass_storage_devices.empty()) {
 | 
			
		||||
		bool has_800kb_disks = false;
 | 
			
		||||
		for(const auto &disk: media.disks) {
 | 
			
		||||
			has_800kb_disks |= disk->get_head_count() > 1;
 | 
			
		||||
			has_800kb_disks |= disk->head_count() > 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(!has_800kb_disks) {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Macintosh {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Macintosh {
 | 
			
		||||
 | 
			
		||||
@@ -18,12 +18,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
 | 
			
		||||
	Model model = Model::MacPlus;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Macintosh) {
 | 
			
		||||
		// Boilerplate for declaring fields and potential values.
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
		}
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Macintosh) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
#include "../Disassembler/AddressMapper.hpp"
 | 
			
		||||
#include "Analyser/Static/Disassembler/6502.hpp"
 | 
			
		||||
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
@@ -22,12 +22,22 @@ using namespace Analyser::Static::Oric;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
 | 
			
		||||
int score(
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly,
 | 
			
		||||
	const std::set<uint16_t> &rom_functions,
 | 
			
		||||
	const std::set<uint16_t> &variable_locations
 | 
			
		||||
) {
 | 
			
		||||
	int score = 0;
 | 
			
		||||
 | 
			
		||||
	for(const auto address : disassembly.outward_calls)		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
			
		||||
	for(const auto address : disassembly.external_stores)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
	for(const auto address : disassembly.external_loads)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
	for(const auto address : disassembly.outward_calls) {
 | 
			
		||||
		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
			
		||||
	}
 | 
			
		||||
	for(const auto address : disassembly.external_stores) {
 | 
			
		||||
		score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
	}
 | 
			
		||||
	for(const auto address : disassembly.external_loads) {
 | 
			
		||||
		score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return score;
 | 
			
		||||
}
 | 
			
		||||
@@ -35,19 +45,32 @@ int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::
 | 
			
		||||
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	const std::set<uint16_t> rom_functions = {
 | 
			
		||||
		0x0228,	0x022b,
 | 
			
		||||
		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
			
		||||
		0xc773,	0xc824,	0xc832,	0xc841,	0xc8c1,	0xc8fe,	0xc91f,	0xc93f,	0xc941,	0xc91e,	0xc98b,	0xc996,	0xc9b3,	0xc9e0,	0xca0a,	0xca1c,
 | 
			
		||||
		0xca1f,	0xca3e,	0xca61,	0xca78,	0xca98,	0xcad2,	0xcb61,	0xcb9f,	0xcc59,	0xcbed,	0xcc0a,	0xcc8c,	0xcc8f,	0xccba,	0xccc9,	0xccfd,
 | 
			
		||||
		0xce0c,	0xce77,	0xce8b,	0xcfac,	0xcf74,	0xd03c,	0xd059,	0xcff0,	0xd087,	0xd0f2,	0xd0fc,	0xd361,	0xd3eb,	0xd47e,	0xd4a6,	0xd401,
 | 
			
		||||
		0xd593,	0xd5a3,	0xd4fa,	0xd595,	0xd730,	0xd767,	0xd816,	0xd82a,	0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd80a,	0xd867,	0xd938,	0xd894,
 | 
			
		||||
		0xd89d,	0xd8ac,	0xd983,	0xd993,	0xd9b5,	0xd93d,	0xd965,	0xda3f,	0xd9c6,	0xda16,	0xdaab,	0xdada,	0xda6b,	0xdb92,	0xdbb9,	0xdc79,
 | 
			
		||||
		0xdd4d,	0xdda3,	0xddbf,	0xd0d0,	0xde77,	0xdef4,	0xdf0b,	0xdf0f,	0xdf04,	0xdf12,	0xdf31,	0xdf4c,	0xdf8c,	0xdfa5,	0xdfcf,	0xe076,
 | 
			
		||||
		0xe0c1,	0xe22a,	0xe27c,	0xe2a6,	0xe313,	0xe34b,	0xe387,	0xe38e,	0xe3d7,	0xe407,	0xe43b,	0xe46f,	0xe4a8,	0xe4f2,	0xe554,	0xe57d,
 | 
			
		||||
		0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,	0xe5b6,	0xe5ea,	0xe563,	0xe5c6,	0xe630,	0xe696,	0xe6ba,	0xe6ca,	0xe725,	0xe7aa,	0xe903,
 | 
			
		||||
		0xe7db,	0xe80d,	0xe987,	0xe9d1,	0xe87d,	0xe905,	0xe965,	0xe974,	0xe994,	0xe9a9,	0xe9bb,	0xec45,	0xeccc,	0xedc4,	0xecc7,	0xed01,
 | 
			
		||||
		0xed09,	0xed70,	0xed81,	0xed8f,	0xe0ad,	0xeee8,	0xeef8,	0xebdf,	0xebe2,	0xebe5,	0xebeb,	0xebee,	0xebf4,	0xebf7,	0xebfa,	0xebe8,
 | 
			
		||||
		0xf43c,	0xf4ef,	0xf523,	0xf561,	0xf535,	0xf57b,	0xf5d3,	0xf71a,	0xf73f,	0xf7e4,	0xf7e0,	0xf82f,	0xf88f,	0xf8af,	0xf8b5,	0xf920,
 | 
			
		||||
		0xf967,	0xf960,	0xf9c9,	0xfa14,	0xfa85,	0xfa9b,	0xfab1,	0xfac7,	0xfafa,	0xfb10,	0xfb26,	0xfbb6,	0xfbfe
 | 
			
		||||
		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,
 | 
			
		||||
		0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
			
		||||
		0xc773,	0xc824,	0xc832,	0xc841,	0xc8c1,	0xc8fe,	0xc91f,	0xc93f,
 | 
			
		||||
		0xc941,	0xc91e,	0xc98b,	0xc996,	0xc9b3,	0xc9e0,	0xca0a,	0xca1c,
 | 
			
		||||
		0xca1f,	0xca3e,	0xca61,	0xca78,	0xca98,	0xcad2,	0xcb61,	0xcb9f,
 | 
			
		||||
		0xcc59,	0xcbed,	0xcc0a,	0xcc8c,	0xcc8f,	0xccba,	0xccc9,	0xccfd,
 | 
			
		||||
		0xce0c,	0xce77,	0xce8b,	0xcfac,	0xcf74,	0xd03c,	0xd059,	0xcff0,
 | 
			
		||||
		0xd087,	0xd0f2,	0xd0fc,	0xd361,	0xd3eb,	0xd47e,	0xd4a6,	0xd401,
 | 
			
		||||
		0xd593,	0xd5a3,	0xd4fa,	0xd595,	0xd730,	0xd767,	0xd816,	0xd82a,
 | 
			
		||||
		0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd80a,	0xd867,	0xd938,	0xd894,
 | 
			
		||||
		0xd89d,	0xd8ac,	0xd983,	0xd993,	0xd9b5,	0xd93d,	0xd965,	0xda3f,
 | 
			
		||||
		0xd9c6,	0xda16,	0xdaab,	0xdada,	0xda6b,	0xdb92,	0xdbb9,	0xdc79,
 | 
			
		||||
		0xdd4d,	0xdda3,	0xddbf,	0xd0d0,	0xde77,	0xdef4,	0xdf0b,	0xdf0f,
 | 
			
		||||
		0xdf04,	0xdf12,	0xdf31,	0xdf4c,	0xdf8c,	0xdfa5,	0xdfcf,	0xe076,
 | 
			
		||||
		0xe0c1,	0xe22a,	0xe27c,	0xe2a6,	0xe313,	0xe34b,	0xe387,	0xe38e,
 | 
			
		||||
		0xe3d7,	0xe407,	0xe43b,	0xe46f,	0xe4a8,	0xe4f2,	0xe554,	0xe57d,
 | 
			
		||||
		0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,	0xe5b6,	0xe5ea,	0xe563,
 | 
			
		||||
		0xe5c6,	0xe630,	0xe696,	0xe6ba,	0xe6ca,	0xe725,	0xe7aa,	0xe903,
 | 
			
		||||
		0xe7db,	0xe80d,	0xe987,	0xe9d1,	0xe87d,	0xe905,	0xe965,	0xe974,
 | 
			
		||||
		0xe994,	0xe9a9,	0xe9bb,	0xec45,	0xeccc,	0xedc4,	0xecc7,	0xed01,
 | 
			
		||||
		0xed09,	0xed70,	0xed81,	0xed8f,	0xe0ad,	0xeee8,	0xeef8,	0xebdf,
 | 
			
		||||
		0xebe2,	0xebe5,	0xebeb,	0xebee,	0xebf4,	0xebf7,	0xebfa,	0xebe8,
 | 
			
		||||
		0xf43c,	0xf4ef,	0xf523,	0xf561,	0xf535,	0xf57b,	0xf5d3,	0xf71a,
 | 
			
		||||
		0xf73f,	0xf7e4,	0xf7e0,	0xf82f,	0xf88f,	0xf8af,	0xf8b5,	0xf920,
 | 
			
		||||
		0xf967,	0xf960,	0xf9c9,	0xfa14,	0xfa85,	0xfa9b,	0xfab1,	0xfac7,
 | 
			
		||||
		0xfafa,	0xfb10,	0xfb26,	0xfbb6,	0xfbfe
 | 
			
		||||
	};
 | 
			
		||||
	const std::set<uint16_t> variable_locations = {
 | 
			
		||||
		0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
 | 
			
		||||
@@ -59,19 +82,32 @@ int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	const std::set<uint16_t> rom_functions = {
 | 
			
		||||
		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247,
 | 
			
		||||
		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
			
		||||
		0xc748,	0xc7fd,	0xc809,	0xc816,	0xc82f,	0xc855,	0xc8c1,	0xc915,	0xc952,	0xc971,	0xc973,	0xc9a0,	0xc9bd,	0xc9c8,	0xc9e5,	0xca12,
 | 
			
		||||
		0xca3c,	0xca4e,	0xca51,	0xca70,	0xca99,	0xcac2,	0xcae2,	0xcb1c,	0xcbab,	0xcbf0,	0xcc59,	0xccb0,	0xccce,	0xcd16,	0xcd19,	0xcd46,
 | 
			
		||||
		0xcd55,	0xcd89,	0xce98,	0xcf03,	0xcf17,	0xcfac,	0xd000,	0xd03c,	0xd059,	0xd07c,	0xd113,	0xd17e,	0xd188,	0xd361,	0xd3eb,	0xd47e,
 | 
			
		||||
		0xd4a6,	0xd4ba,	0xd593,	0xd5a3,	0xd5b5,	0xd650,	0xd730,	0xd767,	0xd816,	0xd82a,	0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd8c5,	0xd922,
 | 
			
		||||
		0xd938,	0xd94f,	0xd958,	0xd967,	0xd983,	0xd993,	0xd9b5,	0xd9de,	0xda0c,	0xda3f,	0xda51,	0xdaa1,	0xdaab,	0xdada,	0xdaf6,	0xdb92,
 | 
			
		||||
		0xdbb9,	0xdcaf,	0xdd51,	0xdda7,	0xddc3,	0xddd4,	0xde77,	0xdef4,	0xdf0b,	0xdf0f,	0xdf13,	0xdf21,	0xdf49,	0xdf4c,	0xdf8c,	0xdfbd,
 | 
			
		||||
		0xdfe7,	0xe076,	0xe0c5,	0xe22e,	0xe27c,	0xe2aa,	0xe313,	0xe34f,	0xe38b,	0xe392,	0xe3db,	0xe407,	0xe43f,	0xe46f,	0xe4ac,	0xe4e0,
 | 
			
		||||
		0xe4f2,	0xe56c,	0xe57d,	0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,	0xe5b6,	0xe5ea,	0xe5f5,	0xe607,	0xe65e,	0xe6c9,	0xe735,	0xe75a,
 | 
			
		||||
		0xe76a,	0xe7b2,	0xe85b,	0xe903,	0xe909,	0xe946,	0xe987,	0xe9d1,	0xeaf0,	0xeb78,	0xebce,	0xebe7,	0xec0c,	0xec21,	0xec33,	0xec45,
 | 
			
		||||
		0xeccc,	0xedc4,	0xede0,	0xee1a,	0xee22,	0xee8c,	0xee9d,	0xeeab,	0xeec9,	0xeee8,	0xeef8,	0xf0c8,	0xf0fd,	0xf110,	0xf11d,	0xf12d,
 | 
			
		||||
		0xf204,	0xf210,	0xf268,	0xf37f,	0xf495,	0xf4ef,	0xf523,	0xf561,	0xf590,	0xf5c1,	0xf602,	0xf71a,	0xf77c,	0xf7e4,	0xf816,	0xf865,
 | 
			
		||||
		0xf88f,	0xf8af,	0xf8b5,	0xf920,	0xf967,	0xf9aa,	0xf9c9,	0xfa14,	0xfa9f,	0xfab5,	0xfacb,	0xfae1,	0xfb14,	0xfb2a,	0xfb40,	0xfbd0,
 | 
			
		||||
		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,
 | 
			
		||||
		0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
			
		||||
		0xc748,	0xc7fd,	0xc809,	0xc816,	0xc82f,	0xc855,	0xc8c1,	0xc915,
 | 
			
		||||
		0xc952,	0xc971,	0xc973,	0xc9a0,	0xc9bd,	0xc9c8,	0xc9e5,	0xca12,
 | 
			
		||||
		0xca3c,	0xca4e,	0xca51,	0xca70,	0xca99,	0xcac2,	0xcae2,	0xcb1c,
 | 
			
		||||
		0xcbab,	0xcbf0,	0xcc59,	0xccb0,	0xccce,	0xcd16,	0xcd19,	0xcd46,
 | 
			
		||||
		0xcd55,	0xcd89,	0xce98,	0xcf03,	0xcf17,	0xcfac,	0xd000,	0xd03c,
 | 
			
		||||
		0xd059,	0xd07c,	0xd113,	0xd17e,	0xd188,	0xd361,	0xd3eb,	0xd47e,
 | 
			
		||||
		0xd4a6,	0xd4ba,	0xd593,	0xd5a3,	0xd5b5,	0xd650,	0xd730,	0xd767,
 | 
			
		||||
		0xd816,	0xd82a,	0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd8c5,	0xd922,
 | 
			
		||||
		0xd938,	0xd94f,	0xd958,	0xd967,	0xd983,	0xd993,	0xd9b5,	0xd9de,
 | 
			
		||||
		0xda0c,	0xda3f,	0xda51,	0xdaa1,	0xdaab,	0xdada,	0xdaf6,	0xdb92,
 | 
			
		||||
		0xdbb9,	0xdcaf,	0xdd51,	0xdda7,	0xddc3,	0xddd4,	0xde77,	0xdef4,
 | 
			
		||||
		0xdf0b,	0xdf0f,	0xdf13,	0xdf21,	0xdf49,	0xdf4c,	0xdf8c,	0xdfbd,
 | 
			
		||||
		0xdfe7,	0xe076,	0xe0c5,	0xe22e,	0xe27c,	0xe2aa,	0xe313,	0xe34f,
 | 
			
		||||
		0xe38b,	0xe392,	0xe3db,	0xe407,	0xe43f,	0xe46f,	0xe4ac,	0xe4e0,
 | 
			
		||||
		0xe4f2,	0xe56c,	0xe57d,	0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,
 | 
			
		||||
		0xe5b6,	0xe5ea,	0xe5f5,	0xe607,	0xe65e,	0xe6c9,	0xe735,	0xe75a,
 | 
			
		||||
		0xe76a,	0xe7b2,	0xe85b,	0xe903,	0xe909,	0xe946,	0xe987,	0xe9d1,
 | 
			
		||||
		0xeaf0,	0xeb78,	0xebce,	0xebe7,	0xec0c,	0xec21,	0xec33,	0xec45,
 | 
			
		||||
		0xeccc,	0xedc4,	0xede0,	0xee1a,	0xee22,	0xee8c,	0xee9d,	0xeeab,
 | 
			
		||||
		0xeec9,	0xeee8,	0xeef8,	0xf0c8,	0xf0fd,	0xf110,	0xf11d,	0xf12d,
 | 
			
		||||
		0xf204,	0xf210,	0xf268,	0xf37f,	0xf495,	0xf4ef,	0xf523,	0xf561,
 | 
			
		||||
		0xf590,	0xf5c1,	0xf602,	0xf71a,	0xf77c,	0xf7e4,	0xf816,	0xf865,
 | 
			
		||||
		0xf88f,	0xf8af,	0xf8b5,	0xf920,	0xf967,	0xf9aa,	0xf9c9,	0xfa14,
 | 
			
		||||
		0xfa9f,	0xfab5,	0xfacb,	0xfae1,	0xfb14,	0xfb2a,	0xfb40,	0xfbd0,
 | 
			
		||||
		0xfc18
 | 
			
		||||
	};
 | 
			
		||||
	const std::set<uint16_t> variable_locations = {
 | 
			
		||||
@@ -102,7 +138,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
 | 
			
		||||
	return !std::memcmp(signature, first_sample.data(), sizeof(signature));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
 | 
			
		||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range_start, const uint16_t range_end) {
 | 
			
		||||
	/*
 | 
			
		||||
		Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
 | 
			
		||||
		use disassembly to test for likely matches.
 | 
			
		||||
@@ -120,8 +156,8 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Grab a disassembly.
 | 
			
		||||
	const auto disassembly =
 | 
			
		||||
		Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
		first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
 | 
			
		||||
 | 
			
		||||
	// Check for references to the Jasmin registers.
 | 
			
		||||
	int register_hits = 0;
 | 
			
		||||
@@ -145,7 +181,12 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
 | 
			
		||||
@@ -153,14 +194,18 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
 | 
			
		||||
	int basic11_votes = 0;
 | 
			
		||||
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
		auto serialiser = tape->serialiser();
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(*serialiser);
 | 
			
		||||
		if(!tape_files.empty()) {
 | 
			
		||||
			for(const auto &file : tape_files) {
 | 
			
		||||
				if(file.data_type == File::MachineCode) {
 | 
			
		||||
					std::vector<uint16_t> entry_points = {file.starting_address};
 | 
			
		||||
					const Analyser::Static::MOS6502::Disassembly disassembly =
 | 
			
		||||
						Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
 | 
			
		||||
						Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
							file.data,
 | 
			
		||||
							Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
 | 
			
		||||
							entry_points
 | 
			
		||||
						);
 | 
			
		||||
 | 
			
		||||
					if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Oric {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,61 +7,61 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Oric.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Oric.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Oric;
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
std::vector<File> Analyser::Static::Oric::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	Storage::Tape::Oric::Parser parser;
 | 
			
		||||
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
		// sync to next lead-in, check that it's one of three 0x16s
 | 
			
		||||
		bool is_fast = parser.sync_and_get_encoding_speed(tape);
 | 
			
		||||
		bool is_fast = parser.sync_and_get_encoding_speed(serialiser);
 | 
			
		||||
		int next_bytes[2];
 | 
			
		||||
		next_bytes[0] = parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		next_bytes[1] = parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		next_bytes[0] = parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		next_bytes[1] = parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
 | 
			
		||||
		if(next_bytes[0] != 0x16 || next_bytes[1] != 0x16) continue;
 | 
			
		||||
 | 
			
		||||
		// get the first byte that isn't a 0x16, check it was a 0x24
 | 
			
		||||
		int byte = 0x16;
 | 
			
		||||
		while(!tape->is_at_end() && byte == 0x16) {
 | 
			
		||||
			byte = parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		while(!serialiser.is_at_end() && byte == 0x16) {
 | 
			
		||||
			byte = parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		}
 | 
			
		||||
		if(byte != 0x24) continue;
 | 
			
		||||
 | 
			
		||||
		// skip two empty bytes
 | 
			
		||||
		parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
 | 
			
		||||
		// get data and launch types
 | 
			
		||||
		File new_file;
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast)) {
 | 
			
		||||
		switch(parser.get_next_byte(serialiser, is_fast)) {
 | 
			
		||||
			case 0x00:	new_file.data_type = File::ProgramType::BASIC;			break;
 | 
			
		||||
			case 0x80:	new_file.data_type = File::ProgramType::MachineCode;	break;
 | 
			
		||||
			default:	new_file.data_type = File::ProgramType::None;			break;
 | 
			
		||||
		}
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast)) {
 | 
			
		||||
		switch(parser.get_next_byte(serialiser, is_fast)) {
 | 
			
		||||
			case 0x80:	new_file.launch_type = File::ProgramType::BASIC;		break;
 | 
			
		||||
			case 0xc7:	new_file.launch_type = File::ProgramType::MachineCode;	break;
 | 
			
		||||
			default:	new_file.launch_type = File::ProgramType::None;			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// read end and start addresses
 | 
			
		||||
		new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
		new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
		new_file.ending_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
 | 
			
		||||
		new_file.ending_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
 | 
			
		||||
		new_file.starting_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
 | 
			
		||||
		new_file.starting_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
 | 
			
		||||
 | 
			
		||||
		// skip an empty byte
 | 
			
		||||
		parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
 | 
			
		||||
		// read file name, up to 16 characters and null terminated
 | 
			
		||||
		char file_name[17];
 | 
			
		||||
		int name_pos = 0;
 | 
			
		||||
		while(name_pos < 16) {
 | 
			
		||||
			file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
			file_name[name_pos] = char(parser.get_next_byte(serialiser, is_fast));
 | 
			
		||||
			if(!file_name[name_pos]) break;
 | 
			
		||||
			name_pos++;
 | 
			
		||||
		}
 | 
			
		||||
@@ -72,11 +72,11 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
 | 
			
		||||
		std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
 | 
			
		||||
		new_file.data.reserve(body_length);
 | 
			
		||||
		for(std::size_t c = 0; c < body_length; c++) {
 | 
			
		||||
			new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
 | 
			
		||||
			new_file.data.push_back(uint8_t(parser.get_next_byte(serialiser, is_fast)));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// only one validation check: was there enough tape?
 | 
			
		||||
		if(!tape->is_at_end()) {
 | 
			
		||||
		if(!serialiser.is_at_end()) {
 | 
			
		||||
			files.push_back(new_file);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
@@ -28,6 +28,6 @@ struct File {
 | 
			
		||||
	std::vector<uint8_t> data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Oric {
 | 
			
		||||
@@ -41,15 +41,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
	bool should_start_jasmin = false;
 | 
			
		||||
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::Oric) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(rom);
 | 
			
		||||
			DeclareField(disk_interface);
 | 
			
		||||
			DeclareField(processor);
 | 
			
		||||
			AnnounceEnum(ROM);
 | 
			
		||||
			AnnounceEnum(DiskInterface);
 | 
			
		||||
			AnnounceEnum(Processor);
 | 
			
		||||
		}
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::Oric) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(rom);
 | 
			
		||||
		DeclareField(disk_interface);
 | 
			
		||||
		DeclareField(processor);
 | 
			
		||||
		AnnounceEnum(ROM);
 | 
			
		||||
		AnnounceEnum(DiskInterface);
 | 
			
		||||
		AnnounceEnum(Processor);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,12 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::PCCompatible {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,29 +8,46 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::PCCompatible {
 | 
			
		||||
 | 
			
		||||
ReflectableEnum(Model,
 | 
			
		||||
	XT,
 | 
			
		||||
	TurboXT,
 | 
			
		||||
	AT
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
constexpr bool is_xt(const Model model) {
 | 
			
		||||
	return model <= Model::TurboXT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr bool is_at(const Model model) {
 | 
			
		||||
	return model >= Model::AT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr bool has_ide(const Model model) {
 | 
			
		||||
	return model >= Model::AT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(VideoAdaptor,
 | 
			
		||||
		MDA,
 | 
			
		||||
		CGA);
 | 
			
		||||
		CGA,
 | 
			
		||||
	);
 | 
			
		||||
	VideoAdaptor adaptor = VideoAdaptor::CGA;
 | 
			
		||||
	Model model = Model::TurboXT;
 | 
			
		||||
 | 
			
		||||
	ReflectableEnum(Speed,
 | 
			
		||||
		ApproximatelyOriginal,
 | 
			
		||||
		Fast);
 | 
			
		||||
	Speed speed = Speed::Fast;
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::PCCompatible) {}
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::PCCompatible) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			AnnounceEnum(VideoAdaptor);
 | 
			
		||||
			AnnounceEnum(Speed);
 | 
			
		||||
			DeclareField(adaptor);
 | 
			
		||||
			DeclareField(speed);
 | 
			
		||||
		}
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		AnnounceEnum(VideoAdaptor);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		DeclareField(adaptor);
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,12 @@
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	if(media.cartridges.empty())
 | 
			
		||||
		return {};
 | 
			
		||||
 | 
			
		||||
@@ -54,7 +59,8 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
 | 
			
		||||
					if(lowercase_name.find("(jp)") == std::string::npos) {
 | 
			
		||||
						target->region =
 | 
			
		||||
							(lowercase_name.find("(us)") == std::string::npos &&
 | 
			
		||||
							lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
 | 
			
		||||
							lowercase_name.find("(ntsc)") == std::string::npos) ?
 | 
			
		||||
								Target::Region::Europe : Target::Region::USA;
 | 
			
		||||
					}
 | 
			
		||||
				} break;
 | 
			
		||||
			}
 | 
			
		||||
@@ -63,9 +69,9 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
 | 
			
		||||
			// If one is found, set the paging scheme appropriately.
 | 
			
		||||
			const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
 | 
			
		||||
			if(
 | 
			
		||||
				data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 &&									// i.e. game is dated between 1987 and 1996
 | 
			
		||||
				data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 &&		// i.e. game is dated between 1987 and 1996
 | 
			
		||||
				(inverse_checksum&0xff) == data[0x7fe8] &&
 | 
			
		||||
				(inverse_checksum >> 8) == data[0x7fe9] &&										// i.e. the standard checksum appears to be present
 | 
			
		||||
				(inverse_checksum >> 8) == data[0x7fe9] &&			// i.e. the standard checksum appears to be present.
 | 
			
		||||
				!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
 | 
			
		||||
			) {
 | 
			
		||||
				target->paging_scheme = Target::PagingScheme::Codemasters;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Sega {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Sega {
 | 
			
		||||
 | 
			
		||||
@@ -37,15 +37,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	Region region = Region::Japan;
 | 
			
		||||
	PagingScheme paging_scheme = PagingScheme::Sega;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::MasterSystem) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(region);
 | 
			
		||||
			AnnounceEnum(Region);
 | 
			
		||||
		}
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::MasterSystem) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(region);
 | 
			
		||||
		AnnounceEnum(Region);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
 | 
			
		||||
constexpr bool is_master_system(const Analyser::Static::Sega::Target::Model model) {
 | 
			
		||||
	return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,82 +9,85 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <bit>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
 | 
			
		||||
// Analysers
 | 
			
		||||
#include "Acorn/StaticAnalyser.hpp"
 | 
			
		||||
#include "Amiga/StaticAnalyser.hpp"
 | 
			
		||||
#include "AmstradCPC/StaticAnalyser.hpp"
 | 
			
		||||
#include "AppleII/StaticAnalyser.hpp"
 | 
			
		||||
#include "AppleIIgs/StaticAnalyser.hpp"
 | 
			
		||||
#include "Atari2600/StaticAnalyser.hpp"
 | 
			
		||||
#include "AtariST/StaticAnalyser.hpp"
 | 
			
		||||
#include "Coleco/StaticAnalyser.hpp"
 | 
			
		||||
#include "Commodore/StaticAnalyser.hpp"
 | 
			
		||||
#include "DiskII/StaticAnalyser.hpp"
 | 
			
		||||
#include "Enterprise/StaticAnalyser.hpp"
 | 
			
		||||
#include "FAT12/StaticAnalyser.hpp"
 | 
			
		||||
#include "Macintosh/StaticAnalyser.hpp"
 | 
			
		||||
#include "MSX/StaticAnalyser.hpp"
 | 
			
		||||
#include "Oric/StaticAnalyser.hpp"
 | 
			
		||||
#include "PCCompatible/StaticAnalyser.hpp"
 | 
			
		||||
#include "Sega/StaticAnalyser.hpp"
 | 
			
		||||
#include "ZX8081/StaticAnalyser.hpp"
 | 
			
		||||
#include "ZXSpectrum/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Acorn/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Amiga/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AmstradCPC/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AppleII/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AppleIIgs/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Atari2600/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AtariST/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Coleco/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Commodore/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/DiskII/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Enterprise/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/FAT12/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Macintosh/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/MSX/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Oric/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/PCCompatible/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Sega/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/ZX8081/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/ZXSpectrum/StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
// Cartridges
 | 
			
		||||
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
 | 
			
		||||
#include "../../Storage/Cartridge/Formats/PRG.hpp"
 | 
			
		||||
#include "Storage/Cartridge/Formats/BinaryDump.hpp"
 | 
			
		||||
#include "Storage/Cartridge/Formats/PRG.hpp"
 | 
			
		||||
 | 
			
		||||
// Disks
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/IMD.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/PCBooter.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/2MG.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/AcornADF.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/D64.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/G64.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/DMK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/FAT12.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/HFE.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/IPF.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/IMD.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/JFD.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/MSA.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/NIB.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/PCBooter.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/SSD.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/STX.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
			
		||||
 | 
			
		||||
// Mass Storage Devices (i.e. usually, hard disks)
 | 
			
		||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/Formats/HDV.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/DAT.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/DSK.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/HDV.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/HFV.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/VHD.hpp"
 | 
			
		||||
 | 
			
		||||
// State Snapshots
 | 
			
		||||
#include "../../Storage/State/SNA.hpp"
 | 
			
		||||
#include "../../Storage/State/SZX.hpp"
 | 
			
		||||
#include "../../Storage/State/Z80.hpp"
 | 
			
		||||
#include "Storage/State/SNA.hpp"
 | 
			
		||||
#include "Storage/State/SZX.hpp"
 | 
			
		||||
#include "Storage/State/Z80.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"
 | 
			
		||||
#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp"
 | 
			
		||||
#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"
 | 
			
		||||
#include "Storage/Tape/Formats/ZXSpectrumTAP.hpp"
 | 
			
		||||
 | 
			
		||||
// Target Platform Types
 | 
			
		||||
#include "../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
 | 
			
		||||
template<class> inline constexpr bool always_false_v = false;
 | 
			
		||||
 | 
			
		||||
@@ -104,14 +107,14 @@ std::string get_extension(const std::string &name) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MediaAccumulator {
 | 
			
		||||
	public:
 | 
			
		||||
public:
 | 
			
		||||
	MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
 | 
			
		||||
		file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
 | 
			
		||||
 | 
			
		||||
	/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
 | 
			
		||||
	/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
 | 
			
		||||
	template <typename InstanceT>
 | 
			
		||||
	void insert(TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
 | 
			
		||||
	void insert(const TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
 | 
			
		||||
		if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
 | 
			
		||||
			media.disks.push_back(instance);
 | 
			
		||||
		} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
 | 
			
		||||
@@ -127,20 +130,23 @@ class MediaAccumulator {
 | 
			
		||||
		potential_platforms_ |= platforms;
 | 
			
		||||
 | 
			
		||||
		// Check whether the instance itself has any input on target platforms.
 | 
			
		||||
		TargetPlatform::TypeDistinguisher *const distinguisher =
 | 
			
		||||
			dynamic_cast<TargetPlatform::TypeDistinguisher *>(instance.get());
 | 
			
		||||
		if(distinguisher) potential_platforms_ &= distinguisher->target_platform_type();
 | 
			
		||||
		TargetPlatform::Distinguisher *const distinguisher =
 | 
			
		||||
			dynamic_cast<TargetPlatform::Distinguisher *>(instance.get());
 | 
			
		||||
		if(distinguisher) {
 | 
			
		||||
			was_distinguished = true;
 | 
			
		||||
			potential_platforms_ &= distinguisher->target_platforms();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
 | 
			
		||||
	template <typename InstanceT, typename... Args>
 | 
			
		||||
	void insert(TargetPlatform::IntType platforms, Args &&... args) {
 | 
			
		||||
	void insert(const TargetPlatform::IntType platforms, Args &&... args) {
 | 
			
		||||
		insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
 | 
			
		||||
	template <typename InstanceT, typename... Args>
 | 
			
		||||
	void try_insert(TargetPlatform::IntType platforms, Args &&... args) {
 | 
			
		||||
	void try_insert(const TargetPlatform::IntType platforms, Args &&... args) {
 | 
			
		||||
		try {
 | 
			
		||||
			insert<InstanceT>(platforms, std::forward<Args>(args)...);
 | 
			
		||||
		} catch(...) {}
 | 
			
		||||
@@ -149,22 +155,23 @@ class MediaAccumulator {
 | 
			
		||||
	/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
 | 
			
		||||
	/// providing the file name as the only construction argument.
 | 
			
		||||
	template <typename InstanceT>
 | 
			
		||||
	void try_standard(TargetPlatform::IntType platforms, const char *extension) {
 | 
			
		||||
	void try_standard(const TargetPlatform::IntType platforms, const char *extension) {
 | 
			
		||||
		if(name_matches(extension))	{
 | 
			
		||||
			try_insert<InstanceT>(platforms, file_name_);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool name_matches(const char *extension) {
 | 
			
		||||
	bool name_matches(const char *const extension) {
 | 
			
		||||
		return extension_ == extension;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Media media;
 | 
			
		||||
	bool was_distinguished = false;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::string &file_name_;
 | 
			
		||||
		TargetPlatform::IntType &potential_platforms_;
 | 
			
		||||
		const std::string extension_;
 | 
			
		||||
private:
 | 
			
		||||
	const std::string &file_name_;
 | 
			
		||||
	TargetPlatform::IntType &potential_platforms_;
 | 
			
		||||
	const std::string extension_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -201,6 +208,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
 | 
			
		||||
 | 
			
		||||
@@ -209,7 +217,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
 | 
			
		||||
	accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore, "d64");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
 | 
			
		||||
	accumulator.try_standard<MassStorage::DAT>(TargetPlatform::Acorn, "dat");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::DMK>>(TargetPlatform::MSX, "dmk");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "do");
 | 
			
		||||
@@ -223,11 +231,12 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::MSX, "dsk");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::OricMFMDSK>>(TargetPlatform::Oric, "dsk");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore, "g64");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore8bit, "g64");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
 | 
			
		||||
		TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
 | 
			
		||||
		TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore |
 | 
			
		||||
		TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
 | 
			
		||||
		"hfe");	// TODO: switch to AllDisk once the MSX stops being so greedy.
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
 | 
			
		||||
@@ -264,13 +273,14 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p81");
 | 
			
		||||
 | 
			
		||||
	static constexpr auto PRGTargets = TargetPlatform::Vic20; //Commodore8bit;	// Disabled until analysis improves.
 | 
			
		||||
	if(accumulator.name_matches("prg")) {
 | 
			
		||||
		// Try instantiating as a ROM; failing that accept as a tape.
 | 
			
		||||
		try {
 | 
			
		||||
			accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore, file_name);
 | 
			
		||||
			accumulator.insert<Cartridge::PRG>(PRGTargets, file_name);
 | 
			
		||||
		} catch(...) {
 | 
			
		||||
			try {
 | 
			
		||||
				accumulator.insert<Tape::PRG>(TargetPlatform::Commodore, file_name);
 | 
			
		||||
				accumulator.insert<Tape::PRG>(PRGTargets, file_name);
 | 
			
		||||
			} catch(...) {}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -285,7 +295,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore, "tap");
 | 
			
		||||
	accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore8bit, "tap");
 | 
			
		||||
	accumulator.try_standard<Tape::OricTAP>(TargetPlatform::Oric, "tap");
 | 
			
		||||
	accumulator.try_standard<Tape::ZXSpectrumTAP>(TargetPlatform::ZXSpectrum, "tap");
 | 
			
		||||
	accumulator.try_standard<Tape::TZX>(TargetPlatform::MSX, "tsx");
 | 
			
		||||
@@ -293,6 +303,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<MassStorage::VHD>(TargetPlatform::PCCompatible, "vhd");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
 | 
			
		||||
 | 
			
		||||
	return accumulator.media;
 | 
			
		||||
@@ -334,14 +346,24 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
			
		||||
	TargetPlatform::IntType potential_platforms = 0;
 | 
			
		||||
	Media media = GetMediaAndPlatforms(file_name, potential_platforms);
 | 
			
		||||
 | 
			
		||||
	int total_options = std::popcount(potential_platforms);
 | 
			
		||||
	const bool is_confident = total_options == 1;
 | 
			
		||||
	// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
 | 
			
		||||
	// The machine-specific static analyser will still run in case it can provide meaningful annotations on
 | 
			
		||||
	// loading command, machine configuration, etc, but the flag will be passed onwards to mean "don't reject this".
 | 
			
		||||
 | 
			
		||||
	// Hand off to platform-specific determination of whether these
 | 
			
		||||
	// things are actually compatible and, if so, how to load them.
 | 
			
		||||
	const auto append = [&](TargetPlatform::IntType platform, auto evaluator) {
 | 
			
		||||
		if(!(potential_platforms & platform)) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		auto new_targets = evaluator(media, file_name, potential_platforms);
 | 
			
		||||
		std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));
 | 
			
		||||
		auto new_targets = evaluator(media, file_name, potential_platforms, is_confident);
 | 
			
		||||
		targets.insert(
 | 
			
		||||
			targets.end(),
 | 
			
		||||
			std::make_move_iterator(new_targets.begin()),
 | 
			
		||||
			std::make_move_iterator(new_targets.end())
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	append(TargetPlatform::Acorn, Acorn::GetTargets);
 | 
			
		||||
@@ -352,7 +374,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
			
		||||
	append(TargetPlatform::Atari2600, Atari2600::GetTargets);
 | 
			
		||||
	append(TargetPlatform::AtariST, AtariST::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Coleco, Coleco::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Commodore, Commodore::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Commodore8bit, Commodore::GetTargets);
 | 
			
		||||
	append(TargetPlatform::DiskII, DiskII::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Enterprise, Enterprise::GetTargets);
 | 
			
		||||
	append(TargetPlatform::FAT12, FAT12::GetTargets);
 | 
			
		||||
@@ -364,13 +386,6 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
			
		||||
	append(TargetPlatform::ZX8081, ZX8081::GetTargets);
 | 
			
		||||
	append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
 | 
			
		||||
 | 
			
		||||
	// Reset any tapes to their initial position.
 | 
			
		||||
	for(const auto &target : targets) {
 | 
			
		||||
		for(auto &tape : target->media.tapes) {
 | 
			
		||||
			tape->reset();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
 | 
			
		||||
	// picked their insertion order carefully.
 | 
			
		||||
	std::stable_sort(targets.begin(), targets.end(),
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,14 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../Machines.hpp"
 | 
			
		||||
#include "Analyser/Machines.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
#include "../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "../../Reflection/Struct.hpp"
 | 
			
		||||
#include "Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
#include "Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "Storage/MassStorage/MassStorageDevice.hpp"
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -64,9 +65,9 @@ struct Target {
 | 
			
		||||
 | 
			
		||||
	Machine machine;
 | 
			
		||||
	Media media;
 | 
			
		||||
	float confidence = 0.0f;
 | 
			
		||||
	float confidence = 0.5f;
 | 
			
		||||
};
 | 
			
		||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
 | 
			
		||||
using TargetList = std::vector<std::unique_ptr<Target>>;
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Attempts, through any available means, to return a list of potential targets for the file with the given name.
 | 
			
		||||
 
 | 
			
		||||
@@ -12,27 +12,32 @@
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/ZX8081.hpp"
 | 
			
		||||
 | 
			
		||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
	std::vector<Storage::Data::ZX8081::File> files;
 | 
			
		||||
	Storage::Tape::ZX8081::Parser parser;
 | 
			
		||||
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
 | 
			
		||||
		if(next_file != nullptr) {
 | 
			
		||||
			files.push_back(*next_file);
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
		const auto next_file = parser.get_next_file(serialiser);
 | 
			
		||||
		if(next_file) {
 | 
			
		||||
			files.push_back(std::move(*next_file));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return files;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType potential_platforms,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
 | 
			
		||||
		media.tapes.front()->reset();
 | 
			
		||||
		const auto serialiser = media.tapes.front()->serialiser();
 | 
			
		||||
		std::vector<Storage::Data::ZX8081::File> files = GetFiles(*serialiser);
 | 
			
		||||
		if(!files.empty()) {
 | 
			
		||||
			Target *const target = new Target;
 | 
			
		||||
			destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::ZX8081 {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::ZX8081 {
 | 
			
		||||
@@ -27,13 +27,15 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
 | 
			
		||||
	bool ZX80_uses_ZX81_ROM = false;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::ZX8081) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(memory_model);
 | 
			
		||||
			DeclareField(is_ZX81);
 | 
			
		||||
			DeclareField(ZX80_uses_ZX81_ROM);
 | 
			
		||||
			AnnounceEnum(MemoryModel);
 | 
			
		||||
		}
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::ZX8081) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(memory_model);
 | 
			
		||||
		DeclareField(is_ZX81);
 | 
			
		||||
		DeclareField(ZX80_uses_ZX81_ROM);
 | 
			
		||||
		AnnounceEnum(MemoryModel);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
#include "Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
bool IsSpectrumTape(Storage::Tape::TapeSerialiser &tape) {
 | 
			
		||||
	using Parser = Storage::Tape::ZXSpectrum::Parser;
 | 
			
		||||
	Parser parser(Parser::MachineType::ZXSpectrum);
 | 
			
		||||
 | 
			
		||||
@@ -97,13 +97,18 @@ bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ... otherwise read a CPM directory and look for a BASIC program called "DISK".
 | 
			
		||||
	const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format);
 | 
			
		||||
	const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format, false);
 | 
			
		||||
	return catalogue && catalogue->is_zx_spectrum_booter();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
@@ -111,7 +116,8 @@ Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Medi
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		bool has_spectrum_tape = false;
 | 
			
		||||
		for(auto &tape: media.tapes) {
 | 
			
		||||
			has_spectrum_tape |= IsSpectrumTape(tape);
 | 
			
		||||
			auto serialiser = tape->serialiser();
 | 
			
		||||
			has_spectrum_tape |= IsSpectrumTape(*serialiser);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(has_spectrum_tape) {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::ZXSpectrum {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::ZXSpectrum {
 | 
			
		||||
 | 
			
		||||
@@ -27,11 +27,13 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
 | 
			
		||||
	Model model = Model::Plus2;
 | 
			
		||||
	bool should_hold_enter = false;
 | 
			
		||||
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::ZXSpectrum) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
		}
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::ZXSpectrum) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ project(CLK
 | 
			
		||||
	VERSION 24.01.22
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set(CMAKE_CXX_STANDARD 17)
 | 
			
		||||
set(CMAKE_CXX_STANDARD 20)
 | 
			
		||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
 | 
			
		||||
set(CMAKE_CXX_EXTENSIONS OFF)
 | 
			
		||||
 | 
			
		||||
@@ -29,6 +29,7 @@ endif()
 | 
			
		||||
message(STATUS "Configuring for ${CLK_UI} UI")
 | 
			
		||||
 | 
			
		||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 | 
			
		||||
include_directories(".")
 | 
			
		||||
include("CLK_SOURCES")
 | 
			
		||||
 | 
			
		||||
add_executable(clksignal ${CLK_SOURCES})
 | 
			
		||||
 
 | 
			
		||||
@@ -56,205 +56,220 @@
 | 
			
		||||
	Boolean operators, but forcing callers and receivers to be explicit as to usage.
 | 
			
		||||
*/
 | 
			
		||||
template <class T> class WrappedInt {
 | 
			
		||||
	public:
 | 
			
		||||
		using IntType = int64_t;
 | 
			
		||||
public:
 | 
			
		||||
	using IntType = int64_t;
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
 | 
			
		||||
		forceinline constexpr WrappedInt() noexcept : length_(0) {}
 | 
			
		||||
	forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
 | 
			
		||||
	forceinline constexpr WrappedInt() noexcept : length_(0) {}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator =(const T &rhs) {
 | 
			
		||||
			length_ = rhs.length_;
 | 
			
		||||
			return *this;
 | 
			
		||||
	forceinline T &operator =(const T &rhs) {
 | 
			
		||||
		length_ = rhs.length_;
 | 
			
		||||
		return *this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator +=(const T &rhs) {
 | 
			
		||||
		length_ += rhs.length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator -=(const T &rhs) {
 | 
			
		||||
		length_ -= rhs.length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator ++() {
 | 
			
		||||
		++ length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator ++(int) {
 | 
			
		||||
		length_ ++;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator --() {
 | 
			
		||||
		-- length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator --(int) {
 | 
			
		||||
		length_ --;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator *=(const T &rhs) {
 | 
			
		||||
		length_ *= rhs.length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator /=(const T &rhs) {
 | 
			
		||||
		length_ /= rhs.length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator %=(const T &rhs) {
 | 
			
		||||
		length_ %= rhs.length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline T &operator &=(const T &rhs) {
 | 
			
		||||
		length_ &= rhs.length_;
 | 
			
		||||
		return *static_cast<T *>(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	forceinline constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	}
 | 
			
		||||
	forceinline constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
	forceinline constexpr T operator *(const T &rhs) const			{	return T(length_ * rhs.length_);	}
 | 
			
		||||
	forceinline constexpr T operator /(const T &rhs) const			{	return T(length_ / rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
	forceinline constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	}
 | 
			
		||||
	forceinline constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
	forceinline constexpr T operator -() const						{	return T(- length_);				}
 | 
			
		||||
 | 
			
		||||
	auto operator <=>(const WrappedInt &) const = default;
 | 
			
		||||
 | 
			
		||||
	forceinline constexpr bool operator !() const					{	return !length_;					}
 | 
			
		||||
	// bool operator () is not supported because it offers an implicit cast to int,
 | 
			
		||||
	// which is prone silently to permit misuse.
 | 
			
		||||
 | 
			
		||||
	/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
 | 
			
		||||
	template<typename Type = IntType> forceinline constexpr Type as() const {
 | 
			
		||||
		if constexpr (sizeof(Type) == sizeof(IntType)) {
 | 
			
		||||
			if constexpr (std::is_same_v<Type, IntType>) {
 | 
			
		||||
				return length_;
 | 
			
		||||
			} else if constexpr (std::is_signed_v<Type>) {
 | 
			
		||||
				// Both integers are the same size, but a signed result is being asked for
 | 
			
		||||
				// from an unsigned original.
 | 
			
		||||
				return length_ > Type(std::numeric_limits<Type>::max()) ?
 | 
			
		||||
					Type(std::numeric_limits<Type>::max()) : Type(length_);
 | 
			
		||||
			} else {
 | 
			
		||||
				// An unsigned result is being asked for from a signed original.
 | 
			
		||||
				return length_ < 0 ? 0 : Type(length_);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator +=(const T &rhs) {
 | 
			
		||||
			length_ += rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
		const auto clamped = std::clamp(
 | 
			
		||||
			length_,
 | 
			
		||||
			IntType(std::numeric_limits<Type>::min()),
 | 
			
		||||
			IntType(std::numeric_limits<Type>::max())
 | 
			
		||||
		);
 | 
			
		||||
		return Type(clamped);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator -=(const T &rhs) {
 | 
			
		||||
			length_ -= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
	/// @returns The underlying int, in its native form.
 | 
			
		||||
	forceinline constexpr IntType as_integral() const { return length_; }
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator ++() {
 | 
			
		||||
			++ length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
	/*!
 | 
			
		||||
		Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
			
		||||
		the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
			
		||||
	*/
 | 
			
		||||
	template <typename Result = T> forceinline Result divide(const T &divisor) {
 | 
			
		||||
		Result r;
 | 
			
		||||
		static_cast<T *>(this)->fill(r, divisor);
 | 
			
		||||
		return r;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator ++(int) {
 | 
			
		||||
			length_ ++;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
	/*!
 | 
			
		||||
		Flushes the value in @c this. The current value is returned, and the internal value
 | 
			
		||||
		is reset to zero.
 | 
			
		||||
	*/
 | 
			
		||||
	template <typename Result> Result flush() {
 | 
			
		||||
		// Jiggery pokery here; switching to function overloading avoids
 | 
			
		||||
		// the namespace-level requirement for template specialisation.
 | 
			
		||||
		Result r;
 | 
			
		||||
		static_cast<T *>(this)->fill(r);
 | 
			
		||||
		return r;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator --() {
 | 
			
		||||
			-- length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
	// operator int() is deliberately not provided, to avoid accidental subtitution of
 | 
			
		||||
	// classes that use this template.
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator --(int) {
 | 
			
		||||
			length_ --;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator *=(const T &rhs) {
 | 
			
		||||
			length_ *= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator /=(const T &rhs) {
 | 
			
		||||
			length_ /= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator %=(const T &rhs) {
 | 
			
		||||
			length_ %= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		forceinline T &operator &=(const T &rhs) {
 | 
			
		||||
			length_ &= rhs.length_;
 | 
			
		||||
			return *static_cast<T *>(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	}
 | 
			
		||||
		forceinline constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr T operator *(const T &rhs) const			{	return T(length_ * rhs.length_);	}
 | 
			
		||||
		forceinline constexpr T operator /(const T &rhs) const			{	return T(length_ / rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	}
 | 
			
		||||
		forceinline constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	}
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr T operator -() const						{	return T(- length_);				}
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		}
 | 
			
		||||
		forceinline constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		}
 | 
			
		||||
		forceinline constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		}
 | 
			
		||||
		forceinline constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		}
 | 
			
		||||
		forceinline constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		}
 | 
			
		||||
		forceinline constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		}
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr bool operator !() const					{	return !length_;					}
 | 
			
		||||
		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
 | 
			
		||||
 | 
			
		||||
		/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
 | 
			
		||||
		template<typename Type = IntType> forceinline constexpr Type as() const {
 | 
			
		||||
			const auto clamped = std::clamp(length_, IntType(std::numeric_limits<Type>::min()), IntType(std::numeric_limits<Type>::max()));
 | 
			
		||||
			return Type(clamped);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// @returns The underlying int, in its native form.
 | 
			
		||||
		forceinline constexpr IntType as_integral() const { return length_; }
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
			
		||||
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
			
		||||
		*/
 | 
			
		||||
		template <typename Result = T> forceinline Result divide(const T &divisor) {
 | 
			
		||||
			Result r;
 | 
			
		||||
			static_cast<T *>(this)->fill(r, divisor);
 | 
			
		||||
			return r;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Flushes the value in @c this. The current value is returned, and the internal value
 | 
			
		||||
			is reset to zero.
 | 
			
		||||
		*/
 | 
			
		||||
		template <typename Result> Result flush() {
 | 
			
		||||
			// Jiggery pokery here; switching to function overloading avoids
 | 
			
		||||
			// the namespace-level requirement for template specialisation.
 | 
			
		||||
			Result r;
 | 
			
		||||
			static_cast<T *>(this)->fill(r);
 | 
			
		||||
			return r;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// operator int() is deliberately not provided, to avoid accidental subtitution of
 | 
			
		||||
		// classes that use this template.
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		IntType length_;
 | 
			
		||||
protected:
 | 
			
		||||
	IntType length_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
 | 
			
		||||
class Cycles: public WrappedInt<Cycles> {
 | 
			
		||||
	public:
 | 
			
		||||
		forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
 | 
			
		||||
		forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
 | 
			
		||||
		forceinline static constexpr Cycles max() {
 | 
			
		||||
			return Cycles(std::numeric_limits<IntType>::max());
 | 
			
		||||
		}
 | 
			
		||||
public:
 | 
			
		||||
	forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
 | 
			
		||||
	forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
 | 
			
		||||
	forceinline static constexpr Cycles max() {
 | 
			
		||||
		return Cycles(std::numeric_limits<IntType>::max());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		friend WrappedInt;
 | 
			
		||||
		void fill(Cycles &result) {
 | 
			
		||||
			result.length_ = length_;
 | 
			
		||||
			length_ = 0;
 | 
			
		||||
		}
 | 
			
		||||
private:
 | 
			
		||||
	friend WrappedInt;
 | 
			
		||||
	void fill(Cycles &result) {
 | 
			
		||||
		result.length_ = length_;
 | 
			
		||||
		length_ = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void fill(Cycles &result, const Cycles &divisor) {
 | 
			
		||||
			result.length_ = length_ / divisor.length_;
 | 
			
		||||
			length_ %= divisor.length_;
 | 
			
		||||
		}
 | 
			
		||||
	void fill(Cycles &result, const Cycles &divisor) {
 | 
			
		||||
		result.length_ = length_ / divisor.length_;
 | 
			
		||||
		length_ %= divisor.length_;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Describes an integer number of half cycles: single clock signal transitions.
 | 
			
		||||
class HalfCycles: public WrappedInt<HalfCycles> {
 | 
			
		||||
	public:
 | 
			
		||||
		forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
 | 
			
		||||
		forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
 | 
			
		||||
		forceinline static constexpr HalfCycles max() {
 | 
			
		||||
			return HalfCycles(std::numeric_limits<IntType>::max());
 | 
			
		||||
		}
 | 
			
		||||
public:
 | 
			
		||||
	forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
 | 
			
		||||
	forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
 | 
			
		||||
	forceinline static constexpr HalfCycles max() {
 | 
			
		||||
		return HalfCycles(std::numeric_limits<IntType>::max());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
 | 
			
		||||
	forceinline constexpr HalfCycles(const Cycles &cycles) noexcept :
 | 
			
		||||
		WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
 | 
			
		||||
 | 
			
		||||
		/// @returns The number of whole cycles completely covered by this span of half cycles.
 | 
			
		||||
		forceinline constexpr Cycles cycles() const {
 | 
			
		||||
			return Cycles(length_ >> 1);
 | 
			
		||||
		}
 | 
			
		||||
	/// @returns The number of whole cycles completely covered by this span of half cycles.
 | 
			
		||||
	forceinline constexpr Cycles cycles() const {
 | 
			
		||||
		return Cycles(length_ >> 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
			
		||||
			the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
 | 
			
		||||
		*/
 | 
			
		||||
		forceinline Cycles divide_cycles(const Cycles &divisor) {
 | 
			
		||||
			const HalfCycles half_divisor = HalfCycles(divisor);
 | 
			
		||||
			const Cycles result(length_ / half_divisor.length_);
 | 
			
		||||
			length_ %= half_divisor.length_;
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
	/*!
 | 
			
		||||
		Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
			
		||||
		the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
 | 
			
		||||
	*/
 | 
			
		||||
	forceinline Cycles divide_cycles(const Cycles &divisor) {
 | 
			
		||||
		const HalfCycles half_divisor = HalfCycles(divisor);
 | 
			
		||||
		const Cycles result(length_ / half_divisor.length_);
 | 
			
		||||
		length_ %= half_divisor.length_;
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		/*!
 | 
			
		||||
			Equivalent to @c divide_cycles(Cycles(1)) but faster.
 | 
			
		||||
		*/
 | 
			
		||||
		forceinline Cycles divide_cycles() {
 | 
			
		||||
			const Cycles result(length_ >> 1);
 | 
			
		||||
			length_ &= 1;
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
	/*!
 | 
			
		||||
		Equivalent to @c divide_cycles(Cycles(1)) but faster.
 | 
			
		||||
	*/
 | 
			
		||||
	forceinline Cycles divide_cycles() {
 | 
			
		||||
		const Cycles result(length_ >> 1);
 | 
			
		||||
		length_ &= 1;
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		friend WrappedInt;
 | 
			
		||||
		void fill(Cycles &result) {
 | 
			
		||||
			result = Cycles(length_ >> 1);
 | 
			
		||||
			length_ &= 1;
 | 
			
		||||
		}
 | 
			
		||||
private:
 | 
			
		||||
	friend WrappedInt;
 | 
			
		||||
	void fill(Cycles &result) {
 | 
			
		||||
		result = Cycles(length_ >> 1);
 | 
			
		||||
		length_ &= 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void fill(HalfCycles &result) {
 | 
			
		||||
			result.length_ = length_;
 | 
			
		||||
			length_ = 0;
 | 
			
		||||
		}
 | 
			
		||||
	void fill(HalfCycles &result) {
 | 
			
		||||
		result.length_ = length_;
 | 
			
		||||
		length_ = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void fill(Cycles &result, const HalfCycles &divisor) {
 | 
			
		||||
			result = Cycles(length_ / (divisor.length_ << 1));
 | 
			
		||||
			length_ %= (divisor.length_ << 1);
 | 
			
		||||
		}
 | 
			
		||||
	void fill(Cycles &result, const HalfCycles &divisor) {
 | 
			
		||||
		result = Cycles(length_ / (divisor.length_ << 1));
 | 
			
		||||
		length_ %= (divisor.length_ << 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		void fill(HalfCycles &result, const HalfCycles &divisor) {
 | 
			
		||||
			result.length_ = length_ / divisor.length_;
 | 
			
		||||
			length_ %= divisor.length_;
 | 
			
		||||
		}
 | 
			
		||||
	void fill(HalfCycles &result, const HalfCycles &divisor) {
 | 
			
		||||
		result.length_ = length_ / divisor.length_;
 | 
			
		||||
		length_ %= divisor.length_;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
 | 
			
		||||
@@ -265,14 +280,14 @@ class HalfCycles: public WrappedInt<HalfCycles> {
 | 
			
		||||
	automatically to gain run_for(HalfCycles).
 | 
			
		||||
*/
 | 
			
		||||
template <class T> class HalfClockReceiver: public T {
 | 
			
		||||
	public:
 | 
			
		||||
		using T::T;
 | 
			
		||||
public:
 | 
			
		||||
	using T::T;
 | 
			
		||||
 | 
			
		||||
		forceinline void run_for(const HalfCycles half_cycles) {
 | 
			
		||||
			half_cycles_ += half_cycles;
 | 
			
		||||
			T::run_for(half_cycles_.flush<Cycles>());
 | 
			
		||||
		}
 | 
			
		||||
	forceinline void run_for(const HalfCycles half_cycles) {
 | 
			
		||||
		half_cycles_ += half_cycles;
 | 
			
		||||
		T::run_for(half_cycles_.flush<Cycles>());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		HalfCycles half_cycles_;
 | 
			
		||||
private:
 | 
			
		||||
	HalfCycles half_cycles_;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user