mirror of
https://github.com/TomHarte/CLK.git
synced 2026-04-25 11:17:26 +00:00
Compare commits
1634 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4be5ee5b35 | |||
| 92e6dc64d4 | |||
| a9d945d6d2 | |||
| 5e465f1ff4 | |||
| 5359964fef | |||
| fa8be26f9f | |||
| aabfe7c284 | |||
| d011b10b5d | |||
| 332b37063f | |||
| b3a9e39be3 | |||
| 67590cf06b | |||
| 236fdacb36 | |||
| f422cda553 | |||
| 2c44d3a7d3 | |||
| 051ce98ecb | |||
| 33ae24c961 | |||
| 4247d0ef40 | |||
| ffababdb45 | |||
| 176bda9eb8 | |||
| 9f0a0443a8 | |||
| fd1a7e78c5 | |||
| 909fa57b27 | |||
| 5630b1c351 | |||
| c4fe38a61f | |||
| 5b4f303e35 | |||
| c9c1bde6e2 | |||
| d01e1f3bb1 | |||
| fd32e63459 | |||
| dbbb1d60fc | |||
| 1ce013bcf7 | |||
| 86bf019aac | |||
| d00546dd77 | |||
| cf33e17688 | |||
| c5c6c5ff72 | |||
| fa0835abd8 | |||
| f232b179ed | |||
| a4a0026cab | |||
| eac7493180 | |||
| 989fb32fba | |||
| 735afcfabb | |||
| 37152a1fad | |||
| 4e86184955 | |||
| d23dbb96c2 | |||
| 4586e4b4c1 | |||
| de5cdbf18c | |||
| 8c2294fc0d | |||
| b0b82782ad | |||
| b9f5802c89 | |||
| 29235f1276 | |||
| 8c74e2a323 | |||
| ae2936b9c3 | |||
| 0d295a6338 | |||
| 3ebd6c6871 | |||
| 6e2cd0ace6 | |||
| af82a0bcda | |||
| 6fe208ae77 | |||
| f569b86c90 | |||
| b622cc9536 | |||
| 7dfd5ea0d0 | |||
| a81309433c | |||
| 902f388cb1 | |||
| 0cc5a9d74f | |||
| 5e98e6502d | |||
| fe7a206fc5 | |||
| c5704aaaff | |||
| e115f09f51 | |||
| 32cd142629 | |||
| b00be303aa | |||
| 273e23bd98 | |||
| 5063e6943d | |||
| ce32747973 | |||
| a8ef8dfb21 | |||
| 7658edca62 | |||
| 056028e07b | |||
| 34992126a8 | |||
| d30d4e8f89 | |||
| f562deca48 | |||
| b2b7aa221b | |||
| b98a9a8487 | |||
| 7f36a8a746 | |||
| 2fe6e9c7fc | |||
| 62919e77d4 | |||
| 871b724290 | |||
| ca23c04ba1 | |||
| 44b8f75611 | |||
| 301df785fe | |||
| 9657109471 | |||
| 139569e291 | |||
| 1383c1dad4 | |||
| 1d2cdd85a3 | |||
| 76082b1271 | |||
| 25dcbf918d | |||
| 6c72c1842b | |||
| 4e4388dc35 | |||
| 54bff80ecc | |||
| 78073aaa11 | |||
| 4df01a7e0d | |||
| f4d15d0640 | |||
| 64842d4de2 | |||
| f52315ac92 | |||
| cc88877109 | |||
| d7568e57c3 | |||
| d49b301fca | |||
| 0113bcbea7 | |||
| 0332bb4f12 | |||
| 82a5d5116a | |||
| 62a6797ef3 | |||
| bb66033682 | |||
| d4aa0799a9 | |||
| fba2d37714 | |||
| e891697f88 | |||
| ee6ac3b4a9 | |||
| c8130f9d6f | |||
| 8abd837c8b | |||
| 02ad080bb8 | |||
| 5887e3e580 | |||
| 1994b2dc9f | |||
| d7a82d00b1 | |||
| 0017bd6d0f | |||
| 15f30995b1 | |||
| 37ca0e4f81 | |||
| e400aa200c | |||
| e168298aa0 | |||
| c4afbf8f2e | |||
| 112aff9887 | |||
| a8207ded4f | |||
| bafef023a5 | |||
| 9b39eebc2d | |||
| 02fdcd6ece | |||
| 49601e0a78 | |||
| cf10abff5b | |||
| e75c27cb66 | |||
| d27f0e3633 | |||
| e19bd0d517 | |||
| 3427120b3f | |||
| ecc623cd6c | |||
| 3ef615f508 | |||
| 1c4c3a6cae | |||
| fd7142f6a1 | |||
| b30fda3c36 | |||
| 7e43b40415 | |||
| 53501a9443 | |||
| c5dc65fc61 | |||
| b389889e1f | |||
| 02a29056b7 | |||
| 5e789b4b0c | |||
| 9ff09a45be | |||
| fc02a3d34b | |||
| 48f983c040 | |||
| dd25d387fe | |||
| 80a503f317 | |||
| 5aa9168dd6 | |||
| b3f01fe314 | |||
| e688d87c22 | |||
| 332fb2f384 | |||
| 5332bcd6b4 | |||
| 55c59e6164 | |||
| 80bfa1859f | |||
| 58e1880773 | |||
| c8c4c99f09 | |||
| 350f424055 | |||
| bc5b7a6725 | |||
| 7b4e71e6dd | |||
| a32202ab72 | |||
| 193c027c8b | |||
| 3dd07b6ac1 | |||
| 12d912b627 | |||
| f847a72696 | |||
| 967c3f6dba | |||
| 6cf87e3aa9 | |||
| cbc6477431 | |||
| 45d0f101a7 | |||
| cba96aee37 | |||
| 17325834b5 | |||
| 3673144a44 | |||
| 4df49d9f18 | |||
| 8b04608d68 | |||
| 2bac276870 | |||
| 213f9850e7 | |||
| 378bffbf84 | |||
| c291d5313d | |||
| 1f6f665639 | |||
| e81233c586 | |||
| b946029394 | |||
| 6095936354 | |||
| fe79a1231d | |||
| 76a5872d17 | |||
| 0d72c75e15 | |||
| 2e0e89c494 | |||
| 7d6b7a5874 | |||
| 48f8ddf53a | |||
| 9aae07b737 | |||
| d7abdc8017 | |||
| cb81156835 | |||
| 1fd8d94e2e | |||
| e4fe127444 | |||
| aeabd5f113 | |||
| 58f7d4065c | |||
| 60f25a3ba4 | |||
| d267571dc6 | |||
| 3c34aa6696 | |||
| 5dc00a2092 | |||
| b20d489bf0 | |||
| df39870587 | |||
| f742eab4be | |||
| e9c8c61dcf | |||
| e5f09002e9 | |||
| d42f005e17 | |||
| 24e060abee | |||
| 8b6d763442 | |||
| e239745f63 | |||
| cfef2b4e19 | |||
| cf93c39881 | |||
| 5d223bce4c | |||
| b454ebc1c9 | |||
| 7cf9910cae | |||
| 79ab1d8cb1 | |||
| 7cd20f5d12 | |||
| 5396d751e1 | |||
| d23e715650 | |||
| 0791bce338 | |||
| 2bcb74072a | |||
| c5f2f17f33 | |||
| 62a8bf4261 | |||
| ebda18b44e | |||
| a8f41b9017 | |||
| 410c19a7da | |||
| a346e2e04b | |||
| 2d114b6677 | |||
| 02e74ca1f4 | |||
| 69122cdec4 | |||
| d730168631 | |||
| 2f210ebe3b | |||
| 693b53baa2 | |||
| 77554879a5 | |||
| 45363922b5 | |||
| 0463c1ceda | |||
| b35a55a658 | |||
| 4da68c9fa8 | |||
| 72f133f31b | |||
| af4a8f6d9c | |||
| b5899a2e42 | |||
| 4ee8f8564e | |||
| ff08c03bc5 | |||
| 95dd430b0d | |||
| 20eb8b1442 | |||
| 2d6a0b3ed0 | |||
| 80f0ce78e0 | |||
| fde0e2434e | |||
| fe2da7fd95 | |||
| 25e783ff2f | |||
| 2eb94f1b66 | |||
| 2cdf6ac8f9 | |||
| 309c58a93d | |||
| 700bd0ddd4 | |||
| bd5a2f240d | |||
| 73054d971c | |||
| 8c7f2491d7 | |||
| 24fcbea6f2 | |||
| fddc9c8c48 | |||
| 294893b7da | |||
| 564542420b | |||
| 3f7e3e6d75 | |||
| 6521d7d02b | |||
| ad162a4e4a | |||
| 676b1f6fdc | |||
| 406ef4e16c | |||
| 217976350b | |||
| e8f860d6fe | |||
| 859e6e2396 | |||
| 51186e615f | |||
| bd8287fda3 | |||
| 287ff99bbc | |||
| 0bbfcedabb | |||
| 812e1e637d | |||
| f20fd38940 | |||
| b4cfabc005 | |||
| c49e160501 | |||
| a0a24902d5 | |||
| 1047bc8a80 | |||
| 0eed49c4cb | |||
| e7f09e2ece | |||
| 89678f1ea7 | |||
| e43ec7d549 | |||
| 95395132f0 | |||
| 89293d8481 | |||
| e6de24557f | |||
| 66d76dc36a | |||
| 06629def62 | |||
| 97aeb5e930 | |||
| bf45b6e20b | |||
| 6ad41326b0 | |||
| 2bbca3c169 | |||
| ae903b0712 | |||
| a2a7f82716 | |||
| 00456c891a | |||
| afd5faaab1 | |||
| bb33cf0f8d | |||
| edc510572a | |||
| bc6cffa95c | |||
| 48ed2912b0 | |||
| a8af262c41 | |||
| dcf49933bc | |||
| 9c014001da | |||
| 4f410088dd | |||
| e1c1b66dc5 | |||
| 23c3a1fa99 | |||
| ef6e1b2f74 | |||
| e130ae0a8a | |||
| 1a1e3281e4 | |||
| a4e55c9362 | |||
| 0b4c51eebd | |||
| 1107f0d9a3 | |||
| 775819432b | |||
| a71a60937f | |||
| 5e661fe96b | |||
| a9f5b17fcb | |||
| b0c2b55fc9 | |||
| 925832aac5 | |||
| 994131e2ea | |||
| f8d27d0ae0 | |||
| fc50af0e17 | |||
| 087d3535f6 | |||
| e9d310962f | |||
| 0f9c89d259 | |||
| 258c37685b | |||
| 56f092a0c3 | |||
| 6c3048ffbf | |||
| c58eba61de | |||
| 8a54773f1b | |||
| 2c483e7b97 | |||
| 1027e9ffdc | |||
| 85d6957e03 | |||
| c3609b66a9 | |||
| 605f4a92d7 | |||
| d395e2bc75 | |||
| e6ccdc5a97 | |||
| a68c7aa45f | |||
| 66e959ab65 | |||
| d68b172a40 | |||
| d3ee778265 | |||
| da96df7df7 | |||
| 4ea82581ec | |||
| 4473d3400e | |||
| 2f1f843e48 | |||
| 53a3d9042e | |||
| 6eb32f98b2 | |||
| 0fad97ed48 | |||
| 27246247a2 | |||
| cbc96e2223 | |||
| 8fdf32cde8 | |||
| 03a94e59e2 | |||
| 2c0610fef8 | |||
| 60b3c51085 | |||
| d7b5a45417 | |||
| e11060bde8 | |||
| 4653de9161 | |||
| 1926ad9215 | |||
| 33d047c703 | |||
| fadda00246 | |||
| a3fed788d8 | |||
| dde31e8687 | |||
| 190fb009bc | |||
| 62574d04c6 | |||
| 2496257bcf | |||
| ab73b4de6b | |||
| 6c1c32baca | |||
| 239cc15c8f | |||
| 6b437c3907 | |||
| 4756f63169 | |||
| 7229acb34f | |||
| 43cb91760a | |||
| 7bb4d052d1 | |||
| 5885bdf0f8 | |||
| 05042b1859 | |||
| 3ca2f72184 | |||
| b076450b73 | |||
| 7a90662c06 | |||
| eb31aaeb7d | |||
| ebfb215246 | |||
| eb97e4e518 | |||
| 61d3e65c05 | |||
| 0ac5681d13 | |||
| 1e27c5759b | |||
| 5e71aedc99 | |||
| d0d8c2316b | |||
| 8f0a5b2191 | |||
| 242b180862 | |||
| feb4766d7b | |||
| a224eea077 | |||
| ecefbc23ae | |||
| e5e0cbfc53 | |||
| 38781c9395 | |||
| e70d72b614 | |||
| fcf648bbb2 | |||
| a8325b6bce | |||
| 22554a9ba4 | |||
| 02a10ef651 | |||
| e3ca44f3ca | |||
| a9abc0dd5f | |||
| cbcc7c718e | |||
| 4377c79068 | |||
| 514993bc2e | |||
| c182176134 | |||
| abd1f10395 | |||
| f279bebc1a | |||
| 4e3fa5a6ff | |||
| 01d355a247 | |||
| 009f71a186 | |||
| de5c311d84 | |||
| eefe34f99e | |||
| 82e3c870b3 | |||
| d44a1d9761 | |||
| e6fd54c14b | |||
| ccb8e90110 | |||
| a4e66f291a | |||
| 3ab3f34bef | |||
| 99da8c4424 | |||
| 9a1bf1cf74 | |||
| 2256e99157 | |||
| 3a5f8b4987 | |||
| 6de5fcc980 | |||
| a454d0d4b7 | |||
| 67339754e3 | |||
| 7316fe00ee | |||
| feb4a7021c | |||
| 5e84b671b6 | |||
| bcefef62f9 | |||
| cc953dda34 | |||
| f9e5b0f0c7 | |||
| 578654411e | |||
| 247e92cfa2 | |||
| 66f605de0f | |||
| 709b0efc9b | |||
| 622679f4c2 | |||
| fdeb421513 | |||
| 8fe25cde8d | |||
| fbd71451f1 | |||
| 0d91ce8e6a | |||
| d71796c88a | |||
| 277748c8f5 | |||
| 8c1358ace9 | |||
| 1254916058 | |||
| f228bee4b8 | |||
| 8094477b09 | |||
| 32a5bf76cd | |||
| 0375d000e6 | |||
| 141d43d3e5 | |||
| 823f7b1d2e | |||
| 6579f011d0 | |||
| 93f768af9b | |||
| f8c11bf217 | |||
| 26ccd930c3 | |||
| 82211c7312 | |||
| 2015c154fe | |||
| ef17d116a8 | |||
| 46fddc44bf | |||
| 0214a77cd7 | |||
| 425ed658f1 | |||
| a53adb561e | |||
| 3c3c55090a | |||
| ebc04c6520 | |||
| 8b0e8f5b13 | |||
| 16132a007e | |||
| b6e41ceea7 | |||
| 7015e46227 | |||
| cce2607c80 | |||
| 9dd2ec8bda | |||
| 068726e0ab | |||
| 89e86ad9bd | |||
| 2e49bc2044 | |||
| 174c8dafbf | |||
| 90a96293de | |||
| 84877c4fec | |||
| a7cceb5fa9 | |||
| ca6359a597 | |||
| b7c3667be1 | |||
| b6dea59db3 | |||
| aa51f13743 | |||
| f34ec03ff0 | |||
| 1363be59b7 | |||
| 622c24ef24 | |||
| 539b0e49d4 | |||
| 0c42976312 | |||
| 3f6b3a4fa0 | |||
| 67e1773495 | |||
| a199b64aa0 | |||
| ebf09aceb2 | |||
| ca226e4295 | |||
| 9261939f62 | |||
| 0349931953 | |||
| d612a385d2 | |||
| ed4f299d55 | |||
| 7cef789d41 | |||
| 66bfb86d42 | |||
| c4a5bc12ef | |||
| 557631f6ba | |||
| 362ffaff7f | |||
| fb5ef200fb | |||
| 5e78ac3af5 | |||
| 719a090b34 | |||
| 3af85da6e0 | |||
| 8fd62aa525 | |||
| 40747f51bd | |||
| f3cef6bd73 | |||
| eef0ee8180 | |||
| 503e974375 | |||
| c959f2fee5 | |||
| 7d5e434cba | |||
| 2720bcdf18 | |||
| c513b7262b | |||
| 57a795df96 | |||
| 6bdd9e4543 | |||
| ede3def37f | |||
| 87d9022280 | |||
| ff0ba7d48b | |||
| b49c47425f | |||
| 3916ba1a42 | |||
| 0b3d22b97c | |||
| 9b8b0f2023 | |||
| 06e0d17be0 | |||
| 239c485f3c | |||
| 5e5fdda0ca | |||
| 4b2dddf3c6 | |||
| c99ec745ca | |||
| 1ec2e455ec | |||
| 69304737c6 | |||
| fe91670127 | |||
| 7a59f94f3d | |||
| 4efe3a333d | |||
| 421bf28582 | |||
| 4c49ffe3d1 | |||
| 26b1ef247b | |||
| 3aafba707a | |||
| ae774e88fa | |||
| ff56dd53cf | |||
| 12f063c178 | |||
| 888148d282 | |||
| 7bba0b82ef | |||
| 41196a862d | |||
| a99ed0e557 | |||
| 654981fb03 | |||
| c473c36d46 | |||
| e863e61af8 | |||
| efb486dd1b | |||
| 25b15fcdd1 | |||
| 1106fbb5ef | |||
| 9e1a29f0ff | |||
| b3c057f911 | |||
| 1c33e9ead9 | |||
| d78f35b940 | |||
| 506236a5ed | |||
| 18b32dbba3 | |||
| 26e40564dc | |||
| b6e8421a0a | |||
| 03c5a3b325 | |||
| a1f33d3fc6 | |||
| b8fca7db80 | |||
| 6ea70cd245 | |||
| 683fea675e | |||
| 811a010a60 | |||
| 019526332d | |||
| 1e90387198 | |||
| 84d6bb47ea | |||
| 512179d92a | |||
| 04344a3723 | |||
| d032207473 | |||
| b33dc2779d | |||
| 28699a1af5 | |||
| ff3fe135a3 | |||
| ff69709926 | |||
| 2162822cec | |||
| 34330baaa0 | |||
| 28d8aab7e5 | |||
| 6a91c89126 | |||
| c350f6fe5e | |||
| 493bf0a666 | |||
| 0305203e61 | |||
| 71d7b1dfad | |||
| 69748a1f1b | |||
| 39c2db38b7 | |||
| f499de3622 | |||
| e8a16a8fce | |||
| 81f2952c97 | |||
| dcf9de1a01 | |||
| 95f57f4eeb | |||
| 7a794b8b6e | |||
| cbaf841f13 | |||
| 6fdca9e89c | |||
| 8c3b3d98f6 | |||
| 6dd2abfb61 | |||
| 32defde397 | |||
| b1969c9528 | |||
| b6d5af81b5 | |||
| 0358853d5a | |||
| eb60f63223 | |||
| fdbb06436d | |||
| bceaa073cd | |||
| ccace48d5a | |||
| 193203bbf7 | |||
| d0cc4e1557 | |||
| d52ae8c662 | |||
| 6afd40cb39 | |||
| 6713baf86b | |||
| 365145e7c0 | |||
| 0413af79e9 | |||
| 868c498e28 | |||
| c5fbbe8a69 | |||
| b698850ce8 | |||
| 0d1fe03369 | |||
| 98b900e886 | |||
| 8210da9e34 | |||
| fac3d99f64 | |||
| d3c216374f | |||
| 105272630e | |||
| 53cc8ecaf0 | |||
| fb8e8b4b3a | |||
| 035713b4d3 | |||
| 54b7dc56b5 | |||
| 7fd39f44d0 | |||
| 691292501a | |||
| a58158ae08 | |||
| ef09b971fa | |||
| e07dee380d | |||
| 125bc5baa6 | |||
| 995444b11b | |||
| 8f2384dbfc | |||
| 0cdd1c23ce | |||
| 4765a39759 | |||
| 7f4047772c | |||
| 45c4ca6bec | |||
| 4a573a5aae | |||
| 5125ff6a8c | |||
| 482d3301ce | |||
| cdeec8ac47 | |||
| 3cef12b53b | |||
| dd098a16a8 | |||
| 61a175e84a | |||
| a5bcd38fe8 | |||
| cad42beef4 | |||
| 5a57958639 | |||
| 260336c1e5 | |||
| 889cb9c78f | |||
| b90e8f5af3 | |||
| 12361d2854 | |||
| d307ddfa8e | |||
| 96fd0b7892 | |||
| 6f1db15d7c | |||
| 1854296ee8 | |||
| 515cc5f326 | |||
| 091be7eafe | |||
| 27a19ea417 | |||
| 9a5e9af67c | |||
| 3a493f2428 | |||
| ca6e34f4b4 | |||
| e1e68312c4 | |||
| c7ff2cece4 | |||
| 8e6f4fa36f | |||
| 4d302da9fa | |||
| e0917dc734 | |||
| 26f82e8143 | |||
| 6518f08bc7 | |||
| 8599123b30 | |||
| f934a1aa10 | |||
| 81be5f809f | |||
| 8bb94804d4 | |||
| c3f64e85ce | |||
| 53057aff5d | |||
| 8cad5ac7e9 | |||
| b4f643f3dd | |||
| 2d659289e8 | |||
| 9883640b63 | |||
| bc3a0c3c91 | |||
| 4e822347a5 | |||
| d5650da8c0 | |||
| d3c77523c3 | |||
| 787a5ce568 | |||
| 91dfd405d1 | |||
| 08c615c493 | |||
| 5483102276 | |||
| 32686d898b | |||
| 4b50c8d96c | |||
| c1780ee26b | |||
| 46b2db00bc | |||
| 7042e457ab | |||
| faeec1701f | |||
| 2a75a1f9f2 | |||
| 8c65dccc53 | |||
| de7c3ba92f | |||
| ac204aadd2 | |||
| 46fc0d677f | |||
| 2d4f4b0036 | |||
| c1de00bf9d | |||
| 4639e1c47c | |||
| 5d2c156bc9 | |||
| 357f98f015 | |||
| 62f23ac27c | |||
| 0936646ef9 | |||
| 856c12f46f | |||
| 7489783837 | |||
| a4f0a4c310 | |||
| bb1ef114f1 | |||
| f1610b6407 | |||
| 5e48a4c724 | |||
| d825c03372 | |||
| d177549dd6 | |||
| 9d1543401f | |||
| 094eb7e252 | |||
| 95e6726468 | |||
| 8fded8f210 | |||
| 19c4940abd | |||
| 7b1f6b3c53 | |||
| 43042c3737 | |||
| 30b50b8a1b | |||
| 095be3072b | |||
| 91831200d6 | |||
| 8295d4511b | |||
| df589d9588 | |||
| b826e1c661 | |||
| 6727e2fe73 | |||
| ecdcee8d4e | |||
| 8b4a4369c1 | |||
| eeb06de916 | |||
| 5018d7d577 | |||
| 1ca279d99d | |||
| 8a149a188c | |||
| 076334bc4e | |||
| e6b45c978c | |||
| a07615445f | |||
| 41d30c2835 | |||
| 71f1635e23 | |||
| 57df6d9bf7 | |||
| fd670d5175 | |||
| 39d4c315c8 | |||
| 6487086354 | |||
| 7d6e24b8ed | |||
| 4922073300 | |||
| 778a02324e | |||
| 8e89aa97a0 | |||
| dfd521e938 | |||
| d47332adf5 | |||
| 14e7ba8fab | |||
| e68a356fd0 | |||
| 6e77b8659c | |||
| 00fad7e424 | |||
| 0a65248bf7 | |||
| 9cff26b163 | |||
| 9309d8c3f2 | |||
| 07e96c10d2 | |||
| d95abc99d9 | |||
| b83c2615de | |||
| 78a2b27e65 | |||
| bae594e34c | |||
| 4ded6fceea | |||
| 0e498829f7 | |||
| ddd090d581 | |||
| 4cd979e5fb | |||
| 2f7a6bb242 | |||
| 3b3b2e61b0 | |||
| ab4fde9bd7 | |||
| a9996f0b81 | |||
| 246d34e072 | |||
| d35efe3f32 | |||
| ebd1a6b47c | |||
| 83980678a0 | |||
| 201393f87d | |||
| 055eb3873e | |||
| dc94d58148 | |||
| 0adaec1665 | |||
| 4ee30dc36f | |||
| 54ff2fa01f | |||
| 03c70b49ba | |||
| 4b2d8e13d1 | |||
| a0c50f0521 | |||
| b15a865c88 | |||
| 8e5bbbbc71 | |||
| 615ebaf711 | |||
| 0882d2b7ce | |||
| 900195efac | |||
| b58b962ccf | |||
| 5255499445 | |||
| d9a2be4250 | |||
| 256e14a8a6 | |||
| 1ab26d4a2f | |||
| 91b2c751af | |||
| edf7617d1e | |||
| 32666d304f | |||
| b65f7b4a6a | |||
| 7c4df23c1c | |||
| a8e60163e1 | |||
| 02ec1b5da6 | |||
| a9a6aba862 | |||
| 03c6a60f68 | |||
| 8ab688687e | |||
| bdec32722e | |||
| ad50e5c754 | |||
| 9c48e44e9e | |||
| 76284eb462 | |||
| 0745c5128a | |||
| 01fbe2d3de | |||
| 9e14c22259 | |||
| dff0111cd5 | |||
| e7452b0ea1 | |||
| 61a0f892c4 | |||
| 4ceab01ed4 | |||
| 9908969eea | |||
| 19a78ef1ac | |||
| 4785c1ae84 | |||
| ef03841efa | |||
| 4747a70ce7 | |||
| cd986cc2dc | |||
| c29d5ca4a8 | |||
| 56b49011d6 | |||
| 48c55211e6 | |||
| 72f68f3b0b | |||
| 7b6dddc994 | |||
| 51fbe4e8c5 | |||
| c148d9ee6c | |||
| 9dfe59a104 | |||
| b6aae65afd | |||
| 9fed93a771 | |||
| cdfb68f261 | |||
| 46450bd080 | |||
| 9a25d601f1 | |||
| fe0834ecda | |||
| 846f745e2c | |||
| 30d40e6f9b | |||
| f7501b10f7 | |||
| 379c513f8a | |||
| 5a6d77e958 | |||
| 6646039ffe | |||
| 5e0994270f | |||
| 44fc801720 | |||
| 405c61f53d | |||
| c40acb9406 | |||
| 7778d2a47e | |||
| 96afb245a5 | |||
| cf0677c30b | |||
| 667614d9de | |||
| 652ede57b3 | |||
| 09a34f880e | |||
| a9f9be330d | |||
| 39568d2464 | |||
| 10e07a9966 | |||
| fe00a69136 | |||
| 9d0c2cd67f | |||
| b5aab442f8 | |||
| 7c010bd1ef | |||
| 1bf898405f | |||
| c490166b35 | |||
| e6862364ed | |||
| cf20c84edd | |||
| 88e776ad5b | |||
| e79a60f5cd | |||
| fd4a91ba72 | |||
| 5705ece2a3 | |||
| c723f20f39 | |||
| 0f7447d539 | |||
| 1a08944854 | |||
| 7b0b06f6df | |||
| d2ad227a24 | |||
| 71d7982d14 | |||
| a94dcc12ef | |||
| b7bfcfa1e3 | |||
| 416ae0ca04 | |||
| 8d66cd4874 | |||
| a701ba8030 | |||
| 0160908522 | |||
| cdd0d6d127 | |||
| 65ee745d6e | |||
| 4141dfc353 | |||
| fb6cd105c3 | |||
| 6ff9168146 | |||
| 7b5e08aab6 | |||
| c7dd4526c1 | |||
| 066036ccdd | |||
| 7c164453a5 | |||
| 8b31cfeafb | |||
| 2ddaf0afa3 | |||
| a8a97b4606 | |||
| a55b63a210 | |||
| 2e4f7cd667 | |||
| bf257a8d9e | |||
| c4e66f7a35 | |||
| efff433aa0 | |||
| ee60e36a16 | |||
| 841fc3cfaf | |||
| 2a44caea6c | |||
| 0f661928ae | |||
| 0961e5cc2e | |||
| df621a8205 | |||
| bfa416ca99 | |||
| 8041b87317 | |||
| b3000f6350 | |||
| 947baab269 | |||
| a41ea90ca7 | |||
| 8b3f0d8fd6 | |||
| bd9740a9a4 | |||
| 3f735e44f1 | |||
| 9e5235fd30 | |||
| 159f3cb780 | |||
| 61469f8e09 | |||
| 71343b5131 | |||
| 82caee6d7d | |||
| 275e75980c | |||
| 2572da872a | |||
| 6934618589 | |||
| 01fd07c372 | |||
| 02f9cf0318 | |||
| 6bc586025a | |||
| 0d34960d60 | |||
| 99b94a31ea | |||
| b0d4bcd26c | |||
| 248ea52e06 | |||
| 9b51fe3db4 | |||
| 5f95696815 | |||
| 8c9df5556d | |||
| 32495f47b3 | |||
| 23bc561524 | |||
| fa2cc0f62e | |||
| a686a167cc | |||
| 8a2468a4fb | |||
| 4cc21a2c20 | |||
| 5350e41da1 | |||
| e07e6b6954 | |||
| 0a60e38d82 | |||
| f53b40e127 | |||
| 4df51a00ed | |||
| 3981c4d101 | |||
| fc3e8f7cef | |||
| f4d67ec5e6 | |||
| 59aafa6c1e | |||
| 75da46dac5 | |||
| 1f1d380e26 | |||
| 35b3e425be | |||
| b4535c489d | |||
| f6bb502e87 | |||
| 6cf825d3d8 | |||
| f766841fad | |||
| 10e4e7f6c6 | |||
| 1277e56435 | |||
| 4089532f81 | |||
| 9790b4d2e9 | |||
| ad37c0d2ac | |||
| b7a1fd4f8f | |||
| be5362e393 | |||
| 4977c9bc4c | |||
| e13dbc03da | |||
| 16fec0679b | |||
| 55361b8552 | |||
| cc67993621 | |||
| 49ba4998d6 | |||
| 03eb381b3b | |||
| e5161faa43 | |||
| de78fb7a1c | |||
| a9ceb5e21a | |||
| e62b41f615 | |||
| 592e339b70 | |||
| 84a9138df7 | |||
| 6db7c4a8eb | |||
| 213bd09a9c | |||
| 8eb246cdec | |||
| caacf8e373 | |||
| c53d42a578 | |||
| cfc5ef4a3c | |||
| e78c1bbec9 | |||
| b1582d33c0 | |||
| 5abcf28a0e | |||
| 4cd57856ce | |||
| a826fd5c0e | |||
| 7de23ec2aa | |||
| d7d2957319 | |||
| fbd81b9930 | |||
| dacb52403a | |||
| e008a02b99 | |||
| 9363453720 | |||
| 9c70615fd1 | |||
| 1c78c65816 | |||
| 2a9a68ca53 | |||
| fb16baab99 | |||
| 54f509c210 | |||
| 5be8e5eff3 | |||
| 29b9f129f6 | |||
| f41629daae | |||
| feea6023f4 | |||
| 262d8cd0d9 | |||
| fbbec04f8c | |||
| 3e4eaee96b | |||
| 5f99a2240d | |||
| 5937387e94 | |||
| b3099d8e71 | |||
| 7721f74200 | |||
| fa58cc05f3 | |||
| c61a9e47b2 | |||
| 148ee266ed | |||
| 8ccec81cc6 | |||
| 668901f71d | |||
| ad6ad144a5 | |||
| d5997a30b2 | |||
| ecc7501377 | |||
| 45262a1a46 | |||
| 3c04e08df2 | |||
| 7c7675179e | |||
| 88ed49a833 | |||
| 0c88e62815 | |||
| 88d34012c4 | |||
| 3be8de6fb0 | |||
| 804fbf5d5f | |||
| 1a68dcbc14 | |||
| a9a72a767d | |||
| afc3a8d373 | |||
| da00e6588c | |||
| d6376d0ddf | |||
| 1cca711560 | |||
| 552f9196af | |||
| c6fa72cd83 | |||
| 42edc46887 | |||
| ec7e343673 | |||
| 69d4d8acb0 | |||
| a7eab8df22 | |||
| 4247da9118 | |||
| cfba8aeb89 | |||
| db26a26926 | |||
| 1551fbeb1f | |||
| d5c53ca624 | |||
| b34702e370 | |||
| 8b1543d9c9 | |||
| e264375a97 | |||
| fd31d07f41 | |||
| fac15f5539 | |||
| 6ad88101f1 | |||
| 2768b66d10 | |||
| d10164be26 | |||
| ce7ff13bbe | |||
| c1d2c159f3 | |||
| d3dbdb153c | |||
| e7218c0321 | |||
| 48d8fdb875 | |||
| b387ca921a | |||
| 53c1a322ed | |||
| 0c502fc9cf | |||
| 5d1e3b6c93 | |||
| 7fc16fa2c8 | |||
| fe6a88c5df | |||
| 7b14df82e0 | |||
| 966b41313d | |||
| 91f1c3322c | |||
| 0b276c5a76 | |||
| b654c2e170 | |||
| 32c88da6c4 | |||
| 3d19d0816b | |||
| 15da707324 | |||
| 387e8b04f9 | |||
| 4ca47be8a8 | |||
| f9d9dc68b7 | |||
| 14a8f4511c | |||
| 97d237eed0 | |||
| 20d0406a24 | |||
| 73e843abd3 | |||
| 8d956da65b | |||
| 9b9cdfe937 | |||
| 3dcba9362c | |||
| 2d3a3ada57 | |||
| 143d1d5e35 | |||
| 13b32b269c | |||
| 592cea6a27 | |||
| e76ca304e6 | |||
| d77d8df1ac | |||
| 4f35d5dabd | |||
| e927feb2d6 | |||
| 98f20c57c1 | |||
| 7a88d31fd9 | |||
| 96326411bf | |||
| 4e882e7d4d | |||
| bff10c1714 | |||
| cee2484108 | |||
| 93078abe87 | |||
| 8caa1a9664 | |||
| d7b46ee03c | |||
| e07b3da983 | |||
| 0f166cee48 | |||
| 08df42d05b | |||
| 2c165c3873 | |||
| 9135402d9e | |||
| 53135ec2c0 | |||
| 4eb8c8dea9 | |||
| f318bec53c | |||
| 7d84d6909e | |||
| 9224645473 | |||
| 6717771f9a | |||
| 99e0902b74 | |||
| c7f2805b05 | |||
| 6e1909647b | |||
| 0c7db56e15 | |||
| 3287ca449e | |||
| 53a8f65ecc | |||
| 113035b374 | |||
| c6e64837c3 | |||
| 82419e6df1 | |||
| faa76ee017 | |||
| ffdefb4106 | |||
| ba7b1c47b9 | |||
| 342b8105c4 | |||
| 367c2b568a | |||
| 0eef2c0d04 | |||
| c9a065107b | |||
| cacacc00f6 | |||
| 1b94cfc72c | |||
| 89fd41124f | |||
| 4e3b0ae3c1 | |||
| 9df6d535e2 | |||
| d545cce276 | |||
| 71b481d3be | |||
| 2710acaae6 | |||
| d79135ea01 | |||
| 1464011f6f | |||
| 409c8a6859 | |||
| 805ce36592 | |||
| 07fa56c53d | |||
| 28fca80023 | |||
| 08c0ee9ca8 | |||
| 2878ab1578 | |||
| 16f850cbcc | |||
| b9177e50d3 | |||
| c3258551d7 | |||
| ea0799c546 | |||
| 4843b7f7b8 | |||
| 61694c625e | |||
| 64b4b99d48 | |||
| dad966b551 | |||
| e46f503641 | |||
| fc4a1e6ace | |||
| 02b9a20c11 | |||
| 1db4e4caed | |||
| 991e176c85 | |||
| 4a41a6bb85 | |||
| 6229e1049b | |||
| 3cfd6ed109 | |||
| ad70180edd | |||
| efd4a83bd2 | |||
| e13b4ab7c9 | |||
| fee89cbaad | |||
| 2bdcba437c | |||
| 2c2216afae | |||
| 0823fc32fe | |||
| 091d19caf5 | |||
| 00a7381f08 | |||
| d494d1e3ee | |||
| b1c331a1df | |||
| b1a4fd085b | |||
| 511ab2afe9 | |||
| 30387ad654 | |||
| 5d528ee1f3 | |||
| 96bb4d50ba | |||
| 45f850adae | |||
| 09341ddbe9 | |||
| eab4274737 | |||
| 49fec1bc10 | |||
| de164469c4 | |||
| f595871cd9 | |||
| ff86cbd48e | |||
| 47bd4dade5 | |||
| ff8180920f | |||
| d4f08f0006 | |||
| 1db756063b | |||
| b44ea31bbf | |||
| ddccb946ff | |||
| 1f6f30ae9e | |||
| 4b19a3f4ed | |||
| c39d0ce2f7 | |||
| cbdf1a941c | |||
| a340635de5 | |||
| d62362db1a | |||
| a0aba69306 | |||
| 765683cd34 | |||
| 93ddf4f0ba | |||
| 8dcccf11bf | |||
| 43353ce892 | |||
| a698fea078 | |||
| 390d9b0fe1 | |||
| fd3ff05b17 | |||
| a5e4c9dd7b | |||
| 37f07dcc5a | |||
| 75bae9a59c | |||
| 1684d88a3b | |||
| 50acbb70da | |||
| 4de1025468 | |||
| c2506dd115 | |||
| f8c9aa8e6c | |||
| e19dc1f067 | |||
| 84776bced1 | |||
| 9162c86e21 | |||
| 88ffcbc62b | |||
| 6aff0b74cd | |||
| 79671890c5 | |||
| edd4ed307f | |||
| f786f8a970 | |||
| a1d10adaa3 | |||
| 7f480e8e56 | |||
| 94b972aaf4 | |||
| 9470775292 | |||
| 93eb63d930 | |||
| ea81096a43 | |||
| 594045b4e7 | |||
| 1449330ed3 | |||
| 07493a6b18 | |||
| 0310db5f24 | |||
| ed6d5a7d38 | |||
| ca7c1bc631 | |||
| 259070c658 | |||
| e1a7dd9b24 | |||
| e5945fbb3d | |||
| 247f636988 | |||
| c58a2ee624 | |||
| 35a1b44c21 | |||
| fd09f06507 | |||
| d66b501a99 | |||
| 349f6766ac | |||
| cd55ed1514 | |||
| 14d4c8accc | |||
| 37bca96bde | |||
| b0e6ae58c4 | |||
| 0375e47359 | |||
| 8450ad2856 | |||
| 015a1fbd53 | |||
| 318b61b4d7 | |||
| 60640517ca | |||
| 051e38f034 | |||
| 300054b9f7 | |||
| 4f9e1e3a6b | |||
| 856bc27bf1 | |||
| 408b774b42 | |||
| f82ef5aad4 | |||
| a92df41eb5 | |||
| 605990929d | |||
| 925ca28659 | |||
| 41e3fa7aa7 | |||
| 5fa27448f8 | |||
| c3428bdaed | |||
| 0539de9c4e | |||
| eed87164b1 | |||
| 0fe726c503 | |||
| f7f2113f1c | |||
| 49d931f5cc | |||
| 4dbd63de08 | |||
| 3a53c349a0 | |||
| a9fe5d5c87 | |||
| 9cecccf5da | |||
| 6cb3bbaa2d | |||
| 0ff6a0bb53 | |||
| 8ba57dec03 | |||
| d749c305ed | |||
| f46ac2d0ed | |||
| d7b7152315 | |||
| da1d52033b | |||
| 01ddc24c02 | |||
| 53a3e88d16 | |||
| bc8d1cc384 | |||
| ed2ba63a5f | |||
| 8a2c009653 | |||
| 55690a4c7f | |||
| 161ad4b143 | |||
| d701990df4 | |||
| 95a2d1013c | |||
| 5d4f3c0b3e | |||
| f8e4023307 | |||
| 609aba7c73 | |||
| bc7ab0eba1 | |||
| 56f271c8ad | |||
| 11190cff1d | |||
| 348a593dc1 | |||
| b67bb50f4a | |||
| 1174f651ab | |||
| dad9777c3e | |||
| 4f6285a8e7 | |||
| 20cecf4702 | |||
| 5763eabff4 | |||
| 0fc753949d | |||
| 083c1b7ca7 | |||
| 8f6b1b11e5 | |||
| 53b7d19c10 | |||
| b0b4f5e51a | |||
| b7414aa59c | |||
| f449045118 | |||
| 1e9ddada37 | |||
| 55d59a1854 | |||
| beb9f38514 | |||
| 00b1865fc8 | |||
| 0f545608c4 | |||
| bde2047184 | |||
| 0a22d8fb9e | |||
| 1bef37d504 | |||
| 7f5d290b66 | |||
| 9461e6f285 | |||
| 3f59a03f29 | |||
| 062b581b55 | |||
| 58d3fdc1c2 | |||
| 2f546842a7 | |||
| f089a85908 | |||
| a6e453a452 | |||
| 3adf3dc547 | |||
| 1d0ea96ae9 | |||
| a4cb17a1cb | |||
| 733da3161b | |||
| 1b1a0f553d | |||
| 972619c1fe | |||
| 61086d5360 | |||
| 37513d726c | |||
| 6510bee327 | |||
| cd36f3f096 | |||
| 407a6f5e31 | |||
| 2b28df280e | |||
| eb763ed82c | |||
| 755f53cce0 | |||
| c190ab40b0 | |||
| a3ad82de42 | |||
| 0f6cd6904d | |||
| 79c89af6ea | |||
| 56f10a9a52 | |||
| c679e2c067 | |||
| 0677987320 | |||
| 5fb6e6780c | |||
| 58ef91a7b1 | |||
| e1ae65b6d1 | |||
| 65307186dc | |||
| 6fa29c204b | |||
| 8219beeb1a | |||
| ace7e24dfb | |||
| 828c2a6883 | |||
| f195dc313d | |||
| 5b8a005f41 | |||
| 9feb75e645 | |||
| 7f8e90bd29 | |||
| 2fd34b649d | |||
| 104054ed1a | |||
| 457b28c22c | |||
| 095c8dcd0c | |||
| 8463e9ed94 | |||
| b6278c6144 | |||
| 1c1e1eee47 | |||
| b37ed9ec60 | |||
| 45f3ef6920 | |||
| 2cd6c4238b | |||
| f6ed0b33eb | |||
| c4f4ca3f90 | |||
| 9a6780616b | |||
| db4eca0a42 | |||
| 6d674edb48 | |||
| c0469a044b | |||
| b9b64eba9a | |||
| 2d74387a00 | |||
| f66b6fc20c | |||
| f0711a9fbc | |||
| 83a8c7215a | |||
| a86f966cb4 | |||
| 74db978b81 | |||
| 1300546a52 | |||
| 3aeb0bba71 | |||
| 03d3efa323 | |||
| f9c220bee0 | |||
| 114c2e2636 | |||
| 75a0e622ad | |||
| 8e2de4ee30 | |||
| b1602261cf | |||
| e5ed11f8ec | |||
| c1ecfd289e | |||
| c5cca15b4e | |||
| fa978315e6 | |||
| c5bffc38f4 | |||
| 88b5f6b148 | |||
| fc04742151 | |||
| c618d18d46 | |||
| 33bc7c00df | |||
| 1ed550d7f9 | |||
| 18b87f2c80 | |||
| fad503ca80 | |||
| 37ec3e4605 | |||
| 70e3d23f26 | |||
| 6ebf415a52 | |||
| 5c31104d0e | |||
| aed8b65e2b | |||
| 906e8aa2b2 | |||
| d0703f95af | |||
| 538b00797d | |||
| 985c555518 | |||
| 5ef26a25ee | |||
| 3db0e30d12 | |||
| a666cabae9 | |||
| 2e8d9018ef | |||
| ae49505e67 | |||
| 09bd5503b4 | |||
| dbe733524c | |||
| 1625f5c0f9 | |||
| 7cd49d07f2 | |||
| a542345456 | |||
| 0b98f21443 | |||
| c42e231e99 | |||
| ab653af4b3 | |||
| 8653f572c8 | |||
| 39b431fb19 | |||
| e158c5bc30 | |||
| d0fdfda4cb | |||
| 668a5ca041 | |||
| 5e3947b8bc | |||
| 233a627c9f | |||
| 6197486d49 | |||
| 60856b974b | |||
| d1eca5dc21 | |||
| 24b281d625 | |||
| b0d1dee38b | |||
| 0751c51803 | |||
| 0005229c1e | |||
| 33c2353107 | |||
| cb98297bb5 | |||
| 0e663e1da8 | |||
| 918a8c5f8b | |||
| 34938e8c62 | |||
| da6efe52ff | |||
| 570f1caa8f | |||
| 6f638805f7 | |||
| c92b0dc886 | |||
| b4b216de84 | |||
| 80f5d7c735 | |||
| 01aeb46664 | |||
| 2cfd2ff624 | |||
| ff12bbbdb6 | |||
| e19fe5d0e2 | |||
| 9670d5f4de | |||
| 863b09c39b | |||
| 6b90de539e | |||
| 4bb53ed6ba | |||
| e6523f3ec1 | |||
| c8ad8c79bd | |||
| b08cc9cb49 | |||
| 4f93dc0adf | |||
| 096f48be33 | |||
| 81398d58a2 | |||
| 7466da5651 | |||
| c8fdde4c5e | |||
| 15583e7975 | |||
| 5acdf39566 | |||
| 2240ada5db | |||
| 9d5c10d440 | |||
| 709f350d60 | |||
| 3d7e016b42 | |||
| b76104d145 | |||
| 702b6d6567 | |||
| 3e93004db6 | |||
| 589903c43c | |||
| f41b54de21 | |||
| 700b848f26 | |||
| a1f6e93e22 | |||
| 1628af2ffc | |||
| 1b5d446635 | |||
| 83a9ef772a | |||
| 1d9c3fb827 | |||
| ff92bdb324 | |||
| 363ad7342a | |||
| 663acd3810 | |||
| c2fc26089e | |||
| 58b464bdfc | |||
| ed766c74e6 | |||
| 1d07b8238c | |||
| 41c6ed7c5a | |||
| f7750af3d0 | |||
| 8854ffddee | |||
| a487619578 | |||
| 0eab6146fc | |||
| 389ba95e5a | |||
| 84d178c0ca | |||
| aed8f8efa8 | |||
| 38325741de | |||
| 891d5c2066 | |||
| 6b7edac6e4 | |||
| ab2a576e1b | |||
| 5a3e4dd47b | |||
| 064c4b4312 | |||
| cbde504057 | |||
| 949cfcfa69 | |||
| 06a005321e | |||
| d3587f595f | |||
| b0158ed7ce | |||
| e5f4300e54 | |||
| 7cc6f8604e | |||
| 657960e7d0 | |||
| aecd7f9283 | |||
| 6f1b30cd24 | |||
| a6ba549b67 | |||
| 84ea04f61d | |||
| 0d52cf5f97 | |||
| b15a083a15 | |||
| a128247ef5 | |||
| 590bd934c0 | |||
| e9826d2e7e | |||
| 58ed63cd18 | |||
| fd1bd3032f | |||
| d7206096ea | |||
| f43e594eca | |||
| ea4fe5e809 | |||
| 9fcb634510 | |||
| c14a4515ce | |||
| 8e71180cd2 | |||
| 08f98aa32f | |||
| e8aa9b9eb2 | |||
| 19f815eeff | |||
| a508f7a463 | |||
| e58d3ee060 | |||
| 268842681a | |||
| 9b357a9fbf | |||
| 6f80018b6e | |||
| 7a1153be65 | |||
| 85d4c24aba | |||
| 48c0ae8fe4 | |||
| e835b2c68c | |||
| 10d20f5d09 | |||
| 88b31ea940 | |||
| 9cb28d23a3 | |||
| ce5aae3f7d | |||
| e7f0eb6746 | |||
| 65a118d1f3 | |||
| 3d2eefc7e7 | |||
| f804c32eee | |||
| b89ecadc3a | |||
| 6d4ff0b89a | |||
| 598003ea39 | |||
| 6ef63790a9 | |||
| 3ffd986a1c | |||
| 0b5cd4c665 | |||
| 0371b0507a | |||
| 9fa71231c4 | |||
| 32beafc12d | |||
| 09e2ef334b | |||
| c0ce62ed2b | |||
| d3ed485e7a | |||
| 31c878b654 | |||
| 3a0f4a0bfc | |||
| 8b88d1294d | |||
| 43fcf46d69 | |||
| 394fe0f1f1 | |||
| 872921f635 | |||
| 7248470950 | |||
| 9d87296316 | |||
| 23c67f7e38 | |||
| bd98d95dbf | |||
| 3addb8d72b | |||
| 5545906063 | |||
| 36edfe9715 | |||
| 030b54a2b6 | |||
| f332613922 | |||
| 088bc14b11 | |||
| 86fa8da8c5 | |||
| abfc73299e | |||
| bd97fd5973 | |||
| f00e7c4a80 | |||
| 49c811b5f5 | |||
| b6c21e071d | |||
| e68129e47e | |||
| 72d7917415 | |||
| 53837fe132 | |||
| 08d094c786 | |||
| a1634ab496 | |||
| d35165bd8e | |||
| b701ce9721 | |||
| f3e18da416 | |||
| 131ab00304 | |||
| 26d7d58a5f | |||
| 02f92a7818 | |||
| b6fff521e4 | |||
| 23f1308231 | |||
| 947e890c59 | |||
| 0f1714de7c | |||
| a7d2b0f63b | |||
| 9c550a8154 | |||
| 49012a21c8 | |||
| f136151064 | |||
| 4838728521 | |||
| 95fac5dc13 | |||
| ac1f7884b5 | |||
| ab411512d4 | |||
| 704495ff42 | |||
| 9acc80260f | |||
| 7759fb7e68 | |||
| 0d71724598 | |||
| ae436f7a51 | |||
| 43ac20cbd2 | |||
| 2d90868f5c | |||
| 60987ae4a7 | |||
| 65c1d99120 | |||
| 35acf88847 | |||
| 45549b5fcd | |||
| 2eb9fb6a08 | |||
| 0d0e1083e6 | |||
| e650f3772a | |||
| e5ff4c65b7 | |||
| 276809f76a | |||
| 5e3840c5f1 | |||
| 6eace2a3ef | |||
| 7817b23857 | |||
| 432854aeb5 | |||
| 433c8f9c3c | |||
| ea25dbfd1e | |||
| 10f8318e79 | |||
| 17ff0c4f65 | |||
| 9abd653fb9 | |||
| ff6753fcdf | |||
| a65551f652 | |||
| f0d807a0fe | |||
| dfcdbe5b6a | |||
| 53e73238fd | |||
| 581454db69 | |||
| 63d501b629 | |||
| 60bd877ed9 | |||
| 44574465c5 | |||
| 2b7382a014 | |||
| 584b6df40d | |||
| e55f61deb2 | |||
| a6c6a1c6da | |||
| bdb5abe47b | |||
| dbe0ebc93e | |||
| 1c2f66e855 | |||
| 7eee3f9e5e | |||
| b7f069e1bd | |||
| 51c8396e32 | |||
| 0efe649ca5 | |||
| 75db0018bc | |||
| 2a9e1ea045 | |||
| 8feb8aaadc | |||
| b8f4385501 | |||
| d8b6d87a1c | |||
| f10702b3ca | |||
| 88248d7062 | |||
| 5ca1659bcc | |||
| 59530a12fd | |||
| aab2dd68b6 |
@@ -1,18 +1,26 @@
|
||||
name: Build
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
|
||||
build-mac-xcodebuild:
|
||||
name: Mac UI / xcodebuild / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-12, macos-13, macos-14]
|
||||
os: [macos-latest] #[macos-13, macos-14, macos-15]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
- name: Make
|
||||
working-directory: OSBindings/Mac
|
||||
run: xcodebuild CODE_SIGN_IDENTITY=-
|
||||
run: |
|
||||
xcodebuild -downloadComponent MetalToolchain
|
||||
xcodebuild CODE_SIGN_IDENTITY=-
|
||||
|
||||
build-sdl-cmake:
|
||||
name: SDL UI / cmake / ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -31,6 +39,7 @@ jobs:
|
||||
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
|
||||
;;
|
||||
macOS)
|
||||
brew uninstall cmake
|
||||
brew install cmake sdl2
|
||||
;;
|
||||
esac
|
||||
@@ -49,11 +58,12 @@ jobs:
|
||||
esac
|
||||
cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -v -j"$jobs"
|
||||
|
||||
build-sdl-scons:
|
||||
name: SDL UI / scons / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-latest]
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -85,3 +95,45 @@ jobs:
|
||||
jobs=1
|
||||
esac
|
||||
scons -j"$jobs"
|
||||
|
||||
build-qt5:
|
||||
name: Qt 5 / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '5.15.2'
|
||||
archives: 'qtbase qtmultimedia qtx11extras icu'
|
||||
- name: Make
|
||||
working-directory: OSBindings/Qt
|
||||
shell: bash
|
||||
run: |
|
||||
qmake -o Makefile clksignal.pro
|
||||
make
|
||||
|
||||
# build-qt6:
|
||||
# name: Qt 6 / ${{ matrix.os }}
|
||||
# strategy:
|
||||
# matrix:
|
||||
# os: [ubuntu-latest]
|
||||
# runs-on: ${{ matrix.os }}
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# - name: Install dependencies
|
||||
# uses: jurplel/install-qt-action@v4
|
||||
# with:
|
||||
# version: '6.8'
|
||||
# archives: qtbase
|
||||
# - name: Make
|
||||
# working-directory: OSBindings/Qt
|
||||
# shell: bash
|
||||
# run: |
|
||||
# qmake -o Makefile clksignal.pro
|
||||
# make
|
||||
|
||||
+25
-25
@@ -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) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
namespace Activity {
|
||||
|
||||
class Source {
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *observer) = 0;
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
float ConfidenceCounter::get_confidence() {
|
||||
float ConfidenceCounter::confidence() const {
|
||||
return float(hits_) / float(hits_ + misses_);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,25 +18,25 @@ namespace Analyser::Dynamic {
|
||||
The initial value of the confidence counter is 0.5.
|
||||
*/
|
||||
class ConfidenceCounter: public ConfidenceSource {
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float get_confidence() final;
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float confidence() const final;
|
||||
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
|
||||
/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
|
||||
void add_miss();
|
||||
/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
|
||||
void add_miss();
|
||||
|
||||
/*!
|
||||
Records an event that could be correct but isn't necessarily so; which can push probability
|
||||
down towards 0.5, but will never push it upwards.
|
||||
*/
|
||||
void add_equivocal();
|
||||
/*!
|
||||
Records an event that could be correct but isn't necessarily so; which can push probability
|
||||
down towards 0.5, but will never push it upwards.
|
||||
*/
|
||||
void add_equivocal();
|
||||
|
||||
private:
|
||||
int hits_ = 1;
|
||||
int misses_ = 1;
|
||||
private:
|
||||
int hits_ = 1;
|
||||
int misses_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Analyser::Dynamic {
|
||||
program is handed to an Atari 2600 then its confidence should grow towards 1.0.
|
||||
*/
|
||||
struct ConfidenceSource {
|
||||
virtual float get_confidence() = 0;
|
||||
virtual float confidence() const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,19 @@
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
|
||||
ConfidenceSummary::ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights
|
||||
) :
|
||||
sources_(sources), weights_(weights) {
|
||||
assert(weights.size() == sources.size());
|
||||
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
|
||||
}
|
||||
|
||||
float ConfidenceSummary::get_confidence() {
|
||||
float ConfidenceSummary::confidence() const {
|
||||
float result = 0.0f;
|
||||
for(std::size_t index = 0; index < sources_.size(); ++index) {
|
||||
result += sources_[index]->get_confidence() * weights_[index];
|
||||
result += sources_[index]->confidence() * weights_[index];
|
||||
}
|
||||
return result / weight_sum_;
|
||||
}
|
||||
|
||||
@@ -18,24 +18,24 @@ namespace Analyser::Dynamic {
|
||||
Summaries a collection of confidence sources by calculating their weighted sum.
|
||||
*/
|
||||
class ConfidenceSummary: public ConfidenceSource {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a summary that will produce the weighted sum of
|
||||
@c sources, each using the corresponding entry of @c weights.
|
||||
public:
|
||||
/*!
|
||||
Instantiates a summary that will produce the weighted sum of
|
||||
@c sources, each using the corresponding entry of @c weights.
|
||||
|
||||
Requires that @c sources and @c weights are of the same length.
|
||||
*/
|
||||
ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights);
|
||||
Requires that @c sources and @c weights are of the same length.
|
||||
*/
|
||||
ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights);
|
||||
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float get_confidence() final;
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float confidence() const final;
|
||||
|
||||
private:
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
const std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
private:
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
const std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,90 +15,90 @@ using namespace Analyser::Dynamic;
|
||||
namespace {
|
||||
|
||||
class MultiStruct: public Reflection::Struct {
|
||||
public:
|
||||
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
|
||||
for(auto device: devices) {
|
||||
options_.emplace_back(device->get_options());
|
||||
public:
|
||||
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
|
||||
for(auto device: devices) {
|
||||
options_.emplace_back(device->get_options());
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
auto options = options_.begin();
|
||||
for(auto device: devices_) {
|
||||
device->set_options(*options);
|
||||
++options;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
keys.insert(new_keys.begin(), new_keys.end());
|
||||
}
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
values.insert(new_values.begin(), new_values.end());
|
||||
}
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return options->count_of(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *get(const std::string &name) final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set(const std::string &name, const void *value, const size_t offset) final {
|
||||
const auto safe_type = type_of(name);
|
||||
if(!safe_type) return;
|
||||
|
||||
// Set this property only where the child's type is the same as that
|
||||
// which was returned from here for type_of.
|
||||
for(auto &options: options_) {
|
||||
const auto type = options->type_of(name);
|
||||
if(!type) continue;
|
||||
|
||||
if(*type == *safe_type) {
|
||||
options->set(name, value, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
auto options = options_.begin();
|
||||
for(auto device: devices_) {
|
||||
device->set_options(*options);
|
||||
++options;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
keys.insert(new_keys.begin(), new_keys.end());
|
||||
}
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
values.insert(new_values.begin(), new_values.end());
|
||||
}
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return options->count_of(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *get(const std::string &name) final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set(const std::string &name, const void *value, size_t offset) final {
|
||||
const auto safe_type = type_of(name);
|
||||
if(!safe_type) return;
|
||||
|
||||
// Set this property only where the child's type is the same as that
|
||||
// which was returned from here for type_of.
|
||||
for(auto &options: options_) {
|
||||
const auto type = options->type_of(name);
|
||||
if(!type) continue;
|
||||
|
||||
if(*type == *safe_type) {
|
||||
options->set(name, value, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Configurable::Device *> &devices_;
|
||||
std::vector<std::unique_ptr<Reflection::Struct>> options_;
|
||||
private:
|
||||
const std::vector<Configurable::Device *> &devices_;
|
||||
std::vector<std::unique_ptr<Reflection::Struct>> options_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -115,6 +115,6 @@ void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &s
|
||||
options->apply();
|
||||
}
|
||||
|
||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
|
||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() const {
|
||||
return std::make_unique<MultiStruct>(devices_);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Configurable/Configurable.hpp"
|
||||
#include "Machines/DynamicMachine.hpp"
|
||||
#include "Configurable/Configurable.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -23,15 +23,15 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiConfigurable: public Configurable::Device {
|
||||
public:
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
|
||||
std::unique_ptr<Reflection::Struct> get_options() final;
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &) final;
|
||||
std::unique_ptr<Reflection::Struct> get_options() const final;
|
||||
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,52 +15,52 @@ using namespace Analyser::Dynamic;
|
||||
namespace {
|
||||
|
||||
class MultiJoystick: public Inputs::Joystick {
|
||||
public:
|
||||
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
|
||||
for(const auto &machine: machines) {
|
||||
const auto &joysticks = machine->get_joysticks();
|
||||
if(joysticks.size() >= index) {
|
||||
joysticks_.push_back(joysticks[index].get());
|
||||
}
|
||||
public:
|
||||
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, const std::size_t index) {
|
||||
for(const auto &machine: machines) {
|
||||
const auto &joysticks = machine->get_joysticks();
|
||||
if(joysticks.size() > index) {
|
||||
joysticks_.push_back(joysticks[index].get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
}
|
||||
const std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, bool is_active) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, float value) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
void set_input(const Input &digital_input, const bool is_active) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_all_inputs() final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
void set_input(const Input &digital_input, const float value) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs;
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
void reset_all_inputs() final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs;
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -22,14 +22,14 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ void MultiKeyboardMachine::clear_all_keys() {
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
|
||||
void MultiKeyboardMachine::set_key_state(const uint16_t key, const bool is_pressed) {
|
||||
for(const auto &machine: machines_) {
|
||||
machine->set_key_state(key, is_pressed);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::can_type(char c) const {
|
||||
bool MultiKeyboardMachine::can_type(const char c) const {
|
||||
bool can_type = true;
|
||||
for(const auto &machine: machines_) {
|
||||
can_type &= machine->can_type(c);
|
||||
@@ -51,12 +51,20 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
|
||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
|
||||
: machines_(machines) {
|
||||
for(const auto &machine: machines_) {
|
||||
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
|
||||
observed_keys_.insert(
|
||||
machine->get_keyboard().observed_keys().begin(),
|
||||
machine->get_keyboard().observed_keys().end()
|
||||
);
|
||||
is_exclusive_ |= machine->get_keyboard().is_exclusive();
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(
|
||||
const Key key,
|
||||
const char value,
|
||||
const bool is_pressed,
|
||||
const bool is_repeat
|
||||
) {
|
||||
bool was_consumed = false;
|
||||
for(const auto &machine: machines_) {
|
||||
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Machines/KeyboardMachine.hpp"
|
||||
#include "Machines/DynamicMachine.hpp"
|
||||
#include "Machines/KeyboardMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -23,34 +23,34 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
private:
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
std::unique_ptr<MultiKeyboard> keyboard_;
|
||||
private:
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
|
||||
|
||||
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
bool can_type(char c) const final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
std::unique_ptr<MultiKeyboard> keyboard_;
|
||||
|
||||
public:
|
||||
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
bool can_type(char c) const final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#include "MultiMediaTarget.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
@@ -18,9 +19,38 @@ MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::
|
||||
}
|
||||
|
||||
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
|
||||
// TODO: copy media afresh for each target machine; media
|
||||
// generally has mutable state.
|
||||
|
||||
bool inserted = false;
|
||||
for(const auto &target : targets_) {
|
||||
inserted |= target->insert_media(media);
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
|
||||
MultiMediaChangeObserver::MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
for(const auto &machine: machines) {
|
||||
auto media_change_observer = machine->media_change_observer();
|
||||
if(media_change_observer) targets_.push_back(media_change_observer);
|
||||
}
|
||||
}
|
||||
|
||||
using ChangeEffect = MachineTypes::MediaChangeObserver::ChangeEffect;
|
||||
|
||||
ChangeEffect MultiMediaChangeObserver::effect_for_file_did_change(const std::string &name) const {
|
||||
if(targets_.empty()) {
|
||||
return ChangeEffect::None;
|
||||
}
|
||||
|
||||
std::unordered_set<ChangeEffect> effects;
|
||||
for(const auto &target: targets_) {
|
||||
effects.insert(target->effect_for_file_did_change(name));
|
||||
}
|
||||
|
||||
// No agreement => restart.
|
||||
if(effects.size() > 1) {
|
||||
return ChangeEffect::RestartMachine;
|
||||
}
|
||||
return *effects.begin();
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../../Machines/MediaTarget.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "Machines/MediaTarget.hpp"
|
||||
#include "Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -23,14 +23,25 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
struct MultiMediaTarget: public MachineTypes::MediaTarget {
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &media) final;
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &) final;
|
||||
|
||||
private:
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
private:
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
};
|
||||
|
||||
struct MultiMediaChangeObserver: public MachineTypes::MediaChangeObserver {
|
||||
public:
|
||||
MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
ChangeEffect effect_for_file_did_change(const std::string &) const final;
|
||||
|
||||
private:
|
||||
std::vector<MachineTypes::MediaChangeObserver *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ template <typename MachineType>
|
||||
void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &function) {
|
||||
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
|
||||
// to a separate queue and this queue will block until all are done.
|
||||
volatile std::size_t outstanding_machines;
|
||||
std::size_t outstanding_machines;
|
||||
std::condition_variable condition;
|
||||
std::mutex mutex;
|
||||
{
|
||||
@@ -33,7 +33,7 @@ void MultiInterface<MachineType>::perform_parallel(const std::function<void(Mach
|
||||
if(machine) function(machine);
|
||||
|
||||
std::lock_guard lock(mutex);
|
||||
outstanding_machines--;
|
||||
--outstanding_machines;
|
||||
condition.notify_all();
|
||||
});
|
||||
}
|
||||
@@ -53,7 +53,7 @@ void MultiInterface<MachineType>::perform_serial(const std::function<void(Machin
|
||||
}
|
||||
|
||||
// MARK: - MultiScanProducer
|
||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
|
||||
scan_target_ = scan_target;
|
||||
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
@@ -80,7 +80,12 @@ void MultiScanProducer::did_change_machine_order() {
|
||||
}
|
||||
|
||||
// MARK: - MultiAudioProducer
|
||||
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
|
||||
MultiAudioProducer::MultiAudioProducer(
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
|
||||
std::recursive_mutex &machines_mutex
|
||||
) :
|
||||
MultiInterface(machines, machines_mutex)
|
||||
{
|
||||
speaker_ = MultiSpeaker::create(machines);
|
||||
}
|
||||
|
||||
@@ -96,10 +101,10 @@ void MultiAudioProducer::did_change_machine_order() {
|
||||
|
||||
// MARK: - MultiTimedMachine
|
||||
|
||||
void MultiTimedMachine::run_for(Time::Seconds duration) {
|
||||
void MultiTimedMachine::run_for(const Time::Seconds duration) {
|
||||
perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
||||
if(delegate_) delegate_->did_run_machines(this);
|
||||
if(delegate_) delegate_->did_run_machines(*this);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../../Machines/MachineTypes.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "Machines/MachineTypes.hpp"
|
||||
#include "Machines/DynamicMachine.hpp"
|
||||
|
||||
#include "MultiSpeaker.hpp"
|
||||
|
||||
@@ -21,88 +21,91 @@
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
template <typename MachineType> class MultiInterface {
|
||||
public:
|
||||
MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
|
||||
public:
|
||||
MultiInterface(
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
|
||||
std::recursive_mutex &machines_mutex
|
||||
) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
protected:
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(MachineType *)> &);
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(MachineType *)> &);
|
||||
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(MachineType *)> &);
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(MachineType *)> &);
|
||||
|
||||
protected:
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
protected:
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
};
|
||||
|
||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine *) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine &) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *const delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void run_for(Time::Seconds duration) final;
|
||||
void run_for(Time::Seconds duration) final;
|
||||
|
||||
private:
|
||||
void run_for(const Cycles) final {}
|
||||
Delegate *delegate_ = nullptr;
|
||||
private:
|
||||
void run_for(Cycles) final {}
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Informs the MultiScanProducer that the order of machines has changed; it
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
/*!
|
||||
Informs the MultiScanProducer that the order of machines has changed; it
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
void set_scan_target(Outputs::Display::ScanTarget *) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
|
||||
private:
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
private:
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
|
||||
public:
|
||||
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
|
||||
public:
|
||||
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &, std::recursive_mutex &);
|
||||
|
||||
/*!
|
||||
Informs the MultiAudio that the order of machines has changed; it
|
||||
uses this as an opportunity to switch speaker delegates as appropriate.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
/*!
|
||||
Informs the MultiAudio that the order of machines has changed; it
|
||||
uses this as an opportunity to switch speaker delegates as appropriate.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
|
||||
private:
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
private:
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -28,7 +28,7 @@ MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speak
|
||||
}
|
||||
}
|
||||
|
||||
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const float maximum) {
|
||||
float ideal = 0.0f;
|
||||
for(const auto &speaker: speakers_) {
|
||||
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
|
||||
@@ -37,7 +37,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
|
||||
return ideal / float(speakers_.size());
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
|
||||
void MultiSpeaker::set_computed_output_rate(const float cycles_per_second, const int buffer_size, const bool stereo) {
|
||||
stereo_output_ = stereo;
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
|
||||
@@ -54,39 +54,39 @@ bool MultiSpeaker::get_is_stereo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_output_volume(float volume) {
|
||||
void MultiSpeaker::set_output_volume(const float volume) {
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_output_volume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker &speaker, const std::vector<int16_t> &buffer) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
if(&speaker != front_speaker_) return;
|
||||
}
|
||||
did_complete_samples(this, buffer, stereo_output_);
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker &speaker) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
if(&speaker != front_speaker_) return;
|
||||
}
|
||||
delegate->speaker_did_change_input_clock(this);
|
||||
delegate->speaker_did_change_input_clock(*this);
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
front_speaker_ = machine->audio_producer()->get_speaker();
|
||||
}
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(delegate) {
|
||||
delegate->speaker_did_change_input_clock(this);
|
||||
delegate->speaker_did_change_input_clock(*this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Outputs/Speaker/Speaker.hpp"
|
||||
#include "Machines/DynamicMachine.hpp"
|
||||
#include "Outputs/Speaker/Speaker.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -25,32 +25,32 @@ namespace Analyser::Dynamic {
|
||||
abreast of the current frontmost machine.
|
||||
*/
|
||||
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
|
||||
public:
|
||||
/*!
|
||||
Provides a construction mechanism that may return nullptr, in the case that all included
|
||||
machines return nullptr as their speaker.
|
||||
*/
|
||||
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
/*!
|
||||
Provides a construction mechanism that may return nullptr, in the case that all included
|
||||
machines return nullptr as their speaker.
|
||||
*/
|
||||
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||
void set_new_front_machine(::Machine::DynamicMachine *machine);
|
||||
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||
void set_new_front_machine(::Machine::DynamicMachine *);
|
||||
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker &, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker &) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
|
||||
bool stereo_output_ = false;
|
||||
bool stereo_output_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@
|
||||
//
|
||||
|
||||
#include "MultiMachine.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::MultiMachine> logger;
|
||||
|
||||
using Logger = Log::Logger<Log::Source::MultiMachine>;
|
||||
}
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
@@ -27,7 +25,9 @@ MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machin
|
||||
audio_producer_(machines_, machines_mutex_),
|
||||
joystick_machine_(machines_),
|
||||
keyboard_machine_(machines_),
|
||||
media_target_(machines_) {
|
||||
media_target_(machines_),
|
||||
media_change_observer_(machines_)
|
||||
{
|
||||
timed_machine_.set_delegate(this);
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ Activity::Source *MultiMachine::activity_source() {
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
#define Provider(type, name, member) \
|
||||
type *MultiMachine::name() { \
|
||||
if(has_picked_) { \
|
||||
#define Provider(type, name, member) \
|
||||
type *MultiMachine::name() { \
|
||||
if(has_picked_) { \
|
||||
return machines_.front()->name(); \
|
||||
} else { \
|
||||
return &member; \
|
||||
} \
|
||||
} else { \
|
||||
return &member; \
|
||||
} \
|
||||
}
|
||||
|
||||
Provider(Configurable::Device, configurable_device, configurable_)
|
||||
@@ -51,6 +51,7 @@ Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
|
||||
Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
|
||||
Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
|
||||
Provider(MachineTypes::MediaTarget, media_target, media_target_)
|
||||
Provider(MachineTypes::MediaChangeObserver, media_change_observer, media_change_observer_)
|
||||
|
||||
MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
|
||||
// TODO.
|
||||
@@ -65,11 +66,11 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
|
||||
(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
|
||||
}
|
||||
|
||||
void MultiMachine::did_run_machines(MultiTimedMachine *) {
|
||||
void MultiMachine::did_run_machines(MultiTimedMachine &) {
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
|
||||
if constexpr (logger.enabled) {
|
||||
auto line = logger.info();
|
||||
if constexpr (Logger::InfoEnabled) {
|
||||
auto line = Logger::info();
|
||||
for(const auto &machine: machines_) {
|
||||
auto timed_machine = machine->timed_machine();
|
||||
line.append("%0.4f %s; ", timed_machine->get_confidence(), timed_machine->debug_type().c_str());
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Machines/DynamicMachine.hpp"
|
||||
#include "Machines/DynamicMachine.hpp"
|
||||
|
||||
#include "Implementation/MultiProducer.hpp"
|
||||
#include "Implementation/MultiConfigurable.hpp"
|
||||
#include "Implementation/MultiProducer.hpp"
|
||||
#include "Implementation/MultiJoystickMachine.hpp"
|
||||
#include "Implementation/MultiKeyboardMachine.hpp"
|
||||
#include "Implementation/MultiMediaTarget.hpp"
|
||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
|
||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp"
|
||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
|
||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.hpp"
|
||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp"
|
||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiMediaTarget.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -38,44 +38,46 @@ namespace Analyser::Dynamic {
|
||||
the others in the set, that machine stops running.
|
||||
*/
|
||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
|
||||
public:
|
||||
/*!
|
||||
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||
requesting this class as a proxy.
|
||||
public:
|
||||
/*!
|
||||
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||
requesting this class as a proxy.
|
||||
|
||||
@returns @c true if the multimachine would discard all but the first machine in this list;
|
||||
@c false otherwise.
|
||||
*/
|
||||
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
|
||||
@returns @c true if the multimachine would discard all but the first machine in this list;
|
||||
@c false otherwise.
|
||||
*/
|
||||
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &);
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&);
|
||||
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
MachineTypes::TimedMachine *timed_machine() final;
|
||||
MachineTypes::ScanProducer *scan_producer() final;
|
||||
MachineTypes::AudioProducer *audio_producer() final;
|
||||
MachineTypes::JoystickMachine *joystick_machine() final;
|
||||
MachineTypes::KeyboardMachine *keyboard_machine() final;
|
||||
MachineTypes::MouseMachine *mouse_machine() final;
|
||||
MachineTypes::MediaTarget *media_target() final;
|
||||
void *raw_pointer() final;
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
MachineTypes::TimedMachine *timed_machine() final;
|
||||
MachineTypes::ScanProducer *scan_producer() final;
|
||||
MachineTypes::AudioProducer *audio_producer() final;
|
||||
MachineTypes::JoystickMachine *joystick_machine() final;
|
||||
MachineTypes::KeyboardMachine *keyboard_machine() final;
|
||||
MachineTypes::MouseMachine *mouse_machine() final;
|
||||
MachineTypes::MediaTarget *media_target() final;
|
||||
MachineTypes::MediaChangeObserver *media_change_observer() final;
|
||||
void *raw_pointer() final;
|
||||
|
||||
private:
|
||||
void did_run_machines(MultiTimedMachine *) final;
|
||||
private:
|
||||
void did_run_machines(MultiTimedMachine &) final;
|
||||
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
|
||||
MultiConfigurable configurable_;
|
||||
MultiTimedMachine timed_machine_;
|
||||
MultiScanProducer scan_producer_;
|
||||
MultiAudioProducer audio_producer_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
MultiConfigurable configurable_;
|
||||
MultiTimedMachine timed_machine_;
|
||||
MultiScanProducer scan_producer_;
|
||||
MultiAudioProducer audio_producer_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
MultiMediaChangeObserver media_change_observer_;
|
||||
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ enum class Machine {
|
||||
AtariST,
|
||||
Amiga,
|
||||
Archimedes,
|
||||
BBCMicro,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
@@ -25,6 +26,7 @@ enum class Machine {
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
Plus4,
|
||||
PCCompatible,
|
||||
Vic20,
|
||||
ZX8081,
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#include "Disk.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
#include "Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "Numeric/CRC.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -44,32 +44,44 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
}
|
||||
|
||||
for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
|
||||
for(std::size_t file_offset = 8; file_offset <= final_file_offset; file_offset += 8) {
|
||||
File new_file;
|
||||
char name[10];
|
||||
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
|
||||
new_file.name = name;
|
||||
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
|
||||
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
|
||||
new_file.load_address = uint32_t(
|
||||
details->samples[0][file_offset] |
|
||||
(details->samples[0][file_offset+1] << 8) |
|
||||
((details->samples[0][file_offset+6]&0x0c) << 14)
|
||||
);
|
||||
new_file.execution_address = uint32_t(
|
||||
details->samples[0][file_offset+2] |
|
||||
(details->samples[0][file_offset+3] << 8) |
|
||||
((details->samples[0][file_offset+6]&0xc0) << 10)
|
||||
);
|
||||
if(names->samples[0][file_offset + 7] & 0x80) {
|
||||
// File is locked; it may not be altered or deleted.
|
||||
new_file.flags |= File::Flags::Locked;
|
||||
}
|
||||
|
||||
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
|
||||
auto data_length = long(
|
||||
details->samples[0][file_offset+4] |
|
||||
(details->samples[0][file_offset+5] << 8) |
|
||||
((details->samples[0][file_offset+6]&0x30) << 12)
|
||||
);
|
||||
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
|
||||
new_file.data.reserve(size_t(data_length));
|
||||
|
||||
if(start_sector < 2) continue;
|
||||
while(data_length > 0) {
|
||||
uint8_t sector = uint8_t(start_sector % 10);
|
||||
uint8_t track = uint8_t(start_sector / 10);
|
||||
start_sector++;
|
||||
const uint8_t sector = uint8_t(start_sector % 10);
|
||||
const uint8_t track = uint8_t(start_sector / 10);
|
||||
++start_sector;
|
||||
|
||||
const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
|
||||
if(!next_sector) break;
|
||||
|
||||
long length_from_sector = std::min(data_length, 256l);
|
||||
const long length_from_sector = std::min(data_length, 256l);
|
||||
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
|
||||
data_length -= length_from_sector;
|
||||
}
|
||||
@@ -133,7 +145,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
}
|
||||
|
||||
// Parse the root directory, at least.
|
||||
for(std::size_t file_offset = 0x005; file_offset < (catalogue->has_large_sectors ? 0x7d7 : 0x4cb); file_offset += 0x1a) {
|
||||
const std::size_t directory_extent = catalogue->has_large_sectors ? 0x7d7 : 0x4cb;
|
||||
for(std::size_t file_offset = 0x005; file_offset < directory_extent; file_offset += 0x1a) {
|
||||
// Obtain the name, which will be at most ten characters long, and will
|
||||
// be terminated by either a NULL character or a \r.
|
||||
char name[11]{};
|
||||
@@ -190,11 +203,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
|
||||
new_file.data.reserve(size);
|
||||
while(new_file.data.size() < size) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
const Storage::Encodings::MFM::Sector *const sector =
|
||||
parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
if(!sector) break;
|
||||
|
||||
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
|
||||
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->samples[0].begin(),
|
||||
sector->samples[0].begin() + ssize_t(length_from_sector)
|
||||
);
|
||||
++start_sector;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "Storage/Disk/Disk.hpp"
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
@@ -27,7 +27,7 @@ struct Catalogue {
|
||||
} bootOption;
|
||||
};
|
||||
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -12,28 +12,45 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Numeric/StringSimilarity.hpp"
|
||||
#include "Numeric/StringSimilarity.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
namespace {
|
||||
bool is_basic(const File &file) {
|
||||
std::size_t pointer = 0;
|
||||
const uint8_t *const data = file.data.data();
|
||||
const std::size_t data_size = file.data.size();
|
||||
while(true) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 0x0d) {
|
||||
return false;
|
||||
}
|
||||
if((data[pointer+1]&0x7f) == 0x7f) break;
|
||||
pointer += data[pointer+3];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
// only one mapped item is allowed
|
||||
// Only one mapped item is allowed.
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 8 or 16 kb in size
|
||||
// Cartridges must be 8 or 16 kb in size.
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
|
||||
|
||||
// is a copyright string present?
|
||||
// Check copyright string.
|
||||
const uint8_t copyright_offset = segment.data[7];
|
||||
if(
|
||||
segment.data[copyright_offset] != 0x00 ||
|
||||
@@ -42,16 +59,16 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
segment.data[copyright_offset+3] != 0x29
|
||||
) continue;
|
||||
|
||||
// is the language entry point valid?
|
||||
// Check language entry point.
|
||||
if(!(
|
||||
(segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) ||
|
||||
(segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0)
|
||||
)) continue;
|
||||
|
||||
// is the service entry point valid?
|
||||
// Check service entry point.
|
||||
if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue;
|
||||
|
||||
// probability of a random binary blob that isn't an Acorn ROM proceeding to here:
|
||||
// Probability of a random binary blob that isn't an Acorn ROM proceeding to here:
|
||||
// 1/(2^32) *
|
||||
// ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) *
|
||||
// 1/4
|
||||
@@ -62,45 +79,44 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
auto target8bit = std::make_unique<ElectronTarget>();
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
const auto early_exit = [](auto &ptr) {
|
||||
TargetList list;
|
||||
list.push_back(std::move(ptr));
|
||||
return list;
|
||||
};
|
||||
|
||||
auto targetElectron = std::make_unique<ElectronTarget>();
|
||||
auto targetBBC = std::make_unique<BBCMicroTarget>();
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
int bbc_hits = 0;
|
||||
int electron_hits = 0;
|
||||
bool format_prefers_bbc = false;
|
||||
|
||||
// Copy appropriate cartridges to the 8-bit target.
|
||||
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
// Copy appropriate cartridges to the 8-bit targets.
|
||||
targetElectron->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
targetBBC->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
|
||||
// If there are any tapes, attempt to get data from the first.
|
||||
// If there are tapes, attempt to get data from the first.
|
||||
if(!media.tapes.empty()) {
|
||||
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
|
||||
std::vector<File> files = GetFiles(tape);
|
||||
tape->reset();
|
||||
auto serialiser = tape->serialiser();
|
||||
std::vector<File> files = GetFiles(*serialiser);
|
||||
|
||||
// continue if there are any files
|
||||
// Continue only if there are any files.
|
||||
if(!files.empty()) {
|
||||
bool is_basic = true;
|
||||
|
||||
// If a file is execute-only, that means *RUN.
|
||||
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
|
||||
|
||||
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
|
||||
// so that's also justification to *RUN
|
||||
std::size_t pointer = 0;
|
||||
uint8_t *const data = &files.front().data[0];
|
||||
const std::size_t data_size = files.front().data.size();
|
||||
while(1) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 13) {
|
||||
is_basic = false;
|
||||
break;
|
||||
}
|
||||
if((data[pointer+1]&0x7f) == 0x7f) break;
|
||||
pointer += data[pointer+3];
|
||||
}
|
||||
|
||||
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
||||
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
targetElectron->loading_command =
|
||||
(files.front().flags & File::Flags::ExecuteOnly) || !is_basic(files.front()) ? "*RUN\n" : "CHAIN\"\"\n";
|
||||
targetElectron->media.tapes = media.tapes;
|
||||
|
||||
target8bit->media.tapes = media.tapes;
|
||||
// TODO: my BBC Micro doesn't yet support tapes; evaluate here in the future.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,25 +131,67 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
// 8-bit options: DFS and Hugo-style ADFS.
|
||||
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
|
||||
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
||||
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
|
||||
target8bit->media.disks = media.disks;
|
||||
target8bit->has_dfs = bool(dfs_catalogue);
|
||||
target8bit->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
target8bit->should_shift_restart = true;
|
||||
} else {
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
// Electron: use the Pres ADFS if using an ADFS, as it leaves Page at &E00.
|
||||
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);
|
||||
|
||||
// Special case: if there's only one file, and it is called CPMDISC,
|
||||
// select a BBC with the Z80 second processor.
|
||||
const auto &files = dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files;
|
||||
if(files.size() == 1 && files[0].name == "$.CPMDISC") {
|
||||
targetBBC->tube_processor = BBCMicroTarget::TubeProcessor::Z80;
|
||||
return early_exit(targetBBC);
|
||||
}
|
||||
|
||||
// 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 &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
targetBBC->should_shift_restart = targetElectron->should_shift_restart = true;
|
||||
} else {
|
||||
// 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";
|
||||
}
|
||||
|
||||
// Further special case: if any of the files have a top word of 0x0003 then
|
||||
// they're for a 6502 second processor, so provide a BBC with one of those.
|
||||
for(const auto &file: files) {
|
||||
if((file.load_address >> 16) == 3) {
|
||||
targetBBC->tube_processor = BBCMicroTarget::TubeProcessor::WDC65C02;
|
||||
return early_exit(targetBBC);
|
||||
}
|
||||
}
|
||||
|
||||
// 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: files) {
|
||||
// Electron: check whether adding the AP6 ROM is justified.
|
||||
// For now this is an incredibly dense text search;
|
||||
// if any of the commands that aren't usually present
|
||||
// on a stock Electron are here, add the AP6 ROM and
|
||||
// some sideways RAM such that the SR commands are useful.
|
||||
for(const auto &command: {
|
||||
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
|
||||
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
|
||||
@@ -141,10 +199,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
"VERIFY", "ZERO"
|
||||
}) {
|
||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
targetElectron->has_ap6_rom = true;
|
||||
targetElectron->has_sideways_ram = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for any 'BBC indicators', i.e. direct access to BBC-specific hardware.
|
||||
// Also currently a dense search.
|
||||
|
||||
const auto hits = [&](const std::initializer_list<uint16_t> collection) {
|
||||
int hits = 0;
|
||||
for(const auto address: collection) {
|
||||
const uint8_t sta_address[3] = {
|
||||
0x8d, uint8_t(address & 0xff), uint8_t(address >> 8)
|
||||
};
|
||||
|
||||
if(std::search(
|
||||
file.data.begin(), file.data.end(),
|
||||
std::begin(sta_address), std::end(sta_address)
|
||||
) != file.data.end()) {
|
||||
++hits;
|
||||
}
|
||||
|
||||
// I think I'll want std::ranges::contains_subrange if/when building for C++23.
|
||||
}
|
||||
return hits;
|
||||
};
|
||||
|
||||
bbc_hits += hits({
|
||||
// The video control registers.
|
||||
0xfe20, 0xfe21,
|
||||
|
||||
// The system VIA.
|
||||
0xfe40, 0xfe41, 0xfe42, 0xfe43, 0xfe44, 0xfe45, 0xfe46, 0xfe47,
|
||||
0xfe48, 0xfe49, 0xfe4a, 0xfe4b, 0xfe4c, 0xfe4d, 0xfe4e, 0xfe4f,
|
||||
|
||||
// The user VIA.
|
||||
0xfe60, 0xfe61, 0xfe62, 0xfe63, 0xfe64, 0xfe65, 0xfe66, 0xfe67,
|
||||
0xfe68, 0xfe69, 0xfe6a, 0xfe6b, 0xfe6c, 0xfe6d, 0xfe6e, 0xfe6f,
|
||||
});
|
||||
// BASIC for "MODE7".
|
||||
static constexpr uint8_t mode7[] = {0xeb, 0x37};
|
||||
bbc_hits += std::search(
|
||||
file.data.begin(), file.data.end(),
|
||||
std::begin(mode7), std::end(mode7)
|
||||
) != file.data.end();
|
||||
|
||||
electron_hits += hits({
|
||||
// ULA addresses that aren't also the BBC's CRTC.
|
||||
0xfe03, 0xfe04, 0xfe05,
|
||||
0xfe06, 0xfe07, 0xfe08,
|
||||
0xfe09, 0xfe0a, 0xfe0b,
|
||||
0xfe0c, 0xfe0d, 0xfe0e,
|
||||
0xfe0f,
|
||||
});
|
||||
}
|
||||
} else if(adfs_catalogue) {
|
||||
// Archimedes options, implicitly: ADFS, non-Hugo.
|
||||
@@ -159,8 +267,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
// Take whatever else comes with a preference for things that don't
|
||||
// have 'boot' or 'read' in them (the latter of which will tend to be
|
||||
// read_me or read_this or similar).
|
||||
constexpr char read[] = "read";
|
||||
constexpr char boot[] = "boot";
|
||||
static constexpr char read[] = "read";
|
||||
static constexpr char boot[] = "boot";
|
||||
const auto has = [&](const char *begin, const char *end) {
|
||||
return std::search(
|
||||
file.name.begin(), file.name.end(),
|
||||
@@ -189,28 +297,38 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
// Enable the Acorn ADFS if a mass-storage device is attached;
|
||||
// unlike the Pres ADFS it retains SCSI logic.
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target8bit->has_acorn_adfs = true;
|
||||
targetElectron->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
targetElectron->has_acorn_adfs = true;
|
||||
|
||||
// Assume some sort of later-era Acorn work is likely to happen;
|
||||
// so ensure *TYPE, etc are present.
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
targetElectron->has_ap6_rom = true;
|
||||
targetElectron->has_sideways_ram = true;
|
||||
|
||||
target8bit->media.mass_storage_devices = media.mass_storage_devices;
|
||||
targetElectron->media.mass_storage_devices = media.mass_storage_devices;
|
||||
|
||||
// Check for a boot option.
|
||||
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
|
||||
const auto sector = targetElectron->media.mass_storage_devices.front()->get_block(1);
|
||||
if(sector[0xfd]) {
|
||||
target8bit->should_shift_restart = true;
|
||||
targetElectron->should_shift_restart = true;
|
||||
} else {
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
targetElectron->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
if(!target8bit->media.empty()) {
|
||||
targets.push_back(std::move(target8bit));
|
||||
if(!targetElectron->media.empty() && !targetBBC->media.empty()) {
|
||||
if(bbc_hits > electron_hits || (bbc_hits == electron_hits && format_prefers_bbc)) {
|
||||
targets.push_back(std::move(targetBBC));
|
||||
} else {
|
||||
targets.push_back(std::move(targetElectron));
|
||||
}
|
||||
} else {
|
||||
if(!targetElectron->media.empty()) {
|
||||
targets.push_back(std::move(targetElectron));
|
||||
} else if(!targetBBC->media.empty()) {
|
||||
targets.push_back(std::move(targetBBC));
|
||||
}
|
||||
}
|
||||
if(!targetArchimedes->media.empty()) {
|
||||
targets.push_back(std::move(targetArchimedes));
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -10,70 +10,68 @@
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
|
||||
#include "Numeric/CRC.hpp"
|
||||
#include "Storage/Tape/Parsers/Acorn.hpp"
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(
|
||||
Storage::Tape::TapeSerialiser &serialiser,
|
||||
Storage::Tape::Acorn::Parser &parser
|
||||
) {
|
||||
auto new_chunk = std::make_unique<File::Chunk>();
|
||||
int shift_register = 0;
|
||||
|
||||
// TODO: move this into the parser
|
||||
const auto shift = [&] {
|
||||
shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9);
|
||||
const auto find = [&](int target) {
|
||||
while(!serialiser.is_at_end() && (shift_register != target)) {
|
||||
shift_register = (shift_register >> 1) | (parser.get_next_bit(serialiser) << 9);
|
||||
}
|
||||
};
|
||||
|
||||
// find next area of high tone
|
||||
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
|
||||
shift();
|
||||
}
|
||||
|
||||
// find next 0x2a (swallowing stop bit)
|
||||
while(!tape->is_at_end() && (shift_register != 0x254)) {
|
||||
shift();
|
||||
}
|
||||
|
||||
// Find first sync byte that follows high tone.
|
||||
find(0x3ff);
|
||||
find(0x254); // i.e. 0x2a wrapped in a 1 start bit and a 0 stop bit.
|
||||
parser.reset_crc();
|
||||
parser.reset_error_flag();
|
||||
|
||||
// read out name
|
||||
char name[11];
|
||||
// Read name.
|
||||
char name[11]{};
|
||||
std::size_t name_ptr = 0;
|
||||
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
|
||||
name[name_ptr] = char(parser.get_next_byte(tape));
|
||||
while(!serialiser.is_at_end() && name_ptr < sizeof(name)) {
|
||||
name[name_ptr] = char(parser.get_next_byte(serialiser));
|
||||
if(!name[name_ptr]) break;
|
||||
++name_ptr;
|
||||
}
|
||||
name[sizeof(name)-1] = '\0';
|
||||
new_chunk->name = name;
|
||||
|
||||
// addresses
|
||||
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
|
||||
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
|
||||
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
|
||||
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
|
||||
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
|
||||
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
|
||||
// Read rest of header fields.
|
||||
new_chunk->load_address = uint32_t(parser.get_next_word(serialiser));
|
||||
new_chunk->execution_address = uint32_t(parser.get_next_word(serialiser));
|
||||
new_chunk->block_number = uint16_t(parser.get_next_short(serialiser));
|
||||
new_chunk->block_length = uint16_t(parser.get_next_short(serialiser));
|
||||
new_chunk->block_flag = uint8_t(parser.get_next_byte(serialiser));
|
||||
new_chunk->next_address = uint32_t(parser.get_next_word(serialiser));
|
||||
|
||||
uint16_t calculated_header_crc = parser.get_crc();
|
||||
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
|
||||
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
|
||||
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
|
||||
const auto matched_crc = [&]() {
|
||||
const uint16_t calculated_crc = parser.get_crc();
|
||||
uint16_t stored_crc = uint16_t(parser.get_next_short(serialiser));
|
||||
stored_crc = uint16_t((stored_crc >> 8) | (stored_crc << 8));
|
||||
return stored_crc == calculated_crc;
|
||||
};
|
||||
|
||||
new_chunk->header_crc_matched = matched_crc();
|
||||
|
||||
if(!new_chunk->header_crc_matched) return nullptr;
|
||||
|
||||
parser.reset_crc();
|
||||
new_chunk->data.reserve(new_chunk->block_length);
|
||||
for(int c = 0; c < new_chunk->block_length; c++) {
|
||||
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
|
||||
}
|
||||
|
||||
// Bit 6 of the block flag means 'empty block'; allow it to override declared block length.
|
||||
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
|
||||
uint16_t calculated_data_crc = parser.get_crc();
|
||||
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
|
||||
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
|
||||
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
|
||||
parser.reset_crc();
|
||||
new_chunk->data.reserve(new_chunk->block_length);
|
||||
for(int c = 0; c < new_chunk->block_length; c++) {
|
||||
new_chunk->data.push_back(uint8_t(parser.get_next_byte(serialiser)));
|
||||
}
|
||||
new_chunk->data_crc_matched = matched_crc();
|
||||
} else {
|
||||
new_chunk->data_crc_matched = true;
|
||||
}
|
||||
@@ -82,67 +80,62 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
}
|
||||
|
||||
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
||||
// find next chunk with a block number of 0
|
||||
while(chunks.size() && chunks.front().block_number) {
|
||||
// Find next chunk with a block number of 0.
|
||||
while(!chunks.empty() && chunks.front().block_number) {
|
||||
chunks.pop_front();
|
||||
}
|
||||
if(chunks.empty()) return nullptr;
|
||||
|
||||
if(!chunks.size()) return nullptr;
|
||||
|
||||
// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
|
||||
// Accumulate sequential blocks until end-of-file bit is set.
|
||||
auto file = std::make_unique<File>();
|
||||
|
||||
uint16_t block_number = 0;
|
||||
|
||||
while(chunks.size()) {
|
||||
while(!chunks.empty()) {
|
||||
if(chunks.front().block_number != block_number) return nullptr;
|
||||
|
||||
bool was_last = chunks.front().block_flag & 0x80;
|
||||
const bool was_last = chunks.front().block_flag & 0x80;
|
||||
file->chunks.push_back(chunks.front());
|
||||
chunks.pop_front();
|
||||
block_number++;
|
||||
++block_number;
|
||||
|
||||
if(was_last) break;
|
||||
}
|
||||
|
||||
// accumulate total data, copy flags appropriately
|
||||
// Grab metadata flags.
|
||||
file->name = file->chunks.front().name;
|
||||
file->load_address = file->chunks.front().load_address;
|
||||
file->execution_address = file->chunks.front().execution_address;
|
||||
// I think the final chunk's flags are the ones that count; TODO: check.
|
||||
if(file->chunks.back().block_flag & 0x01) {
|
||||
// File is locked, which in more generalised terms means it is
|
||||
// for execution only.
|
||||
// File is locked i.e. for execution only.
|
||||
file->flags |= File::Flags::ExecuteOnly;
|
||||
}
|
||||
|
||||
// copy all data into a single big block
|
||||
for(File::Chunk chunk : file->chunks) {
|
||||
// Copy data into a single big block.
|
||||
file->data.reserve(file->chunks.size() * 256);
|
||||
for(auto &chunk : file->chunks) {
|
||||
file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> Analyser::Static::Acorn::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
Storage::Tape::Acorn::Parser parser;
|
||||
|
||||
// populate chunk list
|
||||
// Read all chunks.
|
||||
std::deque<File::Chunk> chunk_list;
|
||||
while(!tape->is_at_end()) {
|
||||
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
|
||||
while(!serialiser.is_at_end()) {
|
||||
const std::unique_ptr<File::Chunk> chunk = GetNextChunk(serialiser, parser);
|
||||
if(chunk) {
|
||||
chunk_list.push_back(*chunk);
|
||||
chunk_list.push_back(std::move(*chunk));
|
||||
}
|
||||
}
|
||||
|
||||
// decompose into file list
|
||||
// Convert to files.
|
||||
std::vector<File> file_list;
|
||||
|
||||
while(chunk_list.size()) {
|
||||
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
||||
while(!chunk_list.empty()) {
|
||||
const std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
||||
if(next_file) {
|
||||
file_list.push_back(*next_file);
|
||||
file_list.push_back(std::move(*next_file));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
@@ -23,14 +24,42 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
|
||||
ElectronTarget() : Analyser::Static::Target(Machine::Electron) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
DeclareField(has_dfs);
|
||||
DeclareField(has_ap6_rom);
|
||||
DeclareField(has_sideways_ram);
|
||||
}
|
||||
ElectronTarget() : Analyser::Static::Target(Machine::Electron) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<ElectronTarget>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
DeclareField(has_dfs);
|
||||
DeclareField(has_ap6_rom);
|
||||
DeclareField(has_sideways_ram);
|
||||
}
|
||||
};
|
||||
|
||||
struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<BBCMicroTarget> {
|
||||
std::string loading_command;
|
||||
bool should_shift_restart = false;
|
||||
|
||||
bool has_1770dfs = false;
|
||||
bool has_adfs = false;
|
||||
bool has_sideways_ram = true;
|
||||
bool has_beebsid = false;
|
||||
|
||||
ReflectableEnum(TubeProcessor, None, WDC65C02, Z80);
|
||||
TubeProcessor tube_processor = TubeProcessor::None;
|
||||
|
||||
BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<BBCMicroTarget>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_1770dfs);
|
||||
DeclareField(has_adfs);
|
||||
DeclareField(has_sideways_ram);
|
||||
DeclareField(has_beebsid);
|
||||
AnnounceEnum(TubeProcessor);
|
||||
DeclareField(tube_processor);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,6 +67,10 @@ struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::S
|
||||
std::string main_program;
|
||||
|
||||
ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<ArchimedesTarget>;
|
||||
void declare_fields() {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool is_confident
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
if(media.disks.empty() && !is_confident) return {};
|
||||
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
@@ -28,13 +28,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
|
||||
FastRAM fast_ram = FastRAM::EightMegabytes;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Amiga) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(fast_ram);
|
||||
DeclareField(chip_ram);
|
||||
AnnounceEnum(FastRAM);
|
||||
AnnounceEnum(ChipRAM);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::Amiga) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(fast_ram);
|
||||
DeclareField(chip_ram);
|
||||
AnnounceEnum(FastRAM);
|
||||
AnnounceEnum(ChipRAM);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,17 +8,26 @@
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
#include "Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string rtrimmed(const std::string &input) {
|
||||
auto trimmed = input;
|
||||
trimmed.erase(std::find_if(trimmed.rbegin(), trimmed.rend(), [](const char ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), trimmed.end());
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
bool strcmp_insensitive(const char *a, const char *b) {
|
||||
if(std::strlen(a) != std::strlen(b)) return false;
|
||||
while(*a) {
|
||||
@@ -63,8 +72,8 @@ std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
|
||||
void InspectCatalogue(
|
||||
const Storage::Disk::CPM::Catalogue &catalogue,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
|
||||
) {
|
||||
std::vector<const Storage::Disk::CPM::File *> candidate_files;
|
||||
candidate_files.reserve(catalogue.files.size());
|
||||
for(const auto &file : catalogue.files) {
|
||||
@@ -104,61 +113,91 @@ void InspectCatalogue(
|
||||
return;
|
||||
}
|
||||
|
||||
// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
|
||||
// that AMSDOS allows to be omitted, pick that one.
|
||||
int basic_files = 0;
|
||||
int implicit_suffixed_files = 0;
|
||||
const auto run_name = [&]() -> std::optional<std::string> {
|
||||
// Collect:
|
||||
//
|
||||
// 1. a set of all files that can be run without specifying an extension plus their appearance counts;
|
||||
// 2. a set of all BASIC file names.
|
||||
std::unordered_map<std::string, int> candidates;
|
||||
std::unordered_set<std::string> basic_names;
|
||||
for(std::size_t c = 0; c < candidate_files.size(); c++) {
|
||||
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
|
||||
if(
|
||||
(candidate_files[c]->type == " " && candidate_files[c]->name == " ") ||
|
||||
!is_implied_extension(candidate_files[c]->type)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t last_basic_file = 0;
|
||||
std::size_t last_implicit_suffixed_file = 0;
|
||||
|
||||
for(std::size_t c = 0; c < candidate_files.size(); c++) {
|
||||
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
|
||||
if(candidate_files[c]->type == " " && candidate_files[c]->name == " ")
|
||||
continue;
|
||||
|
||||
// Check for whether this is [potentially] BASIC.
|
||||
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
|
||||
basic_files++;
|
||||
last_basic_file = c;
|
||||
}
|
||||
|
||||
// Check suffix for emptiness.
|
||||
if(is_implied_extension(candidate_files[c]->type)) {
|
||||
implicit_suffixed_files++;
|
||||
last_implicit_suffixed_file = c;
|
||||
}
|
||||
}
|
||||
if(basic_files == 1 || implicit_suffixed_files == 1) {
|
||||
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
|
||||
target->loading_command = RunCommandFor(*candidate_files[selected_file]);
|
||||
return;
|
||||
}
|
||||
|
||||
// One more guess: if only one remaining candidate file has a different name than the others,
|
||||
// assume it is intended to stand out.
|
||||
std::map<std::string, int> name_counts;
|
||||
std::map<std::string, std::size_t> indices_by_name;
|
||||
std::size_t index = 0;
|
||||
for(const auto &file : candidate_files) {
|
||||
name_counts[file->name]++;
|
||||
indices_by_name[file->name] = index;
|
||||
index++;
|
||||
}
|
||||
if(name_counts.size() == 2) {
|
||||
for(const auto &pair : name_counts) {
|
||||
if(pair.second == 1) {
|
||||
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
|
||||
return;
|
||||
++candidates[candidate_files[c]->name];
|
||||
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
|
||||
basic_names.insert(candidate_files[c]->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Desperation.
|
||||
target->loading_command = "cat\n";
|
||||
// Only one candidate total.
|
||||
if(candidates.size() == 1) {
|
||||
return candidates.begin()->first;
|
||||
}
|
||||
|
||||
// Only one BASIC candidate.
|
||||
if(basic_names.size() == 1) {
|
||||
return *basic_names.begin();
|
||||
}
|
||||
|
||||
// Exactly two candidate names, but only one is a unique name.
|
||||
if(candidates.size() == 2) {
|
||||
const auto item1 = candidates.begin();
|
||||
const auto item2 = std::next(item1);
|
||||
|
||||
if(item1->second == 1 && item2->second != 1) {
|
||||
return item1->first;
|
||||
}
|
||||
if(item2->second == 1 && item1->second != 1) {
|
||||
return item2->first;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from candidates anything that is just a suffixed version of
|
||||
// another name, as long as the other name is three or more characters.
|
||||
std::vector<std::string> to_remove;
|
||||
for(const auto &lhs: candidates) {
|
||||
const auto trimmed = rtrimmed(lhs.first);
|
||||
if(trimmed.size() < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(const auto &rhs: candidates) {
|
||||
if(lhs.first == rhs.first) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(rhs.first.find(trimmed) == 0) {
|
||||
to_remove.push_back(rhs.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const auto &candidate: to_remove) {
|
||||
candidates.erase(candidate);
|
||||
}
|
||||
if(candidates.size() == 1) {
|
||||
return candidates.begin()->first;
|
||||
}
|
||||
|
||||
return {};
|
||||
} ();
|
||||
|
||||
if(run_name.has_value()) {
|
||||
target->loading_command = "run\"" + rtrimmed(*run_name) + "\n";
|
||||
} else {
|
||||
target->loading_command = "cat\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
bool CheckBootSector(
|
||||
const std::shared_ptr<Storage::Disk::Disk> &disk,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
|
||||
) {
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||
@@ -182,7 +221,7 @@ bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
// Limited sophistication here; look for a CPC-style file header, that is
|
||||
// any Spectrum-esque block with a synchronisation character of 0x2c.
|
||||
//
|
||||
@@ -191,7 +230,7 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
Parser parser(Parser::MachineType::AmstradCPC);
|
||||
|
||||
while(true) {
|
||||
const auto block = parser.find_block(tape);
|
||||
const auto block = parser.find_block(serialiser);
|
||||
if(!block) break;
|
||||
|
||||
if(block->type == 0x2c) {
|
||||
@@ -204,7 +243,12 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
@@ -214,7 +258,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
if(!media.tapes.empty()) {
|
||||
bool has_cpc_tape = false;
|
||||
for(auto &tape: media.tapes) {
|
||||
has_cpc_tape |= IsAmstradTape(tape);
|
||||
const auto serialiser = tape->serialiser();
|
||||
has_cpc_tape |= IsAmstradTape(*serialiser);
|
||||
}
|
||||
|
||||
if(has_cpc_tape) {
|
||||
@@ -233,7 +278,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
|
||||
for(auto &disk: media.disks) {
|
||||
// Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk.
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue =
|
||||
Storage::Disk::CPM::GetCatalogue(disk, data_format, false);
|
||||
if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) {
|
||||
InspectCatalogue(*data_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
@@ -247,7 +293,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
}
|
||||
|
||||
// Failing that check for a system catalogue.
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue =
|
||||
Storage::Disk::CPM::GetCatalogue(disk, system_format, false);
|
||||
if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) {
|
||||
InspectCatalogue(*system_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
@@ -26,13 +26,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
// This is used internally for testing; it therefore isn't exposed reflectively.
|
||||
bool catch_ssm_codes = false;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AmstradCPC) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(crtc_type);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(CRTCType);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::AmstradCPC) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
DeclareField(crtc_type);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(CRTCType);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
@@ -36,21 +36,23 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
SCSIController scsi_controller = SCSIController::None;
|
||||
bool has_mockingboard = true;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
DeclareField(scsi_controller);
|
||||
DeclareField(has_mockingboard);
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {}
|
||||
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
AnnounceEnum(SCSIController);
|
||||
}
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
DeclareField(scsi_controller);
|
||||
DeclareField(has_mockingboard);
|
||||
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
AnnounceEnum(SCSIController);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool is_iie(Target::Model model) {
|
||||
constexpr bool is_iie(const Target::Model model) {
|
||||
return model == Target::Model::IIe || model == Target::Model::EnhancedIIe;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
@@ -29,13 +29,15 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Model model = Model::ROM01;
|
||||
MemoryModel memory_model = MemoryModel::EightMB;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleIIgs) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(memory_model);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::AppleIIgs) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
DeclareField(memory_model);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "Analyser/Static/Disassembler/6502.hpp"
|
||||
|
||||
using namespace Analyser::Static::Atari2600;
|
||||
using Target = Analyser::Static::Atari2600::Target;
|
||||
@@ -33,11 +33,13 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
|
||||
// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
||||
// large amounts of memory.
|
||||
bool has_wide_area_store = false;
|
||||
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
|
||||
for(const auto &entry : high_location_disassembly.instructions_by_address) {
|
||||
using Instruction = Analyser::Static::MOS6502::Instruction;
|
||||
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
|
||||
has_wide_area_store |=
|
||||
entry.second.addressing_mode == Instruction::Indirect ||
|
||||
entry.second.addressing_mode == Instruction::IndexedIndirectX ||
|
||||
entry.second.addressing_mode == Instruction::IndirectIndexedY;
|
||||
|
||||
if(has_wide_area_store) break;
|
||||
}
|
||||
@@ -50,13 +52,21 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
|
||||
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor8kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &segment,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
|
||||
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
|
||||
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
|
||||
if(
|
||||
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
||||
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
|
||||
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 &&
|
||||
segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
||||
(
|
||||
segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 ||
|
||||
segment.data[8190] != 0x00 || segment.data[8188] != 0x00
|
||||
) &&
|
||||
segment.data[0] == 0x78
|
||||
) {
|
||||
target.paging_model = Target::PagingModel::ActivisionStack;
|
||||
@@ -88,7 +98,11 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
|
||||
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor16kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Make an assumption that this is the Atari paging model.
|
||||
target.paging_model = Target::PagingModel::Atari16k;
|
||||
|
||||
@@ -108,7 +122,11 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri
|
||||
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor64kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Make an assumption that this is a Tigervision if there is a write to 3F.
|
||||
target.paging_model =
|
||||
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
|
||||
@@ -121,8 +139,12 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
||||
const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
||||
const auto word = [](const uint8_t low, const uint8_t high) {
|
||||
return uint16_t(low | (high << 8));
|
||||
};
|
||||
|
||||
const auto entry_address = word(segment.data[segment.data.size() - 4], segment.data[segment.data.size() - 3]);
|
||||
const auto break_address = word(segment.data[segment.data.size() - 2], segment.data[segment.data.size() - 1]);
|
||||
|
||||
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
|
||||
if(!(address & 0x1000)) return size_t(-1);
|
||||
@@ -130,27 +152,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
||||
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||
const auto disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||
|
||||
switch(segment.data.size()) {
|
||||
case 8192:
|
||||
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 10495:
|
||||
target.paging_model = Target::PagingModel::Pitfall2;
|
||||
break;
|
||||
case 12288:
|
||||
target.paging_model = Target::PagingModel::CBSRamPlus;
|
||||
break;
|
||||
case 16384:
|
||||
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 32768:
|
||||
target.paging_model = Target::PagingModel::Atari32k;
|
||||
break;
|
||||
case 65536:
|
||||
DeterminePagingFor64kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 8192: DeterminePagingFor8kCartridge(target, segment, disassembly); break;
|
||||
case 10495: target.paging_model = Target::PagingModel::Pitfall2; break;
|
||||
case 12288: target.paging_model = Target::PagingModel::CBSRamPlus; break;
|
||||
case 16384: DeterminePagingFor16kCartridge(target, segment, disassembly); break;
|
||||
case 32768: target.paging_model = Target::PagingModel::Atari32k; break;
|
||||
case 65536: DeterminePagingFor64kCartridge(target, segment, disassembly); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -177,7 +188,12 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
}
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// TODO: sanity checking; is this image really for an Atari 2600?
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
@@ -20,11 +20,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
FourMegabytes);
|
||||
MemorySize memory_size = MemorySize::OneMegabyte;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(memory_size);
|
||||
AnnounceEnum(MemorySize);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(memory_size);
|
||||
AnnounceEnum(MemorySize);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
@@ -52,11 +52,20 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return coleco_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
const bool is_confident
|
||||
) {
|
||||
TargetList targets;
|
||||
auto target = std::make_unique<Target>(Machine::ColecoVision);
|
||||
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
if(is_confident) {
|
||||
target->media = media;
|
||||
} else {
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
}
|
||||
if(!target->media.empty())
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Coleco {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
+170
-150
@@ -7,166 +7,168 @@
|
||||
//
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||
#include "../../../Storage/Data/Commodore.hpp"
|
||||
#include "Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||
#include "Storage/Data/Commodore.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
public:
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
emplace_drive(4000000, 300, 2);
|
||||
set_drive(1);
|
||||
get_drive().set_motor_on(true);
|
||||
}
|
||||
public:
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
emplace_drive(4000000, 300, 2);
|
||||
set_drive(1);
|
||||
get_drive().set_motor_on(true);
|
||||
}
|
||||
|
||||
struct Sector {
|
||||
uint8_t sector, track;
|
||||
std::array<uint8_t, 256> data;
|
||||
bool header_checksum_matched;
|
||||
bool data_checksum_matched;
|
||||
};
|
||||
struct Sector {
|
||||
uint8_t sector, track;
|
||||
std::array<uint8_t, 256> data;
|
||||
bool header_checksum_matched;
|
||||
bool data_checksum_matched;
|
||||
};
|
||||
|
||||
/*!
|
||||
Attempts to read the sector located at @c track and @c sector.
|
||||
/*!
|
||||
Attempts to read the sector located at @c track and @c sector.
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> sector(uint8_t track, uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
const Sector *sector(const uint8_t track, const uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
|
||||
if(difference) {
|
||||
int direction = difference < 0 ? -1 : 1;
|
||||
difference *= direction;
|
||||
if(difference) {
|
||||
const int direction = difference < 0 ? -1 : 1;
|
||||
difference *= direction;
|
||||
|
||||
for(int c = 0; c < difference; c++) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
}
|
||||
|
||||
unsigned int zone = 3;
|
||||
if(track >= 18) zone = 2;
|
||||
else if(track >= 25) zone = 1;
|
||||
else if(track >= 31) zone = 0;
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
|
||||
for(int c = 0; c < difference; c++) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
}
|
||||
|
||||
return get_sector(sector);
|
||||
unsigned int zone = 3;
|
||||
if(track >= 18) zone = 2;
|
||||
else if(track >= 25) zone = 1;
|
||||
else if(track >= 31) zone = 0;
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
|
||||
}
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
get_drive().set_disk(disk);
|
||||
return get_sector(sector);
|
||||
}
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::unordered_map<uint16_t, std::unique_ptr<Sector>> sector_cache_;
|
||||
|
||||
void process_input_bit(const int value) override {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block(const int max_index_count) {
|
||||
// find GCR lead-in
|
||||
proceed_to_shift_value(0x3ff);
|
||||
if(shift_register_ != 0x3ff) return 0xff;
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(int value) {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block(int max_index_count) {
|
||||
// find GCR lead-in
|
||||
proceed_to_shift_value(0x3ff);
|
||||
if(shift_register_ != 0x3ff) return 0xff;
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
}
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
unsigned int get_next_byte() {
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 10) run_for(Cycles(1));
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
}
|
||||
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
void proceed_to_shift_value(const unsigned int shift_value) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int get_next_byte() {
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 10) run_for(Cycles(1));
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
void process_index_hole() override {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
const Sector *get_sector(const uint8_t sector) {
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
auto existing = sector_cache_.find(sector_address);
|
||||
if(existing != sector_cache_.end()) return existing->second.get();
|
||||
|
||||
const auto first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(true) {
|
||||
const auto next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
}
|
||||
|
||||
void proceed_to_shift_value(unsigned int shift_value) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
|
||||
void process_index_hole() {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_sector(uint8_t sector) {
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
|
||||
const std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
const Sector *get_next_sector() {
|
||||
auto sector = std::make_unique<Sector>();
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < max_index_count) {
|
||||
// look for a sector header
|
||||
while(1) {
|
||||
const std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
uint8_t checksum = uint8_t(get_next_byte());
|
||||
sector->sector = uint8_t(get_next_byte());
|
||||
sector->track = uint8_t(get_next_byte());
|
||||
uint8_t disk_id[2];
|
||||
disk_id[0] = uint8_t(get_next_byte());
|
||||
disk_id[1] = uint8_t(get_next_byte());
|
||||
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
|
||||
|
||||
// look for the following data
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = uint8_t(get_next_byte());
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
auto pair = sector_cache_.emplace(sector_address, std::move(sector));
|
||||
return pair.first->second.get();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_next_sector() {
|
||||
auto sector = std::make_shared<Sector>();
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < max_index_count) {
|
||||
// look for a sector header
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
uint8_t checksum = uint8_t(get_next_byte());
|
||||
sector->sector = uint8_t(get_next_byte());
|
||||
sector->track = uint8_t(get_next_byte());
|
||||
uint8_t disk_id[2];
|
||||
disk_id[0] = uint8_t(get_next_byte());
|
||||
disk_id[1] = uint8_t(get_next_byte());
|
||||
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
|
||||
|
||||
// look for the following data
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = uint8_t(get_next_byte());
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
@@ -174,20 +176,26 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
CommodoreGCRParser parser;
|
||||
parser.set_disk(disk);
|
||||
|
||||
// find any sector whatsoever to establish the current track
|
||||
std::shared_ptr<CommodoreGCRParser::Sector> sector;
|
||||
|
||||
// assemble directory
|
||||
// Assemble directory.
|
||||
std::vector<uint8_t> directory;
|
||||
uint8_t next_track = 18;
|
||||
uint8_t next_sector = 1;
|
||||
while(1) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
directory.reserve(20 * 1024); // Probably more than plenty.
|
||||
std::set<std::pair<uint8_t, uint8_t>> visited;
|
||||
while(true) {
|
||||
// Don't be fooled by disks that are encoded with a looping directory.
|
||||
const auto key = std::make_pair(next_track, next_sector);
|
||||
if(visited.find(key) != visited.end()) break;
|
||||
visited.insert(key);
|
||||
|
||||
// Append sector to directory and follow next link.
|
||||
const auto sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
|
||||
// Check for end-of-directory.
|
||||
if(!next_track) break;
|
||||
}
|
||||
|
||||
@@ -216,24 +224,36 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
}
|
||||
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
|
||||
std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
const std::size_t number_of_sectors =
|
||||
size_t(directory[header_pointer + 0x1e]) +
|
||||
(size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
if(number_of_sectors) {
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
const auto sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
|
||||
else
|
||||
new_file.data.insert(new_file.data.end(), sector->data.begin() + 2, sector->data.begin() + next_sector);
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + (is_first_sector ? 4 : 2),
|
||||
sector->data.end()
|
||||
);
|
||||
else
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + 2,
|
||||
sector->data.begin() + next_sector
|
||||
);
|
||||
|
||||
is_first_sector = false;
|
||||
is_first_sector = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!next_track) files.push_back(new_file);
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "Storage/Disk/Disk.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
//
|
||||
// File.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/09/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "File.hpp"
|
||||
|
||||
bool Analyser::Static::Commodore::File::is_basic() {
|
||||
// BASIC files are always relocatable (?)
|
||||
if(type != File::RelocatableProgram) return false;
|
||||
|
||||
uint16_t line_address = starting_address;
|
||||
int line_number = -1;
|
||||
|
||||
// decide whether this is a BASIC file based on the proposition that:
|
||||
// (1) they're always relocatable; and
|
||||
// (2) they have a per-line structure of:
|
||||
// [4 bytes: address of start of next line]
|
||||
// [4 bytes: this line number]
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)
|
||||
while(1) {
|
||||
if(size_t(line_address - starting_address) >= data.size() + 2) break;
|
||||
|
||||
uint16_t next_line_address = data[line_address - starting_address];
|
||||
next_line_address |= data[line_address - starting_address + 1] << 8;
|
||||
|
||||
if(!next_line_address) {
|
||||
return true;
|
||||
}
|
||||
if(next_line_address < line_address + 5) break;
|
||||
|
||||
if(size_t(line_address - starting_address) >= data.size() + 5) break;
|
||||
uint16_t next_line_number = data[line_address - starting_address + 2];
|
||||
next_line_number |= data[line_address - starting_address + 3] << 8;
|
||||
|
||||
if(next_line_number <= line_number) break;
|
||||
|
||||
line_number = uint16_t(next_line_number);
|
||||
line_address = next_line_address;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -29,8 +29,6 @@ struct File {
|
||||
Relative
|
||||
} type;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
bool is_basic();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -12,26 +12,33 @@
|
||||
#include "File.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
#include "Storage/Cartridge/Encodings/CommodoreROM.hpp"
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
#include "Analyser/Static/Disassembler/6502.hpp"
|
||||
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
namespace {
|
||||
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
// only one mapped item is allowed
|
||||
// Only one mapped item is allowed ...
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 16 kb in size
|
||||
// ... which must be 16 kb in size.
|
||||
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||
if(segment.start_address != 0xa000) continue;
|
||||
if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
|
||||
@@ -39,126 +46,312 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
vic20_cartridges.push_back(cartridge);
|
||||
}
|
||||
|
||||
// TODO: other machines?
|
||||
|
||||
return vic20_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
struct BASICAnalysis {
|
||||
enum class Version {
|
||||
NotBASIC,
|
||||
BASIC2,
|
||||
BASIC4,
|
||||
BASIC3_5,
|
||||
} minimum_version = Version::NotBASIC;
|
||||
std::vector<uint16_t> machine_code_addresses;
|
||||
};
|
||||
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::Vic20; // TODO: machine estimation
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
std::optional<BASICAnalysis> analyse(const File &file) {
|
||||
BASICAnalysis analysis;
|
||||
|
||||
switch(file.type) {
|
||||
// For 'program' types, proceed with analysis below.
|
||||
case File::RelocatableProgram:
|
||||
case File::NonRelocatableProgram:
|
||||
break;
|
||||
|
||||
// For sequential and relative data stop right now.
|
||||
case File::DataSequence:
|
||||
case File::Relative:
|
||||
return std::nullopt;
|
||||
|
||||
// For user data, try decoding from the starting point.
|
||||
case File::User:
|
||||
analysis.machine_code_addresses.push_back(file.starting_address);
|
||||
return analysis;
|
||||
}
|
||||
|
||||
// Don't form an opinion if file is empty.
|
||||
if(file.data.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uint16_t line_address = file.starting_address;
|
||||
// int previous_line_number = -1;
|
||||
|
||||
const auto byte = [&](uint16_t address) {
|
||||
return file.data[address - file.starting_address];
|
||||
};
|
||||
const auto word = [&](uint16_t address) {
|
||||
return uint16_t(byte(address) | byte(address + 1) << 8);
|
||||
};
|
||||
|
||||
// BASIC programs have a per-line structure of:
|
||||
// [2 bytes: address of start of next line]
|
||||
// [2 bytes: this line number]
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)
|
||||
//
|
||||
// If a SYS is encountered that jumps into the BASIC program then treat that as
|
||||
// a machine code entry point.
|
||||
|
||||
std::unordered_set<uint16_t> visited_lines;
|
||||
while(true) {
|
||||
// Analysis has failed if there isn't at least one complete BASIC line from here.
|
||||
// Fall back on guessing the start address as a machine code entrypoint.
|
||||
if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) {
|
||||
analysis.machine_code_addresses.push_back(file.starting_address);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto next_line_address = word(line_address);
|
||||
// const auto line_number = word(line_address + 2);
|
||||
|
||||
uint16_t code = line_address + 4;
|
||||
const auto next = [&]() -> uint8_t {
|
||||
if(code >= file.starting_address + file.data.size()) {
|
||||
return 0;
|
||||
}
|
||||
return byte(code++);
|
||||
};
|
||||
|
||||
// TODO: sanity check on apparent line contents.
|
||||
// TODO: observe token set (and possibly parameters?) to guess BASIC version.
|
||||
while(true) {
|
||||
const auto token = next();
|
||||
if(!token || token == 0x8f) break;
|
||||
|
||||
switch(token) {
|
||||
case 0x9e: { // SYS; parse following ASCII argument.
|
||||
uint16_t address = 0;
|
||||
while(true) {
|
||||
const auto c = next();
|
||||
if(c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
address = (address * 10) + (c - '0');
|
||||
};
|
||||
analysis.machine_code_addresses.push_back(address);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if a formal end of the program has been declared or if, as some copy protections do,
|
||||
// the linked list of line contents has been made circular.
|
||||
visited_lines.insert(line_address);
|
||||
if(!next_line_address || visited_lines.find(next_line_address) != visited_lines.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// previous_line_number = line_number;
|
||||
line_address = next_line_address;
|
||||
}
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
template <typename TargetT>
|
||||
void set_loading_command(TargetT &target) {
|
||||
if(target.media.disks.empty()) {
|
||||
target.loading_command = "LOAD\"\",1,1\nRUN\n";
|
||||
} else {
|
||||
target.loading_command = "LOAD\"*\",8,1\nRUN\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool obviously_uses_ted(const File &file) {
|
||||
const auto analysis = analyse(file);
|
||||
if(!analysis) return false;
|
||||
|
||||
// Disassemble.
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
file.data,
|
||||
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
|
||||
analysis->machine_code_addresses
|
||||
);
|
||||
|
||||
// Check for interrupt status and paging touches.
|
||||
for(const auto address: {0xff3e, 0xff3f, 0xff09}) {
|
||||
for(const auto &collection: {
|
||||
disassembly.external_loads,
|
||||
disassembly.external_stores,
|
||||
disassembly.external_modifies
|
||||
}) {
|
||||
if(collection.find(uint16_t(address)) != collection.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct FileAnalysis {
|
||||
int device = 0;
|
||||
std::vector<File> files;
|
||||
bool is_disk = false;
|
||||
Analyser::Static::Media media;
|
||||
};
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
|
||||
template <TargetPlatform::Type platform>
|
||||
FileAnalysis analyse_files(const Analyser::Static::Media &media) {
|
||||
FileAnalysis analysis;
|
||||
|
||||
// check disks
|
||||
// Find all valid Commodore files on disks.
|
||||
for(auto &disk : media.disks) {
|
||||
std::vector<File> disk_files = GetFiles(disk);
|
||||
if(!disk_files.empty()) {
|
||||
is_disk = true;
|
||||
files.insert(files.end(), disk_files.begin(), disk_files.end());
|
||||
target->media.disks.push_back(disk);
|
||||
if(!device) device = 8;
|
||||
analysis.is_disk = true;
|
||||
analysis.files.insert(
|
||||
analysis.files.end(),
|
||||
std::make_move_iterator(disk_files.begin()),
|
||||
std::make_move_iterator(disk_files.end())
|
||||
);
|
||||
analysis.media.disks.push_back(disk);
|
||||
if(!analysis.device) analysis.device = 8;
|
||||
}
|
||||
}
|
||||
|
||||
// check tapes
|
||||
// Find all valid Commodore files on tapes.
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> tape_files = GetFiles(tape);
|
||||
tape->reset();
|
||||
auto serialiser = tape->serialiser();
|
||||
std::vector<File> tape_files = GetFiles(*serialiser, platform);
|
||||
if(!tape_files.empty()) {
|
||||
files.insert(files.end(), tape_files.begin(), tape_files.end());
|
||||
target->media.tapes.push_back(tape);
|
||||
if(!device) device = 1;
|
||||
analysis.files.insert(
|
||||
analysis.files.end(),
|
||||
std::make_move_iterator(tape_files.begin()),
|
||||
std::make_move_iterator(tape_files.end())
|
||||
);
|
||||
analysis.media.tapes.push_back(tape);
|
||||
if(!analysis.device) analysis.device = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(!files.empty()) {
|
||||
auto memory_model = Target::MemoryModel::Unexpanded;
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
|
||||
if(files.front().is_basic()) {
|
||||
string_stream << "0";
|
||||
} else {
|
||||
string_stream << "1";
|
||||
return analysis;
|
||||
}
|
||||
|
||||
std::string loading_command(const FileAnalysis &file_analysis) {
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "LOAD\"" << (file_analysis.is_disk ? "*" : "") << "\"," << file_analysis.device;
|
||||
|
||||
const auto analysis = analyse(file_analysis.files[0]);
|
||||
if(analysis && !analysis->machine_code_addresses.empty()) {
|
||||
string_stream << ",1";
|
||||
}
|
||||
string_stream << "\nRUN\n";
|
||||
return string_stream.str();
|
||||
}
|
||||
|
||||
std::pair<TargetPlatform::IntType, std::optional<Vic20Target::MemoryModel>>
|
||||
analyse_starting_address(uint16_t starting_address) {
|
||||
switch(starting_address) {
|
||||
case 0x1c01:
|
||||
// TODO: assume C128.
|
||||
default:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>::error().append(
|
||||
"Unrecognised loading address for Commodore program: %04x", starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
return std::make_pair(TargetPlatform::Vic20 | TargetPlatform::Plus4, Vic20Target::MemoryModel::Unexpanded);
|
||||
|
||||
case 0x1201: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::ThirtyTwoKB);
|
||||
case 0x0401: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::EightKB);
|
||||
case 0x0801: return std::make_pair(TargetPlatform::C64, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
template <TargetPlatform::IntType platform>
|
||||
std::unique_ptr<Analyser::Static::Target> get_target(
|
||||
const Analyser::Static::Media &media,
|
||||
const std::string &file_name,
|
||||
bool is_confident
|
||||
);
|
||||
|
||||
template<>
|
||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Plus4>(
|
||||
const Analyser::Static::Media &media,
|
||||
const std::string &,
|
||||
bool is_confident
|
||||
) {
|
||||
auto target = std::make_unique<Plus4Target>();
|
||||
if(is_confident) {
|
||||
target->media = media;
|
||||
set_loading_command(*target);
|
||||
} else {
|
||||
const auto files = analyse_files<TargetPlatform::Plus4>(media);
|
||||
if(!files.files.empty()) {
|
||||
target->loading_command = loading_command(files);
|
||||
}
|
||||
string_stream << "\nRUN\n";
|
||||
target->loading_command = string_stream.str();
|
||||
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append("Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
memory_model = Target::MemoryModel::Unexpanded;
|
||||
break;
|
||||
case 0x1201:
|
||||
memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
break;
|
||||
case 0x0401:
|
||||
memory_model = Target::MemoryModel::EightKB;
|
||||
break;
|
||||
}
|
||||
|
||||
target->set_memory_model(memory_model);
|
||||
|
||||
// General approach: increase memory size conservatively such that the largest file found will fit.
|
||||
// for(File &file : files) {
|
||||
// std::size_t file_size = file.data.size();
|
||||
// bool is_basic = file.is_basic();
|
||||
|
||||
/*if(is_basic)
|
||||
{
|
||||
// BASIC files may be relocated, so the only limit is size.
|
||||
//
|
||||
// An unexpanded machine has 3583 bytes free for BASIC;
|
||||
// a 3kb expanded machine has 6655 bytes free.
|
||||
if(file_size > 6655)
|
||||
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
|
||||
target->vic20.memory_model = Vic20MemoryModel::EightKB;
|
||||
}
|
||||
else
|
||||
{*/
|
||||
// if(!file.type == File::NonRelocatableProgram)
|
||||
// {
|
||||
// Non-BASIC files may be relocatable but, if so, by what logic?
|
||||
// Given that this is unknown, take starting address as literal
|
||||
// and check against memory windows.
|
||||
//
|
||||
// (ignoring colour memory...)
|
||||
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
|
||||
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
|
||||
// A 32kb expanded Vic has memory in the entire low 32kb.
|
||||
// uint16_t starting_address = file.starting_address;
|
||||
|
||||
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
|
||||
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
|
||||
// if(starting_address + file_size > 0x2000)
|
||||
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
// }
|
||||
// }
|
||||
target->media.disks = media.disks;
|
||||
target->media.tapes = media.tapes;
|
||||
}
|
||||
|
||||
// Attach a 1541 if there are any disks here.
|
||||
target->has_c1541 = !target->media.disks.empty();
|
||||
return target;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
|
||||
const Analyser::Static::Media &media,
|
||||
const std::string &file_name,
|
||||
bool is_confident
|
||||
) {
|
||||
auto target = std::make_unique<Vic20Target>();
|
||||
const auto files = analyse_files<TargetPlatform::Vic20>(media);
|
||||
if(!files.files.empty()) {
|
||||
target->loading_command = loading_command(files);
|
||||
|
||||
const auto model = analyse_starting_address(files.files[0].starting_address);
|
||||
if(model.second.has_value()) {
|
||||
target->set_memory_model(*model.second);
|
||||
}
|
||||
}
|
||||
|
||||
if(is_confident) {
|
||||
target->media = media;
|
||||
set_loading_command(*target);
|
||||
} else {
|
||||
// Strip out inappropriate cartridges but retain all tapes and disks.
|
||||
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
|
||||
target->media.disks = media.disks;
|
||||
target->media.tapes = media.tapes;
|
||||
}
|
||||
|
||||
for(const auto &file : files.files) {
|
||||
// The Vic-20 never has RAM after 0x8000.
|
||||
if(file.ending_address >= 0x8000) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(obviously_uses_ted(file)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect filename for configuration hints.
|
||||
if(!target->media.empty()) {
|
||||
// Inspect filename for configuration hints.
|
||||
using Region = Analyser::Static::Commodore::Vic20Target::Region;
|
||||
|
||||
std::string lowercase_name = file_name;
|
||||
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
|
||||
|
||||
// Hint 1: 'ntsc' anywhere in the name implies America.
|
||||
if(lowercase_name.find("ntsc") != std::string::npos) {
|
||||
target->region = Analyser::Static::Commodore::Target::Region::American;
|
||||
target->region = Region::American;
|
||||
}
|
||||
|
||||
// Potential additional hints: check for TheC64 tags.
|
||||
// Potential additional hints: check for TheC64 tags; these are Vic-20 exclusive.
|
||||
auto final_underscore = lowercase_name.find_last_of('_');
|
||||
if(final_underscore != std::string::npos) {
|
||||
auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
|
||||
@@ -180,10 +373,10 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
|
||||
target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
|
||||
if(!strcmp(next_tag, "tn")) { // i.e. NTSC.
|
||||
target->region = Analyser::Static::Commodore::Target::Region::American;
|
||||
target->region = Region::American;
|
||||
}
|
||||
if(!strcmp(next_tag, "tp")) { // i.e. PAL.
|
||||
target->region = Analyser::Static::Commodore::Target::Region::European;
|
||||
target->region = Region::European;
|
||||
}
|
||||
|
||||
// Unhandled:
|
||||
@@ -194,11 +387,36 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
// RO: this disk image should be treated as read-only.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach a 1540 if there are any disks here.
|
||||
target->has_c1540 = !target->media.disks.empty();
|
||||
// Attach a 1540 if there are any disks here.
|
||||
target->has_c1540 = !target->media.disks.empty();
|
||||
return target;
|
||||
}
|
||||
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType platforms,
|
||||
bool is_confident
|
||||
) {
|
||||
TargetList destination;
|
||||
|
||||
if(platforms & TargetPlatform::Vic20) {
|
||||
auto vic20 = get_target<TargetPlatform::Vic20>(media, file_name, is_confident);
|
||||
if(vic20) {
|
||||
destination.push_back(std::move(vic20));
|
||||
}
|
||||
}
|
||||
|
||||
if(platforms & TargetPlatform::Plus4) {
|
||||
auto plus4 = get_target<TargetPlatform::Plus4>(media, file_name, is_confident);
|
||||
if(plus4) {
|
||||
destination.push_back(std::move(plus4));
|
||||
}
|
||||
}
|
||||
|
||||
return destination;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -7,26 +7,27 @@
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
#include "Storage/Tape/Parsers/Commodore.hpp"
|
||||
|
||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) {
|
||||
Storage::Tape::Commodore::Parser parser(type);
|
||||
std::vector<File> file_list;
|
||||
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(serialiser);
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
while(!serialiser.is_at_end()) {
|
||||
if(!header) {
|
||||
header = parser.get_next_header(tape);
|
||||
header = parser.get_next_header(serialiser);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(header->type) {
|
||||
case Storage::Tape::Commodore::Header::DataSequenceHeader: {
|
||||
File new_file;
|
||||
File &new_file = file_list.emplace_back();
|
||||
new_file.name = header->name;
|
||||
new_file.raw_name = header->raw_name;
|
||||
new_file.starting_address = header->starting_address;
|
||||
@@ -34,38 +35,36 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
new_file.type = File::DataSequence;
|
||||
|
||||
new_file.data.swap(header->data);
|
||||
while(!tape->is_at_end()) {
|
||||
header = parser.get_next_header(tape);
|
||||
while(!serialiser.is_at_end()) {
|
||||
header = parser.get_next_header(serialiser);
|
||||
if(!header) continue;
|
||||
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
|
||||
std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
|
||||
std::ranges::copy(header->data, std::back_inserter(new_file.data));
|
||||
}
|
||||
|
||||
file_list.push_back(new_file);
|
||||
}
|
||||
break;
|
||||
|
||||
case Storage::Tape::Commodore::Header::RelocatableProgram:
|
||||
case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(serialiser);
|
||||
if(data) {
|
||||
File new_file;
|
||||
File &new_file = file_list.emplace_back();
|
||||
new_file.name = header->name;
|
||||
new_file.raw_name = header->raw_name;
|
||||
new_file.starting_address = header->starting_address;
|
||||
new_file.ending_address = header->ending_address;
|
||||
new_file.data.swap(data->data);
|
||||
new_file.type = (header->type == Storage::Tape::Commodore::Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram;
|
||||
|
||||
file_list.push_back(new_file);
|
||||
new_file.type =
|
||||
header->type == Storage::Tape::Commodore::Header::RelocatableProgram
|
||||
? File::RelocatableProgram : File::NonRelocatableProgram;
|
||||
}
|
||||
|
||||
header = parser.get_next_header(tape);
|
||||
header = parser.get_next_header(serialiser);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
header = parser.get_next_header(tape);
|
||||
header = parser.get_next_header(serialiser);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &, TargetPlatform::Type);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,14 +8,28 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
struct Plus4Target: public Analyser::Static::Target, public Reflection::StructImpl<Plus4Target> {
|
||||
// TODO: region, etc.
|
||||
std::string loading_command;
|
||||
bool has_c1541 = false;
|
||||
|
||||
Plus4Target() : Analyser::Static::Target(Machine::Plus4) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Plus4Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_c1541);
|
||||
}
|
||||
};
|
||||
|
||||
struct Vic20Target: public Analyser::Static::Target, public Reflection::StructImpl<Vic20Target> {
|
||||
enum class MemoryModel {
|
||||
Unexpanded,
|
||||
EightKB,
|
||||
@@ -54,17 +68,19 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
bool has_c1540 = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Vic20) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(enabled_ram.bank0);
|
||||
DeclareField(enabled_ram.bank1);
|
||||
DeclareField(enabled_ram.bank2);
|
||||
DeclareField(enabled_ram.bank3);
|
||||
DeclareField(enabled_ram.bank5);
|
||||
DeclareField(region);
|
||||
DeclareField(has_c1540);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
Vic20Target() : Analyser::Static::Target(Machine::Vic20) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Vic20Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(enabled_ram.bank0);
|
||||
DeclareField(enabled_ram.bank1);
|
||||
DeclareField(enabled_ram.bank2);
|
||||
DeclareField(enabled_ram.bank3);
|
||||
DeclareField(enabled_ram.bank5);
|
||||
DeclareField(region);
|
||||
DeclareField(has_c1540);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,12 @@ using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Dis
|
||||
|
||||
struct MOS6502Disassembler {
|
||||
|
||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
|
||||
static void AddToDisassembly(
|
||||
PartialDisassembly &disassembly,
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
uint16_t entry_point
|
||||
) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
uint16_t address = entry_point;
|
||||
while(true) {
|
||||
@@ -75,23 +80,25 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
}
|
||||
|
||||
// Decode operation.
|
||||
#define RM_INSTRUCTION(base, op) \
|
||||
case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
|
||||
instruction.operation = op; \
|
||||
#define RM_INSTRUCTION(base, op) \
|
||||
case base+0x09: case base+0x05: case base+0x15: case base+0x01: \
|
||||
case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define URM_INSTRUCTION(base, op) \
|
||||
#define URM_INSTRUCTION(base, op) \
|
||||
case base+0x07: case base+0x17: case base+0x03: case base+0x13: case base+0x0f: case base+0x1f: case base+0x1b: \
|
||||
instruction.operation = op; \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define M_INSTRUCTION(base, op) \
|
||||
#define M_INSTRUCTION(base, op) \
|
||||
case base+0x0a: case base+0x06: case base+0x16: case base+0x0e: case base+0x1e: \
|
||||
instruction.operation = op; \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define IM_INSTRUCTION(base, op) \
|
||||
#define IM_INSTRUCTION(base, op) \
|
||||
case base: instruction.operation = op; break;
|
||||
|
||||
switch(operation) {
|
||||
default:
|
||||
instruction.operation = Instruction::KIL;
|
||||
@@ -259,7 +266,10 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
|
||||
|
||||
// TODO: something wider-ranging than this
|
||||
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
|
||||
if(
|
||||
instruction.addressing_mode == Instruction::Absolute ||
|
||||
instruction.addressing_mode == Instruction::ZeroPage
|
||||
) {
|
||||
const size_t mapped_address = address_mapper(instruction.operand);
|
||||
const bool is_external = mapped_address >= memory.size();
|
||||
|
||||
@@ -272,20 +282,23 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
case Instruction::ADC: case Instruction::SBC:
|
||||
case Instruction::LAS:
|
||||
case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
|
||||
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
|
||||
case Instruction::STY: case Instruction::STX: case Instruction::STA:
|
||||
case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
|
||||
case Instruction::TAS:
|
||||
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
|
||||
case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
|
||||
case Instruction::DCP: case Instruction::ISC:
|
||||
case Instruction::INC: case Instruction::DEC:
|
||||
case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
|
||||
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -330,5 +343,10 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points) {
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(
|
||||
memory,
|
||||
address_mapper,
|
||||
entry_points,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Analyser::Static::Disassembler {
|
||||
Provides an address mapper that relocates a chunk of memory so that it starts at
|
||||
address @c start_address.
|
||||
*/
|
||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
|
||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(const T start_address) {
|
||||
return [start_address](T argument) {
|
||||
return size_t(argument - start_address);
|
||||
};
|
||||
|
||||
@@ -16,51 +16,55 @@ namespace {
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
class Accessor {
|
||||
public:
|
||||
Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
|
||||
memory_(memory), address_mapper_(address_mapper), address_(address) {}
|
||||
public:
|
||||
Accessor(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
uint16_t address
|
||||
) :
|
||||
memory_(memory), address_mapper_(address_mapper), address_(address) {}
|
||||
|
||||
uint8_t byte() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
address_++;
|
||||
if(mapped_address >= memory_.size()) {
|
||||
overrun_ = true;
|
||||
return 0xff;
|
||||
}
|
||||
return memory_[mapped_address];
|
||||
uint8_t byte() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
++address_;
|
||||
if(mapped_address >= memory_.size()) {
|
||||
overrun_ = true;
|
||||
return 0xff;
|
||||
}
|
||||
return memory_[mapped_address];
|
||||
}
|
||||
|
||||
uint16_t word() {
|
||||
uint8_t low = byte();
|
||||
uint8_t high = byte();
|
||||
return uint16_t(low | (high << 8));
|
||||
}
|
||||
uint16_t word() {
|
||||
uint8_t low = byte();
|
||||
uint8_t high = byte();
|
||||
return uint16_t(low | (high << 8));
|
||||
}
|
||||
|
||||
bool overrun() {
|
||||
return overrun_;
|
||||
}
|
||||
bool overrun() const {
|
||||
return overrun_;
|
||||
}
|
||||
|
||||
bool at_end() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
return mapped_address >= memory_.size();
|
||||
}
|
||||
bool at_end() const {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
return mapped_address >= memory_.size();
|
||||
}
|
||||
|
||||
uint16_t address() {
|
||||
return address_;
|
||||
}
|
||||
uint16_t address() const {
|
||||
return address_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<uint8_t> &memory_;
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper_;
|
||||
uint16_t address_;
|
||||
bool overrun_ = false;
|
||||
private:
|
||||
const std::vector<uint8_t> &memory_;
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper_;
|
||||
uint16_t address_;
|
||||
bool overrun_ = false;
|
||||
};
|
||||
|
||||
constexpr uint8_t x(uint8_t v) { return v >> 6; }
|
||||
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
|
||||
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
|
||||
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
|
||||
constexpr uint8_t z(uint8_t v) { return v & 7; }
|
||||
constexpr uint8_t x(const uint8_t v) { return v >> 6; }
|
||||
constexpr uint8_t y(const uint8_t v) { return (v >> 3) & 7; }
|
||||
constexpr uint8_t q(const uint8_t v) { return (v >> 3) & 1; }
|
||||
constexpr uint8_t p(const uint8_t v) { return (v >> 4) & 3; }
|
||||
constexpr uint8_t z(const uint8_t v) { return v & 7; }
|
||||
|
||||
Instruction::Condition condition_table[] = {
|
||||
Instruction::Condition::NZ, Instruction::Condition::Z,
|
||||
@@ -83,8 +87,12 @@ Instruction::Location register_pair_table2[] = {
|
||||
Instruction::Location::AF
|
||||
};
|
||||
|
||||
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
Instruction::Location register_table[] = {
|
||||
Instruction::Location RegisterTableEntry(
|
||||
const int offset, Accessor &accessor,
|
||||
Instruction &instruction,
|
||||
const bool needs_indirect_offset
|
||||
) {
|
||||
static constexpr Instruction::Location register_table[] = {
|
||||
Instruction::Location::B, Instruction::Location::C,
|
||||
Instruction::Location::D, Instruction::Location::E,
|
||||
Instruction::Location::H, Instruction::Location::L,
|
||||
@@ -92,7 +100,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
|
||||
Instruction::Location::A
|
||||
};
|
||||
|
||||
Instruction::Location location = register_table[offset];
|
||||
const Instruction::Location location = register_table[offset];
|
||||
if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
|
||||
instruction.offset = accessor.byte() - 128;
|
||||
}
|
||||
@@ -100,7 +108,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
|
||||
return location;
|
||||
}
|
||||
|
||||
Instruction::Operation alu_table[] = {
|
||||
constexpr Instruction::Operation alu_table[] = {
|
||||
Instruction::Operation::ADD,
|
||||
Instruction::Operation::ADC,
|
||||
Instruction::Operation::SUB,
|
||||
@@ -111,7 +119,7 @@ Instruction::Operation alu_table[] = {
|
||||
Instruction::Operation::CP
|
||||
};
|
||||
|
||||
Instruction::Operation rotation_table[] = {
|
||||
constexpr Instruction::Operation rotation_table[] = {
|
||||
Instruction::Operation::RLC,
|
||||
Instruction::Operation::RRC,
|
||||
Instruction::Operation::RL,
|
||||
@@ -122,19 +130,32 @@ Instruction::Operation rotation_table[] = {
|
||||
Instruction::Operation::SRL
|
||||
};
|
||||
|
||||
Instruction::Operation block_table[][4] = {
|
||||
{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
|
||||
{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
|
||||
{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
|
||||
{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
|
||||
constexpr Instruction::Operation block_table[][4] = {
|
||||
{
|
||||
Instruction::Operation::LDI, Instruction::Operation::CPI,
|
||||
Instruction::Operation::INI, Instruction::Operation::OUTI
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDD, Instruction::Operation::CPD,
|
||||
Instruction::Operation::IND, Instruction::Operation::OUTD
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDIR, Instruction::Operation::CPIR,
|
||||
Instruction::Operation::INIR, Instruction::Operation::OTIR
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDDR, Instruction::Operation::CPDR,
|
||||
Instruction::Operation::INDR, Instruction::Operation::OTDR
|
||||
},
|
||||
};
|
||||
|
||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
if(!x(operation)) {
|
||||
instruction.operation = rotation_table[y(operation)];
|
||||
instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
@@ -148,7 +169,7 @@ void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
}
|
||||
}
|
||||
|
||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
@@ -170,7 +191,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
if(y(operation) == 6) {
|
||||
instruction.destination = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@@ -179,7 +201,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
if(y(operation) == 6) {
|
||||
instruction.source = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@@ -190,11 +213,13 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
if(q(operation)) {
|
||||
instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand_Indirect;
|
||||
} else {
|
||||
instruction.destination = Instruction::Location::Operand_Indirect;
|
||||
instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
instruction.operand = accessor.word();
|
||||
break;
|
||||
@@ -202,7 +227,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
instruction.operation = Instruction::Operation::NEG;
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
|
||||
instruction.operation =
|
||||
y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN;
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::IM;
|
||||
@@ -253,7 +279,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
} hl_substitution = None;
|
||||
|
||||
while(true) {
|
||||
uint8_t operation = accessor.byte();
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
case 0:
|
||||
@@ -343,15 +369,18 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::INC;
|
||||
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = Instruction::Operation::DEC;
|
||||
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = accessor.byte();
|
||||
break;
|
||||
@@ -374,8 +403,10 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
instruction.operation = Instruction::Operation::HALT;
|
||||
} else {
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@@ -517,10 +548,14 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
instruction.destination == Instruction::Location::HL_Indirect) {
|
||||
|
||||
if(instruction.source == Instruction::Location::HL_Indirect) {
|
||||
instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
instruction.source =
|
||||
hl_substitution == IX ?
|
||||
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
}
|
||||
if(instruction.destination == Instruction::Location::HL_Indirect) {
|
||||
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
instruction.destination =
|
||||
hl_substitution == IX ?
|
||||
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -542,7 +577,12 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
}
|
||||
|
||||
struct Z80Disassembler {
|
||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
|
||||
static void AddToDisassembly(
|
||||
PartialDisassembly &disassembly,
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
const uint16_t entry_point
|
||||
) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
Accessor accessor(memory, address_mapper, entry_point);
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../AppleII/Target.hpp"
|
||||
#include "../AppleIIgs/Target.hpp"
|
||||
#include "../Oric/Target.hpp"
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
#include "Analyser/Static/AppleII/Target.hpp"
|
||||
#include "Analyser/Static//AppleIIgs/Target.hpp"
|
||||
#include "Analyser/Static//Oric/Target.hpp"
|
||||
#include "Analyser/Static//Disassembler/6502.hpp"
|
||||
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
|
||||
#include "Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -47,7 +47,12 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@@ -55,14 +60,15 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
TargetList targets;
|
||||
|
||||
// If the disk image is too large for a 5.25" disk, map this to the IIgs.
|
||||
if(disk->get_maximum_head_position() > Storage::Disk::HeadPosition(40)) {
|
||||
if(disk->maximum_head_position() > Storage::Disk::HeadPosition(40)) {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIIgsTarget()));
|
||||
targets.back()->media = media;
|
||||
return targets;
|
||||
}
|
||||
|
||||
// Grab track 0, sector 0: the boot sector.
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto track_zero =
|
||||
disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
|
||||
|
||||
@@ -89,7 +95,8 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
// If the boot sector looks like it's intended for the Oric, create an Oric.
|
||||
// Otherwise go with the Apple II.
|
||||
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
|
||||
bool did_read_shift_register = false;
|
||||
bool is_oric = false;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::DiskII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/FAT.hpp"
|
||||
#include "Storage/Disk/Parsers/FAT.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -26,7 +26,12 @@ bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@@ -72,7 +77,8 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi
|
||||
|
||||
if(!has_exdos_ini) {
|
||||
if(did_pick_file) {
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
target->loading_command =
|
||||
std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
} else {
|
||||
target->loading_command = ":dir\n";
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -30,20 +30,22 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Speed speed = Speed::FourMHz;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
AnnounceEnum(Speed);
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {}
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
DeclareField(speed);
|
||||
}
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
AnnounceEnum(Speed);
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
DeclareField(speed);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,15 +8,20 @@
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../Enterprise/StaticAnalyser.hpp"
|
||||
#include "../PCCompatible/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Enterprise/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/PCCompatible/StaticAnalyser.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
|
||||
#include "Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
#include "Storage/Disk/Encodings/MFM/SegmentParser.hpp"
|
||||
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType platforms,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@@ -34,12 +39,13 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
|
||||
// If the disk image is very small or large, map it to the PC. That's the only option old enough
|
||||
// to have used 5.25" media.
|
||||
if(disk->get_maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
if(disk->maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
|
||||
}
|
||||
|
||||
// Attempt to grab MFM track 0, sector 1: the boot sector.
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto track_zero =
|
||||
disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::MFM::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(
|
||||
*track_zero,
|
||||
@@ -48,7 +54,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
|
||||
// If no sectors were found, assume this disk was either single density or high density, which both imply the PC.
|
||||
if(sector_map.empty() || sector_map.size() > 10) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
|
||||
}
|
||||
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
|
||||
@@ -77,7 +83,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
if(
|
||||
std::search(sample.begin(), sample.end(), string.begin(), string.end()) != sample.end()
|
||||
) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,5 +102,5 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
// could redirect to an MSX2 with MSX-DOS2? Though it'd be nicer if I had a machine that was pure CP/M.
|
||||
|
||||
// Being unable to prove that this is a PC disk, throw it to the Enterprise.
|
||||
return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms);
|
||||
return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms, false);
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::FAT12 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Storage/Cartridge/Cartridge.hpp"
|
||||
#include "Storage/Cartridge/Cartridge.hpp"
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
@@ -26,7 +26,7 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||
};
|
||||
const Type type;
|
||||
|
||||
Cartridge(const std::vector<Segment> &segments, Type type) :
|
||||
Cartridge(const std::vector<Segment> &segments, const Type type) :
|
||||
Storage::Cartridge::Cartridge(segments), type(type) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/Z80.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
#include "Analyser/Static/Disassembler/Z80.hpp"
|
||||
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -27,7 +27,8 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
if(segment.data.size() & 0x1fff) {
|
||||
std::vector<uint8_t> truncated_data;
|
||||
std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
|
||||
const auto truncated_size =
|
||||
std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
|
||||
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||
output_segments.emplace_back(start_address, truncated_data);
|
||||
} else {
|
||||
@@ -82,7 +83,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
|
||||
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
const size_t data_size = segment.data.size();
|
||||
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
|
||||
|
||||
@@ -101,7 +102,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// Reject cartridge if the ROM header wasn't found.
|
||||
if(!found_start) continue;
|
||||
|
||||
uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
|
||||
const uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
@@ -137,10 +138,12 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
}
|
||||
|
||||
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
|
||||
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_8kb_total =
|
||||
address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
|
||||
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
|
||||
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
const auto konami_with_scc_total =
|
||||
address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
|
||||
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
|
||||
|
||||
@@ -182,7 +185,12 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
return targets;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
|
||||
// Append targets for any cartridges that look correct.
|
||||
@@ -194,7 +202,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
|
||||
|
||||
// Check tapes for loadable files.
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> files_on_tape = GetFiles(tape);
|
||||
const std::vector<File> files_on_tape = GetFiles(tape);
|
||||
if(!files_on_tape.empty()) {
|
||||
switch(files_on_tape.front().type) {
|
||||
case File::Type::ASCII: target->loading_command = "RUN\"CAS:\r"; break;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "Tape.hpp"
|
||||
|
||||
#include "../../../Storage/Tape/Parsers/MSX.hpp"
|
||||
#include "Storage/Tape/Parsers/MSX.hpp"
|
||||
|
||||
using namespace Analyser::Static::MSX;
|
||||
|
||||
@@ -29,12 +29,12 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player(1000000);
|
||||
tape_player.set_motor_control(true);
|
||||
tape_player.set_tape(tape);
|
||||
tape_player.set_tape(tape, TargetPlatform::MSX);
|
||||
|
||||
using Parser = Storage::Tape::MSX::Parser;
|
||||
|
||||
// Get all recognisable files from the tape.
|
||||
while(!tape->is_at_end()) {
|
||||
while(!tape_player.is_at_end()) {
|
||||
// Try to locate and measure a header.
|
||||
std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player);
|
||||
if(!file_speed) continue;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -32,6 +32,6 @@ struct File {
|
||||
File();
|
||||
};
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
@@ -33,15 +33,17 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
);
|
||||
Region region = Region::USA;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(has_msx_music);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(has_msx_music);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool is_confident
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty() && !is_confident) return {};
|
||||
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
@@ -24,7 +29,7 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media
|
||||
if(media.mass_storage_devices.empty()) {
|
||||
bool has_800kb_disks = false;
|
||||
for(const auto &disk: media.disks) {
|
||||
has_800kb_disks |= disk->get_head_count() > 1;
|
||||
has_800kb_disks |= disk->head_count() > 1;
|
||||
}
|
||||
|
||||
if(!has_800kb_disks) {
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
@@ -18,12 +18,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
|
||||
Model model = Model::MacPlus;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Macintosh) {
|
||||
// Boilerplate for declaring fields and potential values.
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::Macintosh) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
#include "Analyser/Static/Disassembler/6502.hpp"
|
||||
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -22,12 +22,22 @@ using namespace Analyser::Static::Oric;
|
||||
|
||||
namespace {
|
||||
|
||||
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
||||
int score(
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly,
|
||||
const std::set<uint16_t> &rom_functions,
|
||||
const std::set<uint16_t> &variable_locations
|
||||
) {
|
||||
int score = 0;
|
||||
|
||||
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.outward_calls) {
|
||||
score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||
}
|
||||
for(const auto address : disassembly.external_stores) {
|
||||
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
}
|
||||
for(const auto address : disassembly.external_loads) {
|
||||
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
@@ -35,19 +45,32 @@ int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::
|
||||
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
const std::set<uint16_t> rom_functions = {
|
||||
0x0228, 0x022b,
|
||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f, 0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
||||
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f, 0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
|
||||
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0, 0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
|
||||
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
|
||||
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f, 0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
|
||||
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
|
||||
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e, 0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
|
||||
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563, 0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
|
||||
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974, 0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
|
||||
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf, 0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
|
||||
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a, 0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
||||
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7, 0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524,
|
||||
0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f,
|
||||
0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
||||
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f,
|
||||
0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
|
||||
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0,
|
||||
0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
|
||||
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a,
|
||||
0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
|
||||
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f,
|
||||
0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
|
||||
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f,
|
||||
0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
|
||||
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e,
|
||||
0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
|
||||
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563,
|
||||
0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
|
||||
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974,
|
||||
0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
|
||||
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf,
|
||||
0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
|
||||
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a,
|
||||
0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
||||
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7,
|
||||
0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
||||
};
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
||||
@@ -59,19 +82,32 @@ int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
const std::set<uint16_t> rom_functions = {
|
||||
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915, 0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
||||
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c, 0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
|
||||
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c, 0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
|
||||
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
|
||||
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de, 0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
|
||||
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
|
||||
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f, 0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
|
||||
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
|
||||
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1, 0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
|
||||
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab, 0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
|
||||
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561, 0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
|
||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524,
|
||||
0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915,
|
||||
0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
||||
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c,
|
||||
0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
|
||||
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c,
|
||||
0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
|
||||
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767,
|
||||
0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
|
||||
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de,
|
||||
0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
|
||||
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4,
|
||||
0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
|
||||
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f,
|
||||
0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
|
||||
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab,
|
||||
0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
|
||||
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1,
|
||||
0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
|
||||
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab,
|
||||
0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
|
||||
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561,
|
||||
0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
|
||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14,
|
||||
0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||
0xfc18
|
||||
};
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
@@ -102,7 +138,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
|
||||
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
||||
}
|
||||
|
||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
|
||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range_start, const uint16_t range_end) {
|
||||
/*
|
||||
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
|
||||
use disassembly to test for likely matches.
|
||||
@@ -120,8 +156,8 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start
|
||||
}
|
||||
|
||||
// Grab a disassembly.
|
||||
const auto disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
|
||||
|
||||
// Check for references to the Jasmin registers.
|
||||
int register_hits = 0;
|
||||
@@ -145,7 +181,12 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
||||
@@ -153,14 +194,18 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
||||
int basic11_votes = 0;
|
||||
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> tape_files = GetFiles(tape);
|
||||
tape->reset();
|
||||
auto serialiser = tape->serialiser();
|
||||
std::vector<File> tape_files = GetFiles(*serialiser);
|
||||
if(!tape_files.empty()) {
|
||||
for(const auto &file : tape_files) {
|
||||
if(file.data_type == File::MachineCode) {
|
||||
std::vector<uint16_t> entry_points = {file.starting_address};
|
||||
const Analyser::Static::MOS6502::Disassembly disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
||||
Analyser::Static::MOS6502::Disassemble(
|
||||
file.data,
|
||||
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
|
||||
entry_points
|
||||
);
|
||||
|
||||
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -7,61 +7,61 @@
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Oric.hpp"
|
||||
#include "Storage/Tape/Parsers/Oric.hpp"
|
||||
|
||||
using namespace Analyser::Static::Oric;
|
||||
|
||||
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<File> Analyser::Static::Oric::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
std::vector<File> files;
|
||||
Storage::Tape::Oric::Parser parser;
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
while(!serialiser.is_at_end()) {
|
||||
// sync to next lead-in, check that it's one of three 0x16s
|
||||
bool is_fast = parser.sync_and_get_encoding_speed(tape);
|
||||
bool is_fast = parser.sync_and_get_encoding_speed(serialiser);
|
||||
int next_bytes[2];
|
||||
next_bytes[0] = parser.get_next_byte(tape, is_fast);
|
||||
next_bytes[1] = parser.get_next_byte(tape, is_fast);
|
||||
next_bytes[0] = parser.get_next_byte(serialiser, is_fast);
|
||||
next_bytes[1] = parser.get_next_byte(serialiser, is_fast);
|
||||
|
||||
if(next_bytes[0] != 0x16 || next_bytes[1] != 0x16) continue;
|
||||
|
||||
// get the first byte that isn't a 0x16, check it was a 0x24
|
||||
int byte = 0x16;
|
||||
while(!tape->is_at_end() && byte == 0x16) {
|
||||
byte = parser.get_next_byte(tape, is_fast);
|
||||
while(!serialiser.is_at_end() && byte == 0x16) {
|
||||
byte = parser.get_next_byte(serialiser, is_fast);
|
||||
}
|
||||
if(byte != 0x24) continue;
|
||||
|
||||
// skip two empty bytes
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
parser.get_next_byte(serialiser, is_fast);
|
||||
parser.get_next_byte(serialiser, is_fast);
|
||||
|
||||
// get data and launch types
|
||||
File new_file;
|
||||
switch(parser.get_next_byte(tape, is_fast)) {
|
||||
switch(parser.get_next_byte(serialiser, is_fast)) {
|
||||
case 0x00: new_file.data_type = File::ProgramType::BASIC; break;
|
||||
case 0x80: new_file.data_type = File::ProgramType::MachineCode; break;
|
||||
default: new_file.data_type = File::ProgramType::None; break;
|
||||
}
|
||||
switch(parser.get_next_byte(tape, is_fast)) {
|
||||
switch(parser.get_next_byte(serialiser, is_fast)) {
|
||||
case 0x80: new_file.launch_type = File::ProgramType::BASIC; break;
|
||||
case 0xc7: new_file.launch_type = File::ProgramType::MachineCode; break;
|
||||
default: new_file.launch_type = File::ProgramType::None; break;
|
||||
}
|
||||
|
||||
// read end and start addresses
|
||||
new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
|
||||
new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
|
||||
new_file.ending_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
|
||||
new_file.ending_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
|
||||
new_file.starting_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
|
||||
new_file.starting_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
|
||||
|
||||
// skip an empty byte
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
parser.get_next_byte(serialiser, is_fast);
|
||||
|
||||
// read file name, up to 16 characters and null terminated
|
||||
char file_name[17];
|
||||
int name_pos = 0;
|
||||
while(name_pos < 16) {
|
||||
file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
|
||||
file_name[name_pos] = char(parser.get_next_byte(serialiser, is_fast));
|
||||
if(!file_name[name_pos]) break;
|
||||
name_pos++;
|
||||
}
|
||||
@@ -72,11 +72,11 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
|
||||
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
|
||||
new_file.data.reserve(body_length);
|
||||
for(std::size_t c = 0; c < body_length; c++) {
|
||||
new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
|
||||
new_file.data.push_back(uint8_t(parser.get_next_byte(serialiser, is_fast)));
|
||||
}
|
||||
|
||||
// only one validation check: was there enough tape?
|
||||
if(!tape->is_at_end()) {
|
||||
if(!serialiser.is_at_end()) {
|
||||
files.push_back(new_file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -28,6 +28,6 @@ struct File {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Oric {
|
||||
@@ -41,15 +41,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
std::string loading_command;
|
||||
bool should_start_jasmin = false;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::Oric) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(rom);
|
||||
DeclareField(disk_interface);
|
||||
DeclareField(processor);
|
||||
AnnounceEnum(ROM);
|
||||
AnnounceEnum(DiskInterface);
|
||||
AnnounceEnum(Processor);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::Oric) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(rom);
|
||||
DeclareField(disk_interface);
|
||||
DeclareField(processor);
|
||||
AnnounceEnum(ROM);
|
||||
AnnounceEnum(DiskInterface);
|
||||
AnnounceEnum(Processor);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::PCCompatible {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,29 +8,46 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::PCCompatible {
|
||||
|
||||
ReflectableEnum(Model,
|
||||
XT,
|
||||
TurboXT,
|
||||
AT
|
||||
);
|
||||
|
||||
constexpr bool is_xt(const Model model) {
|
||||
return model <= Model::TurboXT;
|
||||
}
|
||||
|
||||
constexpr bool is_at(const Model model) {
|
||||
return model >= Model::AT;
|
||||
}
|
||||
|
||||
constexpr bool has_ide(const Model model) {
|
||||
return model >= Model::AT;
|
||||
}
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(VideoAdaptor,
|
||||
MDA,
|
||||
CGA);
|
||||
CGA,
|
||||
);
|
||||
VideoAdaptor adaptor = VideoAdaptor::CGA;
|
||||
Model model = Model::TurboXT;
|
||||
|
||||
ReflectableEnum(Speed,
|
||||
ApproximatelyOriginal,
|
||||
Fast);
|
||||
Speed speed = Speed::Fast;
|
||||
Target() : Analyser::Static::Target(Machine::PCCompatible) {}
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::PCCompatible) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(VideoAdaptor);
|
||||
AnnounceEnum(Speed);
|
||||
DeclareField(adaptor);
|
||||
DeclareField(speed);
|
||||
}
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
AnnounceEnum(VideoAdaptor);
|
||||
AnnounceEnum(Model);
|
||||
DeclareField(adaptor);
|
||||
DeclareField(model);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
if(media.cartridges.empty())
|
||||
return {};
|
||||
|
||||
@@ -54,7 +59,8 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
|
||||
if(lowercase_name.find("(jp)") == std::string::npos) {
|
||||
target->region =
|
||||
(lowercase_name.find("(us)") == std::string::npos &&
|
||||
lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
|
||||
lowercase_name.find("(ntsc)") == std::string::npos) ?
|
||||
Target::Region::Europe : Target::Region::USA;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
@@ -63,9 +69,9 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
|
||||
// If one is found, set the paging scheme appropriately.
|
||||
const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
|
||||
if(
|
||||
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
|
||||
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
|
||||
(inverse_checksum&0xff) == data[0x7fe8] &&
|
||||
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present
|
||||
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present.
|
||||
!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
|
||||
) {
|
||||
target->paging_scheme = Target::PagingScheme::Codemasters;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
@@ -37,15 +37,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Region region = Region::Japan;
|
||||
PagingScheme paging_scheme = PagingScheme::Sega;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::MasterSystem) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::MasterSystem) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
|
||||
constexpr bool is_master_system(const Analyser::Static::Sega::Target::Model model) {
|
||||
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,82 +9,85 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
|
||||
// Analysers
|
||||
#include "Acorn/StaticAnalyser.hpp"
|
||||
#include "Amiga/StaticAnalyser.hpp"
|
||||
#include "AmstradCPC/StaticAnalyser.hpp"
|
||||
#include "AppleII/StaticAnalyser.hpp"
|
||||
#include "AppleIIgs/StaticAnalyser.hpp"
|
||||
#include "Atari2600/StaticAnalyser.hpp"
|
||||
#include "AtariST/StaticAnalyser.hpp"
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "Enterprise/StaticAnalyser.hpp"
|
||||
#include "FAT12/StaticAnalyser.hpp"
|
||||
#include "Macintosh/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "PCCompatible/StaticAnalyser.hpp"
|
||||
#include "Sega/StaticAnalyser.hpp"
|
||||
#include "ZX8081/StaticAnalyser.hpp"
|
||||
#include "ZXSpectrum/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Acorn/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Amiga/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/AmstradCPC/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/AppleII/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/AppleIIgs/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Atari2600/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/AtariST/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Coleco/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Commodore/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/DiskII/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Enterprise/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/FAT12/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Macintosh/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/MSX/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Oric/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/PCCompatible/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/Sega/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/ZX8081/StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/ZXSpectrum/StaticAnalyser.hpp"
|
||||
|
||||
// Cartridges
|
||||
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
|
||||
#include "../../Storage/Cartridge/Formats/PRG.hpp"
|
||||
#include "Storage/Cartridge/Formats/BinaryDump.hpp"
|
||||
#include "Storage/Cartridge/Formats/PRG.hpp"
|
||||
|
||||
// Disks
|
||||
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/IMD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/PCBooter.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/2MG.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/AcornADF.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/IPF.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/IMD.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/JFD.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/PCBooter.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HDV.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
#include "Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "Storage/MassStorage/Formats/HDV.hpp"
|
||||
#include "Storage/MassStorage/Formats/HFV.hpp"
|
||||
#include "Storage/MassStorage/Formats/VHD.hpp"
|
||||
|
||||
// State Snapshots
|
||||
#include "../../Storage/State/SNA.hpp"
|
||||
#include "../../Storage/State/SZX.hpp"
|
||||
#include "../../Storage/State/Z80.hpp"
|
||||
#include "Storage/State/SNA.hpp"
|
||||
#include "Storage/State/SZX.hpp"
|
||||
#include "Storage/State/Z80.hpp"
|
||||
|
||||
// Tapes
|
||||
#include "../../Storage/Tape/Formats/CAS.hpp"
|
||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||
#include "../../Storage/Tape/Formats/CSW.hpp"
|
||||
#include "../../Storage/Tape/Formats/OricTAP.hpp"
|
||||
#include "../../Storage/Tape/Formats/TapePRG.hpp"
|
||||
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
|
||||
#include "../../Storage/Tape/Formats/TZX.hpp"
|
||||
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
|
||||
#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp"
|
||||
#include "Storage/Tape/Formats/CAS.hpp"
|
||||
#include "Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||
#include "Storage/Tape/Formats/CSW.hpp"
|
||||
#include "Storage/Tape/Formats/OricTAP.hpp"
|
||||
#include "Storage/Tape/Formats/TapePRG.hpp"
|
||||
#include "Storage/Tape/Formats/TapeUEF.hpp"
|
||||
#include "Storage/Tape/Formats/TZX.hpp"
|
||||
#include "Storage/Tape/Formats/ZX80O81P.hpp"
|
||||
#include "Storage/Tape/Formats/ZXSpectrumTAP.hpp"
|
||||
|
||||
// Target Platform Types
|
||||
#include "../../Storage/TargetPlatforms.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
|
||||
template<class> inline constexpr bool always_false_v = false;
|
||||
|
||||
@@ -104,14 +107,14 @@ std::string get_extension(const std::string &name) {
|
||||
}
|
||||
|
||||
class MediaAccumulator {
|
||||
public:
|
||||
public:
|
||||
MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
|
||||
file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
|
||||
|
||||
/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
|
||||
/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
|
||||
template <typename InstanceT>
|
||||
void insert(TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
|
||||
void insert(const TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
|
||||
if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
|
||||
media.disks.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
|
||||
@@ -127,20 +130,23 @@ class MediaAccumulator {
|
||||
potential_platforms_ |= platforms;
|
||||
|
||||
// Check whether the instance itself has any input on target platforms.
|
||||
TargetPlatform::TypeDistinguisher *const distinguisher =
|
||||
dynamic_cast<TargetPlatform::TypeDistinguisher *>(instance.get());
|
||||
if(distinguisher) potential_platforms_ &= distinguisher->target_platform_type();
|
||||
TargetPlatform::Distinguisher *const distinguisher =
|
||||
dynamic_cast<TargetPlatform::Distinguisher *>(instance.get());
|
||||
if(distinguisher) {
|
||||
was_distinguished = true;
|
||||
potential_platforms_ &= distinguisher->target_platforms();
|
||||
}
|
||||
}
|
||||
|
||||
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
void insert(const TargetPlatform::IntType platforms, Args &&... args) {
|
||||
insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void try_insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
void try_insert(const TargetPlatform::IntType platforms, Args &&... args) {
|
||||
try {
|
||||
insert<InstanceT>(platforms, std::forward<Args>(args)...);
|
||||
} catch(...) {}
|
||||
@@ -149,22 +155,23 @@ class MediaAccumulator {
|
||||
/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
|
||||
/// providing the file name as the only construction argument.
|
||||
template <typename InstanceT>
|
||||
void try_standard(TargetPlatform::IntType platforms, const char *extension) {
|
||||
void try_standard(const TargetPlatform::IntType platforms, const char *extension) {
|
||||
if(name_matches(extension)) {
|
||||
try_insert<InstanceT>(platforms, file_name_);
|
||||
}
|
||||
}
|
||||
|
||||
bool name_matches(const char *extension) {
|
||||
bool name_matches(const char *const extension) {
|
||||
return extension_ == extension;
|
||||
}
|
||||
|
||||
Media media;
|
||||
bool was_distinguished = false;
|
||||
|
||||
private:
|
||||
const std::string &file_name_;
|
||||
TargetPlatform::IntType &potential_platforms_;
|
||||
const std::string extension_;
|
||||
private:
|
||||
const std::string &file_name_;
|
||||
TargetPlatform::IntType &potential_platforms_;
|
||||
const std::string extension_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -201,6 +208,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
|
||||
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
|
||||
|
||||
@@ -209,7 +217,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
|
||||
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore, "d64");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
|
||||
accumulator.try_standard<MassStorage::DAT>(TargetPlatform::Acorn, "dat");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::DMK>>(TargetPlatform::MSX, "dmk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "do");
|
||||
@@ -223,11 +231,12 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::MSX, "dsk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::OricMFMDSK>>(TargetPlatform::Oric, "dsk");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore, "g64");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore8bit, "g64");
|
||||
|
||||
accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore |
|
||||
TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
|
||||
"hfe"); // TODO: switch to AllDisk once the MSX stops being so greedy.
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
|
||||
@@ -264,13 +273,14 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
|
||||
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p81");
|
||||
|
||||
static constexpr auto PRGTargets = TargetPlatform::Vic20; //Commodore8bit; // Disabled until analysis improves.
|
||||
if(accumulator.name_matches("prg")) {
|
||||
// Try instantiating as a ROM; failing that accept as a tape.
|
||||
try {
|
||||
accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore, file_name);
|
||||
accumulator.insert<Cartridge::PRG>(PRGTargets, file_name);
|
||||
} catch(...) {
|
||||
try {
|
||||
accumulator.insert<Tape::PRG>(TargetPlatform::Commodore, file_name);
|
||||
accumulator.insert<Tape::PRG>(PRGTargets, file_name);
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +295,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
|
||||
|
||||
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore, "tap");
|
||||
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore8bit, "tap");
|
||||
accumulator.try_standard<Tape::OricTAP>(TargetPlatform::Oric, "tap");
|
||||
accumulator.try_standard<Tape::ZXSpectrumTAP>(TargetPlatform::ZXSpectrum, "tap");
|
||||
accumulator.try_standard<Tape::TZX>(TargetPlatform::MSX, "tsx");
|
||||
@@ -293,6 +303,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
|
||||
accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
|
||||
|
||||
accumulator.try_standard<MassStorage::VHD>(TargetPlatform::PCCompatible, "vhd");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
|
||||
|
||||
return accumulator.media;
|
||||
@@ -334,14 +346,24 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
||||
int total_options = std::popcount(potential_platforms);
|
||||
const bool is_confident = total_options == 1;
|
||||
// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
|
||||
// The machine-specific static analyser will still run in case it can provide meaningful annotations on
|
||||
// loading command, machine configuration, etc, but the flag will be passed onwards to mean "don't reject this".
|
||||
|
||||
// Hand off to platform-specific determination of whether these
|
||||
// things are actually compatible and, if so, how to load them.
|
||||
const auto append = [&](TargetPlatform::IntType platform, auto evaluator) {
|
||||
if(!(potential_platforms & platform)) {
|
||||
return;
|
||||
}
|
||||
auto new_targets = evaluator(media, file_name, potential_platforms);
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));
|
||||
auto new_targets = evaluator(media, file_name, potential_platforms, is_confident);
|
||||
targets.insert(
|
||||
targets.end(),
|
||||
std::make_move_iterator(new_targets.begin()),
|
||||
std::make_move_iterator(new_targets.end())
|
||||
);
|
||||
};
|
||||
|
||||
append(TargetPlatform::Acorn, Acorn::GetTargets);
|
||||
@@ -352,7 +374,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
append(TargetPlatform::Atari2600, Atari2600::GetTargets);
|
||||
append(TargetPlatform::AtariST, AtariST::GetTargets);
|
||||
append(TargetPlatform::Coleco, Coleco::GetTargets);
|
||||
append(TargetPlatform::Commodore, Commodore::GetTargets);
|
||||
append(TargetPlatform::Commodore8bit, Commodore::GetTargets);
|
||||
append(TargetPlatform::DiskII, DiskII::GetTargets);
|
||||
append(TargetPlatform::Enterprise, Enterprise::GetTargets);
|
||||
append(TargetPlatform::FAT12, FAT12::GetTargets);
|
||||
@@ -364,13 +386,6 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
append(TargetPlatform::ZX8081, ZX8081::GetTargets);
|
||||
append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
|
||||
|
||||
// Reset any tapes to their initial position.
|
||||
for(const auto &target : targets) {
|
||||
for(auto &tape : target->media.tapes) {
|
||||
tape->reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
|
||||
// picked their insertion order carefully.
|
||||
std::stable_sort(targets.begin(), targets.end(),
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Machines.hpp"
|
||||
#include "Analyser/Machines.hpp"
|
||||
|
||||
#include "../../Storage/Cartridge/Cartridge.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
#include "Storage/Cartridge/Cartridge.hpp"
|
||||
#include "Storage/Disk/Disk.hpp"
|
||||
#include "Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -64,9 +65,9 @@ struct Target {
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
float confidence = 0.0f;
|
||||
float confidence = 0.5f;
|
||||
};
|
||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
|
||||
using TargetList = std::vector<std::unique_ptr<Target>>;
|
||||
|
||||
/*!
|
||||
Attempts, through any available means, to return a list of potential targets for the file with the given name.
|
||||
|
||||
@@ -12,27 +12,32 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Target.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
#include "Storage/Tape/Parsers/ZX8081.hpp"
|
||||
|
||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
|
||||
std::vector<Storage::Data::ZX8081::File> files;
|
||||
Storage::Tape::ZX8081::Parser parser;
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
|
||||
if(next_file != nullptr) {
|
||||
files.push_back(*next_file);
|
||||
while(!serialiser.is_at_end()) {
|
||||
const auto next_file = parser.get_next_file(serialiser);
|
||||
if(next_file) {
|
||||
files.push_back(std::move(*next_file));
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType potential_platforms,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
if(!media.tapes.empty()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||
media.tapes.front()->reset();
|
||||
const auto serialiser = media.tapes.front()->serialiser();
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(*serialiser);
|
||||
if(!files.empty()) {
|
||||
Target *const target = new Target;
|
||||
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
@@ -27,13 +27,15 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
bool ZX80_uses_ZX81_ROM = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::ZX8081) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(memory_model);
|
||||
DeclareField(is_ZX81);
|
||||
DeclareField(ZX80_uses_ZX81_ROM);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::ZX8081) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(memory_model);
|
||||
DeclareField(is_ZX81);
|
||||
DeclareField(ZX80_uses_ZX81_ROM);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
#include "Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
bool IsSpectrumTape(Storage::Tape::TapeSerialiser &tape) {
|
||||
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
||||
Parser parser(Parser::MachineType::ZXSpectrum);
|
||||
|
||||
@@ -97,13 +97,18 @@ bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
}
|
||||
|
||||
// ... otherwise read a CPM directory and look for a BASIC program called "DISK".
|
||||
const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format);
|
||||
const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format, false);
|
||||
return catalogue && catalogue->is_zx_spectrum_booter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
@@ -111,7 +116,8 @@ Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Medi
|
||||
if(!media.tapes.empty()) {
|
||||
bool has_spectrum_tape = false;
|
||||
for(auto &tape: media.tapes) {
|
||||
has_spectrum_tape |= IsSpectrumTape(tape);
|
||||
auto serialiser = tape->serialiser();
|
||||
has_spectrum_tape |= IsSpectrumTape(*serialiser);
|
||||
}
|
||||
|
||||
if(has_spectrum_tape) {
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
@@ -27,11 +27,13 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
Model model = Model::Plus2;
|
||||
bool should_hold_enter = false;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -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})
|
||||
|
||||
+195
-180
@@ -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