From dff765a38cb0d35974a712c839f2efef777adcb8 Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Tue, 2 Jan 2024 11:47:06 -0700 Subject: [PATCH] feat: add test harness, fix ppu windows, rename --- Cargo.lock | 492 +++++++++++------- Cargo.toml | 2 +- {deemgee-opcode => meowgb-opcode}/Cargo.toml | 2 +- {deemgee-opcode => meowgb-opcode}/src/lib.rs | 2 +- meowgb-tests/Cargo.toml | 11 + meowgb-tests/expected_output/cpu_instrs.bin | 5 + meowgb-tests/expected_output/instr_timing.bin | 4 + .../expected_output/intr_1_2_timing-GS.bin | 1 + .../expected_output/intr_2_0_timing.bin | 1 + .../expected_output/intr_lyc_onoff.gb | 1 + meowgb-tests/expected_output/mem_timing.bin | 5 + .../expected_output/stat_irq_blocking.bin | 1 + meowgb-tests/src/main.rs | 169 ++++++ {deemgee => meowgb}/Cargo.lock | 0 {deemgee => meowgb}/Cargo.toml | 4 +- {deemgee => meowgb}/src/gameboy.rs | 17 +- meowgb/src/gameboy/bootrom.rs | 48 ++ {deemgee => meowgb}/src/gameboy/cpu.rs | 4 +- {deemgee => meowgb}/src/gameboy/cpu/alu.rs | 2 +- {deemgee => meowgb}/src/gameboy/cpu/flow.rs | 2 +- .../src/gameboy/cpu/load_store_move.rs | 2 +- {deemgee => meowgb}/src/gameboy/cpu/misc.rs | 2 +- .../src/gameboy/cpu/prefixed.rs | 6 +- {deemgee => meowgb}/src/gameboy/interrupts.rs | 0 {deemgee => meowgb}/src/gameboy/joypad.rs | 0 {deemgee => meowgb}/src/gameboy/mapper.rs | 0 .../src/gameboy/mapper/mbc1.rs | 0 {deemgee => meowgb}/src/gameboy/memory.rs | 0 {deemgee => meowgb}/src/gameboy/ppu.rs | 17 +- {deemgee => meowgb}/src/gameboy/serial.rs | 23 +- {deemgee => meowgb}/src/gameboy/sound.rs | 0 {deemgee => meowgb}/src/gameboy/timer.rs | 0 {deemgee => meowgb}/src/lib.rs | 8 +- {deemgee => meowgb}/src/main.rs | 51 +- {deemgee => meowgb}/src/settings.rs | 0 {deemgee => meowgb}/src/window.rs | 0 {deemgee => meowgb}/tests/flow_opcodes.rs | 2 +- .../tests/load_store_move_opcodes.rs | 2 +- {deemgee => meowgb}/tests/misc_opcodes.rs | 2 +- test-roms/blargg/LICENSE | 1 + test-roms/blargg/roms/cpu_instrs.gb | Bin 0 -> 65536 bytes test-roms/blargg/roms/instr_timing.gb | Bin 0 -> 32768 bytes test-roms/blargg/roms/mem_timing.gb | Bin 0 -> 65536 bytes test-roms/mealybug-tearoom-tests/LICENSE | 21 + test-roms/mealybug-tearoom-tests/README.md | 5 + .../roms/intr_1_2_timing-GS.gb | Bin 0 -> 32768 bytes .../roms/intr_2_0_timing.gb | Bin 0 -> 32768 bytes .../roms/stat_irq_blocking.gb | Bin 0 -> 32768 bytes .../roms/stat_lyc_onoff.gb | Bin 0 -> 32768 bytes 49 files changed, 629 insertions(+), 286 deletions(-) rename {deemgee-opcode => meowgb-opcode}/Cargo.toml (91%) rename {deemgee-opcode => meowgb-opcode}/src/lib.rs (97%) create mode 100644 meowgb-tests/Cargo.toml create mode 100644 meowgb-tests/expected_output/cpu_instrs.bin create mode 100644 meowgb-tests/expected_output/instr_timing.bin create mode 100644 meowgb-tests/expected_output/intr_1_2_timing-GS.bin create mode 100644 meowgb-tests/expected_output/intr_2_0_timing.bin create mode 100644 meowgb-tests/expected_output/intr_lyc_onoff.gb create mode 100644 meowgb-tests/expected_output/mem_timing.bin create mode 100644 meowgb-tests/expected_output/stat_irq_blocking.bin create mode 100644 meowgb-tests/src/main.rs rename {deemgee => meowgb}/Cargo.lock (100%) rename {deemgee => meowgb}/Cargo.toml (89%) rename {deemgee => meowgb}/src/gameboy.rs (98%) create mode 100644 meowgb/src/gameboy/bootrom.rs rename {deemgee => meowgb}/src/gameboy/cpu.rs (99%) rename {deemgee => meowgb}/src/gameboy/cpu/alu.rs (99%) rename {deemgee => meowgb}/src/gameboy/cpu/flow.rs (99%) rename {deemgee => meowgb}/src/gameboy/cpu/load_store_move.rs (99%) rename {deemgee => meowgb}/src/gameboy/cpu/misc.rs (98%) rename {deemgee => meowgb}/src/gameboy/cpu/prefixed.rs (99%) rename {deemgee => meowgb}/src/gameboy/interrupts.rs (100%) rename {deemgee => meowgb}/src/gameboy/joypad.rs (100%) rename {deemgee => meowgb}/src/gameboy/mapper.rs (100%) rename {deemgee => meowgb}/src/gameboy/mapper/mbc1.rs (100%) rename {deemgee => meowgb}/src/gameboy/memory.rs (100%) rename {deemgee => meowgb}/src/gameboy/ppu.rs (98%) rename {deemgee => meowgb}/src/gameboy/serial.rs (56%) rename {deemgee => meowgb}/src/gameboy/sound.rs (100%) rename {deemgee => meowgb}/src/gameboy/timer.rs (100%) rename {deemgee => meowgb}/src/lib.rs (84%) rename {deemgee => meowgb}/src/main.rs (76%) rename {deemgee => meowgb}/src/settings.rs (100%) rename {deemgee => meowgb}/src/window.rs (100%) rename {deemgee => meowgb}/tests/flow_opcodes.rs (99%) rename {deemgee => meowgb}/tests/load_store_move_opcodes.rs (98%) rename {deemgee => meowgb}/tests/misc_opcodes.rs (97%) create mode 100644 test-roms/blargg/LICENSE create mode 100644 test-roms/blargg/roms/cpu_instrs.gb create mode 100644 test-roms/blargg/roms/instr_timing.gb create mode 100644 test-roms/blargg/roms/mem_timing.gb create mode 100644 test-roms/mealybug-tearoom-tests/LICENSE create mode 100644 test-roms/mealybug-tearoom-tests/README.md create mode 100644 test-roms/mealybug-tearoom-tests/roms/intr_1_2_timing-GS.gb create mode 100644 test-roms/mealybug-tearoom-tests/roms/intr_2_0_timing.gb create mode 100644 test-roms/mealybug-tearoom-tests/roms/stat_irq_blocking.gb create mode 100644 test-roms/mealybug-tearoom-tests/roms/stat_lyc_onoff.gb diff --git a/Cargo.lock b/Cargo.lock index 1048ebf..b223dae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom", "once_cell", @@ -29,14 +29,32 @@ dependencies = [ ] [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "ahash" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-activity" version = "0.5.1" @@ -64,6 +82,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -123,9 +147,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "ash" @@ -133,14 +157,14 @@ version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ - "libloading 0.7.2", + "libloading 0.7.4", ] [[package]] name = "async-trait" -version = "0.1.75" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", @@ -155,9 +179,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" @@ -182,9 +206,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bit-set" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -252,9 +276,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" @@ -264,9 +288,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -290,11 +314,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.72" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -317,15 +342,16 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ - "libc", - "num-integer", + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", - "time", - "winapi", + "wasm-bindgen", + "windows-targets 0.48.5", ] [[package]] @@ -459,13 +485,12 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", - "foreign-types 0.3.2", "libc", ] @@ -513,7 +538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" dependencies = [ "bitflags 1.3.2", - "libloading 0.7.2", + "libloading 0.7.4", "winapi", ] @@ -552,36 +577,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deemgee" -version = "0.1.0" -dependencies = [ - "bmp", - "chrono", - "clap", - "config", - "deemgee-opcode", - "env_logger", - "log", - "paste", - "pixels", - "serde", - "sha1", - "thiserror", - "winit", - "winit_input_helper", -] - -[[package]] -name = "deemgee-opcode" -version = "0.1.0" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "digest" version = "0.10.7" @@ -626,6 +621,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.8" @@ -702,7 +703,7 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -725,9 +726,9 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e64cbb8d36508d3e19da95e56e196a84f674fc190881f2cc010000798838aa6" +checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" dependencies = [ "bitflags 1.3.2", "gpu-alloc-types", @@ -757,31 +758,22 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "gpu-descriptor-types", - "hashbrown 0.11.2", + "hashbrown 0.14.3", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", + "bitflags 2.4.1", ] [[package]] @@ -790,7 +782,17 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.7", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.7", + "allocator-api2", ] [[package]] @@ -802,7 +804,7 @@ dependencies = [ "bitflags 1.3.2", "com-rs", "libc", - "libloading 0.7.2", + "libloading 0.7.4", "thiserror", "widestring", "winapi", @@ -832,6 +834,29 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icrate" version = "0.0.4" @@ -860,12 +885,13 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ - "cfg-if", + "equivalent", + "hashbrown 0.14.3", ] [[package]] @@ -881,9 +907,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jni" @@ -943,7 +969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" dependencies = [ "libc", - "libloading 0.7.2", + "libloading 0.7.4", "pkg-config", ] @@ -961,9 +987,9 @@ checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -992,9 +1018,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" @@ -1004,21 +1030,19 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "malloc_buf" @@ -1031,9 +1055,48 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "meowgb" +version = "0.1.0" +dependencies = [ + "bmp", + "chrono", + "clap", + "config", + "env_logger", + "log", + "meowgb-opcode", + "paste", + "pixels", + "serde", + "sha1", + "thiserror", + "winit", + "winit_input_helper", +] + +[[package]] +name = "meowgb-opcode" +version = "0.1.0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "meowgb-tests" +version = "0.1.0" +dependencies = [ + "clap", + "meowgb", + "thiserror", +] [[package]] name = "metal" @@ -1074,7 +1137,7 @@ dependencies = [ "bitflags 1.3.2", "codespan-reporting", "hexf-parse", - "indexmap", + "indexmap 1.9.3", "log", "num-traits", "rustc-hash", @@ -1116,30 +1179,19 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.0" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", - "version_check", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", ] [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1242,34 +1294,32 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", - "instant", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.4.1", "smallvec", - "winapi", + "windows-targets 0.48.5", ] [[package]] name = "paste" -version = "1.0.6" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pathdiff" @@ -1344,9 +1394,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.23" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a3ea4f0dd7f1f3e512cf97bf100819aa547f36a6eccac8dbaae839eb92363e" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "polling" @@ -1370,43 +1420,43 @@ checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" [[package]] name = "proc-macro-crate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" dependencies = [ - "thiserror", - "toml", + "toml_datetime", + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.4" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9926767b8b8244d7b6b64546585121d193c3d0b4856ccd656b7bfa9deb91ab6a" +checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "range-alloc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" [[package]] name = "raw-window-handle" @@ -1414,15 +1464,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -1443,9 +1484,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -1454,9 +1507,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "renderdoc-sys" @@ -1512,9 +1565,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.6" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "safe_arch" @@ -1536,24 +1589,24 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", @@ -1562,9 +1615,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.72" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" +checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" dependencies = [ "itoa", "ryu", @@ -1604,9 +1657,9 @@ dependencies = [ [[package]] name = "slotmap" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] @@ -1650,9 +1703,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.43" +version = "2.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" dependencies = [ "proc-macro2", "quote", @@ -1661,27 +1714,27 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", @@ -1689,23 +1742,29 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.1.44" +name = "toml" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "serde", ] [[package]] -name = "toml" -version = "0.5.8" +name = "toml_datetime" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "serde", + "indexmap 2.1.0", + "toml_datetime", + "winnow", ] [[package]] @@ -1753,15 +1812,15 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -1783,21 +1842,14 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2024,9 +2076,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -2046,6 +2098,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -2246,9 +2307,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winit" -version = "0.29.7" +version = "0.29.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd430cd4560ee9c48885a4ef473b609a56796e37b1e18222abee146143f7457" +checksum = "0dc1a7ae1076890701c7dd71ea35b2aebaf9aeb7b8868ac2d33b1c7e8ef93c00" dependencies = [ "android-activity", "atomic-waker", @@ -2290,6 +2351,15 @@ dependencies = [ "winit", ] +[[package]] +name = "winnow" +version = "0.5.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +dependencies = [ + "memchr", +] + [[package]] name = "xkbcommon-dl" version = "0.4.1" @@ -2317,3 +2387,23 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 12b6605..9a4b189 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["deemgee", "deemgee-opcode"] \ No newline at end of file +members = ["meowgb", "meowgb-opcode", "meowgb-tests"] \ No newline at end of file diff --git a/deemgee-opcode/Cargo.toml b/meowgb-opcode/Cargo.toml similarity index 91% rename from deemgee-opcode/Cargo.toml rename to meowgb-opcode/Cargo.toml index 40d38cf..5ca8fa8 100644 --- a/deemgee-opcode/Cargo.toml +++ b/meowgb-opcode/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "deemgee-opcode" +name = "meowgb-opcode" version = "0.1.0" edition = "2021" diff --git a/deemgee-opcode/src/lib.rs b/meowgb-opcode/src/lib.rs similarity index 97% rename from deemgee-opcode/src/lib.rs rename to meowgb-opcode/src/lib.rs index c0f8c02..ffc8653 100644 --- a/deemgee-opcode/src/lib.rs +++ b/meowgb-opcode/src/lib.rs @@ -56,7 +56,7 @@ pub fn opcode(item: TokenStream) -> TokenStream { let length = length.base10_parse::().expect("Failed to parse opcode length as u8"); let fn_sig = quote::quote! { - pub fn #name(state: &mut Gameboy) -> CycleResult + pub fn #name(state: &mut Gameboy) -> CycleResult }; let mut cycle = Vec::new(); diff --git a/meowgb-tests/Cargo.toml b/meowgb-tests/Cargo.toml new file mode 100644 index 0000000..4532d24 --- /dev/null +++ b/meowgb-tests/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "meowgb-tests" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.4.12", features = ["derive"] } +meowgb = { path = "../meowgb" } +thiserror = "1.0.56" diff --git a/meowgb-tests/expected_output/cpu_instrs.bin b/meowgb-tests/expected_output/cpu_instrs.bin new file mode 100644 index 0000000..ce7553c --- /dev/null +++ b/meowgb-tests/expected_output/cpu_instrs.bin @@ -0,0 +1,5 @@ +cpu_instrs + +01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok + +Passed all tests diff --git a/meowgb-tests/expected_output/instr_timing.bin b/meowgb-tests/expected_output/instr_timing.bin new file mode 100644 index 0000000..a38f303 --- /dev/null +++ b/meowgb-tests/expected_output/instr_timing.bin @@ -0,0 +1,4 @@ +instr_timing + + +Passed diff --git a/meowgb-tests/expected_output/intr_1_2_timing-GS.bin b/meowgb-tests/expected_output/intr_1_2_timing-GS.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/intr_1_2_timing-GS.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/intr_2_0_timing.bin b/meowgb-tests/expected_output/intr_2_0_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/intr_2_0_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/intr_lyc_onoff.gb b/meowgb-tests/expected_output/intr_lyc_onoff.gb new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/intr_lyc_onoff.gb @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/mem_timing.bin b/meowgb-tests/expected_output/mem_timing.bin new file mode 100644 index 0000000..c84657a --- /dev/null +++ b/meowgb-tests/expected_output/mem_timing.bin @@ -0,0 +1,5 @@ +mem_timing + +01:ok 02:ok 03:ok + +Passed all tests diff --git a/meowgb-tests/expected_output/stat_irq_blocking.bin b/meowgb-tests/expected_output/stat_irq_blocking.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/stat_irq_blocking.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/src/main.rs b/meowgb-tests/src/main.rs new file mode 100644 index 0000000..f56c5a1 --- /dev/null +++ b/meowgb-tests/src/main.rs @@ -0,0 +1,169 @@ +use std::{path::{PathBuf, Path}, sync::{RwLock, Arc}, time::{Duration, Instant}}; + +use clap::{Parser, Subcommand}; +use meowgb::gameboy::{Gameboy, serial::SerialWriter}; + +#[derive(Debug, Parser)] +/// DMG Emulator +pub struct CliArgs { + /// game path + pub rom: PathBuf, + #[clap(subcommand)] + pub operation: Operation +} + +#[derive(Debug, Subcommand)] +pub enum Operation { + Test { + /// maximum M-cycles + #[clap(short='m', long)] + maximum_m_cycles: u64, + /// path to expected serial output + #[clap(short='s', long)] + expected_serial: PathBuf, + }, + GenerateOutput { + /// M-cycles to run for + #[clap(short='m', long)] + m_cycles: u64, + /// path to expected serial output + #[clap(short='s', long)] + expected_serial: PathBuf, + } +} + +#[derive(Debug, thiserror::Error)] +pub enum DmgTestError { + #[error("ROM not found")] + RomNotFound, + #[error("ROM reading error: {0}")] + RomRead(std::io::Error), + #[error("Missing serial output file")] + SerialOutputFileNotFound, + #[error("Error reading serial output file: {0}")] + SerialOutputFileRead(std::io::Error), + #[error("Error writing serial output file: {0}")] + SerialOutputFileWrite(std::io::Error), + #[error("Serial mismatch\nExpected: {0}\nFound: {1}")] + SerialDifferent(String, String), +} + +#[derive(Debug, Clone)] +pub struct SyncWriter(pub Arc>>); + +impl SyncWriter { + pub fn new() -> Self { + Self(Arc::new(RwLock::new(Vec::new()))) + } + + pub fn compare(&self, expected: &[u8]) -> bool { + self.0.read().unwrap().as_slice() == expected + } + + pub fn into_inner(self) -> Vec { + std::sync::Arc::into_inner(self.0).unwrap().into_inner().unwrap() + } +} + +impl SerialWriter for SyncWriter { + fn write_byte(&mut self, byte: u8) { + self.0.write().unwrap().write_byte(byte); + } +} + +fn generate_output(rom: &Path, m_cycles: u64, expected_serial: &Path) -> Result { + let rom = { + if !rom.is_file() { + return Err(DmgTestError::RomNotFound); + } + std::fs::read(rom).map_err(DmgTestError::RomRead)? + }; + + let sync_writer = SyncWriter::new(); + + let mut gameboy = Gameboy::new(None, sync_writer.clone()); + gameboy.load_cartridge(rom); + + let instant = std::time::Instant::now(); + + for _ in 0..m_cycles { + gameboy.tick_4(); + } + + drop(gameboy); + + let serial_content = sync_writer.into_inner(); + std::fs::write(expected_serial, &serial_content).map_err(DmgTestError::SerialOutputFileWrite)?; + + + Ok(instant.elapsed()) +} + +fn run_test(rom: &Path, maximum_m_cycles: u64, expected_serial: &Path) -> Result<(u64, Duration), DmgTestError> { + let rom = { + if !rom.is_file() { + return Err(DmgTestError::RomNotFound); + } + std::fs::read(rom).map_err(DmgTestError::RomRead)? + }; + + let expected_serial = { + if !expected_serial.is_file() { + return Err(DmgTestError::SerialOutputFileNotFound); + } + std::fs::read(expected_serial).map_err(DmgTestError::SerialOutputFileRead)? + }; + + let sync_writer = SyncWriter::new(); + + let mut gameboy = Gameboy::new(None, sync_writer.clone()); + gameboy.load_cartridge(rom); + + let instant = Instant::now(); + + let mut cycle_counter = 0; + + while cycle_counter < maximum_m_cycles { + gameboy.tick_4(); + + cycle_counter += 1; + + if sync_writer.compare(&expected_serial) { + return Ok((cycle_counter, instant.elapsed())); + } + } + + drop(gameboy); + + match sync_writer.compare(&expected_serial) { + true => Ok((cycle_counter, instant.elapsed())), + false => Err(DmgTestError::SerialDifferent(expected_serial.into_iter().map(char::from).collect(), sync_writer.into_inner().into_iter().map(char::from).collect())), + } +} + +fn main() { + let args = CliArgs::parse(); + + match args.operation { + Operation::Test { maximum_m_cycles, expected_serial } => match run_test(args.rom.as_path(), maximum_m_cycles, expected_serial.as_path()) { + Ok((m_cycles, duration)) => { + println!("Success! Ran {} M-Cycles in {}ms", m_cycles, duration.as_millis()); + }, + Err(why) => { + eprintln!("{}", why); + std::process::exit(1); + } + }, + Operation::GenerateOutput { m_cycles, expected_serial } => match generate_output(args.rom.as_path(), m_cycles, expected_serial.as_path()) { + Ok(duration) => { + println!("Successfully written serial output to {} in {} M-Cycles ({}ms), please verify it is correct", expected_serial.display(), m_cycles, duration.as_millis()); + }, + Err(why) => { + eprintln!("{}", why); + std::process::exit(1); + } + }, + } + + +} diff --git a/deemgee/Cargo.lock b/meowgb/Cargo.lock similarity index 100% rename from deemgee/Cargo.lock rename to meowgb/Cargo.lock diff --git a/deemgee/Cargo.toml b/meowgb/Cargo.toml similarity index 89% rename from deemgee/Cargo.toml rename to meowgb/Cargo.toml index 7100d92..abb98ed 100644 --- a/deemgee/Cargo.toml +++ b/meowgb/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "deemgee" +name = "meowgb" version = "0.1.0" edition = "2021" @@ -10,7 +10,7 @@ bmp = "0.5.0" chrono = "0.4.19" clap = { version = "4.4.12", features = ["derive"] } config = "0.13.4" -deemgee-opcode = { path = "../deemgee-opcode" } +meowgb-opcode = { path = "../meowgb-opcode" } env_logger = "0.10.1" log = "0.4.14" paste = "1.0.6" diff --git a/deemgee/src/gameboy.rs b/meowgb/src/gameboy.rs similarity index 98% rename from deemgee/src/gameboy.rs rename to meowgb/src/gameboy.rs index 8a59079..7ce3fbb 100644 --- a/deemgee/src/gameboy.rs +++ b/meowgb/src/gameboy.rs @@ -1,10 +1,11 @@ +pub mod bootrom; mod cpu; mod interrupts; mod joypad; pub mod mapper; mod memory; pub mod ppu; -mod serial; +pub mod serial; mod sound; mod timer; @@ -18,7 +19,7 @@ use timer::Timer; use self::{ cpu::Registers, mapper::{mbc1::MBC1, NoMBC}, - serial::Serial, + serial::{Serial, SerialWriter}, sound::Sound, }; @@ -109,7 +110,7 @@ fn test_ringbuffer() { ); } -pub struct Gameboy { +pub struct Gameboy { pub ppu: Ppu, pub memory: Memory, pub cartridge: Option>, @@ -117,7 +118,7 @@ pub struct Gameboy { pub timer: Timer, pub registers: Registers, pub joypad: Joypad, - pub serial: Serial, + pub serial: Serial, pub dma: DmaState, pub sound: Sound, @@ -137,15 +138,15 @@ pub struct Gameboy { pub tick_count: u8, } -impl Gameboy { - pub fn new(bootrom: Option<[u8; 0x100]>) -> Self { +impl Gameboy { + pub fn new(bootrom: Option<[u8; 0x100]>, serial_writer: S) -> Self { Self { memory: Memory::new(bootrom), cartridge: None, interrupts: Interrupts::new(), timer: Timer::new(), joypad: Joypad::new(), - serial: Serial::new(), + serial: Serial::new(serial_writer), dma: DmaState::new(), ppu: Ppu::new(bootrom.is_some()), registers: match bootrom.is_some() { @@ -342,7 +343,7 @@ impl Gameboy { self.ppu.dump_bg_tiles(); } "dumpfb" => { - println!("Written to: {}", self.ppu.dump_fb()); + println!("Written to: {}", self.ppu.dump_fb_to_file()); } "dumpoam" => { for x in 0..self.ppu.oam.len() { diff --git a/meowgb/src/gameboy/bootrom.rs b/meowgb/src/gameboy/bootrom.rs new file mode 100644 index 0000000..9145888 --- /dev/null +++ b/meowgb/src/gameboy/bootrom.rs @@ -0,0 +1,48 @@ +use std::{path::Path, io::Read}; + +use sha1::{Digest, Sha1}; + +#[derive(Debug, thiserror::Error)] +pub enum BootromParseError { + #[error("Bootrom file cannot be found")] + BootromNotFound, + #[error("IO error whilst reading bootrom: {0}")] + Io(#[from] std::io::Error), + #[error("Bootrom size is {0} bytes, expected 256 bytes")] + InvalidSize(u64), + #[error("Bootrom has an invalid SHA1 (expected \"4ed31ec6b0b175bb109c0eb5fd3d193da823339f\")")] + InvalidHash, + #[error("Failed to open bootrom file: {0}")] + FileOpen(std::io::Error), + #[error("Failed to read bootrom file: {0}")] + FileRead(std::io::Error), +} + +pub fn verify_parse_bootrom(path: &Path) -> Result<[u8; 0x100], BootromParseError> { + if !path.is_file() { + return Err(BootromParseError::BootromNotFound); + } + + let mut bootrom_slice = [0u8; 0x100]; + + let mut file = std::fs::File::open(path).map_err(BootromParseError::FileOpen)?; + let metadata = file.metadata()?; + + if metadata.len() != 256 { + return Err(BootromParseError::InvalidSize(metadata.len())); + } + + file.read_exact(&mut bootrom_slice).map_err(BootromParseError::FileRead)?; + + let mut hash_ctx = Sha1::new(); + hash_ctx.update(&bootrom_slice); + let digest = hash_ctx.finalize(); + + if digest.as_slice() + != b"\x4e\xd3\x1e\xc6\xb0\xb1\x75\xbb\x10\x9c\x0e\xb5\xfd\x3d\x19\x3d\xa8\x23\x33\x9f" + { + return Err(BootromParseError::InvalidHash); + } + + Ok(bootrom_slice) +} \ No newline at end of file diff --git a/deemgee/src/gameboy/cpu.rs b/meowgb/src/gameboy/cpu.rs similarity index 99% rename from deemgee/src/gameboy/cpu.rs rename to meowgb/src/gameboy/cpu.rs index c41f492..8e3ecf1 100644 --- a/deemgee/src/gameboy/cpu.rs +++ b/meowgb/src/gameboy/cpu.rs @@ -4,7 +4,7 @@ mod load_store_move; mod misc; mod prefixed; -use super::Gameboy; +use super::{Gameboy, serial::SerialWriter}; macro_rules! define_register { ($lident:ident, $rident:ident) => { @@ -140,7 +140,7 @@ impl Registers { } } -pub fn tick_cpu(state: &mut Gameboy) { +pub fn tick_cpu(state: &mut Gameboy) { state.registers.mem_op_happened = false; if state.joypad.interrupt_triggered { diff --git a/deemgee/src/gameboy/cpu/alu.rs b/meowgb/src/gameboy/cpu/alu.rs similarity index 99% rename from deemgee/src/gameboy/cpu/alu.rs rename to meowgb/src/gameboy/cpu/alu.rs index 05a8e7d..e3defb3 100644 --- a/deemgee/src/gameboy/cpu/alu.rs +++ b/meowgb/src/gameboy/cpu/alu.rs @@ -1,4 +1,4 @@ -use deemgee_opcode::opcode; +use meowgb_opcode::opcode; use super::CycleResult; use crate::gameboy::Gameboy; diff --git a/deemgee/src/gameboy/cpu/flow.rs b/meowgb/src/gameboy/cpu/flow.rs similarity index 99% rename from deemgee/src/gameboy/cpu/flow.rs rename to meowgb/src/gameboy/cpu/flow.rs index a20a9e2..4c6ad8d 100644 --- a/deemgee/src/gameboy/cpu/flow.rs +++ b/meowgb/src/gameboy/cpu/flow.rs @@ -1,4 +1,4 @@ -use deemgee_opcode::opcode; +use meowgb_opcode::opcode; use super::CycleResult; use crate::gameboy::Gameboy; diff --git a/deemgee/src/gameboy/cpu/load_store_move.rs b/meowgb/src/gameboy/cpu/load_store_move.rs similarity index 99% rename from deemgee/src/gameboy/cpu/load_store_move.rs rename to meowgb/src/gameboy/cpu/load_store_move.rs index 97d0a46..870e021 100644 --- a/deemgee/src/gameboy/cpu/load_store_move.rs +++ b/meowgb/src/gameboy/cpu/load_store_move.rs @@ -1,4 +1,4 @@ -use deemgee_opcode::opcode; +use meowgb_opcode::opcode; use crate::gameboy::{cpu::CycleResult, Gameboy}; diff --git a/deemgee/src/gameboy/cpu/misc.rs b/meowgb/src/gameboy/cpu/misc.rs similarity index 98% rename from deemgee/src/gameboy/cpu/misc.rs rename to meowgb/src/gameboy/cpu/misc.rs index a6a2c29..5c33229 100644 --- a/deemgee/src/gameboy/cpu/misc.rs +++ b/meowgb/src/gameboy/cpu/misc.rs @@ -1,4 +1,4 @@ -use deemgee_opcode::opcode; +use meowgb_opcode::opcode; use super::CycleResult; use crate::gameboy::Gameboy; diff --git a/deemgee/src/gameboy/cpu/prefixed.rs b/meowgb/src/gameboy/cpu/prefixed.rs similarity index 99% rename from deemgee/src/gameboy/cpu/prefixed.rs rename to meowgb/src/gameboy/cpu/prefixed.rs index d29235c..bf54e9b 100644 --- a/deemgee/src/gameboy/cpu/prefixed.rs +++ b/meowgb/src/gameboy/cpu/prefixed.rs @@ -1,9 +1,9 @@ -use deemgee_opcode::opcode; +use meowgb_opcode::opcode; use super::CycleResult; -use crate::gameboy::Gameboy; +use crate::gameboy::{Gameboy, serial::SerialWriter}; -pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult { +pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult { let opcode = match state.registers.current_prefixed_opcode { Some(prefixed_opcode) => prefixed_opcode, None => match state.registers.mem_read_hold.take() { diff --git a/deemgee/src/gameboy/interrupts.rs b/meowgb/src/gameboy/interrupts.rs similarity index 100% rename from deemgee/src/gameboy/interrupts.rs rename to meowgb/src/gameboy/interrupts.rs diff --git a/deemgee/src/gameboy/joypad.rs b/meowgb/src/gameboy/joypad.rs similarity index 100% rename from deemgee/src/gameboy/joypad.rs rename to meowgb/src/gameboy/joypad.rs diff --git a/deemgee/src/gameboy/mapper.rs b/meowgb/src/gameboy/mapper.rs similarity index 100% rename from deemgee/src/gameboy/mapper.rs rename to meowgb/src/gameboy/mapper.rs diff --git a/deemgee/src/gameboy/mapper/mbc1.rs b/meowgb/src/gameboy/mapper/mbc1.rs similarity index 100% rename from deemgee/src/gameboy/mapper/mbc1.rs rename to meowgb/src/gameboy/mapper/mbc1.rs diff --git a/deemgee/src/gameboy/memory.rs b/meowgb/src/gameboy/memory.rs similarity index 100% rename from deemgee/src/gameboy/memory.rs rename to meowgb/src/gameboy/memory.rs diff --git a/deemgee/src/gameboy/ppu.rs b/meowgb/src/gameboy/ppu.rs similarity index 98% rename from deemgee/src/gameboy/ppu.rs rename to meowgb/src/gameboy/ppu.rs index 847d6c4..70eb3eb 100644 --- a/deemgee/src/gameboy/ppu.rs +++ b/meowgb/src/gameboy/ppu.rs @@ -564,8 +564,8 @@ impl Ppu { oam_entry.y.wrapping_sub(16).wrapping_add(sprite_height as u8); if oam_entry.x > 0 - && self.ly.wrapping_add(self.scy) < real_oam_y - && self.ly.wrapping_add(self.scy) >= oam_entry.y.wrapping_sub(16) + && self.ly < real_oam_y + && self.ly >= oam_entry.y.wrapping_sub(16) && self.sprite_count < 10 { self.sprite_buffer[self.sprite_count] = Some(oam_entry); @@ -744,6 +744,8 @@ impl Ppu { mut window_drawn, draw_only_sprites, ) => { + // assert_eq!(scy, self.scy); + // assert_eq!(scx, self.scx); let wx_match = (drawn_pixels as usize + 7) >= self.wx as usize; let scrolled_y = self.ly.wrapping_add(scy) as usize; let scrolled_x = drawn_pixels.wrapping_add(scx) as usize; @@ -762,13 +764,14 @@ impl Ppu { if self.window_enabled() && wx_match && self.wy_match { window_drawn = true; - let tilemap_idx = - drawn_pixels as usize / 8 + ((self.ly as usize / 8) * 32); + let window_x = (drawn_pixels as u8).wrapping_sub(self.wx.wrapping_sub(7)) as usize; + let window_y = self.ly.wrapping_sub(self.wy) as usize; + let tilemap_idx = window_x / 8 + ((window_y / 8) * 32); let tilemap_value = self.read_window_tile_map()[tilemap_idx]; (bg_color_id, bg_color) = Self::parse_tile_color( self.read_bg_win_tile(tilemap_value), - drawn_pixels as usize % 8, - self.ly as usize % 8, + window_x % 8, + window_y % 8, &self.bgp, ); } @@ -889,7 +892,7 @@ impl Ppu { out } - pub fn dump_fb(&self) -> String { + pub fn dump_fb_to_file(&self) -> String { let mut image = bmp::Image::new(FB_WIDTH, FB_HEIGHT); for y in 0..FB_HEIGHT { diff --git a/deemgee/src/gameboy/serial.rs b/meowgb/src/gameboy/serial.rs similarity index 56% rename from deemgee/src/gameboy/serial.rs rename to meowgb/src/gameboy/serial.rs index b9b51c3..56dd5e7 100644 --- a/deemgee/src/gameboy/serial.rs +++ b/meowgb/src/gameboy/serial.rs @@ -1,15 +1,27 @@ use std::io::Write; -pub struct Serial { +pub trait SerialWriter { + fn write_byte(&mut self, byte: u8); +} + +impl SerialWriter for T { + fn write_byte(&mut self, byte: u8) { + self.write_all(&[byte]).expect(format!("writing serial to {} failed", std::any::type_name::()).as_str()); + self.flush().expect(format!("flushing serial to {} failed", std::any::type_name::()).as_str()); + } +} + +pub struct Serial { pub sb: u8, pub sc: u8, internal_tick: u16, + writer: S, } -impl Serial { - pub fn new() -> Serial { - Self { sb: 0, sc: 0, internal_tick: 0 } +impl Serial { + pub fn new(writer: S) -> Serial { + Self { sb: 0, sc: 0, internal_tick: 0, writer } } pub fn set_transfer_in_process(&mut self, value: bool) { @@ -36,8 +48,7 @@ impl Serial { if self.internal_tick < 128 { self.internal_tick += 1; } else { - std::io::stdout().write_all(&[self.sb]).expect("writing stdout failed"); - std::io::stdout().flush().expect("flushing stdout failed"); + self.writer.write_byte(self.sb); self.sb = 0; self.set_transfer_in_process(false); self.internal_tick = 0; diff --git a/deemgee/src/gameboy/sound.rs b/meowgb/src/gameboy/sound.rs similarity index 100% rename from deemgee/src/gameboy/sound.rs rename to meowgb/src/gameboy/sound.rs diff --git a/deemgee/src/gameboy/timer.rs b/meowgb/src/gameboy/timer.rs similarity index 100% rename from deemgee/src/gameboy/timer.rs rename to meowgb/src/gameboy/timer.rs diff --git a/deemgee/src/lib.rs b/meowgb/src/lib.rs similarity index 84% rename from deemgee/src/lib.rs rename to meowgb/src/lib.rs index a868e86..c067f8c 100644 --- a/deemgee/src/lib.rs +++ b/meowgb/src/lib.rs @@ -1,13 +1,13 @@ +use std::io::Stdout; + pub mod gameboy; #[allow(unused)] mod settings; -#[allow(unused)] -mod window; pub fn setup_test_emulator( test_opcodes: [u8; ROM_LENGTH], -) -> gameboy::Gameboy { - let mut gameboy = gameboy::Gameboy::new(None); +) -> gameboy::Gameboy { + let mut gameboy = gameboy::Gameboy::new(None, std::io::stdout()); let mut cartridge = gameboy::mapper::NoMBC { rom: [0u8; 0x8000], ram: None }; diff --git a/deemgee/src/main.rs b/meowgb/src/main.rs similarity index 76% rename from deemgee/src/main.rs rename to meowgb/src/main.rs index 9c9d65f..eb5166c 100644 --- a/deemgee/src/main.rs +++ b/meowgb/src/main.rs @@ -10,9 +10,8 @@ use std::{ use chrono::{Duration, Utc}; use clap::Parser; -use gameboy::Gameboy; +use gameboy::{Gameboy, bootrom::{BootromParseError, verify_parse_bootrom}}; use settings::DeemgeeConfig; -use sha1::{Digest, Sha1}; use window::EmulatorWindowEvent; use crate::window::GameboyEvent; @@ -33,12 +32,8 @@ pub struct CliArgs { #[derive(Debug, thiserror::Error)] pub enum DmgError { - #[error("Bootrom Not Found")] - BootromNotFound, - #[error("Bootrom Incorrect Size (expected 256 bytes, found {0} bytes)")] - BootromInvalidSize(u64), - #[error("Bootrom SHA1 failed (expected 4ed31ec6b0b175bb109c0eb5fd3d193da823339f)")] - BootromInvalidHash, + #[error(transparent)] + Bootrom(BootromParseError), #[error("Game Not Found")] GameNotFound, #[error("IO Error: {0}")] @@ -79,42 +74,12 @@ pub fn run_gameboy( rx: Receiver, tx: Sender, ) -> Result<(), DmgError> { - let mut bootrom = None; - if let Some(bootrom_path) = args.bootrom { - if !bootrom_path.is_file() { - return Err(DmgError::BootromNotFound); - } + let bootrom = match args.bootrom.as_deref() { + Some(path) => Some(verify_parse_bootrom(path).map_err(DmgError::Bootrom)?), + None => None, + }; - let brom_md = std::fs::metadata(bootrom_path.as_path())?; - - if brom_md.len() != 256 { - return Err(DmgError::BootromInvalidSize(brom_md.len())); - } - - let mut bootrom_slice = [0u8; 0x100]; - - let bootrom_vec = std::fs::read(bootrom_path)?; - - if bootrom_vec.len() != 256 { - return Err(DmgError::BootromInvalidSize(bootrom_vec.len() as u64)); - } - - let mut hash_ctx = Sha1::new(); - hash_ctx.update(&bootrom_vec); - let digest = hash_ctx.finalize(); - - if digest.as_slice() - != b"\x4e\xd3\x1e\xc6\xb0\xb1\x75\xbb\x10\x9c\x0e\xb5\xfd\x3d\x19\x3d\xa8\x23\x33\x9f" - { - return Err(DmgError::BootromInvalidHash); - } - - bootrom_slice.copy_from_slice(&bootrom_vec); - - bootrom = Some(bootrom_slice) - } - - let mut gameboy = Gameboy::new(bootrom); + let mut gameboy = Gameboy::new(bootrom, std::io::stdout()); if args.debug { gameboy.single_step = true; diff --git a/deemgee/src/settings.rs b/meowgb/src/settings.rs similarity index 100% rename from deemgee/src/settings.rs rename to meowgb/src/settings.rs diff --git a/deemgee/src/window.rs b/meowgb/src/window.rs similarity index 100% rename from deemgee/src/window.rs rename to meowgb/src/window.rs diff --git a/deemgee/tests/flow_opcodes.rs b/meowgb/tests/flow_opcodes.rs similarity index 99% rename from deemgee/tests/flow_opcodes.rs rename to meowgb/tests/flow_opcodes.rs index fb6ff5b..cd84faa 100644 --- a/deemgee/tests/flow_opcodes.rs +++ b/meowgb/tests/flow_opcodes.rs @@ -1,4 +1,4 @@ -use deemgee::setup_test_emulator; +use meowgb::setup_test_emulator; macro_rules! conditional_jump_relative_testgen { ($flag:ident, $not_opcode:literal, $opcode:literal) => { diff --git a/deemgee/tests/load_store_move_opcodes.rs b/meowgb/tests/load_store_move_opcodes.rs similarity index 98% rename from deemgee/tests/load_store_move_opcodes.rs rename to meowgb/tests/load_store_move_opcodes.rs index 3d9095f..caabcfc 100644 --- a/deemgee/tests/load_store_move_opcodes.rs +++ b/meowgb/tests/load_store_move_opcodes.rs @@ -1,4 +1,4 @@ -use deemgee::setup_test_emulator; +use meowgb::setup_test_emulator; macro_rules! ld_reg_imm_u16_testgen { ($hireg:ident, $loreg:ident, $opcode:literal) => { diff --git a/deemgee/tests/misc_opcodes.rs b/meowgb/tests/misc_opcodes.rs similarity index 97% rename from deemgee/tests/misc_opcodes.rs rename to meowgb/tests/misc_opcodes.rs index b9477d3..8aac8a6 100644 --- a/deemgee/tests/misc_opcodes.rs +++ b/meowgb/tests/misc_opcodes.rs @@ -1,4 +1,4 @@ -use deemgee::setup_test_emulator; +use meowgb::setup_test_emulator; #[test] fn test_nop() { diff --git a/test-roms/blargg/LICENSE b/test-roms/blargg/LICENSE new file mode 100644 index 0000000..88b6187 --- /dev/null +++ b/test-roms/blargg/LICENSE @@ -0,0 +1 @@ +All rights of these test ROMs belong to Blargg. They were never shipped with a license, and both of the offical websites for them are down now. If they do not want these ROMs being used here, please contact me and I will remove them. diff --git a/test-roms/blargg/roms/cpu_instrs.gb b/test-roms/blargg/roms/cpu_instrs.gb new file mode 100644 index 0000000000000000000000000000000000000000..7b06221b23dcd84644e7910bb8ba91a957c9ba04 GIT binary patch literal 65536 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 MfFZyT_;UpQ3qRkrD*ylh literal 0 HcmV?d00001 diff --git a/test-roms/blargg/roms/instr_timing.gb b/test-roms/blargg/roms/instr_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..61d2b207161f63a9b4d89f9a8ed81dc3bc6e50c5 GIT binary patch literal 32768 zcmeI4Z)_9i9mk)$vyTuQlK`r25W+dMZa`tUt=2`GdqNbIp$SrLh)UI}Ts2~r;U-yv zh-n-zkZJ3-7hAPA>zh^6q)vM?*1cF7kdU3TO|IcVLn_fBmDN_8Otola7u}!~+50|c zhZozVOt5KU`aQYle)oO;{Qi9I;mE64-B~7@b)VMT{4n3Fjq>GBVzEmtY@IU7rdWWr zu%@-@i1tbp86ZH_o3w_U*yojZ4?xf8)*bGfx=@4t?|RH~J1AIC$XDew03m zE_n#f{`Gm!K18?uZ7dyZ-MOJ%|9n%6ek)>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm`6D2w3{;f+4S=-kB;^qWkrKo z$7k>OoM@N-nXV7D^^fTyF zyG7ym`iFRZ<;nBsmWN5g5Bz}q& zLF|hx{BEksvPZ0L-L)rnFJ08tJIq<~r6-n}^0)i=53EPng)ZL<{4dt{ZXv_-7v|2* zLE2Xr!}g(xknVK0bgeU3OPAjWcN$LTIQM0XSL_hGBs~8Co^RP5j9<1c=xaM_o|Qaa z&+gTcIy@~-ghEcIF<;UhvDbDd@Xn2|;P_QQ(%6NElz7nbP_J16@?OL0EQKATyYx6V zXG>kqSW}0Slfq70*a?Llu&69rx^wf14yX7TuqqmiJYEuDcnV-^hx3 zTeCmS!u>=x#Lfg6&%*vBSc5e-mA2p*mD(NSc4@1_pUi3B3b9|RzOioo?xTMFwv99O z9kmswYQXqDI{Vk%uU|K$QZG&+g1}#I&2AgZsb)@X+Pv|1ot^NM@#{DPyeDhCUf2Ua z&l(>U_FG4C-cVa%PsKiq@5OM_#@ zx+C6~L&CVjOK$PbRwv(t8Vo(O*-DTWXTTopQ-xNf}GOSp5t*{rzPb?k^hj;8~ z+v059+&PXIxA9I_KAxIrOk?qQ+#8Blw@jr*B)fUJ+M8)jK!k0Xbe-DmV(22i$qL3q#BAb(`2$5fWtj38XG(ri$$3x_G>9< zF_B`afEG|O9%zroQ;3x^ld8~B(@cu>DX}aP!TOlEgf-MakBBR-hK(>0C8CL35gr$w z#m;&h)QkAv9W)K5!9gk(W64-dTo^Y^6Mhr%SS%J#AikM2P2}ZSlNqKth>#4k*cM|^ zk-ipXs%Y4UJ;T_GQbkL#{Bm_6pU<<{$y5p(MZRcJFCG?n`egiRPa~v=9~FK8Q8W<; z&5IX{Ml}Xs3)N-#i$z81VqTM^{y(6JPBdn5_$Vh_d*6-KS524h<#po za2eLL0Mf!b^V46|k1n1W;|H?hLq9M$dSayS#K0>9gU6c1*WWEj#_59W8cpKY4o7_4 zE0&bw*RuSNbK*Z9t$Od@j_2q>Z;~RR$#nzE8d?^^(DEwUTlPP_DT;zTB}rk5(y%L< zA%6*fnn=`vc~;j_Lt_H7ij12q`Q^H04GrUs%MA^APkRv?x?Vb_ytcX(eq>n$7H#$Q ztF4n2#i#gW{GfgnR)Jm745JGEk`c5|@`?I|mV{PhpQzurTK!m$BtZ{ZdlnC({uRAi ze*ZY{mH!{C->>+5D(dn3eE4XAQykFRck@BXx)A=>@g`;GhH`yTZBLHmgS z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y i5g-CYfCvx)B0vO)01+SpM1Tko0U|&Ih`@i7z~2Eshv>Bc literal 0 HcmV?d00001 diff --git a/test-roms/blargg/roms/mem_timing.gb b/test-roms/blargg/roms/mem_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..78766b579f74433332c546c9cf06f8b972a4c2d8 GIT binary patch literal 65536 zcmeI4Z*Ual9mk*Bz1ti~I1*s&0s%Gz(-1_?f0*jIGD=|_OG0NHu}&pNm7~ZJP$*nN za{QZ8+nHLO&RBg>tkV{!Z)EHX5Sd~2Zn?;MJdCt@J*T33VkllQa|d082zUKHdm;Rh zI@*pew7;GEJRQJ(XrGQBt$h+RnUV1Y&rJ=o!=5(|GHcI4M}LoxlZtm}AG?V%pC;*V zR&t7#)5|rfPo%8Rj@nY0UbYQ8VJB_Va#C*%G@9l4v)X6fKkKDtf4VI_Aw6+wu&y&U z_|byy!B;wZ2DdHVIH+B#eb8K<_V{&=UAHE8BvG{b@=~TZFK1M*ZmE)P<~JOTpjFVDpofI-^0*~x{bCn#Z=BwoXAUcDQD6_p!2hVNkh#}c+&7ay9zc2R)#g}n_*@9T782( zz42Ey{Pz-nQs&(?yi;;?na`GVcinKYQuytPI~KI$@a^8BxwFO*J$_j#mONQ4AO<#n z>5AyGN2*y4NG_zbCUCjWokHQ#y40tIoC&&aU!(i&Z|R0Tq0oF>(uOR(-mXK|SoCe2 zzRezwY!9+iC_XU|Y<<5r{aDM{gtg0VzQb;|ueGP^MHJp{kN-@styG8NN3}Y*t=G?> zxp2BZDCxgL`JajM9&uxg69Yu4v5x*daq82u2`d!{x{ii$+M%#Q^9wCw9DR%^e^?aq zYA0h@eglooWw-ltF_aR9f??>=PVM|~*_7LMxpJY<%os-aL9S@+(G=VSe^uqmo$BZT z(Q;Ol#$B7YhFIo!-NC}bsyEc?k@M{7LfP46mobGwo9P~VVI3>lWh=!Gdf|9HfYrO; zaPHZ0UUtEq)mb{Gc|r-x40^2_mShwbV6pU_={rMtrp3~?9uIyzckvW9v1BHZY%+jQMr6QmINBu?dAKt?!<`cWA?Z}b!AenSbC$i6J}9~h4>V-KM6NZz9FNZ$xqJkoBiq!8`-mpcf)ns zx#>Rbj9Ae>R_cI$g}w>Ku5Lcbw0)IHmX}j{DQJX=o|o)9_&A zw^iv++jG&*-TDXi0`mdCn^kYmEn2(qB;9;`Gdd%lr7e>OoW@C+)>4%=s#wxms`+%k zQ`|l4maYw0V4@aZ>PLqWqLtLZ(Bw?NA7jw9#)%na{Ea^CoIVMo%V8N;3Zq2k3Hz?1 z^_D2Sy19AAjQUBL$rGn-gpV`1BO_lIsyLX+qS2`P4H7Ly@nKBW!y%k6;p$IOiY8(w#j$km1llX9ov2d74N-Y|k z%R-T^^^r)3siM9bM_OEnvv{pqt02F&Ary_nS3KOSh;%3%?iJ!QB;T|8Hi<%Wb7%%)k+7;HBiU#7528iW6E zZ#ay8xpA23;cypRGF(Nq2n&hst0AU{1NBg64eDa3;)t{U!BVEbzn?|c$K$9d`impv z`a=OXy*@g}O~a+|9}?{VqBw*f9In4eD5Ns9l_?FPy+}xOU0fGksBnS5DQ>kXY_1r+ zBC(j8jcl{zAi6h+*>;-mF2l=`JCcp*% zDrUISOa)bG5$6;8AAD!0s!JldC{I2e-Rjoz0P%eCGM`h{aoH2f{`!rd#Y*pvF z6{}WsEw2{ec8=}Qx7hg3^k002o3fw(y$a7E+{n#pY2?mvo1uUR5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvzQZzut?OEIx@2;x{3^`qW9ix zZ?U)9+bVkRZU3F#I}5#c_M+Z9>*~E9G`J@_toL?f=)Do=_-lr%_kPhB)_V^cd|ZN0 zPVlA#f7O5@aYjPuy+s|Ix_a-j0j1*S=)GqoKBM=3&=7j>n-Z7ly|b6=y&p~+*bPx$ zOA$KN-Hb{5VcTI(!k&TcfbE361e*_Qhusfb0*k=9U~$+fST$@StQB?}tl(qvkgx1b z&u4PF9dC39{vZ|!+%@s;r*BF;lJG8{Tb}r ze*=3~?A=p*tC=YIT9i_`MN%u9_kWIT-bahgJNE9wJ9i&$Uhy*ue%`?E543szg3mH4 zMg)ie5g-CYfCvx)B0vO)z~>X7{Xgyh|JT3&*B%)D`+v=WtN#7J{fhnn=mTHP-~VsH zeqO#=1ZjoI-gH0&*N9+>2%1H3y$I%rKo`Mw5j-h^XGE|=1Up6Wk_dK-V800ZMQ~UI z846v4+LC?UA=SA-Sf7ld~06TAh$6K2o;CQaVB|KLFT%=uS;BcNyaHGtVH zxAEf30T>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y O5g-CYfCzlO2>cuVGucxB literal 0 HcmV?d00001 diff --git a/test-roms/mealybug-tearoom-tests/LICENSE b/test-roms/mealybug-tearoom-tests/LICENSE new file mode 100644 index 0000000..13a7cca --- /dev/null +++ b/test-roms/mealybug-tearoom-tests/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Matt Currie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test-roms/mealybug-tearoom-tests/README.md b/test-roms/mealybug-tearoom-tests/README.md new file mode 100644 index 0000000..0b2bb6e --- /dev/null +++ b/test-roms/mealybug-tearoom-tests/README.md @@ -0,0 +1,5 @@ +# Mealybug Tearoom Tests + +These test ROMs were written by "mattcurrie", and are licensed by them under the MIT license, a copy of their license is in [LICENSE](./LICENSE). + +The original source code can be found at [https://github.com/mattcurrie/mealybug-tearoom-tests](https://github.com/mattcurrie/mealybug-tearoom-tests). diff --git a/test-roms/mealybug-tearoom-tests/roms/intr_1_2_timing-GS.gb b/test-roms/mealybug-tearoom-tests/roms/intr_1_2_timing-GS.gb new file mode 100644 index 0000000000000000000000000000000000000000..64ca5f87bae1d481ba3ce43680a5fe7e9c623eaf GIT binary patch literal 32768 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 literal 0 HcmV?d00001