├── .cargo └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benches └── emulator.rs ├── examples └── headless.rs ├── flake.lock ├── flake.nix ├── index.html └── src ├── apu.rs ├── c6502.rs ├── common.rs ├── headless_protocol.rs ├── joystick.rs ├── lib.rs ├── main.rs ├── mapper.rs ├── nes.rs ├── ppu.rs └── serialization.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-emscripten] 2 | rustflags = [ 3 | "-C", "link-args=--embed-file roms/mario.nes", 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | target 3 | roms 4 | *.rlib 5 | *.log 6 | *.state 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.13" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi 0.3.9", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "0.1.7" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.0.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.2.1" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 42 | 43 | [[package]] 44 | name = "bstr" 45 | version = "0.2.13" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" 48 | dependencies = [ 49 | "lazy_static", 50 | "memchr", 51 | "regex-automata", 52 | "serde", 53 | ] 54 | 55 | [[package]] 56 | name = "bumpalo" 57 | version = "3.4.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 60 | 61 | [[package]] 62 | name = "byteorder" 63 | version = "1.3.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 66 | 67 | [[package]] 68 | name = "bytes" 69 | version = "0.5.6" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 72 | 73 | [[package]] 74 | name = "cast" 75 | version = "0.2.3" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" 78 | dependencies = [ 79 | "rustc_version", 80 | ] 81 | 82 | [[package]] 83 | name = "cfg-if" 84 | version = "0.1.10" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 87 | 88 | [[package]] 89 | name = "clap" 90 | version = "2.33.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 93 | dependencies = [ 94 | "bitflags", 95 | "textwrap 0.11.0", 96 | "unicode-width", 97 | ] 98 | 99 | [[package]] 100 | name = "clap" 101 | version = "3.0.0-beta.5" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" 104 | dependencies = [ 105 | "atty", 106 | "bitflags", 107 | "clap_derive", 108 | "indexmap", 109 | "lazy_static", 110 | "os_str_bytes", 111 | "strsim", 112 | "termcolor", 113 | "textwrap 0.14.2", 114 | "unicase", 115 | ] 116 | 117 | [[package]] 118 | name = "clap_derive" 119 | version = "3.0.0-beta.5" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" 122 | dependencies = [ 123 | "heck", 124 | "proc-macro-error", 125 | "proc-macro2", 126 | "quote", 127 | "syn", 128 | ] 129 | 130 | [[package]] 131 | name = "cloudabi" 132 | version = "0.0.3" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 135 | dependencies = [ 136 | "bitflags", 137 | ] 138 | 139 | [[package]] 140 | name = "criterion" 141 | version = "0.3.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8" 144 | dependencies = [ 145 | "atty", 146 | "cast", 147 | "clap 2.33.1", 148 | "criterion-plot", 149 | "csv", 150 | "itertools", 151 | "lazy_static", 152 | "num-traits", 153 | "oorandom", 154 | "plotters", 155 | "rayon", 156 | "regex", 157 | "serde", 158 | "serde_cbor", 159 | "serde_derive", 160 | "serde_json", 161 | "tinytemplate", 162 | "walkdir", 163 | ] 164 | 165 | [[package]] 166 | name = "criterion-plot" 167 | version = "0.4.3" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" 170 | dependencies = [ 171 | "cast", 172 | "itertools", 173 | ] 174 | 175 | [[package]] 176 | name = "crossbeam-deque" 177 | version = "0.7.3" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 180 | dependencies = [ 181 | "crossbeam-epoch", 182 | "crossbeam-utils", 183 | "maybe-uninit", 184 | ] 185 | 186 | [[package]] 187 | name = "crossbeam-epoch" 188 | version = "0.8.2" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 191 | dependencies = [ 192 | "autocfg 1.0.0", 193 | "cfg-if", 194 | "crossbeam-utils", 195 | "lazy_static", 196 | "maybe-uninit", 197 | "memoffset", 198 | "scopeguard", 199 | ] 200 | 201 | [[package]] 202 | name = "crossbeam-queue" 203 | version = "0.2.3" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" 206 | dependencies = [ 207 | "cfg-if", 208 | "crossbeam-utils", 209 | "maybe-uninit", 210 | ] 211 | 212 | [[package]] 213 | name = "crossbeam-utils" 214 | version = "0.7.2" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 217 | dependencies = [ 218 | "autocfg 1.0.0", 219 | "cfg-if", 220 | "lazy_static", 221 | ] 222 | 223 | [[package]] 224 | name = "csv" 225 | version = "1.1.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" 228 | dependencies = [ 229 | "bstr", 230 | "csv-core", 231 | "itoa", 232 | "ryu", 233 | "serde", 234 | ] 235 | 236 | [[package]] 237 | name = "csv-core" 238 | version = "0.1.10" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 241 | dependencies = [ 242 | "memchr", 243 | ] 244 | 245 | [[package]] 246 | name = "either" 247 | version = "1.5.3" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 250 | 251 | [[package]] 252 | name = "env_logger" 253 | version = "0.7.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 256 | dependencies = [ 257 | "atty", 258 | "humantime", 259 | "log", 260 | "regex", 261 | "termcolor", 262 | ] 263 | 264 | [[package]] 265 | name = "fuchsia-cprng" 266 | version = "0.1.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 269 | 270 | [[package]] 271 | name = "fuchsia-zircon" 272 | version = "0.3.3" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 275 | dependencies = [ 276 | "bitflags", 277 | "fuchsia-zircon-sys", 278 | ] 279 | 280 | [[package]] 281 | name = "fuchsia-zircon-sys" 282 | version = "0.3.3" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 285 | 286 | [[package]] 287 | name = "half" 288 | version = "1.6.0" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" 291 | 292 | [[package]] 293 | name = "hashbrown" 294 | version = "0.11.2" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 297 | 298 | [[package]] 299 | name = "heck" 300 | version = "0.3.3" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 303 | dependencies = [ 304 | "unicode-segmentation", 305 | ] 306 | 307 | [[package]] 308 | name = "hermit-abi" 309 | version = "0.1.15" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 312 | dependencies = [ 313 | "libc", 314 | ] 315 | 316 | [[package]] 317 | name = "humantime" 318 | version = "1.3.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 321 | dependencies = [ 322 | "quick-error", 323 | ] 324 | 325 | [[package]] 326 | name = "indexmap" 327 | version = "1.7.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 330 | dependencies = [ 331 | "autocfg 1.0.0", 332 | "hashbrown", 333 | ] 334 | 335 | [[package]] 336 | name = "iovec" 337 | version = "0.1.4" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 340 | dependencies = [ 341 | "libc", 342 | ] 343 | 344 | [[package]] 345 | name = "itertools" 346 | version = "0.9.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 349 | dependencies = [ 350 | "either", 351 | ] 352 | 353 | [[package]] 354 | name = "itoa" 355 | version = "0.4.6" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 358 | 359 | [[package]] 360 | name = "js-sys" 361 | version = "0.3.42" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "52732a3d3ad72c58ad2dc70624f9c17b46ecd0943b9a4f1ee37c4c18c5d983e2" 364 | dependencies = [ 365 | "wasm-bindgen", 366 | ] 367 | 368 | [[package]] 369 | name = "kernel32-sys" 370 | version = "0.2.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 373 | dependencies = [ 374 | "winapi 0.2.8", 375 | "winapi-build", 376 | ] 377 | 378 | [[package]] 379 | name = "lazy_static" 380 | version = "1.4.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 383 | 384 | [[package]] 385 | name = "libc" 386 | version = "0.2.72" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" 389 | 390 | [[package]] 391 | name = "log" 392 | version = "0.4.11" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 395 | dependencies = [ 396 | "cfg-if", 397 | ] 398 | 399 | [[package]] 400 | name = "maybe-uninit" 401 | version = "2.0.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 404 | 405 | [[package]] 406 | name = "memchr" 407 | version = "2.4.1" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 410 | 411 | [[package]] 412 | name = "memoffset" 413 | version = "0.5.5" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" 416 | dependencies = [ 417 | "autocfg 1.0.0", 418 | ] 419 | 420 | [[package]] 421 | name = "mio" 422 | version = "0.6.22" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 425 | dependencies = [ 426 | "cfg-if", 427 | "fuchsia-zircon", 428 | "fuchsia-zircon-sys", 429 | "iovec", 430 | "kernel32-sys", 431 | "libc", 432 | "log", 433 | "miow", 434 | "net2", 435 | "slab", 436 | "winapi 0.2.8", 437 | ] 438 | 439 | [[package]] 440 | name = "mio-uds" 441 | version = "0.6.8" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 444 | dependencies = [ 445 | "iovec", 446 | "libc", 447 | "mio", 448 | ] 449 | 450 | [[package]] 451 | name = "miow" 452 | version = "0.2.1" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 455 | dependencies = [ 456 | "kernel32-sys", 457 | "net2", 458 | "winapi 0.2.8", 459 | "ws2_32-sys", 460 | ] 461 | 462 | [[package]] 463 | name = "nes-emulator" 464 | version = "0.1.0" 465 | dependencies = [ 466 | "clap 3.0.0-beta.5", 467 | "criterion", 468 | "env_logger", 469 | "log", 470 | "sdl2", 471 | "tokio", 472 | ] 473 | 474 | [[package]] 475 | name = "net2" 476 | version = "0.2.34" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 479 | dependencies = [ 480 | "cfg-if", 481 | "libc", 482 | "winapi 0.3.9", 483 | ] 484 | 485 | [[package]] 486 | name = "num" 487 | version = "0.1.42" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 490 | dependencies = [ 491 | "num-integer", 492 | "num-iter", 493 | "num-traits", 494 | ] 495 | 496 | [[package]] 497 | name = "num-integer" 498 | version = "0.1.43" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 501 | dependencies = [ 502 | "autocfg 1.0.0", 503 | "num-traits", 504 | ] 505 | 506 | [[package]] 507 | name = "num-iter" 508 | version = "0.1.41" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" 511 | dependencies = [ 512 | "autocfg 1.0.0", 513 | "num-integer", 514 | "num-traits", 515 | ] 516 | 517 | [[package]] 518 | name = "num-traits" 519 | version = "0.2.12" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 522 | dependencies = [ 523 | "autocfg 1.0.0", 524 | ] 525 | 526 | [[package]] 527 | name = "num_cpus" 528 | version = "1.13.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 531 | dependencies = [ 532 | "hermit-abi", 533 | "libc", 534 | ] 535 | 536 | [[package]] 537 | name = "oorandom" 538 | version = "11.1.2" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" 541 | 542 | [[package]] 543 | name = "os_str_bytes" 544 | version = "4.2.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" 547 | dependencies = [ 548 | "memchr", 549 | ] 550 | 551 | [[package]] 552 | name = "pin-project-lite" 553 | version = "0.1.7" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" 556 | 557 | [[package]] 558 | name = "plotters" 559 | version = "0.2.15" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb" 562 | dependencies = [ 563 | "js-sys", 564 | "num-traits", 565 | "wasm-bindgen", 566 | "web-sys", 567 | ] 568 | 569 | [[package]] 570 | name = "proc-macro-error" 571 | version = "1.0.4" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 574 | dependencies = [ 575 | "proc-macro-error-attr", 576 | "proc-macro2", 577 | "quote", 578 | "syn", 579 | "version_check", 580 | ] 581 | 582 | [[package]] 583 | name = "proc-macro-error-attr" 584 | version = "1.0.4" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 587 | dependencies = [ 588 | "proc-macro2", 589 | "quote", 590 | "version_check", 591 | ] 592 | 593 | [[package]] 594 | name = "proc-macro2" 595 | version = "1.0.32" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 598 | dependencies = [ 599 | "unicode-xid", 600 | ] 601 | 602 | [[package]] 603 | name = "quick-error" 604 | version = "1.2.3" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 607 | 608 | [[package]] 609 | name = "quote" 610 | version = "1.0.10" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 613 | dependencies = [ 614 | "proc-macro2", 615 | ] 616 | 617 | [[package]] 618 | name = "rand" 619 | version = "0.6.5" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 622 | dependencies = [ 623 | "autocfg 0.1.7", 624 | "libc", 625 | "rand_chacha", 626 | "rand_core 0.4.2", 627 | "rand_hc", 628 | "rand_isaac", 629 | "rand_jitter", 630 | "rand_os", 631 | "rand_pcg", 632 | "rand_xorshift", 633 | "winapi 0.3.9", 634 | ] 635 | 636 | [[package]] 637 | name = "rand_chacha" 638 | version = "0.1.1" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 641 | dependencies = [ 642 | "autocfg 0.1.7", 643 | "rand_core 0.3.1", 644 | ] 645 | 646 | [[package]] 647 | name = "rand_core" 648 | version = "0.3.1" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 651 | dependencies = [ 652 | "rand_core 0.4.2", 653 | ] 654 | 655 | [[package]] 656 | name = "rand_core" 657 | version = "0.4.2" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 660 | 661 | [[package]] 662 | name = "rand_hc" 663 | version = "0.1.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 666 | dependencies = [ 667 | "rand_core 0.3.1", 668 | ] 669 | 670 | [[package]] 671 | name = "rand_isaac" 672 | version = "0.1.1" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 675 | dependencies = [ 676 | "rand_core 0.3.1", 677 | ] 678 | 679 | [[package]] 680 | name = "rand_jitter" 681 | version = "0.1.4" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 684 | dependencies = [ 685 | "libc", 686 | "rand_core 0.4.2", 687 | "winapi 0.3.9", 688 | ] 689 | 690 | [[package]] 691 | name = "rand_os" 692 | version = "0.1.3" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 695 | dependencies = [ 696 | "cloudabi", 697 | "fuchsia-cprng", 698 | "libc", 699 | "rand_core 0.4.2", 700 | "rdrand", 701 | "winapi 0.3.9", 702 | ] 703 | 704 | [[package]] 705 | name = "rand_pcg" 706 | version = "0.1.2" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 709 | dependencies = [ 710 | "autocfg 0.1.7", 711 | "rand_core 0.4.2", 712 | ] 713 | 714 | [[package]] 715 | name = "rand_xorshift" 716 | version = "0.1.1" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 719 | dependencies = [ 720 | "rand_core 0.3.1", 721 | ] 722 | 723 | [[package]] 724 | name = "rayon" 725 | version = "1.3.1" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" 728 | dependencies = [ 729 | "autocfg 1.0.0", 730 | "crossbeam-deque", 731 | "either", 732 | "rayon-core", 733 | ] 734 | 735 | [[package]] 736 | name = "rayon-core" 737 | version = "1.7.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" 740 | dependencies = [ 741 | "crossbeam-deque", 742 | "crossbeam-queue", 743 | "crossbeam-utils", 744 | "lazy_static", 745 | "num_cpus", 746 | ] 747 | 748 | [[package]] 749 | name = "rdrand" 750 | version = "0.4.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 753 | dependencies = [ 754 | "rand_core 0.3.1", 755 | ] 756 | 757 | [[package]] 758 | name = "regex" 759 | version = "1.3.9" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 762 | dependencies = [ 763 | "aho-corasick", 764 | "memchr", 765 | "regex-syntax", 766 | "thread_local", 767 | ] 768 | 769 | [[package]] 770 | name = "regex-automata" 771 | version = "0.1.9" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" 774 | dependencies = [ 775 | "byteorder", 776 | ] 777 | 778 | [[package]] 779 | name = "regex-syntax" 780 | version = "0.6.18" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 783 | 784 | [[package]] 785 | name = "rustc_version" 786 | version = "0.2.3" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 789 | dependencies = [ 790 | "semver", 791 | ] 792 | 793 | [[package]] 794 | name = "ryu" 795 | version = "1.0.5" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 798 | 799 | [[package]] 800 | name = "same-file" 801 | version = "1.0.6" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 804 | dependencies = [ 805 | "winapi-util", 806 | ] 807 | 808 | [[package]] 809 | name = "scopeguard" 810 | version = "1.1.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 813 | 814 | [[package]] 815 | name = "sdl2" 816 | version = "0.32.2" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "d051a07231e303f5f719da78cb6f7394f6d5b54f733aef5b0b447804a83edd7b" 819 | dependencies = [ 820 | "bitflags", 821 | "lazy_static", 822 | "libc", 823 | "num", 824 | "rand", 825 | "sdl2-sys", 826 | ] 827 | 828 | [[package]] 829 | name = "sdl2-sys" 830 | version = "0.32.6" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "34e71125077d297d57e4c1acfe8981b5bdfbf5a20e7b589abfdcb33bf1127f86" 833 | dependencies = [ 834 | "cfg-if", 835 | "libc", 836 | ] 837 | 838 | [[package]] 839 | name = "semver" 840 | version = "0.9.0" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 843 | dependencies = [ 844 | "semver-parser", 845 | ] 846 | 847 | [[package]] 848 | name = "semver-parser" 849 | version = "0.7.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 852 | 853 | [[package]] 854 | name = "serde" 855 | version = "1.0.114" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 858 | 859 | [[package]] 860 | name = "serde_cbor" 861 | version = "0.11.1" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" 864 | dependencies = [ 865 | "half", 866 | "serde", 867 | ] 868 | 869 | [[package]] 870 | name = "serde_derive" 871 | version = "1.0.114" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 874 | dependencies = [ 875 | "proc-macro2", 876 | "quote", 877 | "syn", 878 | ] 879 | 880 | [[package]] 881 | name = "serde_json" 882 | version = "1.0.56" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" 885 | dependencies = [ 886 | "itoa", 887 | "ryu", 888 | "serde", 889 | ] 890 | 891 | [[package]] 892 | name = "slab" 893 | version = "0.4.2" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 896 | 897 | [[package]] 898 | name = "strsim" 899 | version = "0.10.0" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 902 | 903 | [[package]] 904 | name = "syn" 905 | version = "1.0.82" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 908 | dependencies = [ 909 | "proc-macro2", 910 | "quote", 911 | "unicode-xid", 912 | ] 913 | 914 | [[package]] 915 | name = "termcolor" 916 | version = "1.1.0" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 919 | dependencies = [ 920 | "winapi-util", 921 | ] 922 | 923 | [[package]] 924 | name = "textwrap" 925 | version = "0.11.0" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 928 | dependencies = [ 929 | "unicode-width", 930 | ] 931 | 932 | [[package]] 933 | name = "textwrap" 934 | version = "0.14.2" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 937 | dependencies = [ 938 | "unicode-width", 939 | ] 940 | 941 | [[package]] 942 | name = "thread_local" 943 | version = "1.0.1" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 946 | dependencies = [ 947 | "lazy_static", 948 | ] 949 | 950 | [[package]] 951 | name = "tinytemplate" 952 | version = "1.1.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" 955 | dependencies = [ 956 | "serde", 957 | "serde_json", 958 | ] 959 | 960 | [[package]] 961 | name = "tokio" 962 | version = "0.2.21" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" 965 | dependencies = [ 966 | "bytes", 967 | "lazy_static", 968 | "libc", 969 | "mio", 970 | "mio-uds", 971 | "pin-project-lite", 972 | ] 973 | 974 | [[package]] 975 | name = "unicase" 976 | version = "2.6.0" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 979 | dependencies = [ 980 | "version_check", 981 | ] 982 | 983 | [[package]] 984 | name = "unicode-segmentation" 985 | version = "1.8.0" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 988 | 989 | [[package]] 990 | name = "unicode-width" 991 | version = "0.1.8" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 994 | 995 | [[package]] 996 | name = "unicode-xid" 997 | version = "0.2.1" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1000 | 1001 | [[package]] 1002 | name = "version_check" 1003 | version = "0.9.3" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1006 | 1007 | [[package]] 1008 | name = "walkdir" 1009 | version = "2.3.1" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1012 | dependencies = [ 1013 | "same-file", 1014 | "winapi 0.3.9", 1015 | "winapi-util", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "wasm-bindgen" 1020 | version = "0.2.65" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "f3edbcc9536ab7eababcc6d2374a0b7bfe13a2b6d562c5e07f370456b1a8f33d" 1023 | dependencies = [ 1024 | "cfg-if", 1025 | "wasm-bindgen-macro", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "wasm-bindgen-backend" 1030 | version = "0.2.65" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "89ed2fb8c84bfad20ea66b26a3743f3e7ba8735a69fe7d95118c33ec8fc1244d" 1033 | dependencies = [ 1034 | "bumpalo", 1035 | "lazy_static", 1036 | "log", 1037 | "proc-macro2", 1038 | "quote", 1039 | "syn", 1040 | "wasm-bindgen-shared", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "wasm-bindgen-macro" 1045 | version = "0.2.65" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "eb071268b031a64d92fc6cf691715ca5a40950694d8f683c5bb43db7c730929e" 1048 | dependencies = [ 1049 | "quote", 1050 | "wasm-bindgen-macro-support", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "wasm-bindgen-macro-support" 1055 | version = "0.2.65" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "cf592c807080719d1ff2f245a687cbadb3ed28b2077ed7084b47aba8b691f2c6" 1058 | dependencies = [ 1059 | "proc-macro2", 1060 | "quote", 1061 | "syn", 1062 | "wasm-bindgen-backend", 1063 | "wasm-bindgen-shared", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "wasm-bindgen-shared" 1068 | version = "0.2.65" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "72b6c0220ded549d63860c78c38f3bcc558d1ca3f4efa74942c536ddbbb55e87" 1071 | 1072 | [[package]] 1073 | name = "web-sys" 1074 | version = "0.3.42" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "8be2398f326b7ba09815d0b403095f34dd708579220d099caae89be0b32137b2" 1077 | dependencies = [ 1078 | "js-sys", 1079 | "wasm-bindgen", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "winapi" 1084 | version = "0.2.8" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1087 | 1088 | [[package]] 1089 | name = "winapi" 1090 | version = "0.3.9" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1093 | dependencies = [ 1094 | "winapi-i686-pc-windows-gnu", 1095 | "winapi-x86_64-pc-windows-gnu", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "winapi-build" 1100 | version = "0.1.1" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1103 | 1104 | [[package]] 1105 | name = "winapi-i686-pc-windows-gnu" 1106 | version = "0.4.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1109 | 1110 | [[package]] 1111 | name = "winapi-util" 1112 | version = "0.1.5" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1115 | dependencies = [ 1116 | "winapi 0.3.9", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "winapi-x86_64-pc-windows-gnu" 1121 | version = "0.4.0" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1124 | 1125 | [[package]] 1126 | name = "ws2_32-sys" 1127 | version = "0.2.1" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1130 | dependencies = [ 1131 | "winapi 0.2.8", 1132 | "winapi-build", 1133 | ] 1134 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nes-emulator" 3 | version = "0.1.0" 4 | authors = ["Michael Burge "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "nes-emulator" 9 | path = "src/main.rs" 10 | 11 | [[bin]] 12 | name = "headless" 13 | path = "examples/headless.rs" 14 | 15 | [[bench]] 16 | name = "emulator" 17 | harness = false 18 | 19 | [dev-dependencies] 20 | criterion = "0.3" 21 | 22 | [dependencies] 23 | sdl2 = "0.32" 24 | log = "0.4.11" 25 | tokio = {version = "0.2", features = ["uds"]} 26 | clap = "3.0.0-beta.5" 27 | env_logger = "0.7" 28 | 29 | [profile.release] 30 | opt-level = 3 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nes-emulator 2 | 3 | ## Building 4 | 5 | ``` 6 | $ rustc --version 7 | rustc 1.32.0 (9fda7c223 2019-01-16) 8 | $ cargo --version 9 | cargo 1.32.0 (8610973aa 2019-01-02) 10 | 11 | $ cargo build --release 12 | $ cargo run --release --bin nes-emulator 13 | ``` 14 | 15 | The emulator loads a ROM in iNES format located at the hardcoded path `roms/mario.nes`. 16 | 17 | On Windows, you may need to statically-link SDL by enabling the appropriate feature: 18 | ``` 19 | $ cargo run --release --bin nes-emulator --features 'sdl2/bundled' 20 | ``` 21 | ## Inputs 22 | 23 | The emulator has been tested with an Xbox 360 controller, but should work with any controller the SDL library recognizes. 24 | 25 | Additionally, these keyboard keys control the emulator: 26 | * Escape: Exits the emulator 27 | * Pause: (Developer use) Breaks a command-line debugger 28 | * F5: Saves a savestate 29 | * F6: Loads the most recent savestate 30 | * F7: Restart the current ROM and playback a video of recorded inputs 31 | * F8: Set video recording start point 32 | * Tab: Toggles "turbo mode", which removes the 60 FPS limit. -------------------------------------------------------------------------------- /benches/emulator.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | extern crate nes_emulator; 4 | 5 | use nes_emulator::joystick::Joystick; 6 | use nes_emulator::nes::load_ines; 7 | use nes_emulator::nes::read_ines; 8 | 9 | use criterion::{criterion_main, Criterion, Throughput}; 10 | use std::time::Duration; 11 | 12 | fn criterion_benchmark(c: &mut Criterion) { 13 | let joystick1 = Box::new(Joystick::new()); 14 | let joystick2 = Box::new(Joystick::new()); 15 | let ines = read_ines("roms/mario.nes".to_string()).unwrap(); 16 | let mut nes = load_ines(ines, joystick1, joystick2); 17 | let mut group = c.benchmark_group("Mario"); 18 | for &size in &[100, 1_000] { 19 | group.throughput(Throughput::Elements(size as u64)); 20 | group.measurement_time(Duration::from_secs(30)); 21 | group.sample_size(10); 22 | group.bench_function(format!("frames {}", size), |b| { 23 | b.iter(|| { 24 | for _ in 0..size { 25 | nes.run_frame() 26 | } 27 | }) 28 | }); 29 | } 30 | } 31 | 32 | criterion_group!(benches, criterion_benchmark); 33 | criterion_main!(benches); 34 | -------------------------------------------------------------------------------- /examples/headless.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | use clap::{Parser, ValueHint}; 4 | use core::ptr::null_mut; 5 | use log::{debug, info, trace}; 6 | use nes_emulator::{ 7 | common::Clocked, 8 | headless_protocol::{ 9 | Command::{self, *}, 10 | ReadWrite, RenderStyle, StdInOut, 11 | }, 12 | joystick::Joystick, 13 | mapper::AddressSpace, 14 | nes::{load_ines, read_ines, Nes}, 15 | serialization::{read_value, Savable}, 16 | }; 17 | use std::{ 18 | fs::File, 19 | io::{BufWriter, Read, Write}, 20 | net::{TcpListener, TcpStream}, 21 | os::unix::{ 22 | io::FromRawFd, 23 | net::{UnixListener, UnixStream}, 24 | }, 25 | path::{Path, PathBuf}, 26 | }; 27 | 28 | #[derive(Parser)] 29 | #[clap(name = "headless")] 30 | struct Opts { 31 | #[clap(long = "host")] 32 | host: Option, 33 | #[clap(long = "socket")] 34 | socket: Option, 35 | } 36 | 37 | fn main() { 38 | env_logger::init(); 39 | let opts = Opts::parse(); 40 | let command_loop = |mut headless: Headless| loop { 41 | let command = read_value::>(&mut headless.fh); 42 | match command { 43 | None => break, 44 | Some(command) => { 45 | headless.dispatch_command(command); 46 | headless.emit_sync_byte(); 47 | } 48 | } 49 | }; 50 | match opts.host { 51 | None => {} 52 | Some(ref host) => { 53 | let listener = TcpListener::bind(host.clone()) 54 | .expect(&*format!("Unable to connect to host at {:?}", host)); 55 | info!("Listening on {}", host); 56 | for stream in listener.incoming() { 57 | match stream { 58 | Ok(stream) => { 59 | debug!( 60 | "read_timeout={:?} stream={:?}", 61 | stream.read_timeout(), 62 | stream 63 | ); 64 | let headless = Headless::new(stream); 65 | command_loop(headless); 66 | } 67 | Err(err) => { 68 | panic!("Error: {:?}", err); 69 | } 70 | } 71 | } 72 | } 73 | }; 74 | match opts.socket { 75 | None => {} 76 | Some(ref socket) => { 77 | let path = Path::new(socket); 78 | if path.exists() { 79 | std::fs::remove_file(&path).expect("Unable to clear existing socket"); 80 | } 81 | let listener = UnixListener::bind(path) 82 | .expect(&*format!("Unable to connect to socket at {:?}", path)); 83 | info!("Listening on {:?}", path); 84 | for stream in listener.incoming() { 85 | match stream { 86 | Ok(stream) => { 87 | debug!( 88 | "read_timeout={:?} stream={:?}", 89 | stream.read_timeout(), 90 | stream 91 | ); 92 | let headless = Headless::new(stream); 93 | command_loop(headless); 94 | } 95 | Err(err) => { 96 | panic!("Error: {:?}", err); 97 | } 98 | } 99 | } 100 | } 101 | }; 102 | info!("Defaulting to stdin/stdout for IO"); 103 | // Standard stdout() object is line-buffered 104 | let stdin = unsafe { File::from_raw_fd(0) }; 105 | let stdout = unsafe { File::from_raw_fd(1) }; 106 | let headless = Headless::new(StdInOut(stdin, stdout)); 107 | command_loop(headless); 108 | } 109 | 110 | struct Headless { 111 | joystick1: *mut Joystick, 112 | joystick2: *mut Joystick, 113 | nes: Option>, 114 | fh: Box, 115 | is_synchronized: bool, 116 | num_commands: u64, 117 | is_rendering: bool, 118 | } 119 | 120 | impl Headless { 121 | pub fn new(fh: RW) -> Self { 122 | let nes = None; 123 | Self { 124 | joystick1: null_mut(), 125 | joystick2: null_mut(), 126 | nes: nes, 127 | fh: Box::new(fh), 128 | is_synchronized: true, 129 | num_commands: 0, 130 | is_rendering: true, 131 | } 132 | } 133 | fn dispatch_command(&mut self, command: Command) { 134 | debug!("Received command: {:?}", command); 135 | match command { 136 | LoadRom(_, filename) => { 137 | let mut joystick1 = Box::new(Joystick::new()); 138 | let mut joystick2 = Box::new(Joystick::new()); 139 | self.joystick1 = &mut *joystick1; 140 | self.joystick2 = &mut *joystick2; 141 | match read_ines(filename.clone()) { 142 | Ok(ines) => { 143 | let mut nes = load_ines(ines, joystick1, joystick2); 144 | nes.apu.is_recording = false; // TODO - Expose some way to retrieve recorded sound 145 | self.nes = Some(Box::new(nes)); 146 | } 147 | x @ Err { .. } => panic!("Error loading rom file {:?} - {:?}", filename, x), 148 | } 149 | } 150 | StepFrame => { 151 | if self.is_rendering { 152 | self.nes.as_mut().unwrap().run_frame(); 153 | } else { 154 | self.nes.as_mut().unwrap().run_frame_headless(); 155 | } 156 | } 157 | RenderFrame(render_style) => { 158 | let bytes: Vec = match render_style { 159 | RenderStyle::Plain => self.nes.as_ref().unwrap().ppu.display.to_vec(), 160 | RenderStyle::Rgb => self.nes.as_ref().unwrap().ppu.render().to_vec(), 161 | }; 162 | self.fh 163 | .write(&bytes) 164 | .expect(&*format!("Unable to write bytes for {:?}", render_style)); 165 | } 166 | SetInputs(controller_id, button_mask) => { 167 | assert_eq!( 168 | controller_id, 0, 169 | "Unsupported controller_id {}", 170 | controller_id 171 | ); 172 | unsafe { (*self.joystick1).set_buttons(button_mask) }; 173 | } 174 | SaveState(filename) => { 175 | let mut file = File::create(filename).unwrap(); 176 | self.nes.as_ref().unwrap().save(&mut file); 177 | } 178 | LoadState(filename) => { 179 | let mut file = File::open(filename).unwrap(); 180 | self.nes.as_mut().unwrap().load(&mut file); 181 | } 182 | GetInfo => panic!("Unimplemented"), 183 | Step => self.nes.as_mut().unwrap().clock(), 184 | SaveTas => panic!("Unimplemented"), 185 | Peek(address) => { 186 | let result = self.nes.as_ref().unwrap().cpu.peek(address); 187 | trace!("peek({})={}", address, result); 188 | result.save(&mut self.fh); 189 | } 190 | Poke(address, value) => self.nes.as_mut().unwrap().cpu.poke(address, value), 191 | SetRendering(is_rendering) => self.is_rendering = is_rendering, 192 | } 193 | } 194 | 195 | fn emit_sync_byte(&mut self) { 196 | self.num_commands += 1; 197 | if self.is_synchronized { 198 | let x = (self.num_commands % 256) as u8; 199 | x.save(&mut self.fh); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1637014545, 6 | "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "flake-utils_2": { 19 | "locked": { 20 | "lastModified": 1637014545, 21 | "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=", 22 | "owner": "numtide", 23 | "repo": "flake-utils", 24 | "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "type": "github" 31 | } 32 | }, 33 | "nixpkgs": { 34 | "locked": { 35 | "lastModified": 1637967717, 36 | "narHash": "sha256-PxrKuOkaUbt5USFdgrUoMc1Z4FTMuJLQn0uqlKwKuus=", 37 | "owner": "NixOS", 38 | "repo": "nixpkgs", 39 | "rev": "bcc1eba8086913106ec094994181cd73a1f9a212", 40 | "type": "github" 41 | }, 42 | "original": { 43 | "id": "nixpkgs", 44 | "type": "indirect" 45 | } 46 | }, 47 | "nixpkgs_2": { 48 | "locked": { 49 | "lastModified": 1637453606, 50 | "narHash": "sha256-Gy6cwUswft9xqsjWxFYEnx/63/qzaFUwatcbV5GF/GQ=", 51 | "owner": "NixOS", 52 | "repo": "nixpkgs", 53 | "rev": "8afc4e543663ca0a6a4f496262cd05233737e732", 54 | "type": "github" 55 | }, 56 | "original": { 57 | "owner": "NixOS", 58 | "ref": "nixpkgs-unstable", 59 | "repo": "nixpkgs", 60 | "type": "github" 61 | } 62 | }, 63 | "root": { 64 | "inputs": { 65 | "flake-utils": "flake-utils", 66 | "nixpkgs": "nixpkgs", 67 | "rust-overlay": "rust-overlay" 68 | } 69 | }, 70 | "rust-overlay": { 71 | "inputs": { 72 | "flake-utils": "flake-utils_2", 73 | "nixpkgs": "nixpkgs_2" 74 | }, 75 | "locked": { 76 | "lastModified": 1637892877, 77 | "narHash": "sha256-EjqFJAEiYDrL/AIreKDDb0JDMvObv+UONHMBPnb+8Do=", 78 | "owner": "oxalica", 79 | "repo": "rust-overlay", 80 | "rev": "3e10e13c0b96536c2d4e00910557196e1ab09bd6", 81 | "type": "github" 82 | }, 83 | "original": { 84 | "owner": "oxalica", 85 | "repo": "rust-overlay", 86 | "type": "github" 87 | } 88 | } 89 | }, 90 | "root": "root", 91 | "version": 7 92 | } 93 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "NES Emulator"; 3 | 4 | inputs = { 5 | rust-overlay.url = "github:oxalica/rust-overlay"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils, rust-overlay }: 10 | let supportedSystems = [ "x86_64-linux" ]; 11 | in 12 | flake-utils.lib.eachSystem supportedSystems (system: 13 | let 14 | overlays = [ (import rust-overlay)]; 15 | pkgs = import nixpkgs { inherit system overlays; }; 16 | rust = pkgs.rust-bin.nightly."2021-11-20".default; 17 | rustPlatform = pkgs.makeRustPlatform { cargo = rust; rustc = rust; }; 18 | pkg = rustPlatform.buildRustPackage { 19 | name = "nes-emulator"; 20 | src = ./.; 21 | buildInputs = [ pkgs.SDL2 ]; 22 | cargoSha256 = "sha256-IPZyBBrqi4kroBIfdLXPoHLKfZiwrjmkwC2bKDNw/XA="; 23 | }; 24 | in { 25 | packages = { 26 | nes-emulator = pkg; 27 | }; 28 | defaultPackage = pkg; 29 | defaultApp = pkg; 30 | }); 31 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Emscripten-Generated Code 8 | 93 | 94 | 95 |
96 |
Downloading...
97 | 98 | 99 | Resize canvas 100 | Lock/hide mouse pointer     101 | 103 | 104 | 105 | 106 |
107 | 108 |
109 | 110 |
111 | 112 |
113 | 114 | 115 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/apu.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(dead_code)] // TODO 4 | #![allow(unused_variables)] 5 | 6 | use crate::common::{get_bit, ternary, Clocked}; 7 | use crate::mapper::AddressSpace; 8 | use crate::serialization::Savable; 9 | 10 | use std::collections::VecDeque; 11 | use std::io::Read; 12 | use std::io::Write; 13 | 14 | // https://wiki.nesdev.com/w/index.php/2A03 15 | pub enum ApuPort { 16 | SQ1_VOL, 17 | SQ1_SWEEP, 18 | SQ1_LO, 19 | SQ1_HI, 20 | SQ2_VOL, 21 | SQ2_SWEEP, 22 | SQ2_LO, 23 | SQ2_HI, 24 | TRI_LINEAR, 25 | TRI_LO, 26 | TRI_HI, 27 | NOISE_VOL, 28 | NOISE_LO, 29 | NOISE_HI, 30 | DMC_FREQ, 31 | DMC_RAW, 32 | DMC_START, 33 | DMC_LEN, 34 | SND_CHN, 35 | FRAME_COUNTER, 36 | } 37 | 38 | use ApuPort::*; 39 | 40 | const ENABLE_PULSE1: bool = true; 41 | const ENABLE_PULSE2: bool = true; 42 | const ENABLE_TRIANGLE: bool = true; 43 | const ENABLE_NOISE: bool = true; 44 | const ENABLE_DMC: bool = false; 45 | 46 | const SAMPLES_PER_FRAME: f64 = 735.0; // 44100 Hz audio / 60 FPS 47 | const CLOCKS_PER_FRAME: f64 = 29780.0; 48 | 49 | pub fn map_apu_port(ptr: u16) -> Option { 50 | match ptr { 51 | 0x4000 => Some(SQ1_VOL), 52 | 0x4001 => Some(SQ1_SWEEP), 53 | 0x4002 => Some(SQ1_LO), 54 | 0x4003 => Some(SQ1_HI), 55 | 0x4004 => Some(SQ2_VOL), 56 | 0x4005 => Some(SQ2_SWEEP), 57 | 0x4006 => Some(SQ2_LO), 58 | 0x4007 => Some(SQ2_HI), 59 | 0x4008 => Some(TRI_LINEAR), 60 | //0x4009 => Some(APU_DUMMY1), 61 | 0x400A => Some(TRI_LO), 62 | 0x400B => Some(TRI_HI), 63 | 0x400C => Some(NOISE_VOL), 64 | //0x400D => Some(APU_DUMMY2), 65 | 0x400E => Some(NOISE_LO), 66 | 0x400F => Some(NOISE_HI), 67 | 0x4010 => Some(DMC_FREQ), 68 | 0x4011 => Some(DMC_RAW), 69 | 0x4012 => Some(DMC_START), 70 | 0x4013 => Some(DMC_LEN), 71 | 0x4015 => Some(SND_CHN), 72 | 0x4017 => Some(FRAME_COUNTER), 73 | _ => None, 74 | } 75 | } 76 | 77 | // ttps://wiki.nesdev.com/w/index.php/APU_Frame_Counter 78 | struct FrameCounter { 79 | step: u16, 80 | interrupt_inhibit: bool, 81 | mode: bool, // false=4-step, true=5-step 82 | } 83 | 84 | impl FrameCounter { 85 | pub fn new() -> FrameCounter { 86 | FrameCounter { 87 | step: 0, 88 | interrupt_inhibit: false, 89 | mode: false, 90 | } 91 | } 92 | pub fn is_quarter_frame_edge(&self) -> bool { 93 | match (self.mode, self.step) { 94 | (_, 3728) => true, 95 | (_, 7456) => true, 96 | (_, 11185) => true, 97 | (false, 14914) => true, 98 | (true, 18640) => true, 99 | _ => false, 100 | } 101 | } 102 | pub fn is_half_frame_edge(&self) -> bool { 103 | match (self.mode, self.step) { 104 | (_, 7456) => true, 105 | (false, 14914) => true, 106 | (true, 18640) => true, 107 | _ => false, 108 | } 109 | } 110 | pub fn is_frame_edge(&self) -> bool { 111 | match (self.mode, self.step) { 112 | (false, 14914) => true, 113 | _ => false, 114 | } 115 | } 116 | pub fn write_control(&mut self, value: u8) { 117 | self.mode = get_bit(value, 7) > 0; 118 | self.interrupt_inhibit = get_bit(value, 6) > 0; 119 | } 120 | } 121 | 122 | impl Clocked for FrameCounter { 123 | fn clock(&mut self) { 124 | self.step += 1; 125 | let cap = ternary(self.mode, 18641, 14915); 126 | // self.step %= cap; 127 | if self.step >= cap { 128 | self.step -= cap; 129 | } 130 | } 131 | } 132 | 133 | pub struct Apu { 134 | pub samples: Vec, 135 | pub is_recording: bool, 136 | cycle: u64, 137 | sample_rate: f64, 138 | sample_timer: f64, 139 | frame_counter: FrameCounter, 140 | pulse1: Pulse, 141 | pulse2: Pulse, 142 | triangle: Triangle, 143 | noise: Noise, 144 | dmc: Dmc, 145 | } 146 | 147 | impl Savable for Apu { 148 | fn save(&self, fh: &mut dyn Write) { 149 | // TODO 150 | } 151 | fn load(&mut self, fh: &mut dyn Read) { 152 | // TODO 153 | } 154 | } 155 | 156 | impl Clocked for Apu { 157 | fn clock(&mut self) { 158 | if self.cycle % 2 == 0 { 159 | self.frame_counter.clock(); 160 | self.pulse1.clock(); 161 | self.pulse2.clock(); 162 | self.noise.clock(); 163 | self.dmc.clock(); 164 | } 165 | self.triangle.clock(); 166 | if self.frame_counter.is_half_frame_edge() { 167 | self.pulse1.clock_half_frame(); 168 | self.pulse2.clock_half_frame(); 169 | self.triangle.clock_half_frame(); 170 | self.noise.clock_half_frame(); 171 | self.dmc.clock_half_frame(); 172 | } 173 | if self.frame_counter.is_quarter_frame_edge() { 174 | self.pulse1.clock_quarter_frame(); 175 | self.pulse2.clock_quarter_frame(); 176 | self.triangle.clock_quarter_frame(); 177 | self.noise.clock_quarter_frame(); 178 | self.dmc.clock_half_frame(); 179 | } 180 | if self.sample_timer <= 0.0 && self.is_recording { 181 | let sample = self.sample(); 182 | //let sample = 0.1; 183 | self.samples.push(sample); 184 | self.sample_timer += self.sample_rate; 185 | } 186 | self.sample_timer -= 1.0; 187 | self.cycle += 1; 188 | } 189 | } 190 | 191 | impl Apu { 192 | pub fn new() -> Apu { 193 | Apu { 194 | cycle: 0, 195 | is_recording: true, 196 | samples: Vec::new(), 197 | sample_rate: CLOCKS_PER_FRAME / SAMPLES_PER_FRAME, 198 | sample_timer: 0.0, 199 | frame_counter: FrameCounter::new(), 200 | pulse1: Pulse::new(false), 201 | pulse2: Pulse::new(true), 202 | triangle: Triangle::new(), 203 | noise: Noise::new(), 204 | dmc: Dmc::new(), 205 | } 206 | } 207 | pub fn reset(&mut self) { 208 | // TODO - Implement reset 209 | // self.pulse1.reset(); 210 | // self.pulse2.reset(); 211 | // self.triangle.reset(); 212 | // self.noise.reset(); 213 | // self.dmc.reset(); 214 | } 215 | pub fn sample(&self) -> f32 { 216 | // https://wiki.nesdev.com/w/index.php/APU_Mixer 217 | let pulse1 = ternary(ENABLE_PULSE1, self.pulse1.sample(), 0.0); 218 | let pulse2 = ternary(ENABLE_PULSE2, self.pulse2.sample(), 0.0); 219 | let triangle = ternary(ENABLE_TRIANGLE, self.triangle.sample(), 0.0); 220 | let noise = ternary(ENABLE_NOISE, self.noise.sample(), 0.0); 221 | let dmc = ternary(ENABLE_DMC, self.dmc.sample(), 0.0); 222 | 223 | let pulse_out = 0.00752 * (pulse1 + pulse2); 224 | //let tnd_out = triangle / 15.0 ; // 0.00851 * triangle + 0.00494 * noise + 0.00335 * dmc; 225 | let tnd_out = 0.00851 * triangle + 0.00494 * noise + 0.00335 * dmc; 226 | let output = pulse_out + tnd_out; 227 | //return 0.5; 228 | return output as f32; 229 | } 230 | fn read_status(&self) -> u8 { 231 | // TODO - Clear the frame interrupt flag 232 | return (self.pulse1.is_enabled() as u8) << 0 233 | | (self.pulse2.is_enabled() as u8) << 1 234 | | (self.triangle.is_enabled() as u8) << 2 235 | | (self.noise.is_enabled() as u8) << 3 236 | | (self.dmc.is_enabled() as u8) << 4; 237 | } 238 | fn write_status(&mut self, v: u8) { 239 | let enable_pulse1 = v & 0b00001; 240 | let enable_pulse2 = v & 0b00010; 241 | let enable_triangle = v & 0b00100; 242 | let enable_noise = v & 0b01000; 243 | let enable_dmc = v & 0b10000; 244 | self.pulse1.set_enabled(enable_pulse1 > 0); 245 | self.pulse2.set_enabled(enable_pulse2 > 0); 246 | self.triangle.set_enabled(enable_triangle > 0); 247 | self.noise.set_enabled(enable_noise > 0); 248 | self.dmc.set_enabled(enable_dmc > 0); 249 | } 250 | } 251 | 252 | impl AddressSpace for Apu { 253 | fn peek(&self, ptr: u16) -> u8 { 254 | match map_apu_port(ptr) { 255 | Some(SND_CHN) => self.read_status(), 256 | _ => { 257 | //eprintln!("DEBUG - APU READ - {:x}", ptr); 258 | return 0; 259 | } 260 | } 261 | } 262 | fn poke(&mut self, ptr: u16, v: u8) { 263 | // eprintln!("DEBUG - APU WRITE - {:x} {:x}", ptr, v); 264 | match map_apu_port(ptr) { 265 | Some(SQ1_VOL) => self.pulse1.set_volume(v), 266 | Some(SQ1_SWEEP) => self.pulse1.set_sweep(v), 267 | Some(SQ1_LO) => self.pulse1.set_timer_low(v), 268 | Some(SQ1_HI) => self.pulse1.set_timer_high(v), 269 | Some(SQ2_VOL) => self.pulse2.set_volume(v), 270 | Some(SQ2_SWEEP) => self.pulse2.set_sweep(v), 271 | Some(SQ2_LO) => self.pulse2.set_timer_low(v), 272 | Some(SQ2_HI) => self.pulse2.set_timer_high(v), 273 | Some(TRI_LINEAR) => self.triangle.write_linear_counter(v), 274 | Some(TRI_LO) => self.triangle.write_timer_low(v), 275 | Some(TRI_HI) => self.triangle.write_timer_high(v), 276 | Some(NOISE_VOL) => self.noise.set_volume(v), 277 | Some(NOISE_LO) => self.noise.set_period(v), 278 | Some(NOISE_HI) => self.noise.set_length(v), 279 | Some(DMC_FREQ) => { /* TODO */ } 280 | Some(DMC_RAW) => { /* TODO */ } 281 | Some(DMC_START) => { /* TODO */ } 282 | Some(DMC_LEN) => { /* TODO */ } 283 | Some(SND_CHN) => self.write_status(v), 284 | Some(FRAME_COUNTER) => self.frame_counter.write_control(v), 285 | None => panic!("Unexpected APU port {:x} {:x}", ptr, v), 286 | } 287 | } 288 | } 289 | 290 | struct LengthCounter { 291 | enabled: bool, 292 | halt: bool, 293 | counter: u8, // 5-bit value 294 | } 295 | 296 | impl Clocked for LengthCounter { 297 | fn clock(&mut self) { 298 | // eprintln!("DEBUG - LENGTH COUNTER CLOCKED {} {} {}", self.enabled, self.halt, self.counter); 299 | if self.counter == 0 || self.halt { 300 | } else { 301 | self.counter -= 1; 302 | } 303 | } 304 | } 305 | 306 | const LENGTH_COUNTER_LOOKUP_TABLE: [u8; 32] = [ 307 | /*00-0F*/ 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, /*10-1F*/ 12, 308 | 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, 309 | ]; 310 | 311 | // https://wiki.nesdev.com/w/index.php/APU_Length_Counter 312 | impl LengthCounter { 313 | pub fn new() -> LengthCounter { 314 | LengthCounter { 315 | enabled: true, 316 | halt: false, 317 | counter: 0, 318 | } 319 | } 320 | pub fn is_silenced(&self) -> bool { 321 | return self.counter == 0 || !self.enabled; 322 | } 323 | pub fn is_enabled(&self) -> bool { 324 | self.enabled 325 | } 326 | pub fn set_enabled(&mut self, x: bool) { 327 | if !x { 328 | self.counter = 0; 329 | } 330 | self.enabled = x; 331 | } 332 | pub fn set_halt(&mut self, x: bool) { 333 | self.halt = x; 334 | } 335 | pub fn set_load(&mut self, x: u8) { 336 | if self.enabled { 337 | self.counter = LENGTH_COUNTER_LOOKUP_TABLE[x as usize & 0x1F]; 338 | } 339 | } 340 | } 341 | 342 | struct LinearCounter { 343 | counter: u8, 344 | reload_value: u8, 345 | reload: bool, 346 | enabled: bool, 347 | } 348 | 349 | // https://wiki.nesdev.com/w/index.php/APU_Triangle 350 | impl LinearCounter { 351 | pub fn new() -> LinearCounter { 352 | LinearCounter { 353 | counter: 0, 354 | reload_value: 0, 355 | reload: false, 356 | enabled: false, 357 | } 358 | } 359 | pub fn set_reload_value(&mut self, value: u8) { 360 | self.reload_value = value; 361 | } 362 | pub fn set_reload_flag(&mut self, value: bool) { 363 | self.reload = value; 364 | } 365 | pub fn is_silenced(&self) -> bool { 366 | return self.counter == 0; 367 | } 368 | pub fn set_enabled(&mut self, value: bool) { 369 | self.enabled = value; 370 | } 371 | } 372 | 373 | impl Clocked for LinearCounter { 374 | fn clock(&mut self) { 375 | if self.reload { 376 | self.counter = self.reload_value; 377 | } else { 378 | if self.counter != 0 { 379 | self.counter -= 1; 380 | if self.counter == 0 { 381 | //eprintln!("DEBUG - LINEAR COUNTER SET TO 0 - {}", self.reload_value); 382 | } 383 | } 384 | } 385 | if self.enabled { 386 | self.reload = false; 387 | } 388 | } 389 | } 390 | 391 | struct Triangle { 392 | timer_period: u16, 393 | timer: u16, 394 | sequencer_step: u8, 395 | length_counter: LengthCounter, 396 | linear_counter: LinearCounter, 397 | } 398 | 399 | impl Clocked for Triangle { 400 | fn clock(&mut self) { 401 | if self.timer == 0 { 402 | //eprintln!("DEBUG - TRIANGLE STEP {} {}", self.sequencer_step, self.timer_period); 403 | self.timer = self.timer_period; 404 | if !self.is_silenced() { 405 | self.sequencer_step += 1; 406 | self.sequencer_step &= 0x1F; 407 | } 408 | } else { 409 | self.timer -= 1; 410 | } 411 | } 412 | } 413 | 414 | impl Triangle { 415 | pub fn new() -> Triangle { 416 | Triangle { 417 | timer_period: 0, 418 | timer: 0, 419 | sequencer_step: 0, 420 | length_counter: LengthCounter::new(), 421 | linear_counter: LinearCounter::new(), 422 | } 423 | } 424 | fn is_silenced(&self) -> bool { 425 | self.length_counter.is_silenced() || self.linear_counter.is_silenced() 426 | } 427 | pub fn is_enabled(&self) -> bool { 428 | self.length_counter.is_enabled() 429 | } 430 | pub fn set_enabled(&mut self, x: bool) { 431 | self.length_counter.set_enabled(x); 432 | self.linear_counter.set_enabled(x); 433 | } 434 | pub fn write_enabled(&mut self, x: bool) { 435 | self.length_counter.set_enabled(x); 436 | } 437 | pub fn write_linear_counter(&mut self, x: u8) { 438 | //eprintln!("DEBUG - WRITE LINEAR COUNTER {}", x); 439 | let flag = (x & 0x80) > 0; 440 | self.length_counter.set_halt(flag); 441 | self.linear_counter.set_reload_flag(flag); 442 | self.linear_counter.set_reload_value(x & 0x7f); 443 | } 444 | pub fn write_timer_low(&mut self, x: u8) { 445 | self.timer_period &= 0xFF00; 446 | self.timer_period |= x as u16; 447 | } 448 | pub fn write_timer_high(&mut self, x: u8) { 449 | //eprintln!("DEBUG - WRITE TIMER HIGH {}", x); 450 | self.timer_period &= 0x00FF; 451 | self.timer_period |= (x as u16 & 0x7) << 8; 452 | self.length_counter.set_load(x >> 3); 453 | self.linear_counter.set_reload_flag(true); 454 | } 455 | pub fn sample(&self) -> f64 { 456 | let step = self.sequencer_step; 457 | return if step < 16 { 15 - step } else { step - 16 } as f64; 458 | } 459 | pub fn clock_half_frame(&mut self) { 460 | self.length_counter.clock(); 461 | } 462 | pub fn clock_quarter_frame(&mut self) { 463 | self.linear_counter.clock(); 464 | } 465 | } 466 | 467 | // https://wiki.nesdev.com/w/index.php/APU_Sweep 468 | struct Sweep { 469 | negation: bool, // false=one's complement; true = two's complement 470 | enabled: bool, 471 | divider_period: u8, 472 | divider: u8, 473 | negate: bool, 474 | shift_count: u8, 475 | reload: bool, 476 | period: u16, 477 | timer: u16, // A copy of the timer on the unit 478 | } 479 | 480 | impl Clocked for Sweep { 481 | // Clocked by Frame Counter, half-frame 482 | fn clock(&mut self) { 483 | // eprintln!("DEBUG - SWEEP - silenced:{} nt:{} e:{} dp:{} d:{} n:{} sc:{} r:{} p:{} t:{}", 484 | // self.is_muted(), 485 | // self.negation, 486 | // self.enabled, 487 | // self.divider_period, 488 | // self.divider, 489 | // self.negate, 490 | // self.shift_count, 491 | // self.reload, 492 | // self.period, 493 | // self.timer); 494 | 495 | if self.divider == 0 && self.enabled && !self.is_muted() && self.shift_count != 0 { 496 | self.period = self.target_period(); 497 | } 498 | if self.divider == 0 || self.reload { 499 | self.divider = self.divider_period; 500 | self.reload = false; 501 | } else { 502 | self.divider -= 1; 503 | } 504 | } 505 | } 506 | 507 | impl Sweep { 508 | pub fn new(negation: bool) -> Sweep { 509 | Sweep { 510 | negation: negation, 511 | enabled: false, 512 | divider_period: 0, 513 | divider: 0, 514 | negate: false, 515 | shift_count: 0, 516 | reload: false, 517 | period: 0, 518 | timer: 0, 519 | } 520 | } 521 | pub fn set_period(&mut self, period: u16) { 522 | self.period = period; 523 | } 524 | pub fn is_muted(&self) -> bool { 525 | return self.period < 8 || self.target_period() > 0x7ff; 526 | } 527 | pub fn write_control(&mut self, x: u8) { 528 | self.shift_count = x & 0x7; 529 | self.negate = get_bit(x, 3) > 0; 530 | self.divider_period = (x >> 4) & 0x7; 531 | self.enabled = get_bit(x, 7) > 0; 532 | } 533 | pub fn period(&self) -> u16 { 534 | return self.period; 535 | } 536 | fn target_period(&self) -> u16 { 537 | let time = self.timer; 538 | let mut change = time >> self.shift_count; 539 | if self.negate { 540 | change = self.negate(change); 541 | } 542 | let period = self.period; 543 | return period.wrapping_add(change); 544 | } 545 | 546 | fn negate(&self, value: u16) -> u16 { 547 | if self.negation { 548 | (-(value as i16)) as u16 549 | } else { 550 | (-(value as i16) - 1) as u16 551 | } 552 | } 553 | } 554 | 555 | struct Envelope { 556 | looping: bool, 557 | constant: bool, 558 | period: u8, 559 | divider: u8, 560 | volume: u8, 561 | start: bool, 562 | } 563 | 564 | impl Clocked for Envelope { 565 | // Clocked by quarter-frame 566 | fn clock(&mut self) { 567 | // eprintln!("DEBUG - ENVELOPE - {} {} {} {} {} {} {}", 568 | // self.looping, self.constant, self.period, self.divider, self.volume, self.start, self.sample()); 569 | if self.start { 570 | self.start = false; 571 | self.volume = 15; 572 | self.divider = self.period; 573 | } else if self.divider > 0 { 574 | self.divider -= 1; 575 | } else { 576 | if self.volume > 0 { 577 | self.volume -= 1; 578 | } else if self.looping { 579 | self.volume = 15; 580 | } 581 | self.divider = self.period; 582 | } 583 | } 584 | } 585 | 586 | impl Envelope { 587 | pub fn new() -> Envelope { 588 | Envelope { 589 | looping: false, 590 | constant: false, 591 | period: 0, 592 | divider: 0, 593 | volume: 0, 594 | start: false, 595 | } 596 | } 597 | pub fn set_control(&mut self, v: u8) { 598 | self.period = v & 0xf; 599 | self.volume = v & 0xf; 600 | self.constant = get_bit(v, 4) > 0; 601 | self.looping = get_bit(v, 5) > 0; 602 | } 603 | 604 | pub fn sample(&self) -> f64 { 605 | if self.constant { 606 | self.period as f64 607 | } else { 608 | self.volume as f64 609 | } 610 | } 611 | pub fn reset(&mut self) { 612 | self.start = true; 613 | } 614 | } 615 | 616 | struct Pulse { 617 | pub sweep: Sweep, 618 | timer_period: u16, 619 | timer: u16, 620 | duty_cycle: u8, 621 | sequencer_step: u8, 622 | envelope: Envelope, 623 | length_counter: LengthCounter, 624 | } 625 | 626 | const PULSE_SEQUENCER_DUTY_TABLE: [u8; 4] = [0b01000000, 0b01100000, 0b01111000, 0b10011111]; 627 | 628 | impl Pulse { 629 | pub fn new(negation: bool) -> Pulse { 630 | Pulse { 631 | sweep: Sweep::new(negation), 632 | timer_period: 0, 633 | timer: 0, 634 | duty_cycle: 0, 635 | sequencer_step: 0, 636 | envelope: Envelope::new(), 637 | length_counter: LengthCounter::new(), 638 | } 639 | } 640 | pub fn set_volume(&mut self, v: u8) { 641 | self.envelope.set_control(v); 642 | self.length_counter.set_halt(get_bit(v, 5) > 0); 643 | self.duty_cycle = v >> 6; 644 | } 645 | pub fn set_sweep(&mut self, v: u8) { 646 | self.sweep.write_control(v); 647 | } 648 | pub fn set_timer_low(&mut self, v: u8) { 649 | self.timer_period &= 0xFF00; 650 | self.timer_period |= v as u16; 651 | let period = self.timer_period; 652 | self.sweep.set_period(period); 653 | } 654 | pub fn set_timer_high(&mut self, v: u8) { 655 | //eprintln!("DEBUG - PULSE TIMER HIGH {} {} {}", 0x 656 | self.timer_period &= 0x00FF; 657 | self.timer_period |= ((v & 0x7) as u16) << 8; 658 | self.length_counter.set_load(v >> 3); 659 | self.sequencer_step = 0; 660 | self.envelope.reset(); 661 | let period = self.timer_period; 662 | self.sweep.set_period(period); 663 | } 664 | fn sequencer(&self) -> f64 { 665 | let duty = PULSE_SEQUENCER_DUTY_TABLE[self.duty_cycle as usize]; 666 | let x = (duty >> (7 - self.sequencer_step % 8)) & 1; 667 | return x as f64; 668 | } 669 | fn sample(&self) -> f64 { 670 | // eprintln!("DEBUG - PULSE - {} {} {} {}", 671 | // self.sweep.is_muted(), 672 | // self.length_counter.is_silenced(), 673 | // self.sequencer(), 674 | // self.envelope.sample()); 675 | if !self.is_muted() { 676 | return self.sequencer() * self.envelope.sample(); 677 | } else { 678 | return 0.0; 679 | } 680 | } 681 | fn is_muted(&self) -> bool { 682 | self.sweep.is_muted() || self.length_counter.is_silenced() 683 | } 684 | fn is_enabled(&self) -> bool { 685 | self.length_counter.is_enabled() 686 | } 687 | fn set_enabled(&mut self, v: bool) { 688 | self.length_counter.set_enabled(v); 689 | } 690 | fn clock_quarter_frame(&mut self) { 691 | self.envelope.clock(); 692 | } 693 | fn clock_half_frame(&mut self) { 694 | self.timer_period = self.sweep.period(); 695 | self.sweep.set_period(self.timer_period); 696 | self.sweep.clock(); 697 | self.length_counter.clock(); 698 | } 699 | } 700 | 701 | impl Clocked for Pulse { 702 | // Clocked every APU cycle(2 CPU cycles) 703 | fn clock(&mut self) { 704 | if self.timer == 0 { 705 | self.sequencer_step = self.sequencer_step.wrapping_sub(1) & 0x7; 706 | self.timer = self.timer_period; 707 | } else { 708 | self.timer -= 1; 709 | } 710 | } 711 | } 712 | 713 | const NOISE_PERIOD_LOOKUP_TABLE: [u16; 16] = [ 714 | 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068, 715 | ]; 716 | 717 | struct Noise { 718 | envelope: Envelope, 719 | length_counter: LengthCounter, 720 | mode: bool, 721 | period: u16, 722 | feedback: u16, 723 | timer: u16, 724 | } 725 | 726 | impl Clocked for Noise { 727 | // Clocked every APU cycle(= 2 CPU cycles) 728 | fn clock(&mut self) { 729 | if self.timer == 0 { 730 | self.timer = self.period; 731 | let feedback = self.feedback; 732 | self.feedback = self.next_feedback(feedback); 733 | } else { 734 | self.timer -= 1; 735 | } 736 | } 737 | } 738 | 739 | impl Noise { 740 | pub fn new() -> Noise { 741 | Noise { 742 | envelope: Envelope::new(), 743 | length_counter: LengthCounter::new(), 744 | mode: false, 745 | period: 0, 746 | feedback: 1, 747 | timer: 0, 748 | } 749 | } 750 | fn sample(&self) -> f64 { 751 | if get_bit(self.feedback as u8, 0) > 0 && !self.length_counter.is_silenced() { 752 | self.envelope.sample() 753 | } else { 754 | 0.0 755 | } 756 | } 757 | fn is_enabled(&self) -> bool { 758 | self.length_counter.is_enabled() 759 | } 760 | pub fn set_volume(&mut self, v: u8) { 761 | self.length_counter.set_halt(get_bit(v, 5) > 0); 762 | self.envelope.set_control(v); 763 | } 764 | pub fn set_enabled(&mut self, v: bool) { 765 | self.length_counter.set_enabled(v); 766 | } 767 | pub fn set_period(&mut self, v: u8) { 768 | self.mode = (v & 0x8) > 0; 769 | self.period = NOISE_PERIOD_LOOKUP_TABLE[(v & 0xf) as usize]; 770 | } 771 | pub fn set_length(&mut self, v: u8) { 772 | self.length_counter.set_load(v >> 3); 773 | self.envelope.reset(); 774 | } 775 | fn next_feedback(&self, mut feedback: u16) -> u16 { 776 | let new_bit = (get_bit(feedback as u8, 0) > 0) 777 | ^ (get_bit(feedback as u8, ternary(self.mode, 6, 1)) > 0); 778 | feedback >>= 1; 779 | feedback |= ternary(new_bit, 1 << 14, 0); 780 | return feedback; 781 | } 782 | pub fn clock_quarter_frame(&mut self) {} 783 | pub fn clock_half_frame(&mut self) { 784 | self.length_counter.clock(); 785 | } 786 | } 787 | 788 | struct Dmc {} 789 | 790 | impl Dmc { 791 | pub fn new() -> Dmc { 792 | Dmc {} 793 | } 794 | pub fn sample(&self) -> f64 { 795 | return 0.0; 796 | } 797 | pub fn is_enabled(&self) -> bool { 798 | // TODO 799 | false 800 | } 801 | pub fn set_enabled(&mut self, v: bool) { 802 | // TODO 803 | } 804 | pub fn clock_half_frame(&mut self) { 805 | // TODO 806 | } 807 | pub fn clock_quarter_frame(&mut self) { 808 | // TODO 809 | } 810 | } 811 | 812 | impl Clocked for Dmc { 813 | fn clock(&mut self) { 814 | // TODO 815 | } 816 | } 817 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | pub trait Clocked { 2 | fn clock(&mut self); 3 | } 4 | 5 | pub fn get_bit(x: u8, i: u8) -> u8 { 6 | return (x >> i) & 1; 7 | } 8 | 9 | pub fn run_clocks(x: &mut dyn Clocked, num_clocks: u32) { 10 | for _i in 0..num_clocks { 11 | x.clock(); 12 | } 13 | } 14 | 15 | pub fn ternary(cond: bool, on_true: T, on_false: T) -> T { 16 | if cond { 17 | on_true 18 | } else { 19 | on_false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/headless_protocol.rs: -------------------------------------------------------------------------------- 1 | use crate::serialization::Savable; 2 | use log::{debug, error, trace}; 3 | use std::io::{BufWriter, Read, Write}; 4 | 5 | #[cfg(unix)] 6 | use std::{ 7 | ffi::OsStr, 8 | fs::File, 9 | net::{TcpStream, ToSocketAddrs}, 10 | os::unix::net::UnixStream, 11 | path::Path, 12 | }; 13 | 14 | #[derive(Debug, Clone)] 15 | pub enum Command { 16 | LoadRom(bool, String), 17 | StepFrame, 18 | RenderFrame(RenderStyle), 19 | SetInputs(u8, u8), 20 | SaveState(String), 21 | LoadState(String), 22 | GetInfo, 23 | Step, 24 | SaveTas, 25 | Peek(u16), 26 | Poke(u16, u8), 27 | SetRendering(bool), 28 | } 29 | 30 | impl Default for Command { 31 | fn default() -> Self { 32 | StepFrame 33 | } 34 | } 35 | 36 | use Command::*; 37 | 38 | #[repr(u8)] 39 | #[derive(Debug, Clone, Copy)] 40 | pub enum RenderStyle { 41 | Plain = 0, 42 | Rgb = 1, 43 | } 44 | 45 | impl Savable for Option { 46 | fn save(&self, fh: &mut dyn Write) { 47 | trace!("Sending command: {:?}", self); 48 | let mut fh = &mut BufWriter::new(fh); 49 | match self.clone().expect("Empty command received") { 50 | LoadRom(record_tas, filename) => { 51 | write_byte(fh, 1); 52 | write_byte(fh, if record_tas { 1 } else { 0 }); 53 | write_value::(fh, filename); 54 | } 55 | StepFrame => { 56 | write_byte(fh, 2); 57 | } 58 | RenderFrame(render_style) => { 59 | write_byte(fh, 3); 60 | write_byte(fh, render_style as u8); 61 | } 62 | SetInputs(controller_id, inputs) => { 63 | write_byte(fh, 4); 64 | write_byte(fh, controller_id); 65 | write_byte(fh, inputs); 66 | } 67 | SaveState(filename) => { 68 | write_byte(fh, 5); 69 | write_value::(fh, filename); 70 | } 71 | LoadState(filename) => { 72 | write_byte(fh, 6); 73 | write_value::(fh, filename); 74 | } 75 | GetInfo => { 76 | write_byte(fh, 7); 77 | } 78 | Step => { 79 | write_byte(fh, 8); 80 | } 81 | SaveTas => { 82 | write_byte(fh, 9); 83 | } 84 | Peek(address) => { 85 | write_byte(fh, 10); 86 | write_value(fh, address); 87 | } 88 | Poke(address, value) => { 89 | write_byte(fh, 11); 90 | write_value(fh, address); 91 | write_byte(fh, value); 92 | } 93 | SetRendering(is_rendering) => { 94 | write_byte(fh, 12); 95 | write_value(fh, is_rendering); 96 | } 97 | }; 98 | fh.flush().expect("Unable to flush buffer"); 99 | } 100 | fn load(&mut self, fh: &mut dyn Read) { 101 | let command = read_byte(fh); 102 | *self = Some(match command { 103 | 1 => { 104 | let record_tas = read_value::(fh); 105 | let filename = read_value::(fh); 106 | LoadRom(record_tas, filename) 107 | } 108 | 2 => StepFrame, 109 | 3 => { 110 | let style_byte = read_value::(fh); 111 | let render_style = unsafe { std::mem::transmute::(style_byte) }; 112 | RenderFrame(render_style) 113 | } 114 | 4 => SetInputs(read_value::(fh), read_value::(fh)), 115 | 5 => SaveState(read_value::(fh)), 116 | 6 => LoadState(read_value::(fh)), 117 | 7 => GetInfo, 118 | 8 => Step, 119 | 9 => SaveTas, 120 | 10 => Peek(read_value::(fh)), 121 | 11 => Poke(read_value::(fh), read_value::(fh)), 122 | 12 => SetRendering(read_value::(fh)), 123 | x => { 124 | error!("Received command {}. Probably a sync error", x); 125 | *self = None; 126 | return; 127 | } 128 | }) 129 | } 130 | } 131 | impl Savable for Command { 132 | fn save(&self, fh: &mut dyn Write) { 133 | Some(self.clone()).save(fh) 134 | } 135 | fn load(&mut self, fh: &mut dyn Read) { 136 | *self = read_value::>(fh).expect("Unable to read command"); 137 | } 138 | } 139 | 140 | fn write_byte(w: &mut dyn Write, byte: u8) { 141 | byte.save(w); 142 | } 143 | fn write_value(w: &mut dyn Write, t: T) { 144 | t.save(w); 145 | } 146 | fn read_byte(r: &mut dyn Read) -> u8 { 147 | let mut x: u8 = 0; 148 | x.load(r); 149 | x 150 | } 151 | fn read_bytes(r: &mut dyn Read, num_bytes: usize) -> Vec { 152 | let mut bytes = vec![0; num_bytes]; 153 | r.read_exact(&mut bytes) 154 | .expect(&*format!("Unable to read {} bytes", num_bytes)); 155 | bytes 156 | } 157 | 158 | fn read_value(r: &mut dyn Read) -> T { 159 | let mut t = T::default(); 160 | t.load(r); 161 | t 162 | } 163 | 164 | pub struct SocketHeadlessClient(Box); 165 | impl SocketHeadlessClient { 166 | pub fn new(t: T) -> Self { 167 | SocketHeadlessClient(Box::new(t)) 168 | } 169 | pub fn load_rom(&mut self, save_tas: bool, filename: String) { 170 | LoadRom(save_tas, filename).save(&mut self.0); 171 | self.sync(); 172 | } 173 | pub fn step_frame(&mut self) { 174 | StepFrame.save(&mut self.0); 175 | self.sync(); 176 | } 177 | pub fn render_frame(&mut self, render_style: RenderStyle) -> Vec { 178 | RenderFrame(render_style).save(&mut self.0); 179 | let bytes = match render_style as u8 { 180 | 0 => read_bytes(&mut self.0, crate::ppu::UNRENDER_SIZE), 181 | 1 => read_bytes(&mut self.0, crate::ppu::RENDER_SIZE), 182 | x => panic!("Unknown render style {:?}", x), 183 | }; 184 | self.sync(); 185 | bytes 186 | } 187 | pub fn set_inputs(&mut self, controller_id: u8, inputs: u8) { 188 | SetInputs(controller_id, inputs).save(&mut self.0); 189 | self.sync(); 190 | } 191 | pub fn save_state(&mut self, filename: String) { 192 | SaveState(filename).save(&mut self.0); 193 | self.sync(); 194 | } 195 | pub fn load_state(&mut self, filename: String) { 196 | LoadState(filename).save(&mut self.0); 197 | self.sync(); 198 | } 199 | pub fn get_info(&mut self) { 200 | GetInfo.save(&mut self.0); 201 | self.sync(); 202 | } 203 | pub fn step(&mut self) { 204 | Step.save(&mut self.0); 205 | self.sync(); 206 | } 207 | pub fn save_tas(&mut self) { 208 | SaveTas.save(&mut self.0); 209 | self.sync(); 210 | } 211 | pub fn peek(&mut self, address: u16) -> u8 { 212 | Peek(address).save(&mut self.0); 213 | let x = read_value::(&mut self.0); 214 | self.sync(); 215 | trace!("Peek({})={}", address, x); 216 | x 217 | } 218 | pub fn poke(&mut self, address: u16, value: u8) { 219 | Poke(address, value).save(&mut self.0); 220 | self.sync(); 221 | } 222 | pub fn set_rendering(&mut self, is_rendering: bool) { 223 | SetRendering(is_rendering).save(&mut self.0); 224 | self.sync(); 225 | } 226 | fn sync(&mut self) { 227 | let byte = read_value::(&mut self.0); 228 | trace!("sync={}", byte); 229 | } 230 | } 231 | 232 | #[allow(dead_code)] 233 | pub fn connect_tcp(host: &str) -> SocketHeadlessClient { 234 | let stream = TcpStream::connect(host).expect(&*format!( 235 | "Unable to connect to {:?}", 236 | host.to_socket_addrs() 237 | )); 238 | SocketHeadlessClient::new(stream) 239 | } 240 | 241 | pub fn connect_socket>(p: P) -> SocketHeadlessClient { 242 | let stream = UnixStream::connect(p.as_ref()).expect(&*format!( 243 | "Unable to connect to domain socket at {:?}", 244 | p.as_ref() 245 | )); 246 | SocketHeadlessClient::new(stream) 247 | } 248 | 249 | pub trait ReadWrite: Read + Write + Send + Sync {} 250 | pub struct StdInOut(pub File, pub File); 251 | impl Read for StdInOut { 252 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 253 | self.0.read(buf) 254 | } 255 | } 256 | impl Write for StdInOut { 257 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 258 | self.1.write(buf) 259 | } 260 | fn flush(&mut self) -> std::io::Result<()> { 261 | self.1.flush() 262 | } 263 | } 264 | 265 | impl ReadWrite for StdInOut {} 266 | impl ReadWrite for TcpStream {} 267 | impl ReadWrite for UnixStream {} 268 | -------------------------------------------------------------------------------- /src/joystick.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use core::cell::Cell; 4 | use std::io::Read; 5 | use std::io::Write; 6 | 7 | use crate::common::get_bit; 8 | use crate::mapper::AddressSpace; 9 | use crate::serialization::Savable; 10 | 11 | #[derive(Debug)] 12 | pub struct Joystick { 13 | buttons: u8, 14 | buttons_register: Cell, 15 | strobe_active: bool, 16 | } 17 | 18 | impl Savable for Joystick { 19 | fn save(&self, fh: &mut dyn Write) { 20 | self.buttons_register.get().save(fh); 21 | self.strobe_active.save(fh); 22 | } 23 | fn load(&mut self, fh: &mut dyn Read) { 24 | let mut buttons_register = 0; 25 | buttons_register.load(fh); 26 | self.buttons_register.set(buttons_register); 27 | self.strobe_active.load(fh); 28 | } 29 | } 30 | 31 | impl Joystick { 32 | pub fn new() -> Joystick { 33 | Joystick { 34 | buttons: 0, 35 | buttons_register: Cell::new(0), 36 | strobe_active: false, 37 | } 38 | } 39 | pub fn set_buttons(&mut self, button_mask: u8) { 40 | self.buttons = button_mask; 41 | } 42 | fn get_next_button(&self) -> u8 { 43 | let byte = self.buttons_register.get() & 1; 44 | let new_buttons_register = self.buttons_register.get() >> 1; 45 | self.buttons_register.set(new_buttons_register); 46 | return byte; 47 | } 48 | fn reset_from_strobe(&self) { 49 | if self.strobe_active { 50 | let buttons = self.buttons; 51 | self.buttons_register.set(buttons); 52 | } 53 | } 54 | } 55 | 56 | impl AddressSpace for Joystick { 57 | fn peek(&self, _ptr: u16) -> u8 { 58 | self.reset_from_strobe(); 59 | return self.get_next_button(); 60 | } 61 | fn poke(&mut self, _ptr: u16, v: u8) { 62 | self.strobe_active = get_bit(v, 0) > 0; 63 | self.reset_from_strobe(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod apu; 2 | pub mod c6502; 3 | pub mod common; 4 | pub mod headless_protocol; 5 | pub mod joystick; 6 | pub mod mapper; 7 | pub mod nes; 8 | pub mod ppu; 9 | pub mod serialization; 10 | 11 | extern crate sdl2; 12 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_mut)] 3 | 4 | mod apu; 5 | mod c6502; 6 | mod common; 7 | mod joystick; 8 | mod mapper; 9 | mod nes; 10 | mod ppu; 11 | mod serialization; 12 | 13 | extern crate sdl2; 14 | 15 | use sdl2::audio::{AudioCallback, AudioQueue, AudioSpecDesired}; 16 | use sdl2::controller::GameController; 17 | use sdl2::event::Event; 18 | use sdl2::keyboard::Keycode; 19 | use sdl2::pixels::Color; 20 | use sdl2::pixels::PixelFormatEnum; 21 | use sdl2::render::Canvas; 22 | use sdl2::render::Texture; 23 | use sdl2::render::TextureAccess; 24 | use sdl2::render::TextureCreator; 25 | use sdl2::video::Window; 26 | use sdl2::video::WindowContext; 27 | use sdl2::AudioSubsystem; 28 | use sdl2::EventPump; 29 | use sdl2::GameControllerSubsystem; 30 | use sdl2::VideoSubsystem; 31 | use std::fs::File; 32 | use std::io::ErrorKind; 33 | use std::os::raw::c_int; 34 | use std::ptr::NonNull; 35 | use std::time::{Duration, Instant}; 36 | 37 | use core::ptr::null_mut; 38 | 39 | use crate::apu::Apu; 40 | use crate::joystick::Joystick; 41 | use crate::mapper::AddressSpace; 42 | use crate::nes::Nes; 43 | use crate::nes::Tas; 44 | use crate::nes::{load_ines, read_ines}; 45 | use crate::ppu::*; 46 | use crate::serialization::Savable; 47 | 48 | extern "C" { 49 | fn emscripten_set_main_loop(m: extern "C" fn(), fps: c_int, infinite: c_int); 50 | } 51 | 52 | // https://wiki.nesdev.com/w/index.php/Cycle_reference_chart 53 | const CLOCKS_PER_FRAME: u32 = 29780; 54 | const APU_FREQUENCY: i32 = 240; 55 | const AUDIO_FREQUENCY: usize = 44100; 56 | const SAMPLES_PER_FRAME: usize = 1024; 57 | const SCALE: usize = 4; 58 | const RECORDING: bool = true; 59 | const ROM_BEGIN_SAVESTATE: &'static str = "initial.state"; 60 | const DEFAULT_SAVESTATE: &'static str = "save.state"; 61 | const DEFAULT_RECORDING: &'static str = "save.video"; 62 | 63 | struct GlobalState { 64 | sdl_context: *mut sdl2::Sdl, 65 | joystick1: *mut Joystick, 66 | joystick2: *mut Joystick, 67 | video_subsystem: *mut VideoSubsystem, 68 | audio_subsystem: *mut AudioSubsystem, 69 | controller_subsystem: *mut GameControllerSubsystem, 70 | canvas: *mut Canvas, 71 | event_pump: *mut EventPump, 72 | nes: *mut Nes, 73 | audio_device: *mut AudioQueue, 74 | texture: *mut Texture<'static>, 75 | sdl_controller1: *mut sdl2::controller::GameController, 76 | sdl_controller2: *mut sdl2::controller::GameController, 77 | tas: *mut Tas, 78 | tas_frame: usize, 79 | turbo_mode: bool, 80 | } 81 | 82 | static mut GLOBAL_STATE: Option = None; 83 | static mut TEXTURE_CREATOR: Option> = None; 84 | fn main() { 85 | let mut sdl_context = Box::new(sdl2::init().unwrap()); 86 | let mut video_subsystem = Box::new(sdl_context.video().unwrap()); 87 | let mut controller_subsystem = Box::new(sdl_context.game_controller().unwrap()); 88 | let mut audio_subsystem = Box::new(sdl_context.audio().unwrap()); 89 | let mut joystick1 = Box::new(Joystick::new()); 90 | let mut joystick2 = Box::new(Joystick::new()); 91 | let joystick1_ptr = (&mut *joystick1) as *mut Joystick; 92 | let joystick2_ptr = (&mut *joystick2) as *mut Joystick; 93 | let window = video_subsystem 94 | .window( 95 | "NES emulator", 96 | (RENDER_WIDTH * SCALE) as u32, 97 | (RENDER_HEIGHT * SCALE) as u32, 98 | ) 99 | .position_centered() 100 | .build() 101 | .unwrap(); 102 | 103 | let mut canvas = Box::new(window.into_canvas().build().unwrap()); 104 | let texture_creator = canvas.texture_creator(); 105 | let mut texture = { 106 | let mut tex = texture_creator 107 | .create_texture( 108 | PixelFormatEnum::RGB24, 109 | TextureAccess::Streaming, 110 | RENDER_WIDTH as u32, 111 | RENDER_HEIGHT as u32, 112 | ) 113 | .unwrap(); 114 | unsafe { Box::new(std::mem::transmute(tex)) } 115 | }; 116 | let mut nes = Box::new(create_nes(joystick1, joystick2)); 117 | match File::open(ROM_BEGIN_SAVESTATE) { 118 | Ok(mut fh) => nes.load(&mut fh), 119 | Err(ref e) if e.kind() == ErrorKind::NotFound => { 120 | if let Ok(mut fh) = File::create(ROM_BEGIN_SAVESTATE) { 121 | nes.save(&mut fh); 122 | } 123 | } 124 | Err(e) => eprintln!("DEBUG - Unhandled file error - {:?}", e), 125 | } 126 | let desired_spec = AudioSpecDesired { 127 | freq: Some(AUDIO_FREQUENCY as i32), 128 | channels: Some(1), 129 | //samples: Some(8820), 130 | samples: Some(SAMPLES_PER_FRAME as u16), 131 | }; 132 | let mut tas = Box::new(Tas::new()); 133 | // let audio_device = audio_subsystem.open_playback(None, &desired_spec, |spec| { 134 | // ApuSampler { 135 | // apu: NonNull::from(&mut nes.apu), 136 | // volume: 1.0, 137 | // resample_step:0, 138 | // sample: 0.0, 139 | // last_sample: 0.0, 140 | // last_time: Instant::now(), 141 | // } 142 | // }).unwrap(); 143 | canvas.set_draw_color(Color::RGB(0, 255, 255)); 144 | canvas.clear(); 145 | canvas.present(); 146 | 147 | let mut audio_device = Box::new(audio_subsystem.open_queue(None, &desired_spec).unwrap()); 148 | audio_device.resume(); 149 | let mut event_pump = Box::new(sdl_context.event_pump().unwrap()); 150 | unsafe { 151 | GLOBAL_STATE = Some(GlobalState { 152 | sdl_context: &mut *sdl_context, 153 | joystick1: joystick1_ptr, 154 | joystick2: joystick2_ptr, 155 | video_subsystem: &mut *video_subsystem, 156 | audio_subsystem: &mut *audio_subsystem, 157 | controller_subsystem: &mut *controller_subsystem, 158 | canvas: &mut *canvas, 159 | event_pump: &mut *event_pump, 160 | nes: &mut *nes, 161 | audio_device: &mut *audio_device, 162 | texture: &mut *texture, 163 | sdl_controller1: null_mut(), 164 | sdl_controller2: null_mut(), 165 | tas: &mut *tas, 166 | tas_frame: 0, 167 | turbo_mode: false, 168 | }); 169 | } 170 | 171 | if cfg!(target_os = "emscripten") { 172 | // void emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop); 173 | unsafe { emscripten_set_main_loop(main_loop, 60, 1) }; 174 | loop {} 175 | } else { 176 | let mut every_second = Instant::now(); 177 | let mut num_frames = 0; 178 | loop { 179 | let now = Instant::now(); 180 | main_loop(); 181 | let after = Instant::now(); 182 | num_frames += 1; 183 | if after - every_second >= Duration::from_millis(1000) { 184 | eprintln!( 185 | "DEBUG - FPS - {} {:?} {:?}", 186 | num_frames, 187 | after - every_second, 188 | after - now 189 | ); 190 | num_frames = 0; 191 | every_second = after; 192 | } 193 | //SDL_Delay(time_to_next_frame()); 194 | } 195 | } 196 | //std::unreachable!(); 197 | } 198 | 199 | extern "C" fn main_loop() { 200 | let now = Instant::now(); 201 | let st = unsafe { GLOBAL_STATE.as_mut().unwrap() }; 202 | // let mut sdl_context = unsafe { &mut *st.sdl_context }; 203 | let joystick1: &mut Joystick = unsafe { &mut *st.joystick1 }; 204 | let joystick2: &mut Joystick = unsafe { &mut *st.joystick2 }; 205 | let mut nes = unsafe { &mut *st.nes }; 206 | let mut event_pump = unsafe { &mut *st.event_pump }; 207 | let mut audio_device = unsafe { &mut *st.audio_device }; 208 | let mut canvas = unsafe { &mut *st.canvas }; 209 | let mut texture = unsafe { &mut *st.texture }; 210 | let mut controller_subsystem = unsafe { &mut *st.controller_subsystem }; 211 | let mut tas = unsafe { &mut *st.tas }; 212 | // eprintln!("DEBUG - POINTERS - ({:p}, {:?}) ({:p}, {:?}) {:p} {:p} {:p} {:p} {:p}", 213 | // joystick1, 214 | // joystick1, 215 | // joystick2, 216 | // joystick2, 217 | // nes, 218 | // event_pump, 219 | // audio_device, 220 | // canvas, 221 | // texture, 222 | // ); 223 | 224 | for event in event_pump.poll_iter() { 225 | match event { 226 | // Exit game 227 | Event::Quit { .. } 228 | | Event::KeyDown { 229 | keycode: Some(Keycode::Escape), 230 | .. 231 | } => { 232 | std::process::exit(0); 233 | } 234 | // Break CPU debugger 235 | Event::KeyDown { 236 | keycode: Some(Keycode::Pause), 237 | .. 238 | } => { 239 | nes.break_debugger(); 240 | } 241 | // Save state 242 | Event::KeyDown { 243 | keycode: Some(Keycode::F5), 244 | .. 245 | } => { 246 | let mut file = File::create(DEFAULT_SAVESTATE).unwrap(); 247 | nes.save(&mut file); 248 | let mut tas_file = File::create(DEFAULT_RECORDING).unwrap(); 249 | tas.save(&mut tas_file); 250 | } 251 | // Load State 252 | Event::KeyDown { 253 | keycode: Some(Keycode::F6), 254 | .. 255 | } => { 256 | let mut file = File::open(DEFAULT_SAVESTATE).unwrap(); 257 | nes.load(&mut file); 258 | let mut tas_file = File::open(DEFAULT_RECORDING).unwrap(); 259 | tas.load(&mut tas_file); 260 | } 261 | // Play recording from initial state 262 | Event::KeyDown { 263 | keycode: Some(Keycode::F7), 264 | .. 265 | } => { 266 | let mut tas_fh = File::open(DEFAULT_RECORDING).unwrap(); 267 | tas.load(&mut tas_fh); 268 | let mut ss_fh = File::open(ROM_BEGIN_SAVESTATE).unwrap(); 269 | nes.load(&mut ss_fh); 270 | st.tas_frame = 0; 271 | nes.cpu.poke(0x075a, 3); 272 | } 273 | // Begin recording at current point 274 | Event::KeyDown { 275 | keycode: Some(Keycode::F8), 276 | .. 277 | } => { 278 | let mut ss_fh = File::create(ROM_BEGIN_SAVESTATE).unwrap(); 279 | nes.save(&mut ss_fh); 280 | *tas = Tas::new(); 281 | st.tas_frame = 0; 282 | } 283 | // Attach controller 284 | Event::ControllerDeviceAdded { which: id, .. } => { 285 | eprintln!("DEBUG - CONTROLLER ADDED - {}", id); 286 | match id { 287 | 0 => { 288 | st.sdl_controller1 = 289 | Box::leak(Box::new(controller_subsystem.open(id).unwrap())) 290 | } 291 | 1 => { 292 | st.sdl_controller2 = 293 | Box::leak(Box::new(controller_subsystem.open(id).unwrap())) 294 | } 295 | _ => eprintln!("DEBUG - UNEXPECTED CONTROLLER ID {}", id), 296 | } 297 | } 298 | // Toggle Turbo Mode 299 | Event::KeyDown { 300 | keycode: Some(Keycode::Tab), 301 | .. 302 | } => { 303 | st.turbo_mode = !st.turbo_mode; 304 | } 305 | _ => {} 306 | } 307 | } 308 | 309 | let frame = st.tas_frame; 310 | let j1_bmask = tas.get_inputs(frame).unwrap_or_else(|| { 311 | let buttons = get_button_mask(st.sdl_controller1); 312 | if RECORDING { 313 | tas.record_frame(frame, buttons); 314 | } 315 | buttons 316 | }); 317 | st.tas_frame += 1; 318 | let j2_bmask = get_button_mask(st.sdl_controller2); 319 | joystick1.set_buttons(j1_bmask); 320 | joystick2.set_buttons(j2_bmask); 321 | nes.run_frame(); 322 | present_frame(&mut canvas, &mut texture, &nes.ppu.render()); 323 | enqueue_frame_audio(&audio_device, &mut nes.apu.samples); 324 | 325 | canvas.present(); 326 | 327 | let after = Instant::now(); 328 | let target_millis = Duration::from_millis(1000 / 60); 329 | let sleep_millis = target_millis.checked_sub(after - now); 330 | match sleep_millis { 331 | None => {} // Took too long last frame 332 | Some(sleep_millis) => { 333 | //eprintln!("DEBUG - SLEEP - {:?}", sleep_millis); 334 | if !st.turbo_mode { 335 | ::std::thread::sleep(sleep_millis); 336 | } 337 | } 338 | } 339 | } 340 | 341 | struct ApuSampler { 342 | apu: NonNull>, 343 | volume: f32, 344 | resample_step: u32, 345 | sample: f32, 346 | last_sample: f32, 347 | last_time: Instant, 348 | } 349 | 350 | unsafe impl std::marker::Send for ApuSampler {} 351 | 352 | const SAMPLES_PER_SECOND: u32 = 1789920; 353 | const CLOCK_FREQUENCY: u32 = 30000; 354 | const SAMPLES_PER_CLOCK: u32 = SAMPLES_PER_SECOND / CLOCK_FREQUENCY; 355 | 356 | impl ApuSampler { 357 | fn resample(last_sample: &mut f32, samples: &[f32], resamples: &mut [f32]) { 358 | let num_samples = samples.len(); 359 | let num_resamples = resamples.len(); 360 | 361 | let ratio = num_samples as f32 / num_resamples as f32; 362 | let mut t = 0.0f32; 363 | let mut sample_idx = 0; 364 | for i in resamples.iter_mut() { 365 | *i = match samples.get(sample_idx) { 366 | None => *last_sample, 367 | Some(sample) => { 368 | *last_sample = *sample; 369 | t * *sample + (1.0 - t) * *last_sample 370 | } 371 | }; 372 | if t >= 1.0 { 373 | sample_idx += t as usize; 374 | t %= 1.0; 375 | } 376 | t += ratio; 377 | } 378 | } 379 | } 380 | 381 | impl AudioCallback for ApuSampler { 382 | type Channel = f32; 383 | 384 | fn callback(&mut self, out: &mut [f32]) { 385 | let apu: &mut Apu = unsafe { self.apu.as_mut() }; 386 | let new_time = Instant::now(); 387 | // eprintln!("SAMPLES {} {} {:?}", out.len(), apu.samples.len(), (new_time - self.last_time)); 388 | let samples_slice = apu.samples.as_slice(); 389 | // eprintln!("DEBUG - SAMPLES - {} {} {:?}", samples_slice.len(), out.len(), new_time - self.last_time); 390 | ApuSampler::resample(&mut self.last_sample, samples_slice, out); 391 | apu.samples.clear(); 392 | 393 | self.last_time = new_time; 394 | } 395 | } 396 | 397 | fn create_nes(joystick1: Box, joystick2: Box) -> Nes { 398 | //let filename = "roms/donkey_kong.nes"; 399 | let filename = "roms/mario.nes"; 400 | match read_ines(filename.to_string()) { 401 | e @ Err { .. } => panic!("Unable to load ROM {} {:?}", filename, e), 402 | Ok(rom) => load_ines(rom, joystick1, joystick2), 403 | } 404 | } 405 | 406 | fn present_frame(canvas: &mut Canvas, texture: &mut Texture, ppu_pixels: &[u8]) { 407 | texture.update(None, ppu_pixels, RENDER_WIDTH * 3).unwrap(); 408 | canvas.clear(); 409 | canvas.copy(&texture, None, None).unwrap(); 410 | canvas.present(); 411 | } 412 | 413 | fn enqueue_frame_audio(audio: &AudioQueue, samples: &mut Vec) { 414 | let xs = samples.as_slice(); 415 | let bytes_per_sample: u32 = 8; 416 | if audio.size() as usize <= 2 * (bytes_per_sample as usize) * SAMPLES_PER_FRAME { 417 | audio.queue(&xs); 418 | } else { 419 | eprintln!( 420 | "DEBUG - SAMPLE OVERFLOW - {}", 421 | audio.size() / bytes_per_sample 422 | ); 423 | } 424 | samples.clear(); 425 | } 426 | 427 | struct SquareWave { 428 | phase_inc: f32, 429 | phase: f32, 430 | volume: f32, 431 | } 432 | 433 | impl AudioCallback for SquareWave { 434 | type Channel = f32; 435 | 436 | fn callback(&mut self, out: &mut [f32]) { 437 | // Generate a square wave 438 | for x in out.iter_mut() { 439 | *x = if self.phase <= 0.5 { 440 | self.volume 441 | } else { 442 | -self.volume 443 | }; 444 | self.phase = (self.phase + self.phase_inc) % 1.0; 445 | } 446 | } 447 | } 448 | 449 | fn get_button_bit(controller: *mut GameController, button_id: u8) -> u8 { 450 | // Button order: A,B, Select,Start,Up,Down,Left,Right 451 | let button = match button_id { 452 | 0 => sdl2::controller::Button::A, 453 | 1 => sdl2::controller::Button::B, 454 | 2 => sdl2::controller::Button::Back, 455 | 3 => sdl2::controller::Button::Start, 456 | 4 => sdl2::controller::Button::DPadUp, 457 | 5 => sdl2::controller::Button::DPadDown, 458 | 6 => sdl2::controller::Button::DPadLeft, 459 | 7 => sdl2::controller::Button::DPadRight, 460 | _ => panic!("Unknown button"), 461 | }; 462 | unsafe { 463 | match controller.as_ref() { 464 | None => { 465 | // eprintln!("DEBUG - ZERO"); 466 | 0 467 | } 468 | Some(controller) => { 469 | //eprintln!("DEBUG - NOT ZERO"); 470 | controller.button(button) as u8 471 | } 472 | } 473 | } 474 | } 475 | 476 | fn get_button_mask(controller: *mut GameController) -> u8 { 477 | let mut button_mask = 0; 478 | button_mask |= get_button_bit(controller, 0) << 0; 479 | button_mask |= get_button_bit(controller, 1) << 1; 480 | button_mask |= get_button_bit(controller, 2) << 2; 481 | button_mask |= get_button_bit(controller, 3) << 3; 482 | button_mask |= get_button_bit(controller, 4) << 4; 483 | button_mask |= get_button_bit(controller, 5) << 5; 484 | button_mask |= get_button_bit(controller, 6) << 6; 485 | button_mask |= get_button_bit(controller, 7) << 7; 486 | return button_mask; 487 | } 488 | -------------------------------------------------------------------------------- /src/mapper.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(dead_code)] 3 | 4 | use std::borrow::BorrowMut; 5 | use std::cell::UnsafeCell; 6 | use std::io::Read; 7 | use std::io::Write; 8 | 9 | use crate::common::ternary; 10 | use crate::serialization::Savable; 11 | 12 | pub trait AddressSpace: Savable { 13 | // Minimal definition 14 | fn peek(&self, ptr: u16) -> u8; 15 | fn poke(&mut self, ptr: u16, v: u8); 16 | 17 | // Helper methods 18 | fn peek16(&self, ptr: u16) -> u16 { 19 | let low = self.peek(ptr); 20 | let high = self.peek(ptr.wrapping_add(1)); 21 | let result = (low as u16) + ((high as u16) << 8); 22 | //eprintln!("DEBUG - PEEK16 - {:x} {:x} {:x} {:x} {:x} {:x}", high, low, result, high as u16, low as u16, (high as u16) << 8); 23 | return result; 24 | } 25 | fn peek_offset(&self, ptr: u16, os: i16) -> u8 { 26 | return self.peek(ptr.wrapping_add(os as u16)); 27 | } 28 | fn peek_offset16(&self, ptr: u16, os: i16) -> u16 { 29 | return self.peek16(ptr.wrapping_add(os as u16)); 30 | } 31 | fn poke_offset(&mut self, ptr: u16, os: i16, v: u8) { 32 | self.poke(ptr.wrapping_add(os as u16), v); 33 | } 34 | } 35 | 36 | pub struct Ram { 37 | bs: Vec, 38 | } 39 | 40 | impl Ram { 41 | pub fn new(size: usize) -> Ram { 42 | Ram { bs: vec![0; size] } 43 | } 44 | } 45 | 46 | impl Savable for Ram { 47 | fn save(&self, fh: &mut dyn Write) { 48 | self.bs.save(fh); 49 | } 50 | fn load(&mut self, fh: &mut dyn Read) { 51 | self.bs.load(fh); 52 | } 53 | } 54 | 55 | impl AddressSpace for Ram { 56 | fn peek(&self, ptr: u16) -> u8 { 57 | return self.bs[ptr as usize]; 58 | } 59 | fn poke(&mut self, ptr: u16, v: u8) { 60 | self.bs[ptr as usize] = v; 61 | } 62 | } 63 | 64 | impl AddressSpace for *mut T { 65 | fn peek(&self, ptr: u16) -> u8 { 66 | let t: &T = unsafe { &**self as &T }; 67 | return t.peek(ptr); 68 | } 69 | fn poke(&mut self, ptr: u16, x: u8) { 70 | let t: &mut T = unsafe { &mut **self as &mut T }; 71 | t.poke(ptr, x); 72 | } 73 | } 74 | 75 | impl Savable for *mut T { 76 | // The pointer should still be valid, since this Trait updates objects in-place. 77 | fn save(&self, _: &mut dyn Write) {} 78 | fn load(&mut self, _: &mut dyn Read) {} 79 | } 80 | 81 | pub struct Rom { 82 | bs: Vec, 83 | } 84 | 85 | impl Rom { 86 | pub fn new(bs: Vec) -> Rom { 87 | Rom { bs: bs } 88 | } 89 | } 90 | 91 | impl Savable for Rom { 92 | fn save(&self, fh: &mut dyn Write) { 93 | self.bs.save(fh); 94 | } 95 | fn load(&mut self, fh: &mut dyn Read) { 96 | self.bs.load(fh); 97 | } 98 | } 99 | 100 | impl AddressSpace for Rom { 101 | fn peek(&self, ptr: u16) -> u8 { 102 | let value = self.bs[ptr as usize]; 103 | //eprintln!("DEBUG - ROM-ACCESS - ({:?}, {:?})", ptr, value); 104 | return value; 105 | } 106 | fn poke(&mut self, ptr: u16, value: u8) { 107 | panic!( 108 | "Rom - Attempted to write read-only memory at {} - value={}", 109 | ptr, value 110 | ); 111 | } 112 | } 113 | 114 | pub struct MirroredAddressSpace { 115 | base: Box, 116 | base_begin: u16, 117 | base_end: u16, 118 | extended_begin: u16, 119 | extended_end: u16, 120 | } 121 | 122 | impl MirroredAddressSpace { 123 | // If a memory range has been mirrored to another, map a pointer to the "base range" or fail if it lies outside. 124 | fn map_address(&self, ptr: u16) -> u16 { 125 | if ptr < self.extended_begin || ptr > self.extended_end { 126 | panic!( 127 | "map_address: Out of mapped range ({:?} not in range [{:?}, {:?}]", 128 | ptr, self.extended_begin, self.extended_end 129 | ); 130 | } 131 | let width = self.base_end - self.base_begin + 1; 132 | let relptr = ptr - self.extended_begin; 133 | if relptr >= width { 134 | return (ptr - self.extended_begin) % width + self.base_begin; 135 | } else { 136 | return relptr + self.base_begin; 137 | } 138 | } 139 | } 140 | 141 | impl Savable for MirroredAddressSpace { 142 | fn save(&self, fh: &mut dyn Write) { 143 | self.base.save(fh); 144 | self.base_begin.save(fh); 145 | self.base_end.save(fh); 146 | self.extended_begin.save(fh); 147 | self.extended_end.save(fh); 148 | } 149 | fn load(&mut self, fh: &mut dyn Read) { 150 | self.base.load(fh); 151 | self.base_begin.load(fh); 152 | self.base_end.load(fh); 153 | self.extended_begin.load(fh); 154 | self.extended_end.load(fh); 155 | } 156 | } 157 | 158 | impl AddressSpace for MirroredAddressSpace { 159 | fn peek(&self, ptr: u16) -> u8 { 160 | return self.base.peek(self.map_address(ptr)); 161 | } 162 | fn poke(&mut self, ptr: u16, value: u8) { 163 | let space_ptr = self.map_address(ptr); 164 | self.base.poke(space_ptr, value); 165 | } 166 | } 167 | 168 | pub struct NullAddressSpace {} 169 | impl NullAddressSpace { 170 | pub fn new() -> NullAddressSpace { 171 | NullAddressSpace {} 172 | } 173 | } 174 | 175 | impl Savable for NullAddressSpace { 176 | fn save(&self, _fh: &mut dyn Write) {} 177 | fn load(&mut self, _fh: &mut dyn Read) {} 178 | } 179 | 180 | impl AddressSpace for NullAddressSpace { 181 | fn peek(&self, ptr: u16) -> u8 { 182 | eprintln!("DEBUG - READ FROM NULL MAP {:x}", ptr); 183 | return 0; 184 | } 185 | fn poke(&mut self, ptr: u16, value: u8) { 186 | eprintln!("DEBUG - WRITE TO NULL MAP {:x} {:x}", ptr, value); 187 | } 188 | } 189 | 190 | type UsesOriginalAddress = bool; 191 | struct Mapping(u16, u16, Box, UsesOriginalAddress); 192 | impl Mapping { 193 | fn map_ptr(&self, ptr: u16) -> Option { 194 | let Mapping(range_begin, range_end, _, use_original_address) = *self; 195 | if ptr >= range_begin && ptr <= range_end { 196 | let space_ptr = ternary(use_original_address, ptr, ptr - range_begin); 197 | return Some(space_ptr); 198 | } 199 | return None; 200 | } 201 | } 202 | 203 | impl Savable for Mapping { 204 | fn save(&self, fh: &mut dyn Write) { 205 | self.0.save(fh); 206 | self.1.save(fh); 207 | self.2.save(fh); 208 | self.3.save(fh); 209 | } 210 | fn load(&mut self, fh: &mut dyn Read) { 211 | self.0.load(fh); 212 | self.1.load(fh); 213 | self.2.load(fh); 214 | self.3.load(fh); 215 | } 216 | } 217 | 218 | pub struct Mapper { 219 | mappings: Vec, 220 | } 221 | 222 | impl Savable for Mapper { 223 | fn save(&self, fh: &mut dyn Write) { 224 | self.mappings.as_slice().save(fh); 225 | } 226 | fn load(&mut self, fh: &mut dyn Read) { 227 | self.mappings.as_mut_slice().load(fh); 228 | } 229 | } 230 | 231 | impl Mapper { 232 | pub fn new() -> Mapper { 233 | Mapper { 234 | mappings: Vec::new(), 235 | } 236 | } 237 | fn print_mappings(&self) { 238 | for Mapping(range_begin, range_end, _, use_original_address) in self.mappings.iter() { 239 | eprintln!( 240 | "[{:x}, {:x}] - {:?}", 241 | range_begin, range_end, use_original_address 242 | ); 243 | } 244 | } 245 | 246 | fn lookup_address_space(&self, ptr: u16) -> (usize, u16) { 247 | for space_idx in 0..self.mappings.len() { 248 | if let Some(space_ptr) = self.mappings[space_idx].map_ptr(ptr) { 249 | return (space_idx, space_ptr); 250 | } 251 | } 252 | eprintln!("lookup_address_space - Unmapped pointer {:?}.", ptr); 253 | eprintln!("Mappings:"); 254 | self.print_mappings(); 255 | panic!(); 256 | } 257 | pub fn map_address_space( 258 | &mut self, 259 | begin: u16, 260 | end: u16, 261 | space: Box, 262 | use_original: bool, 263 | ) { 264 | self.mappings.push(Mapping(begin, end, space, use_original)); 265 | } 266 | 267 | pub fn map_ram(&mut self, begin: u16, end: u16) { 268 | let size = end - begin; 269 | let space: Ram = Ram { 270 | bs: vec![0; size as usize], 271 | }; 272 | self.map_address_space(begin, end, Box::new(space), false); 273 | } 274 | pub fn map_rom(&mut self, begin: u16, end: u16, bytes: &[u8]) { 275 | let space: Rom = Rom { bs: bytes.to_vec() }; 276 | self.map_address_space(begin, end, Box::new(space), false); 277 | } 278 | pub fn map_null(&mut self, begin: u16, end: u16) { 279 | let space: NullAddressSpace = NullAddressSpace {}; 280 | self.map_address_space(begin, end, Box::new(space), true); 281 | } 282 | pub fn map_mirrored( 283 | &mut self, 284 | begin: u16, 285 | end: u16, 286 | extended_begin: u16, 287 | extended_end: u16, 288 | space: Box, 289 | use_original: bool, 290 | ) { 291 | let base_begin = if use_original { begin } else { 0 }; 292 | let base_end = base_begin + (end - begin); 293 | let space: MirroredAddressSpace = MirroredAddressSpace { 294 | base: space, 295 | base_begin: base_begin, 296 | base_end, 297 | extended_begin, 298 | extended_end, 299 | }; 300 | self.map_address_space(extended_begin, extended_end, Box::new(space), true); 301 | } 302 | } 303 | 304 | impl AddressSpace for Mapper { 305 | fn peek(&self, ptr: u16) -> u8 { 306 | let (space_idx, space_ptr) = self.lookup_address_space(ptr); 307 | let Mapping(_, _, space, _) = &self.mappings[space_idx]; 308 | //eprintln!("DEBUG - MEMORY-ACCESS - ({:?}, {:?})", space_idx, space_ptr); 309 | let value = space.peek(space_ptr); 310 | //eprintln!("DEBUG - MEMORY-ACCESS-RESULT - ({:x})", value); 311 | return value; 312 | } 313 | fn poke(&mut self, ptr: u16, value: u8) { 314 | let (space_idx, space_ptr) = self.lookup_address_space(ptr); 315 | let &mut Mapping(_, _, ref mut space, _) = self.mappings.get_mut(space_idx).unwrap(); 316 | space.poke(space_ptr, value); 317 | } 318 | } 319 | 320 | #[derive(Debug, PartialEq, Copy, Clone)] 321 | pub enum AccessType { 322 | Read, 323 | Write, 324 | } 325 | 326 | pub type LoggedAddressSpaceRecord = (usize, AccessType, u16, u8); 327 | 328 | pub struct LoggedAddressSpace { 329 | pub space: Box, 330 | pub log: UnsafeCell>, 331 | } 332 | 333 | impl Savable for LoggedAddressSpace { 334 | fn save(&self, _fh: &mut dyn Write) { 335 | panic!("save() unimplemented"); 336 | } 337 | fn load(&mut self, _fh: &mut dyn Read) { 338 | panic!("load() unimplemented"); 339 | } 340 | } 341 | 342 | impl LoggedAddressSpace { 343 | pub fn new(space: Box) -> LoggedAddressSpace { 344 | LoggedAddressSpace { 345 | space: space, 346 | log: UnsafeCell::new(vec![]), 347 | } 348 | } 349 | pub fn get_log(&self) -> &mut Vec { 350 | return unsafe { &mut *self.log.get() }; 351 | } 352 | pub fn copy_log(&self) -> Vec { 353 | return self.get_log().clone(); 354 | } 355 | } 356 | 357 | impl AddressSpace for LoggedAddressSpace { 358 | fn peek(&self, ptr: u16) -> u8 { 359 | let v = self.space.peek(ptr); 360 | let log = self.get_log(); 361 | let record = (log.len(), AccessType::Read, ptr, v); 362 | log.push(record); 363 | return v; 364 | } 365 | fn poke(&mut self, ptr: u16, v: u8) { 366 | let log = self.get_log(); 367 | let record = (log.len(), AccessType::Write, ptr, v); 368 | log.push(record); 369 | self.space.poke(ptr, v); 370 | } 371 | } 372 | 373 | mod tests { 374 | use super::AddressSpace; 375 | use super::Mapper; 376 | use super::Rom; 377 | 378 | #[test] 379 | fn test_rom() { 380 | let mut mapper = Mapper::new(); 381 | let bs = [1, 2, 3]; 382 | mapper.map_rom(0x1000, 0x1002, &bs); 383 | assert_eq!(mapper.peek(0x1001), 2); 384 | } 385 | #[test] 386 | fn test_mirrored() { 387 | let mut mapper = Mapper::new(); 388 | let bs = vec![1, 2, 3]; 389 | let rom: Rom = Rom::new(bs); 390 | mapper.map_mirrored(0x1000, 0x1002, 0x5000, 0x6000, Box::new(rom), false); 391 | assert_eq!(mapper.peek(0x5002), 3); 392 | assert_eq!(mapper.peek(0x5005), 3); 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/nes.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(dead_code)] 4 | 5 | use crate::apu::Apu; 6 | use crate::apu::ApuPort::*; 7 | use crate::c6502::C6502; 8 | use crate::common::*; 9 | use crate::joystick::Joystick; 10 | use crate::mapper::*; 11 | use crate::mapper::{Mapper, Ram}; 12 | use crate::ppu::CpuPpuInterconnect; 13 | use crate::ppu::PaletteControl; 14 | use crate::ppu::Ppu; 15 | use crate::ppu::PpuPort; 16 | use crate::ppu::PpuPort::*; 17 | use crate::serialization::Savable; 18 | 19 | use core::mem::transmute_copy; 20 | use std::fmt; 21 | use std::fs::File; 22 | use std::io; 23 | use std::io::Read; 24 | use std::io::Write; 25 | use std::ops::DerefMut; 26 | 27 | pub struct Nes { 28 | pub cpu: Box, 29 | pub apu: Box, 30 | pub ppu: Box, 31 | } 32 | 33 | impl Nes { 34 | fn new(cpu_mapper: Box) -> Nes { 35 | return Nes { 36 | cpu: Box::new(C6502::new(cpu_mapper)), 37 | apu: Box::new(Apu::new()), 38 | ppu: Box::new(Ppu::new()), 39 | }; 40 | } 41 | } 42 | struct HiddenBytes(Vec); 43 | 44 | impl fmt::Debug for HiddenBytes { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | let HiddenBytes(vec) = self; 47 | write!(f, "[<>]", vec.len()) 48 | } 49 | } 50 | 51 | #[derive(Debug)] 52 | pub struct Ines { 53 | num_prg_chunks: u8, 54 | num_chr_chunks: u8, 55 | mapper: u8, 56 | mirroring: bool, 57 | has_battery_backed_ram: bool, 58 | has_trainer: bool, 59 | has_four_screen_vram: bool, 60 | is_vs_unisystem: bool, 61 | is_playchoice10: bool, 62 | prg_rom: HiddenBytes, 63 | chr_rom: HiddenBytes, 64 | } 65 | 66 | pub fn read_ines(filename: String) -> Result { 67 | // https://wiki.nesdev.com/w/index.php/INES 68 | let mut file = File::open(filename)?; 69 | // Header 70 | let mut header: [u8; 16] = [0; 16]; 71 | file.read_exact(&mut header)?; 72 | assert!(header[0] == 0x4e); 73 | assert!(header[1] == 0x45); 74 | assert!(header[2] == 0x53); 75 | assert!(header[3] == 0x1a); 76 | let num_prg_chunks = header[4]; 77 | let num_chr_chunks = header[5]; 78 | let mut prg_rom: Vec = Vec::new(); 79 | for _i in 0..num_prg_chunks { 80 | let mut bf: Vec = vec![0; 16384]; 81 | file.read_exact(&mut bf)?; 82 | prg_rom.append(&mut bf); 83 | } 84 | let mut chr_rom: Vec = Vec::new(); 85 | for _i in 0..num_chr_chunks { 86 | let mut bf: Vec = vec![0; 8192]; 87 | file.read_exact(&mut bf)?; 88 | chr_rom.append(&mut bf); 89 | } 90 | let ret = Ines { 91 | num_prg_chunks: num_prg_chunks, 92 | num_chr_chunks: header[5], 93 | mirroring: get_bit(header[6], 0) > 0, 94 | has_battery_backed_ram: get_bit(header[6], 1) > 0, 95 | has_trainer: get_bit(header[6], 2) > 0, 96 | has_four_screen_vram: get_bit(header[6], 3) > 0, 97 | is_playchoice10: false, // TODO 98 | is_vs_unisystem: false, // TODO 99 | mapper: (header[6] >> 4) + ((header[7] >> 4) << 4), 100 | prg_rom: HiddenBytes(prg_rom), 101 | chr_rom: HiddenBytes(chr_rom), 102 | }; 103 | // eprintln!("DEBUG - INES LOADED - {:?}", ret); 104 | return Ok(ret); 105 | } 106 | 107 | pub fn load_ines( 108 | rom: Ines, 109 | joystick1: Box, 110 | joystick2: Box, 111 | ) -> Nes { 112 | if rom.mapper != 0 { 113 | panic!("Only mapper 0 supported. Found {}", rom.mapper); 114 | } 115 | let cpu_mapper: Mapper = { 116 | let HiddenBytes(bytes) = rom.prg_rom; 117 | let cartridge = Rom::new(bytes); 118 | let mut mapper = Mapper::new(); 119 | match rom.num_prg_chunks { 120 | 1 => mapper.map_mirrored(0x0000, 0x3FFF, 0x8000, 0xFFFF, Box::new(cartridge), true), 121 | 2 => mapper.map_mirrored(0x0000, 0x7FFF, 0x8000, 0xFFFF, Box::new(cartridge), true), 122 | _ => panic!("load_ines - Unexpected number of PRG chunks"), 123 | }; 124 | mapper 125 | }; 126 | let ppu_mapper: Rom = { 127 | let HiddenBytes(bytes) = rom.chr_rom; 128 | let cartridge_ppu = Rom::new(bytes); 129 | cartridge_ppu 130 | }; 131 | let mut ret = Nes::new(Box::new(NullAddressSpace::new())); 132 | ret.map_nes_cpu(joystick1, joystick2, Box::new(cpu_mapper)); 133 | ret.map_nes_ppu(Box::new(ppu_mapper)); 134 | return ret; 135 | } 136 | 137 | impl Nes { 138 | pub fn run_frame(&mut self) { 139 | run_clocks(self, 29780); 140 | } 141 | pub fn run_frame_headless(&mut self) { 142 | let cpu_clocks_per_scanline = 114; // 113.667 143 | // 0 and 241 are the pre-render and post-render scanlines 144 | for _i in 0..241 { 145 | run_clocks(&mut *self.cpu, cpu_clocks_per_scanline); 146 | // TODO: Signal on is_scanline_irq 147 | } 148 | // TODO: Vblank should only be triggered if rendering is enabled. 149 | self.cpu.nmi(); 150 | for _i in 242..261 { 151 | run_clocks(&mut *self.cpu, cpu_clocks_per_scanline); 152 | } 153 | } 154 | pub fn break_debugger(&mut self) { 155 | self.cpu.break_debugger(); 156 | } 157 | pub fn current_frame(&self) -> u32 { 158 | return self.ppu.current_frame(); 159 | } 160 | fn map_nes_cpu( 161 | &mut self, 162 | joystick1: Box, 163 | _joystick2: Box, 164 | cartridge: Box, 165 | ) { 166 | let mut mapper: Mapper = Mapper::new(); 167 | let cpu_ram: Ram = Ram::new(0x800); 168 | let cpu_ppu: CpuPpuInterconnect = 169 | CpuPpuInterconnect::new(self.ppu.deref_mut(), self.cpu.deref_mut()); 170 | let apu = self.apu.deref_mut() as *mut Apu; 171 | // https://wiki.nesdev.com/w/index.php/CPU_memory_map 172 | // NOTE: These are checked in-order, so put frequently-used components first 173 | mapper.map_address_space(0x4020, 0xFFFF, cartridge, true); 174 | mapper.map_mirrored(0x0000, 0x07ff, 0x0000, 0x1fff, Box::new(cpu_ram), false); 175 | mapper.map_mirrored(0x2000, 0x2007, 0x2000, 0x3fff, Box::new(cpu_ppu), true); 176 | mapper.map_address_space(0x4000, 0x4013, Box::new(apu), true); 177 | mapper.map_address_space(0x4015, 0x4015, Box::new(apu), true); 178 | mapper.map_address_space(0x4017, 0x4017, Box::new(apu), true); // TODO - 0x4017 is also mapped to joystick2 179 | mapper.map_address_space(0x4017, 0x4017, _joystick2, false); // TODO -- Transfers ownership of joystick2 so it isn't deallocated 180 | mapper.map_address_space(0x4016, 0x4016, joystick1, false); 181 | mapper.map_address_space(0x4014, 0x4014, Box::new(cpu_ppu), true); 182 | mapper.map_null(0x4018, 0x401F); // APU test mode 183 | 184 | self.cpu.mapper = Box::new(mapper); 185 | self.cpu.initialize(); 186 | } 187 | fn map_nes_ppu(&mut self, cartridge_ppu: Box) { 188 | // https://wiki.nesdev.com/w/index.php/PPU_memory_map 189 | let mut mapper: Mapper = Mapper::new(); 190 | let ppu_ram: Ram = Ram::new(0x800); 191 | let palette_ram: PaletteControl = PaletteControl::new(); 192 | // Pattern table 193 | mapper.map_address_space(0x0000, 0x1FFF, cartridge_ppu, true); 194 | // Nametables 195 | mapper.map_mirrored(0x2000, 0x27FF, 0x2000, 0x3EFF, Box::new(ppu_ram), false); 196 | mapper.map_mirrored(0x3f00, 0x3f1f, 0x3f00, 0x3fff, Box::new(palette_ram), true); 197 | 198 | self.ppu.mapper = Box::new(mapper); 199 | } 200 | } 201 | 202 | impl Clocked for Nes { 203 | fn clock(&mut self) { 204 | self.cpu.clock(); 205 | for _i in 1..3 { 206 | self.ppu.clock(); 207 | } 208 | if self.ppu.is_vblank_nmi { 209 | //eprintln!("DEBUG - VBLANK-NMI DETECTED"); 210 | self.cpu.nmi(); 211 | self.ppu.is_vblank_nmi = false; 212 | } else if self.ppu.is_scanline_irq { 213 | self.cpu.irq(); 214 | self.ppu.is_scanline_irq = false; 215 | } 216 | self.apu.clock(); 217 | } 218 | } 219 | 220 | use crate::serialization::file_position; 221 | 222 | impl Savable for Nes { 223 | fn save(&self, fh: &mut dyn Write) { 224 | self.cpu.save(fh); 225 | self.apu.save(fh); 226 | self.ppu.save(fh); 227 | 0xF00Fu32.save(fh); 228 | } 229 | fn load(&mut self, fh: &mut dyn Read) { 230 | self.cpu.load(fh); 231 | self.apu.load(fh); 232 | self.ppu.load(fh); 233 | let mut check = 0u32; 234 | check.load(fh); 235 | assert_eq!(check, 0xf00f); 236 | } 237 | } 238 | 239 | pub struct Tas { 240 | inputs: Vec, 241 | } 242 | 243 | impl Tas { 244 | pub fn new() -> Tas { 245 | Tas { inputs: Vec::new() } 246 | } 247 | pub fn get_inputs(&self, frame: usize) -> Option { 248 | if frame >= self.inputs.len() { 249 | None 250 | } else { 251 | Some(self.inputs[frame]) 252 | } 253 | } 254 | pub fn record_frame(&mut self, frame: usize, buttons: u8) { 255 | self.inputs.truncate(frame); 256 | self.inputs.push(buttons); 257 | } 258 | } 259 | 260 | impl Savable for Tas { 261 | fn save(&self, fh: &mut dyn Write) { 262 | self.inputs.save(fh); 263 | } 264 | fn load(&mut self, fh: &mut dyn Read) { 265 | self.inputs.load(fh); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/ppu.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(dead_code)] 3 | #![allow(unused_variables)] 4 | 5 | use crate::c6502::C6502; 6 | use crate::common::*; 7 | use crate::mapper::{AddressSpace, Mapper}; 8 | use crate::serialization::Savable; 9 | 10 | use std::io::Read; 11 | use std::io::Write; 12 | use std::mem::transmute; 13 | 14 | //use std::vec; 15 | 16 | pub type SystemColor = u8; // [0,64) 17 | type PatternColor = u8; // [0,4) 18 | type PatternId = u8; 19 | type PaletteId = u8; // [0, 4) 20 | type TileIndex = u8; 21 | type Attribute = u8; // An entry in the attribute table 22 | type SpriteId = u8; 23 | 24 | const COLOR_TRANSPARENT: u8 = 0; 25 | 26 | const ADDRESS_NAMETABLE0: u16 = 0x2000; 27 | const ADDRESS_ATTRIBUTE_TABLE0: u16 = 0x23C0; 28 | const NAMETABLE_SIZE: u16 = 0x0400; 29 | const ADDRESS_UNIVERSAL_BACKGROUND_COLOR: u16 = 0x3f00; 30 | const ADDRESS_BACKGROUND_PALETTE0: u16 = 0x3f00; 31 | const SPRITE_HEIGHT: u8 = 8; 32 | const SPRITE_WIDTH: u8 = 8; 33 | const SCANLINE_PRERENDER: u16 = 261; 34 | const SCANLINE_RENDER: u16 = 0; 35 | const SCANLINE_POSTRENDER: u16 = 240; 36 | const SCANLINE_VBLANK: u16 = 241; 37 | const GLOBAL_BACKGROUND_COLOR: PaletteColor = PaletteColor { color: 0 }; 38 | 39 | pub const RENDER_WIDTH: usize = 256; 40 | pub const RENDER_HEIGHT: usize = 240; 41 | pub const UNRENDER_SIZE: usize = RENDER_WIDTH * RENDER_HEIGHT; 42 | pub const RENDER_SIZE: usize = UNRENDER_SIZE * 3; 43 | 44 | pub struct Ppu { 45 | pub display: [u8; UNRENDER_SIZE], 46 | pub oam: [u8; 256], 47 | pub mapper: Box, 48 | pub is_vblank_nmi: bool, 49 | pub is_scanline_irq: bool, 50 | 51 | registers: PpuRegisters, 52 | sprite_pattern_table: bool, // Is the sprite pattern table the 'right' one? 53 | background_pattern_table: bool, // Is the background pattern table the right one? 54 | sprite_overflow: bool, 55 | sprite0_hit: bool, 56 | sprite_size: bool, // false=8x8, true=8x16 57 | frame_parity: bool, // Toggled every frame 58 | open_bus: u8, // Open bus shared by all PPU registers 59 | ppu_master_select: bool, 60 | generate_vblank_nmi: bool, 61 | ppudata_buffer: u8, 62 | 63 | oam_ptr: u8, 64 | 65 | clocks_until_nmi: u8, 66 | nmi_occurred: bool, 67 | frame: u32, 68 | scanline: u16, 69 | cycle: u16, 70 | sprite_count: u8, 71 | tile_pattern_low_shift: u16, 72 | tile_pattern_high_shift: u16, 73 | tile_palette_shift: u16, 74 | // Used to store fetched tile attributes until they're stored in cycles 0 mod 8. 75 | tile_nametable: u8, 76 | tile_attribute: u8, 77 | tile_pattern_low: u8, 78 | tile_pattern_high: u8, 79 | sprite_patterns: [u16; 8], 80 | sprite_palettes: [u8; 8], 81 | sprite_xs: [u8; 8], 82 | sprite_priorities: [bool; 8], 83 | sprite_indices: [u8; 8], 84 | } 85 | 86 | impl Savable for Ppu { 87 | fn save(&self, fh: &mut dyn Write) { 88 | self.display.save(fh); 89 | self.oam.save(fh); 90 | self.mapper.save(fh); 91 | self.is_vblank_nmi.save(fh); 92 | self.is_scanline_irq.save(fh); 93 | self.registers.save(fh); 94 | self.sprite_pattern_table.save(fh); 95 | self.background_pattern_table.save(fh); 96 | self.sprite_overflow.save(fh); 97 | self.sprite0_hit.save(fh); 98 | self.sprite_size.save(fh); 99 | self.frame_parity.save(fh); 100 | self.open_bus.save(fh); 101 | self.ppu_master_select.save(fh); 102 | self.generate_vblank_nmi.save(fh); 103 | self.ppudata_buffer.save(fh); 104 | self.oam_ptr.save(fh); 105 | self.clocks_until_nmi.save(fh); 106 | self.nmi_occurred.save(fh); 107 | self.frame.save(fh); 108 | self.scanline.save(fh); 109 | self.cycle.save(fh); 110 | self.sprite_count.save(fh); 111 | self.tile_pattern_low_shift.save(fh); 112 | self.tile_pattern_high_shift.save(fh); 113 | self.tile_palette_shift.save(fh); 114 | self.tile_nametable.save(fh); 115 | self.tile_attribute.save(fh); 116 | self.tile_pattern_low.save(fh); 117 | self.tile_pattern_high.save(fh); 118 | self.sprite_patterns.save(fh); 119 | self.sprite_palettes.save(fh); 120 | self.sprite_xs.save(fh); 121 | self.sprite_priorities.save(fh); 122 | self.sprite_indices.save(fh); 123 | } 124 | fn load(&mut self, fh: &mut dyn Read) { 125 | self.display.load(fh); 126 | self.oam.load(fh); 127 | self.mapper.load(fh); 128 | self.is_vblank_nmi.load(fh); 129 | self.is_scanline_irq.load(fh); 130 | self.registers.load(fh); 131 | self.sprite_pattern_table.load(fh); 132 | self.background_pattern_table.load(fh); 133 | self.sprite_overflow.load(fh); 134 | self.sprite0_hit.load(fh); 135 | self.sprite_size.load(fh); 136 | self.frame_parity.load(fh); 137 | self.open_bus.load(fh); 138 | self.ppu_master_select.load(fh); 139 | self.generate_vblank_nmi.load(fh); 140 | self.ppudata_buffer.load(fh); 141 | self.oam_ptr.load(fh); 142 | self.clocks_until_nmi.load(fh); 143 | self.nmi_occurred.load(fh); 144 | self.frame.load(fh); 145 | self.scanline.load(fh); 146 | self.cycle.load(fh); 147 | self.sprite_count.load(fh); 148 | self.tile_pattern_low_shift.load(fh); 149 | self.tile_pattern_high_shift.load(fh); 150 | self.tile_palette_shift.load(fh); 151 | self.tile_nametable.load(fh); 152 | self.tile_attribute.load(fh); 153 | self.tile_pattern_low.load(fh); 154 | self.tile_pattern_high.load(fh); 155 | self.sprite_patterns.load(fh); 156 | self.sprite_palettes.load(fh); 157 | self.sprite_xs.load(fh); 158 | self.sprite_priorities.load(fh); 159 | self.sprite_indices.load(fh); 160 | } 161 | } 162 | 163 | struct PpuRegisters { 164 | // Register is only 15 bits in hardware. 165 | /* 166 | yyy NN YYYYY XXXXX 167 | y = fine y scroll 168 | N = nametable select 169 | Y = coarse Y scroll 170 | X = coarse X scroll 171 | */ 172 | v: u16, 173 | t: u16, // t is the address of the top-left onscreen tile 174 | x: u8, // Fine x scroll 175 | w: bool, // First-or-second write toggle(PPUSCROLL and PPUADDR) 176 | 177 | vram_increment: bool, // false=increment by 1; true = increment by 32 178 | is_greyscale: bool, 179 | background_enabled: bool, 180 | sprites_enabled: bool, 181 | emphasize_red: bool, 182 | emphasize_green: bool, 183 | emphasize_blue: bool, 184 | show_leftmost_background: bool, 185 | show_leftmost_sprite: bool, 186 | } 187 | 188 | impl Savable for PpuRegisters { 189 | fn save(&self, fh: &mut dyn Write) { 190 | self.v.save(fh); 191 | self.t.save(fh); 192 | self.x.save(fh); 193 | self.w.save(fh); 194 | 195 | self.vram_increment.save(fh); 196 | self.is_greyscale.save(fh); 197 | self.background_enabled.save(fh); 198 | self.sprites_enabled.save(fh); 199 | self.emphasize_red.save(fh); 200 | self.emphasize_green.save(fh); 201 | self.emphasize_blue.save(fh); 202 | self.show_leftmost_background.save(fh); 203 | self.show_leftmost_sprite.save(fh); 204 | } 205 | fn load(&mut self, fh: &mut dyn Read) { 206 | self.v.load(fh); 207 | self.t.load(fh); 208 | self.x.load(fh); 209 | self.w.load(fh); 210 | 211 | self.vram_increment.load(fh); 212 | self.is_greyscale.load(fh); 213 | self.background_enabled.load(fh); 214 | self.sprites_enabled.load(fh); 215 | self.emphasize_red.load(fh); 216 | self.emphasize_green.load(fh); 217 | self.emphasize_blue.load(fh); 218 | self.show_leftmost_background.load(fh); 219 | self.show_leftmost_sprite.load(fh); 220 | } 221 | } 222 | 223 | impl PpuRegisters { 224 | // https://wiki.nesdev.com/w/index.php/PPU_scrolling 225 | pub fn new() -> PpuRegisters { 226 | PpuRegisters { 227 | v: 0, 228 | t: 0, 229 | x: 0, 230 | w: false, 231 | 232 | vram_increment: false, 233 | is_greyscale: false, 234 | background_enabled: false, 235 | sprites_enabled: false, 236 | emphasize_red: false, 237 | emphasize_green: false, 238 | emphasize_blue: false, 239 | show_leftmost_background: false, 240 | show_leftmost_sprite: false, 241 | } 242 | } 243 | pub fn copy_y(&mut self) { 244 | let mask: u16 = 0b111101111100000; 245 | self.v &= !mask; 246 | self.v |= self.t & mask; 247 | } 248 | pub fn copy_x(&mut self) { 249 | let mask: u16 = 0b10000011111; 250 | self.v &= !mask; 251 | self.v |= self.t & mask; 252 | } 253 | fn reset_horizontal_position(&mut self) { 254 | let mask: u16 = 0b10000011111; 255 | self.v &= !mask; 256 | self.v |= self.t & mask; 257 | } 258 | 259 | pub fn increment_x(&mut self) { 260 | let v = self.v; 261 | if (self.v & 0x001F) == 31 { 262 | self.v &= !0x001F; 263 | self.v ^= 0x0400; 264 | } else { 265 | self.v = (self.v + 1) & 0x7FFF; 266 | } 267 | //eprintln!("DEBUG - COARSE-X {:x} {:x}", v, self.v); 268 | } 269 | pub fn scanline(&self) -> u16 { 270 | let coarse_y_mask: u16 = 0b1111100000; 271 | let fine_y_mask: u16 = 0b111000000000000; 272 | let y = (self.v & coarse_y_mask) >> 2 | (self.v & fine_y_mask) >> 12; 273 | return y; 274 | } 275 | pub fn increment_y(&mut self) { 276 | let mut v = self.v; 277 | if (v & 0x7000) != 0x7000 { 278 | self.v += 0x1000; 279 | } else { 280 | v &= !0x7000; 281 | let mut y: u16 = (v & 0x03e0) >> 5; 282 | if y == 29 { 283 | y = 0; 284 | v ^= 0x0800; 285 | } else if y == 31 { 286 | y = 0; 287 | } else { 288 | y += 1; 289 | } 290 | self.v = (v & !0x03E0) | (y << 5); 291 | } 292 | } 293 | pub fn write_control(&mut self, px: u8) { 294 | let x = px as u16; 295 | let mask: u16 = 0b110000000000; 296 | self.t &= !mask; 297 | self.t |= (x & 0x3) << 10; 298 | self.vram_increment = get_bit(px, 2) > 0; 299 | // eprintln!("DEBUG - PPU CONTROL WRITE {:x} {}", px, self.vram_increment); 300 | } 301 | 302 | pub fn write_mask(&mut self, v: u8) { 303 | // eprintln!("DEBUG - PPU MASK WRITE {:x}", v); 304 | self.is_greyscale = get_bit(v, 0) > 0; 305 | self.show_leftmost_background = get_bit(v, 1) > 0; 306 | self.show_leftmost_sprite = get_bit(v, 2) > 0; 307 | self.background_enabled = get_bit(v, 3) > 0; 308 | self.sprites_enabled = get_bit(v, 4) > 0; 309 | self.emphasize_red = get_bit(v, 5) > 0; 310 | self.emphasize_green = get_bit(v, 6) > 0; 311 | self.emphasize_blue = get_bit(v, 7) > 0; 312 | } 313 | 314 | pub fn read_status(&mut self) { 315 | self.w = false; 316 | } 317 | pub fn write_scroll(&mut self, px: u8) { 318 | let x = px as u16; 319 | if !self.w { 320 | // First write 321 | self.t &= !0b11111; 322 | self.t |= x >> 3; 323 | self.x = px & 0x7; 324 | } else { 325 | self.t &= !0b111001111100000; 326 | self.t |= ((x >> 3) & 0x1F) << 5; // FED 327 | self.t |= (x & 0x3) << 12; // CBA 328 | } 329 | self.w = !self.w; 330 | } 331 | 332 | pub fn write_address(&mut self, x: u8) { 333 | if !self.w { 334 | self.t &= 0x00FF; 335 | self.t |= ((x & 0b00111111) as u16) << 8; 336 | } else { 337 | self.t &= 0xFF00; 338 | self.t |= x as u16; 339 | self.v = self.t; 340 | } 341 | self.w = !self.w; 342 | // eprintln!("DEBUG - PPU WRITE ADDRESS - {:x} {}", self.v, self.w); 343 | } 344 | fn is_rendering(&self) -> bool { 345 | let scanline = self.scanline(); 346 | return self.is_rendering_enabled() 347 | && ((scanline == SCANLINE_PRERENDER) || scanline < SCANLINE_POSTRENDER); 348 | } 349 | pub fn vram_ptr(&self) -> u16 { 350 | return self.v; 351 | } 352 | pub fn is_sprites_enabled(&self) -> bool { 353 | return self.sprites_enabled; 354 | } 355 | pub fn is_background_enabled(&self) -> bool { 356 | return self.background_enabled; 357 | } 358 | pub fn is_rendering_enabled(&self) -> bool { 359 | return self.sprites_enabled || self.background_enabled; 360 | } 361 | 362 | fn advance_vram_ptr(&mut self) { 363 | // TODO - VRAM ptr is supposed to increment in a weird way during rendering. 364 | if self.is_rendering() && false { 365 | self.increment_x(); 366 | self.increment_y(); 367 | } else { 368 | let increment = ternary(self.vram_increment, 32, 1); 369 | // eprintln!("DEBUG - VRAM INCREMENT - {} {}", self.vram_increment, increment); 370 | self.v = self.v.wrapping_add(increment) & 0x3FFF; 371 | } 372 | } 373 | 374 | pub fn fine_x(&self) -> u8 { 375 | return self.x; 376 | } 377 | 378 | pub fn fine_y(&self) -> u8 { 379 | return ((self.v >> 12) & 0x7) as u8; 380 | } 381 | 382 | pub fn tile_x(&self) -> u8 { 383 | return (self.v & 0b11111) as u8; 384 | } 385 | 386 | pub fn tile_y(&self) -> u8 { 387 | return ((self.v & 0b1111100000) >> 5) as u8; 388 | } 389 | 390 | pub fn tile_address(&self) -> u16 { 391 | return ADDRESS_NAMETABLE0 | (self.v & 0x0FFF); 392 | } 393 | pub fn attribute_address(&self) -> u16 { 394 | let v = self.v; 395 | return ADDRESS_ATTRIBUTE_TABLE0 | (v & 0x0c00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07); 396 | } 397 | } 398 | 399 | #[derive(Copy, Clone, Debug)] 400 | pub enum PpuPort { 401 | PPUCTRL, 402 | PPUMASK, 403 | PPUSTATUS, 404 | OAMADDR, 405 | OAMDATA, 406 | PPUSCROLL, 407 | PPUADDR, 408 | PPUDATA, 409 | OAMDMA, 410 | } 411 | 412 | impl AddressSpace for Ppu { 413 | fn peek(&self, ptr: u16) -> u8 { 414 | return self.mapper.peek(ptr); 415 | } 416 | fn poke(&mut self, ptr: u16, v: u8) { 417 | self.mapper.poke(ptr, v); 418 | } 419 | } 420 | 421 | #[derive(Copy, Clone)] 422 | struct Sprite { 423 | index: u8, 424 | x: u8, 425 | y: u8, 426 | tile_index: u8, 427 | palette: u8, 428 | is_front: bool, // priority 429 | flip_horizontal: bool, 430 | flip_vertical: bool, 431 | } 432 | 433 | /* a=ABCDEFGH, b=12345678, combine_bitplanes(a,b) = A1B2C3D4E5F6G7H8 */ 434 | fn combine_bitplanes(mut a: u8, mut b: u8) -> u16 { 435 | let mut out = 0u16; 436 | for i in 0..8 { 437 | out |= (((a & 1) << 1 | (b & 1)) as u16) << (i * 2); 438 | a >>= 1; 439 | b >>= 1; 440 | } 441 | return out; 442 | } 443 | 444 | fn reverse_bits(mut a: u8) -> u8 { 445 | let mut out = 0u8; 446 | for i in 0..8 { 447 | out <<= 1; 448 | out |= a & 1; 449 | a >>= 1; 450 | } 451 | return out; 452 | } 453 | 454 | #[derive(Copy, Clone)] 455 | pub struct CpuPpuInterconnect { 456 | ppu: *mut Ppu, 457 | cpu: *mut C6502, 458 | } 459 | 460 | impl Savable for CpuPpuInterconnect { 461 | fn save(&self, fh: &mut dyn Write) { 462 | self.ppu.save(fh); 463 | self.cpu.save(fh); 464 | } 465 | fn load(&mut self, fh: &mut dyn Read) { 466 | self.ppu.load(fh); 467 | self.cpu.load(fh); 468 | } 469 | } 470 | 471 | impl CpuPpuInterconnect { 472 | pub fn new(ppu: &mut Ppu, cpu: &mut C6502) -> CpuPpuInterconnect { 473 | CpuPpuInterconnect { ppu: ppu, cpu: cpu } 474 | } 475 | } 476 | 477 | use PpuPort::*; 478 | 479 | pub fn map_ppu_port(ptr: u16) -> Option { 480 | match ptr { 481 | 0x2000 => Some(PPUCTRL), 482 | 0x2001 => Some(PPUMASK), 483 | 0x2002 => Some(PPUSTATUS), 484 | 0x2003 => Some(OAMADDR), 485 | 0x2004 => Some(OAMDATA), 486 | 0x2005 => Some(PPUSCROLL), 487 | 0x2006 => Some(PPUADDR), 488 | 0x2007 => Some(PPUDATA), 489 | 0x4014 => Some(OAMDMA), 490 | _ => None, 491 | } 492 | } 493 | 494 | impl AddressSpace for CpuPpuInterconnect { 495 | fn peek(&self, ptr: u16) -> u8 { 496 | let ppu: &mut Ppu = unsafe { &mut *self.ppu }; 497 | match map_ppu_port(ptr) { 498 | Some(PPUCTRL) => ppu.open_bus, 499 | Some(PPUMASK) => ppu.open_bus, 500 | Some(PPUSTATUS) => ppu.read_status(), 501 | Some(OAMADDR) => ppu.open_bus, 502 | Some(OAMDATA) => ppu.read_oam_data(), 503 | Some(PPUSCROLL) => ppu.open_bus, 504 | Some(PPUADDR) => ppu.open_bus, 505 | Some(PPUDATA) => ppu.read_data(), 506 | Some(OAMDMA) => ppu.open_bus, 507 | port => panic!("INVALID PPU PORT READ {:?} {:x}", port, ptr), 508 | } 509 | } 510 | fn poke(&mut self, ptr: u16, value: u8) { 511 | let ppu: &mut Ppu = unsafe { &mut *self.ppu }; 512 | ppu.open_bus = value; 513 | match map_ppu_port(ptr) { 514 | Some(PPUCTRL) => ppu.write_control(value), 515 | Some(PPUMASK) => ppu.write_mask(value), 516 | Some(PPUSTATUS) => {} 517 | Some(OAMADDR) => ppu.write_oam_address(value), 518 | Some(OAMDATA) => ppu.write_oam_data(value), 519 | Some(PPUSCROLL) => ppu.write_scroll(value), 520 | Some(PPUADDR) => ppu.write_address(value), 521 | Some(PPUDATA) => ppu.write_data(value), 522 | Some(OAMDMA) => { 523 | let cpu = unsafe { &*self.cpu }; 524 | let ptr_base = (value as u16) << 8; 525 | for i in 0..=255 { 526 | let addr = ptr_base + i; 527 | let v = cpu.peek(addr); 528 | ppu.oam[ppu.oam_ptr as usize] = v; 529 | ppu.oam_ptr = ppu.oam_ptr.wrapping_add(1); 530 | } 531 | } 532 | port => panic!("INVALID PPU PORT WRITE {:?} {:x} {:x}", port, ptr, value), 533 | } 534 | } 535 | } 536 | 537 | pub struct PaletteControl { 538 | memory: [u8; 32], 539 | } 540 | 541 | impl PaletteControl { 542 | pub fn new() -> PaletteControl { 543 | PaletteControl { memory: [0; 32] } 544 | } 545 | fn map_ptr(&self, ptr: u16) -> usize { 546 | let remapped = match ptr { 547 | 0x3f10 => 0x3f00, 548 | 0x3f14 => 0x3f04, 549 | 0x3f18 => 0x3f08, 550 | 0x3f1c => 0x3f0c, 551 | _ => ptr, 552 | }; 553 | return (remapped - 0x3f00) as usize; 554 | } 555 | } 556 | 557 | impl Savable for PaletteControl { 558 | fn save(&self, fh: &mut dyn Write) { 559 | self.memory.save(fh); 560 | } 561 | fn load(&mut self, fh: &mut dyn Read) { 562 | self.memory.load(fh); 563 | } 564 | } 565 | 566 | impl AddressSpace for PaletteControl { 567 | fn peek(&self, ptr: u16) -> u8 { 568 | let true_ptr = self.map_ptr(ptr); 569 | return self.memory[true_ptr]; 570 | } 571 | fn poke(&mut self, ptr: u16, v: u8) { 572 | let true_ptr = self.map_ptr(ptr); 573 | self.memory[true_ptr] = v; 574 | } 575 | } 576 | 577 | #[derive(Copy, Clone)] 578 | enum PaletteType { 579 | Sprite, 580 | Background, 581 | } 582 | 583 | impl Clocked for Ppu { 584 | // High-level pattern here taken from https://github.com/fogleman/nes/blob/master/nes/ppu.go 585 | fn clock(&mut self) { 586 | // https://wiki.nesdev.com/w/index.php/PPU_rendering 587 | self.tick_counters(); 588 | 589 | let is_visible_line = self.scanline < 240; 590 | let is_visible_cycle = self.cycle >= 1 && self.cycle <= 256; 591 | let is_fetch_line = self.scanline == SCANLINE_PRERENDER || is_visible_line; 592 | let is_prefetch_cycle = self.cycle >= 321 && self.cycle <= 336; 593 | let is_fetch_cycle = is_prefetch_cycle || is_visible_cycle; 594 | 595 | //eprintln!("DEBUG - CYCLE - {} {} {} {} {} {}", self.cycle, self.scanline, self.frame, self.is_rendering_enabled(), is_visible_line, is_visible_cycle); 596 | // Background logic 597 | if self.is_rendering_enabled() { 598 | if is_visible_line && is_visible_cycle { 599 | self.render_pixel(); 600 | } 601 | if is_fetch_line && is_fetch_cycle { 602 | self.shift_registers(); 603 | match self.cycle % 8 { 604 | 0 => self.shift_new_tile(), 605 | 1 => self.fetch_bg_tile(), 606 | 3 => self.fetch_bg_attribute(), 607 | 5 => self.fetch_bg_pattern_low(), 608 | 7 => self.fetch_bg_pattern_high(), 609 | _ => {} 610 | } 611 | } 612 | if self.scanline == SCANLINE_PRERENDER && self.cycle >= 280 && self.cycle <= 304 { 613 | self.registers.copy_y(); 614 | } 615 | if is_fetch_line { 616 | if is_fetch_cycle && self.cycle % 8 == 0 { 617 | self.registers.increment_x(); 618 | } 619 | if self.cycle == 256 { 620 | self.registers.increment_y(); 621 | } 622 | if self.cycle == 257 { 623 | self.registers.copy_x(); 624 | } 625 | } 626 | } 627 | 628 | // Sprite logic 629 | if self.is_rendering_enabled() { 630 | if self.cycle == 257 { 631 | if is_visible_line { 632 | self.evaluate_sprites(); 633 | } else { 634 | self.sprite_count = 0; 635 | } 636 | } 637 | } 638 | // Vblank 639 | if self.scanline == 241 && self.cycle == 1 { 640 | // eprintln!("DEBUG - VBLANK HIT - {}", self.generate_vblank_nmi); 641 | self.set_vblank(true); 642 | } 643 | if self.scanline == SCANLINE_PRERENDER && self.cycle == 1 { 644 | self.set_vblank(false); 645 | self.sprite0_hit = false; 646 | self.sprite_overflow = false; 647 | } 648 | } 649 | } 650 | 651 | #[derive(Debug)] 652 | struct PaletteColor { 653 | color: u8, 654 | } 655 | 656 | impl PaletteColor { 657 | pub fn new_from_parts(palette: u8, color: u8) -> PaletteColor { 658 | PaletteColor { 659 | color: (palette * 4 + color), 660 | } 661 | } 662 | pub fn address(&self) -> u16 { 663 | // https://wiki.nesdev.com/w/index.php/PPU_palettes 664 | let base_address = ADDRESS_BACKGROUND_PALETTE0; 665 | let palette_size = 4; 666 | let address: u16 = 667 | base_address + palette_size * (self.palette() as u16) + (self.palette_color() as u16); 668 | address 669 | } 670 | pub fn palette(&self) -> u8 { 671 | return self.color / 4; 672 | } 673 | pub fn palette_color(&self) -> u8 { 674 | return self.color % 4; 675 | } 676 | pub fn is_transparent(&self) -> bool { 677 | return self.palette_color() == 0; 678 | } 679 | pub fn is_opaque(&self) -> bool { 680 | return !self.is_transparent(); 681 | } 682 | } 683 | 684 | // https://wiki.nesdev.com/w/index.php/PPU_rendering 685 | impl Ppu { 686 | pub fn new() -> Ppu { 687 | let mapper = Mapper::new(); 688 | Ppu { 689 | display: [0; UNRENDER_SIZE], 690 | oam: [0; 256], 691 | mapper: Box::new(mapper), 692 | is_vblank_nmi: false, 693 | is_scanline_irq: false, 694 | 695 | registers: PpuRegisters::new(), 696 | sprite_pattern_table: false, 697 | background_pattern_table: false, 698 | sprite_overflow: false, 699 | sprite0_hit: false, 700 | sprite_size: false, 701 | frame_parity: false, 702 | open_bus: 0, 703 | ppu_master_select: false, 704 | generate_vblank_nmi: false, 705 | ppudata_buffer: 0, 706 | 707 | oam_ptr: 0, 708 | 709 | clocks_until_nmi: 0, 710 | nmi_occurred: false, 711 | frame: 0, 712 | scanline: 0, 713 | cycle: 0, 714 | sprite_count: 0, 715 | tile_pattern_low_shift: 0, 716 | tile_pattern_high_shift: 0, 717 | tile_palette_shift: 0, 718 | tile_nametable: 0, 719 | tile_attribute: 0, 720 | tile_pattern_low: 0, 721 | tile_pattern_high: 0, 722 | sprite_patterns: [0; 8], 723 | sprite_palettes: [0; 8], 724 | sprite_xs: [0; 8], 725 | sprite_priorities: [false; 8], 726 | sprite_indices: [0; 8], 727 | } 728 | } 729 | 730 | pub fn current_frame(&self) -> u32 { 731 | return self.frame; 732 | } 733 | 734 | pub fn render(&self) -> [u8; RENDER_SIZE] { 735 | let mut ret = [0; RENDER_SIZE]; 736 | for i in 0..UNRENDER_SIZE { 737 | let c = self.display[i]; 738 | let (r, g, b) = self.lookup_system_pixel(c); 739 | ret[i * 3 + 0] = r; 740 | ret[i * 3 + 1] = g; 741 | ret[i * 3 + 2] = b; 742 | } 743 | return ret; 744 | } 745 | 746 | // pub fn render(&self, buf: &mut [u8]) { 747 | // buf.copy_from_slice(&self.display[0..RENDER_SIZE]); 748 | // } 749 | 750 | fn shift_new_tile(&mut self) { 751 | let background_tile = self.tile_nametable; 752 | let attribute = self.tile_attribute; 753 | let idx_x = self.registers.tile_x(); 754 | let idx_y = self.registers.tile_y(); 755 | let palette = self.split_attribute_entry(attribute, idx_x, idx_y) as u16; 756 | let pattern_low = self.tile_pattern_low; 757 | let pattern_high = self.tile_pattern_high; 758 | self.tile_pattern_low_shift |= pattern_low as u16; 759 | self.tile_pattern_high_shift |= pattern_high as u16; 760 | self.tile_palette_shift <<= 2; 761 | self.tile_palette_shift |= palette; 762 | } 763 | 764 | fn fetch_bg_tile(&mut self) { 765 | let tile_address = self.registers.tile_address(); 766 | self.tile_nametable = self.peek(tile_address); 767 | } 768 | fn fetch_bg_attribute(&mut self) { 769 | let address = self.registers.attribute_address(); 770 | self.tile_attribute = self.peek(address); 771 | } 772 | fn fetch_bg_pattern_low(&mut self) { 773 | let (ptr_low, _) = self.locate_pattern_row( 774 | PaletteType::Background, 775 | self.tile_nametable, 776 | self.registers.fine_y(), 777 | ); 778 | self.tile_pattern_low = self.peek(ptr_low); 779 | } 780 | fn fetch_bg_pattern_high(&mut self) { 781 | let (_, ptr_high) = self.locate_pattern_row( 782 | PaletteType::Background, 783 | self.tile_nametable, 784 | self.registers.fine_y(), 785 | ); 786 | self.tile_pattern_high = self.peek(ptr_high); 787 | } 788 | 789 | fn fetch_tile_color_from_shift(&mut self) -> PaletteColor { 790 | let fine_x = self.registers.fine_x() as u16; 791 | let low = (((self.tile_pattern_low_shift << fine_x) & 0x8000) > 0) as u8; 792 | let high = (((self.tile_pattern_high_shift << fine_x) & 0x8000) > 0) as u8; 793 | let color = low | (high << 1); 794 | let x = self.cycle - 1; 795 | let palette = (self.tile_palette_shift >> ternary((x % 8 + fine_x) > 7, 0, 2)) & 0x3; 796 | return PaletteColor::new_from_parts(palette as u8, color as u8); 797 | } 798 | 799 | fn shift_registers(&mut self) { 800 | self.tile_pattern_low_shift <<= 1; 801 | self.tile_pattern_high_shift <<= 1; 802 | } 803 | 804 | fn tick_counters(&mut self) { 805 | if self.clocks_until_nmi > 0 { 806 | self.clocks_until_nmi -= 1; 807 | if self.clocks_until_nmi == 0 && self.generate_vblank_nmi && self.nmi_occurred { 808 | self.is_vblank_nmi = true; 809 | } 810 | } 811 | if self.is_rendering_enabled() { 812 | if self.frame_parity && self.scanline == 261 && self.cycle == 339 { 813 | self.cycle = 0; 814 | self.scanline = 0; 815 | self.frame += 1; 816 | self.frame_parity = !self.frame_parity; 817 | return; 818 | } 819 | } 820 | self.cycle += 1; 821 | if self.cycle > 340 { 822 | self.cycle = 0; 823 | self.scanline += 1; 824 | if self.scanline > 261 { 825 | self.scanline = 0; 826 | self.frame += 1; 827 | self.frame_parity = !self.frame_parity; 828 | } 829 | } 830 | } 831 | 832 | fn render_pixel(&mut self) { 833 | let x = self.cycle - 1; 834 | let y = self.scanline; 835 | let bg_color = self.background_pixel(); 836 | let (i, sprite_color) = self.sprite_pixel(); 837 | // Sprite 0 test 838 | if self.sprite_indices[i as usize] == 00 && sprite_color.is_opaque() && bg_color.is_opaque() 839 | { 840 | self.sprite0_hit = true; 841 | } 842 | // Determine display color 843 | let color = if self.sprite_priorities[i as usize] && sprite_color.is_opaque() { 844 | sprite_color 845 | } else if bg_color.is_opaque() { 846 | bg_color 847 | } else if sprite_color.is_opaque() { 848 | sprite_color 849 | } else { 850 | GLOBAL_BACKGROUND_COLOR 851 | }; 852 | // eprintln!("DEBUG - COLOR - {:?}", color); 853 | let system_color = self.peek(color.address()); 854 | self.write_system_pixel(x, y, system_color); 855 | } 856 | 857 | fn background_pixel(&mut self) -> PaletteColor { 858 | if !self.is_background_enabled() { 859 | return GLOBAL_BACKGROUND_COLOR; 860 | } 861 | return self.fetch_tile_color_from_shift(); 862 | } 863 | fn sprite_pixel(&mut self) -> (u8, PaletteColor) { 864 | if !self.is_sprites_enabled() { 865 | return (0, GLOBAL_BACKGROUND_COLOR); 866 | } 867 | let x = self.cycle - 1; 868 | for i in 0..self.sprite_count as usize { 869 | let spritex = self.sprite_xs[i]; 870 | let xsub = x as i16 - spritex as i16; 871 | if xsub < 0 || xsub > 7 { 872 | continue; 873 | } 874 | let palette = self.sprite_palettes[i]; 875 | let color = (self.sprite_patterns[i] >> ((7 - xsub) * 2)) & 0x3; 876 | let palette_color = PaletteColor::new_from_parts(palette, color as u8); 877 | if palette_color.is_transparent() { 878 | continue; 879 | } 880 | return (i as u8, palette_color); 881 | } 882 | return (0, GLOBAL_BACKGROUND_COLOR); 883 | } 884 | fn is_sprites_enabled(&self) -> bool { 885 | return self.registers.is_sprites_enabled(); 886 | } 887 | fn is_background_enabled(&self) -> bool { 888 | return self.registers.is_background_enabled(); 889 | } 890 | 891 | fn is_rendering_enabled(&self) -> bool { 892 | return self.registers.is_rendering_enabled(); 893 | } 894 | 895 | fn evaluate_sprites(&mut self) { 896 | let height = ternary(self.sprite_size, 16, 8); 897 | 898 | let mut count = 0; 899 | for i in 0..64 { 900 | let sprite = self.lookup_sprite(i); 901 | let pattern = self.fetch_sprite_pattern(&sprite, self.scanline); 902 | match pattern { 903 | None => continue, 904 | Some(pattern) => { 905 | if count < 8 { 906 | self.sprite_patterns[count] = pattern; 907 | self.sprite_xs[count] = sprite.x; 908 | self.sprite_priorities[count] = sprite.is_front; 909 | self.sprite_indices[count] = i as u8; 910 | self.sprite_palettes[count] = sprite.palette; 911 | } 912 | count += 1; 913 | } 914 | }; 915 | } 916 | 917 | if count > 8 { 918 | count = 8; 919 | self.sprite_overflow = true; 920 | } 921 | self.sprite_count = count as u8; 922 | } 923 | 924 | fn fetch_sprite_pattern(&self, sprite: &Sprite, row: u16) -> Option { 925 | let is_size_16 = self.sprite_size; 926 | let row = row as i16 - sprite.y as i16; 927 | let height = ternary(is_size_16, 16, 8); 928 | if row < 0 || row >= height { 929 | return None; 930 | } 931 | let row = ternary(sprite.flip_vertical, height - 1 - row, row); 932 | let tile = sprite.tile_index; //TODO -- ternary(row >= 8, sprite.tile_index+1, sprite.tile_index); 933 | let row = ternary(row >= 8, row - 8, row); 934 | let (tile_row0, tile_row1) = self.fetch_pattern_row(PaletteType::Sprite, tile, row as u8); 935 | let (tile_row0, tile_row1) = ternary( 936 | sprite.flip_horizontal, 937 | (reverse_bits(tile_row0), reverse_bits(tile_row1)), 938 | (tile_row0, tile_row1), 939 | ); 940 | return Some(combine_bitplanes(tile_row1, tile_row0)); 941 | } 942 | 943 | fn locate_pattern_row( 944 | &self, 945 | palette_type: PaletteType, 946 | tile_index: u8, 947 | ysub: u8, 948 | ) -> (u16, u16) { 949 | // https://wiki.nesdev.com/w/index.php/PPU_pattern_tables 950 | let ptr_pattern_table_base = 0x0000; 951 | let size_pattern_table = 0x1000; 952 | let size_tile = 16; 953 | let is_pattern_table_right = match palette_type { 954 | PaletteType::Sprite => self.sprite_pattern_table, 955 | PaletteType::Background => self.background_pattern_table, 956 | }; 957 | let ptr_tile: u16 = ptr_pattern_table_base 958 | + size_pattern_table * (is_pattern_table_right as u16) 959 | + (size_tile * tile_index as u16); 960 | let ptr_tile_row0 = ptr_tile + (ysub as u16); 961 | let ptr_tile_row1 = ptr_tile_row0 + 8; // The bits of the color id are stored in separate bit planes. 962 | (ptr_tile_row0, ptr_tile_row1) 963 | } 964 | fn fetch_pattern_row(&self, palette_type: PaletteType, tile_index: u8, ysub: u8) -> (u8, u8) { 965 | let (ptr_tile_row0, ptr_tile_row1) = 966 | self.locate_pattern_row(palette_type, tile_index, ysub); 967 | let tile_row0 = self.peek(ptr_tile_row0); 968 | let tile_row1 = self.peek(ptr_tile_row1); 969 | return (tile_row0, tile_row1); 970 | } 971 | 972 | fn fetch_scanline_sprites(&mut self, y: u16) -> Vec { 973 | let mut vec = Vec::new(); 974 | for i in 0..64 { 975 | let sprite = self.lookup_sprite(i); 976 | if y >= sprite.y as u16 && y < (sprite.y as u16 + SPRITE_HEIGHT as u16) { 977 | vec.push(sprite); 978 | } 979 | if vec.len() >= 8 { 980 | self.sprite_overflow = true; 981 | return vec; 982 | } 983 | } 984 | return vec; 985 | } 986 | 987 | fn find_matching_sprite(&self, x: u16, sprites: &Vec) -> Option { 988 | for sprite in sprites { 989 | if x >= (sprite.x as u16) && x < (sprite.x as u16 + SPRITE_WIDTH as u16) { 990 | return Some(sprite.clone()); 991 | } 992 | } 993 | return None; 994 | } 995 | 996 | fn lookup_sprite(&self, i: usize) -> Sprite { 997 | let attribute = self.oam[i * 4 + 2]; 998 | return Sprite { 999 | index: i as u8, 1000 | y: self.oam[i * 4 + 0], 1001 | tile_index: self.oam[i * 4 + 1], 1002 | palette: (attribute & 3) + 4, 1003 | is_front: get_bit(attribute, 5) == 0, 1004 | flip_horizontal: get_bit(attribute, 6) > 0, 1005 | flip_vertical: get_bit(attribute, 7) > 0, 1006 | x: self.oam[i * 4 + 3], 1007 | }; 1008 | } 1009 | 1010 | fn lookup_global_background_color(&self) -> SystemColor { 1011 | return self.peek(ADDRESS_UNIVERSAL_BACKGROUND_COLOR); 1012 | } 1013 | 1014 | fn split_attribute_entry(&self, entry: Attribute, idx_x: u8, idx_y: u8) -> PaletteId { 1015 | let (left, top) = ((idx_x % 4) < 2, (idx_y % 4) < 2); 1016 | let palette_id = match (left, top) { 1017 | (true, true) => (entry >> 0) & 0x3, 1018 | (false, true) => (entry >> 2) & 0x3, 1019 | (true, false) => (entry >> 4) & 0x3, 1020 | (false, false) => (entry >> 6) & 0x3, 1021 | }; 1022 | //eprintln!("DEBUG - ATTRIBUTE ENTRY - {:x} {}", entry, palette_id); 1023 | return palette_id; 1024 | } 1025 | 1026 | pub fn write_control(&mut self, v: u8) { 1027 | self.registers.write_control(v); 1028 | self.sprite_pattern_table = get_bit(v, 3) > 0; 1029 | self.background_pattern_table = get_bit(v, 4) > 0; 1030 | self.sprite_size = get_bit(v, 5) > 0; 1031 | self.ppu_master_select = get_bit(v, 6) > 0; 1032 | self.generate_vblank_nmi = get_bit(v, 7) > 0; 1033 | } 1034 | 1035 | pub fn write_mask(&mut self, v: u8) { 1036 | self.registers.write_mask(v); 1037 | } 1038 | 1039 | pub fn read_status(&mut self) -> u8 { 1040 | let ret = (self.open_bus & 0b00011111) 1041 | | ((self.sprite_overflow as u8) << 5) 1042 | | ((self.sprite0_hit as u8) << 6) 1043 | | ((self.nmi_occurred as u8) << 7); 1044 | self.set_vblank(false); 1045 | self.registers.read_status(); 1046 | return ret; 1047 | } 1048 | pub fn write_oam_address(&mut self, v: u8) { 1049 | self.oam_ptr = v; 1050 | } 1051 | pub fn read_oam_data(&mut self) -> u8 { 1052 | let ptr: u8 = self.oam_ptr; 1053 | return self.oam[ptr as usize]; 1054 | } 1055 | pub fn write_oam_data(&mut self, v: u8) { 1056 | let ptr: u8 = self.oam_ptr; 1057 | self.oam[ptr as usize] = v; 1058 | self.oam_ptr = self.oam_ptr.wrapping_add(1); 1059 | } 1060 | pub fn write_scroll(&mut self, v: u8) { 1061 | self.registers.write_scroll(v); 1062 | } 1063 | pub fn write_address(&mut self, v: u8) { 1064 | self.registers.write_address(v); 1065 | } 1066 | pub fn read_data(&mut self) -> u8 { 1067 | let ptr = self.registers.vram_ptr(); 1068 | self.registers.advance_vram_ptr(); 1069 | let val = self.peek(ptr); 1070 | if ptr < 0x3f00 { 1071 | let old_val = self.ppudata_buffer; 1072 | self.ppudata_buffer = val; 1073 | old_val 1074 | } else { 1075 | val 1076 | } 1077 | } 1078 | pub fn write_data(&mut self, v: u8) { 1079 | let ptr = self.registers.vram_ptr(); 1080 | self.registers.advance_vram_ptr(); 1081 | // eprintln!("DEBUG - PPU WRITE DATA - {:x} {:x} {:x}", ptr, v, self.registers.vram_ptr()); 1082 | self.poke(ptr, v); 1083 | } 1084 | fn lookup_system_pixel(&self, i: SystemColor) -> RgbColor { 1085 | return SYSTEM_PALETTE[i as usize]; 1086 | } 1087 | fn write_system_pixel(&mut self, x: u16, y: u16, c: SystemColor) { 1088 | if x >= 256 || y >= 240 { 1089 | return; 1090 | } 1091 | let i = (x + 256 * y) as usize; 1092 | self.display[i] = c; 1093 | } 1094 | fn set_vblank(&mut self, new_vblank: bool) { 1095 | let vblank = self.nmi_occurred; 1096 | if vblank != new_vblank { 1097 | //eprintln!("DEBUG - VBLANK CHANGED FROM {:?} TO {:?}", vblank, new_vblank); 1098 | } 1099 | self.nmi_occurred = new_vblank; 1100 | self.clocks_until_nmi = 15; 1101 | } 1102 | } 1103 | 1104 | type RgbColor = (u8, u8, u8); 1105 | type SystemPalette = [RgbColor; 64]; 1106 | 1107 | // The NES can refer to 64 separate colors. This table has RGB values for each. 1108 | pub const SYSTEM_PALETTE: SystemPalette = [ 1109 | // 0x 1110 | (124, 124, 124), // x0 1111 | (0, 0, 252), // x1 1112 | (0, 0, 188), // x2 1113 | (68, 40, 188), // x3 1114 | (148, 0, 132), // x4 1115 | (168, 0, 32), // x5 1116 | (168, 16, 0), // x6 1117 | (136, 20, 0), // x7 1118 | (80, 48, 0), // x8 1119 | (0, 120, 0), // x9 1120 | (0, 104, 0), // xA 1121 | (0, 88, 0), // xB 1122 | (0, 64, 88), // xC 1123 | (0, 0, 0), // xD 1124 | (0, 0, 0), // xE 1125 | (0, 0, 0), // xF 1126 | // 1x 1127 | (188, 188, 188), // x0 1128 | (0, 120, 248), // x1 1129 | (0, 88, 248), // x2 1130 | (104, 68, 252), // x3 1131 | (216, 0, 204), // x4 1132 | (228, 0, 88), // x5 1133 | (248, 56, 0), // x6 1134 | (228, 92, 16), // x7 1135 | (172, 124, 0), // x8 1136 | (0, 184, 0), // x9 1137 | (0, 168, 0), // xA 1138 | (0, 168, 68), // xB 1139 | (0, 136, 136), // xC 1140 | (0, 0, 0), // xD 1141 | (0, 0, 0), // xE 1142 | (0, 0, 0), // xF 1143 | // 2x 1144 | (248, 248, 248), // x0 1145 | (60, 188, 252), // x1 1146 | (104, 136, 252), // x2 1147 | (152, 120, 248), // x3 1148 | (248, 120, 248), // x4 1149 | (248, 88, 152), // x5 1150 | (248, 120, 88), // x6 1151 | (252, 160, 68), // x7 1152 | (248, 184, 0), // x8 1153 | (184, 248, 24), // x9 1154 | (88, 216, 84), // xA 1155 | (88, 248, 152), // xB 1156 | (0, 232, 216), // xC 1157 | (120, 120, 120), // xD 1158 | (0, 0, 0), // xE 1159 | (0, 0, 0), // xF 1160 | // 3x 1161 | (252, 252, 252), // x0 1162 | (164, 228, 252), // x1 1163 | (184, 184, 248), // x2 1164 | (216, 184, 248), // x3 1165 | (248, 184, 248), // x4 1166 | (248, 164, 192), // x5 1167 | (240, 208, 176), // x6 1168 | (252, 224, 168), // x7 1169 | (248, 216, 120), // x8 1170 | (216, 248, 120), // x9 1171 | (184, 248, 184), // xA 1172 | (184, 248, 216), // xB 1173 | (0, 252, 252), // xC 1174 | (216, 216, 216), // xD 1175 | (0, 0, 0), // xE 1176 | (0, 0, 0), // xF 1177 | ]; 1178 | 1179 | mod tests { 1180 | use super::*; 1181 | 1182 | #[test] 1183 | fn test_combine_bitplanes() { 1184 | let a = 0b10011110; 1185 | let b = 0b01101100; 1186 | let o = 0b1001011011111000; 1187 | assert_eq!(combine_bitplanes(a, b), o); 1188 | } 1189 | #[test] 1190 | fn test_reverse_bits() { 1191 | let a = 0b10011110; 1192 | let o = 0b01111001; 1193 | assert_eq!(reverse_bits(a), o); 1194 | } 1195 | } 1196 | -------------------------------------------------------------------------------- /src/serialization.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_must_use)] // TODO 2 | #![allow(dead_code)] 3 | 4 | use std::default::Default; 5 | use std::fs::File; 6 | use std::io::Read; 7 | use std::io::Write; 8 | 9 | pub trait Savable { 10 | fn save(&self, fh: &mut dyn Write); 11 | fn load(&mut self, fh: &mut dyn Read); 12 | } 13 | 14 | impl Savable for bool { 15 | fn save(&self, fh: &mut dyn Write) { 16 | let bytes = [*self as u8]; 17 | fh.write_all(&bytes); 18 | } 19 | fn load(&mut self, fh: &mut dyn Read) { 20 | let mut bytes = [0]; 21 | fh.read_exact(&mut bytes); 22 | *self = bytes[0] > 0; 23 | } 24 | } 25 | 26 | impl Savable for u8 { 27 | fn save(&self, fh: &mut dyn Write) { 28 | let bytes = [*self as u8]; 29 | fh.write_all(&bytes); 30 | } 31 | fn load(&mut self, fh: &mut dyn Read) { 32 | let mut bytes = [0]; 33 | fh.read_exact(&mut bytes); 34 | *self = bytes[0]; 35 | } 36 | } 37 | 38 | impl Savable for u16 { 39 | fn save(&self, fh: &mut dyn Write) { 40 | let bytes = [(*self & 0xff) as u8, ((*self >> 8) & 0xff) as u8]; 41 | fh.write_all(&bytes); 42 | } 43 | fn load(&mut self, fh: &mut dyn Read) { 44 | let mut bytes = [0; 2]; 45 | fh.read_exact(&mut bytes); 46 | *self = 0; 47 | *self |= bytes[0] as u16; 48 | *self |= (bytes[1] as u16) << 8; 49 | } 50 | } 51 | 52 | impl Savable for u32 { 53 | fn save(&self, fh: &mut dyn Write) { 54 | let bytes = [ 55 | ((*self >> 0) & 0xff) as u8, 56 | ((*self >> 8) & 0xff) as u8, 57 | ((*self >> 16) & 0xff) as u8, 58 | ((*self >> 24) & 0xff) as u8, 59 | ]; 60 | fh.write_all(&bytes); 61 | } 62 | fn load(&mut self, fh: &mut dyn Read) { 63 | let mut bytes = [0u8; 4]; 64 | fh.read_exact(&mut bytes); 65 | *self = 0; 66 | *self |= (bytes[0] as u32) << 0; 67 | *self |= (bytes[1] as u32) << 8; 68 | *self |= (bytes[2] as u32) << 16; 69 | *self |= (bytes[3] as u32) << 24; 70 | } 71 | } 72 | 73 | impl Savable for u64 { 74 | fn save(&self, fh: &mut dyn Write) { 75 | let bytes = [ 76 | ((*self >> 0) & 0xff) as u8, 77 | ((*self >> 8) & 0xff) as u8, 78 | ((*self >> 16) & 0xff) as u8, 79 | ((*self >> 24) & 0xff) as u8, 80 | ((*self >> 32) & 0xff) as u8, 81 | ((*self >> 40) & 0xff) as u8, 82 | ((*self >> 48) & 0xff) as u8, 83 | ((*self >> 56) & 0xff) as u8, 84 | ]; 85 | fh.write_all(&bytes); 86 | } 87 | fn load(&mut self, fh: &mut dyn Read) { 88 | let mut bytes = [0u8; 8]; 89 | fh.read_exact(&mut bytes); 90 | *self = 0; 91 | *self |= (bytes[0] as u64) << 0; 92 | *self |= (bytes[1] as u64) << 8; 93 | *self |= (bytes[2] as u64) << 16; 94 | *self |= (bytes[3] as u64) << 24; 95 | *self |= (bytes[4] as u64) << 32; 96 | *self |= (bytes[5] as u64) << 40; 97 | *self |= (bytes[6] as u64) << 48; 98 | *self |= (bytes[7] as u64) << 56; 99 | } 100 | } 101 | 102 | impl Savable for usize { 103 | fn save(&self, fh: &mut dyn Write) { 104 | (*self as u64).save(fh); 105 | } 106 | fn load(&mut self, fh: &mut dyn Read) { 107 | let mut x: u64 = *self as u64; 108 | x.load(fh); 109 | *self = x as usize; 110 | } 111 | } 112 | 113 | impl Savable for [T] { 114 | fn save(&self, fh: &mut dyn Write) { 115 | let len: usize = self.len(); 116 | len.save(fh); 117 | for i in self.iter() { 118 | i.save(fh); 119 | } 120 | } 121 | fn load(&mut self, fh: &mut dyn Read) { 122 | let mut len = 0usize; 123 | len.load(fh); 124 | for i in 0..len { 125 | self[i].load(fh); 126 | } 127 | } 128 | } 129 | 130 | impl Savable for Vec { 131 | fn save(&self, fh: &mut dyn Write) { 132 | let len: usize = self.len(); 133 | len.save(fh); 134 | for i in self.iter() { 135 | i.save(fh); 136 | } 137 | } 138 | fn load(&mut self, fh: &mut dyn Read) { 139 | let mut len = 0usize; 140 | len.load(fh); 141 | self.truncate(0); 142 | self.reserve(len); 143 | for _ in 0..len { 144 | let mut x: T = Default::default(); 145 | x.load(fh); 146 | self.push(x); 147 | } 148 | } 149 | } 150 | 151 | impl Savable for String { 152 | fn save(&self, fh: &mut dyn Write) { 153 | let len = self.len() as u32; 154 | len.save(fh); 155 | for byte in self.bytes() { 156 | byte.save(fh); 157 | } 158 | } 159 | fn load(&mut self, fh: &mut dyn Read) { 160 | let len = read_value::(fh) as usize; 161 | let mut bytes = vec![0; len]; 162 | for i in 0..len { 163 | bytes[i] = read_value::(fh); 164 | } 165 | *self = String::from_utf8(bytes).expect("Invalid utf8"); 166 | } 167 | } 168 | 169 | pub fn read_value(fh: &mut dyn Read) -> T { 170 | let mut t: T = Default::default(); 171 | t.load(fh); 172 | t 173 | } 174 | 175 | use std::io::Seek; 176 | use std::io::SeekFrom; 177 | 178 | pub fn file_position(fh: &mut File) -> u64 { 179 | fh.seek(SeekFrom::Current(0)).unwrap() 180 | } 181 | --------------------------------------------------------------------------------