mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-30 14:16:04 +00:00 
			
		
		
		
	Compare commits
	
		
			1632 Commits
		
	
	
		
			2024-04-20
			...
			OptionalFu
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 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 | ||
|  | bdb5abe47b | ||
|  | dbe0ebc93e | ||
|  | 1c2f66e855 | ||
|  | 7eee3f9e5e | ||
|  | b7f069e1bd | ||
|  | 51c8396e32 | ||
|  | 0efe649ca5 | ||
|  | 75db0018bc | ||
|  | 2a9e1ea045 | ||
|  | 8feb8aaadc | ||
|  | b8f4385501 | ||
|  | d8b6d87a1c | ||
|  | f10702b3ca | ||
|  | 88248d7062 | ||
|  | 5ca1659bcc | ||
|  | 59530a12fd | ||
|  | aab2dd68b6 | ||
|  | 83f5065642 | ||
|  | 7e3a331eba | ||
|  | b5932edff3 | ||
|  | 12846317cb | ||
|  | 051f0546c7 | ||
|  | eece8c54a4 | ||
|  | 69ba94e379 | ||
|  | 0de7057d6f | ||
|  | 3dcbb40c55 | ||
|  | 91b263f0cf | ||
|  | bcd558867d | ||
|  | a9c8ef642c | ||
|  | 43887b42b1 | ||
|  | 30b1b36e63 | ||
|  | ef11262721 | ||
|  | 2d049f5fdc | ||
|  | 05f0a122f4 | ||
|  | 1977675a73 | ||
|  | 4ceaab7c26 | ||
|  | 6c33177548 | ||
|  | 76ca607021 | ||
|  | 59e1a5e5f6 | ||
|  | 4f55b2a554 | ||
|  | 78b2a89554 | ||
|  | 1e84a735e6 | ||
|  | 9e8801d867 | ||
|  | 0e58f7fa69 | ||
|  | 94058d498c | ||
|  | 2621bcc005 | ||
|  | e750866ab6 | ||
|  | 00b3007b9f | ||
|  | dbc0ecde31 | ||
|  | 0e30e2d865 | ||
|  | ba1879ef78 | ||
|  | 7a145d72f9 | ||
|  | f742266177 | ||
|  | 63737c09aa | ||
|  | 09e8d4ba0a | ||
|  | 7d728c37ee | ||
|  | e46b12e359 | ||
|  | b3012bd89e | ||
|  | f521c12d85 | ||
|  | 58f04848a9 | ||
|  | 6488f46850 | ||
|  | 5f4b798cff | ||
|  | ffa8f1db04 | ||
|  | cf2711f6dd | ||
|  | 25eded1895 | ||
|  | a8ac8f4a23 | ||
|  | c67a53e95b | ||
|  | edf4ba2533 | ||
|  | 71d337c10e | ||
|  | eb9e5fb727 | ||
|  | f133000656 | ||
|  | 71361638bb | ||
|  | edc7fe9c72 | ||
|  | 3110041a06 | ||
|  | ff78e4172d | ||
|  | a1c23be73d | ||
|  | eb2b1cb093 | ||
|  | f2245b8066 | ||
|  | 793b6d1deb | ||
|  | 537b91fa3f | ||
|  | b7777c9ca3 | ||
|  | 5235262855 | ||
|  | 7b90c36463 | ||
|  | 6407ab0673 | ||
|  | 78ec9e5a60 | ||
|  | 778ac6e6d1 | ||
|  | 5280f5aba2 | ||
|  | 67add0da93 | ||
|  | a32da9a6e1 | ||
|  | b6b70bb7ff | ||
|  | 6d769c9e89 | ||
|  | 0c683c2c81 | ||
|  | 8e51bd7578 | ||
|  | 6d6dfa4f44 | ||
|  | 7d044ad0ab | ||
|  | 826f4c1d48 | ||
|  | 3be5d60b1e | ||
|  | 26375dc023 | ||
|  | 8d0d7abd5a | ||
|  | ef03ddf2ae | ||
|  | 1d8b33d7ae | ||
|  | 308b3ca448 | ||
|  | ca67afea4c | ||
|  | 0b11fc259b | ||
|  | 18ffb9294f | ||
|  | c82517c9fd | ||
|  | 6d42c9aaf9 | ||
|  | 02ee3a7804 | ||
|  | bdf1dff976 | ||
|  | e6724a701a | ||
|  | d90eedfc8c | ||
|  | 63009d00b4 | ||
|  | 6a2261d217 | ||
|  | c3ad2154b5 | ||
|  | 3d61861737 | ||
|  | 7545786436 | ||
|  | a997b6c677 | ||
|  | 72d4f638aa | ||
|  | b15ff6d442 | ||
|  | cb70967971 | ||
|  | 42aea2663c | ||
|  | a882faa7f6 | ||
|  | 5da01e4fd8 | ||
|  | 71c5a1d419 | ||
|  | 03c3da7338 | ||
|  | 47b276ca0b | ||
|  | c7747ec5a0 | ||
|  | 5a84e98256 | ||
|  | b66d69b60c | ||
|  | dfaea5a922 | ||
|  | f4da417c3a | ||
|  | fa7fff86eb | ||
|  | d480f9eae2 | ||
|  | 4f1aef90b8 | ||
|  | 24f4538eb7 | ||
|  | 38d096cad6 | ||
|  | b82af9c471 | ||
|  | 0bff2089c4 | ||
|  | 36d9c40d7b | ||
|  | becb6ce2e0 | ||
|  | 56b65780d2 | ||
|  | 265d151879 | ||
|  | c485097eed | ||
|  | f86e9fe086 | ||
|  | c91ce4cfea | ||
|  | 8e64a854fc | 
							
								
								
									
										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-11, 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) { | ||||
| 	auto delegate = delegate_.load(std::memory_order::memory_order_relaxed); | ||||
| 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) { | ||||
| 	auto delegate = delegate_.load(std::memory_order::memory_order_relaxed); | ||||
| 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::memory_order_relaxed); | ||||
| 	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]{}; | ||||
| @@ -145,8 +158,9 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std:: | ||||
| 		} | ||||
| 		name[c] = '\0'; | ||||
|  | ||||
| 		// Skip if the name is empty. | ||||
| 		if(name[0] == '\0') continue; | ||||
| 		// An empty name implies the directory has ended; files are always listed in case-insensitive | ||||
| 		// sorted order, with that list being terminated by a '\0'. | ||||
| 		if(name[0] == '\0') break; | ||||
|  | ||||
| 		// Populate a file then. | ||||
| 		File new_file; | ||||
| @@ -189,16 +203,36 @@ 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; | ||||
| 		} | ||||
|  | ||||
| 		catalogue->files.push_back(std::move(new_file)); | ||||
| 	} | ||||
|  | ||||
| 	// Include the directory title. | ||||
| 	const char *title, *name; | ||||
| 	if(catalogue->has_large_sectors) { | ||||
| 		title = reinterpret_cast<const char *>(&root_directory[0x7dd]); | ||||
| 		name = reinterpret_cast<const char *>(&root_directory[0x7f0]); | ||||
| 	} else { | ||||
| 		title = reinterpret_cast<const char *>(&root_directory[0x4d9]); | ||||
| 		name = reinterpret_cast<const char *>(&root_directory[0x4cc]); | ||||
| 	} | ||||
|  | ||||
| 	catalogue->name = std::string(title, strnlen(title, 19)); | ||||
| 	if(catalogue->name.empty() || catalogue->name == "$") { | ||||
| 		catalogue->name = std::string(name, strnlen(name, 10)); | ||||
| 	} | ||||
|  | ||||
| 	return catalogue; | ||||
| } | ||||
|   | ||||
| @@ -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,25 +12,45 @@ | ||||
| #include "Tape.hpp" | ||||
| #include "Target.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 || | ||||
| @@ -39,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 | ||||
| @@ -59,78 +79,96 @@ 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 &, TargetPlatform::IntType) { | ||||
| 	auto target8bit = std::make_unique<Target>(); | ||||
| 	auto targetArchimedes = std::make_unique<Analyser::Static::Target>(Machine::Archimedes); | ||||
| 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. | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(!media.disks.empty()) { | ||||
| 		// TODO: below requires an [8-bit compatible] 'Hugo' ADFS catalogue, disallowing | ||||
| 		// [Archimedes-exclusive] 'Nick' catalogues. | ||||
| 		// | ||||
| 		// Would be better to form the appropriate target in the latter case. | ||||
| 		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front(); | ||||
| 		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue; | ||||
|  | ||||
| 		// Get any sort of catalogue that can be found. | ||||
| 		dfs_catalogue = GetDFSCatalogue(disk); | ||||
| 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | ||||
|  | ||||
| 		// 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", | ||||
| @@ -138,41 +176,136 @@ 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. | ||||
| 			targetArchimedes->media.disks = media.disks; | ||||
|  | ||||
| 			// Also look for the best possible startup program name, if it can be discerned. | ||||
| 			std::multimap<double, std::string, std::greater<double>> options; | ||||
| 			for(const auto &file: adfs_catalogue->files) { | ||||
| 				// Skip non-Pling files. | ||||
| 				if(file.name[0] != '!') continue; | ||||
|  | ||||
| 				// 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). | ||||
| 				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(), | ||||
| 						begin, end - 1, // i.e. don't compare the trailing NULL. | ||||
| 						[](char lhs, char rhs) { | ||||
| 							return std::tolower(lhs) == rhs; | ||||
| 						} | ||||
| 					) != file.name.end(); | ||||
| 				}; | ||||
| 				const auto has_read = has(std::begin(read), std::end(read)); | ||||
| 				const auto has_boot = has(std::begin(boot), std::end(boot)); | ||||
|  | ||||
| 				const auto probability = | ||||
| 					Numeric::similarity(file.name, adfs_catalogue->name) + | ||||
| 					Numeric::similarity(file.name, file_name) - | ||||
| 					((has_read || has_boot) ? 0.2 : 0.0); | ||||
| 				options.emplace(probability, file.name); | ||||
| 			} | ||||
|  | ||||
| 			if(!options.empty()) { | ||||
| 				targetArchimedes->main_program = options.begin()->second; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 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,13 +8,13 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include "Analyser/Static/StaticAnalyser.hpp" | ||||
| #include "Reflection/Struct.hpp" | ||||
| #include <string> | ||||
|  | ||||
| namespace Analyser::Static::Acorn { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> { | ||||
| 	bool has_acorn_adfs = false; | ||||
| 	bool has_pres_adfs = false; | ||||
| 	bool has_dfs = false; | ||||
| @@ -23,15 +23,46 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl< | ||||
| 	bool should_shift_restart = false; | ||||
| 	std::string loading_command; | ||||
|  | ||||
| 	Target() : 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; | ||||
|  | ||||
| 	BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {} | ||||
|  | ||||
| private: | ||||
| 	friend Reflection::StructImpl<BBCMicroTarget>; | ||||
| 	void declare_fields() { | ||||
| 		DeclareField(has_1770dfs); | ||||
| 		DeclareField(has_adfs); | ||||
| 		DeclareField(has_sideways_ram); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> { | ||||
| 	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) { | ||||
| @@ -228,26 +273,14 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi | ||||
| 	} | ||||
|  | ||||
| 	if(!media.disks.empty()) { | ||||
| 		Storage::Disk::CPM::ParameterBlock data_format; | ||||
| 		data_format.sectors_per_track = 9; | ||||
| 		data_format.tracks = 40; | ||||
| 		data_format.block_size = 1024; | ||||
| 		data_format.first_sector = 0xc1; | ||||
| 		data_format.catalogue_allocation_bitmap = 0xc000; | ||||
| 		data_format.reserved_tracks = 0; | ||||
|  | ||||
| 		Storage::Disk::CPM::ParameterBlock system_format; | ||||
| 		system_format.sectors_per_track = 9; | ||||
| 		system_format.tracks = 40; | ||||
| 		system_format.block_size = 1024; | ||||
| 		system_format.first_sector = 0x41; | ||||
| 		system_format.catalogue_allocation_bitmap = 0xc000; | ||||
| 		system_format.reserved_tracks = 2; | ||||
| 		const auto data_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format(); | ||||
| 		const auto system_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format(); | ||||
|  | ||||
| 		for(auto &disk: media.disks) { | ||||
| 			// Check for an ordinary catalogue. | ||||
| 			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format); | ||||
| 			if(data_catalogue) { | ||||
| 			// 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, false); | ||||
| 			if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) { | ||||
| 				InspectCatalogue(*data_catalogue, target); | ||||
| 				target->media.disks.push_back(disk); | ||||
| 				continue; | ||||
| @@ -260,8 +293,9 @@ 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); | ||||
| 			if(system_catalogue) { | ||||
| 			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); | ||||
| 				continue; | ||||
|   | ||||
| @@ -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 { | ||||
| @@ -20,11 +20,21 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta | ||||
| 	Model model = Model::CPC464; | ||||
| 	std::string loading_command; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::AmstradCPC) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(model); | ||||
| 			AnnounceEnum(Model); | ||||
| 		} | ||||
| 	ReflectableEnum(CRTCType, Type0, Type1, Type2, Type3); | ||||
| 	CRTCType crtc_type = CRTCType::Type2; | ||||
|  | ||||
| 	// This is used internally for testing; it therefore isn't exposed reflectively. | ||||
| 	bool catch_ssm_codes = false; | ||||
|  | ||||
| 	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,14 +8,17 @@ | ||||
|  | ||||
| #include "StaticAnalyser.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> | ||||
|  | ||||
| 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); | ||||
|  | ||||
| @@ -35,28 +38,77 @@ bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||
| bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||
| 	Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk); | ||||
|  | ||||
| 	// Get logical sector 1; the Spectrum appears to support various physical | ||||
| 	// sectors as sector 1. | ||||
| 	const Storage::Encodings::MFM::Sector *boot_sector = nullptr; | ||||
| 	uint8_t sector_mask = 0; | ||||
| 	while(!boot_sector) { | ||||
| 		boot_sector = parser.sector(0, 0, sector_mask + 1); | ||||
| 		sector_mask += 0x40; | ||||
| 		if(!sector_mask) break; | ||||
| 	} | ||||
| 	// Grab absolutely any sector from the first track to determine general encoding. | ||||
| 	const Storage::Encodings::MFM::Sector *any_sector = parser.any_sector(0, 0); | ||||
| 	if(!any_sector) return false; | ||||
|  | ||||
| 	// Determine the sector base and get logical sector 1. | ||||
| 	const uint8_t sector_base = any_sector->address.sector & 0xc0; | ||||
| 	const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, sector_base + 1); | ||||
| 	if(!boot_sector) return false; | ||||
|  | ||||
| 	// Test that the contents of the boot sector sum to 3, modulo 256. | ||||
| 	uint8_t byte_sum = 0; | ||||
| 	for(auto byte: boot_sector->samples[0]) { | ||||
| 		byte_sum += byte; | ||||
| 	Storage::Disk::CPM::ParameterBlock cpm_format{}; | ||||
| 	switch(sector_base) { | ||||
| 		case 0x40:	cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format();	break; | ||||
| 		case 0xc0:	cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format();		break; | ||||
|  | ||||
| 		default: { | ||||
| 			// Check the first ten bytes of the first sector for the disk format; if these are all | ||||
| 			// the same value then instead substitute a default format. | ||||
| 			std::array<uint8_t, 10> format; | ||||
| 			std::copy(boot_sector->samples[0].begin(), boot_sector->samples[0].begin() + 10, format.begin()); | ||||
| 			if(std::all_of(format.begin(), format.end(), [&](const uint8_t v) { return v == format[0]; })) { | ||||
| 				format = {0x00, 0x00, 0x28, 0x09, 0x02, 0x01, 0x03, 0x02, 0x2a, 0x52}; | ||||
| 			} | ||||
|  | ||||
| 			// Parse those ten bytes as: | ||||
| 			// | ||||
| 			// Byte 0: disc type | ||||
| 			// Byte 1: sidedness | ||||
| 			//		bits 0-6: arrangement | ||||
| 			//			0 => single sided | ||||
| 			//			1 => double sided, flip sides | ||||
| 			//			2 => double sided, up and over | ||||
| 			//		bit 7: double-track | ||||
| 			// Byte 2: number of tracks per side | ||||
| 			// Byte 3: number of sectors per track | ||||
| 			// Byte 4: Log2(sector size) - 7 | ||||
| 			// Byte 5: number of reserved tracks | ||||
| 			// Byte 6: Log2(block size) - 7 | ||||
| 			// Byte 7: number of directory blocks | ||||
| 			// Byte 8: gap length (read/write) | ||||
| 			// Byte 9: gap length(format) | ||||
| 			cpm_format.sectors_per_track = format[3]; | ||||
| 			cpm_format.tracks = format[2]; | ||||
| 			cpm_format.block_size = 128 << format[6]; | ||||
| 			cpm_format.first_sector = sector_base + 1; | ||||
| 			cpm_format.reserved_tracks = format[5]; | ||||
|  | ||||
| 			// i.e. bits set downward from 0x4000 for as many blocks as form the catalogue. | ||||
| 			cpm_format.catalogue_allocation_bitmap = 0x8000 - (0x8000 >> format[7]); | ||||
| 		} break; | ||||
| 	} | ||||
| 	return byte_sum == 3; | ||||
|  | ||||
| 	// If the boot sector sums to 3 modulo 256 then this is a Spectrum disk. | ||||
| 	const auto byte_sum = static_cast<uint8_t>( | ||||
| 		std::accumulate(boot_sector->samples[0].begin(), boot_sector->samples[0].end(), 0)); | ||||
| 	if(byte_sum == 3) { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	// ... otherwise read a CPM directory and look for a BASIC program called "DISK". | ||||
| 	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; | ||||
| @@ -64,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