├── .github └── workflows │ └── lint.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── bin ├── gb2json.rs └── gbstats.rs ├── header.rs ├── lib.rs ├── opcodes.rs ├── parser.rs └── util.rs /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | - "!main" 8 | - "!master" 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | RUST_BACKTRACE: 1 13 | 14 | jobs: 15 | lint: 16 | name: Lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Install Rust 21 | id: rustc_install 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | profile: minimal 25 | override: true 26 | components: rustfmt, clippy 27 | - name: Check 28 | run: | 29 | cargo check 30 | - name: Formatting 31 | run: | 32 | cargo fmt --all -- --check 33 | - name: Clippy 34 | run: | 35 | cargo clippy 36 | - name: Assert no files have changed 37 | run: | 38 | git status 39 | ! [[ $(git status -s) ]] 40 | test: 41 | name: Test 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | - name: Install Rust 46 | id: rustc_install 47 | uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | override: true 51 | - name: Test 52 | run: | 53 | cargo test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /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 = "arrayvec" 7 | version = "0.5.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "gameboy-rom" 25 | version = "0.4.0" 26 | dependencies = [ 27 | "nom", 28 | "serde", 29 | "serde_json", 30 | ] 31 | 32 | [[package]] 33 | name = "itoa" 34 | version = "1.0.8" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" 37 | 38 | [[package]] 39 | name = "lexical-core" 40 | version = "0.7.6" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 43 | dependencies = [ 44 | "arrayvec", 45 | "bitflags", 46 | "cfg-if", 47 | "ryu", 48 | "static_assertions", 49 | ] 50 | 51 | [[package]] 52 | name = "memchr" 53 | version = "2.5.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 56 | 57 | [[package]] 58 | name = "nom" 59 | version = "5.1.3" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" 62 | dependencies = [ 63 | "lexical-core", 64 | "memchr", 65 | "version_check", 66 | ] 67 | 68 | [[package]] 69 | name = "proc-macro2" 70 | version = "1.0.63" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 73 | dependencies = [ 74 | "unicode-ident", 75 | ] 76 | 77 | [[package]] 78 | name = "quote" 79 | version = "1.0.29" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" 82 | dependencies = [ 83 | "proc-macro2", 84 | ] 85 | 86 | [[package]] 87 | name = "ryu" 88 | version = "1.0.14" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" 91 | 92 | [[package]] 93 | name = "serde" 94 | version = "1.0.166" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" 97 | dependencies = [ 98 | "serde_derive", 99 | ] 100 | 101 | [[package]] 102 | name = "serde_derive" 103 | version = "1.0.166" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" 106 | dependencies = [ 107 | "proc-macro2", 108 | "quote", 109 | "syn", 110 | ] 111 | 112 | [[package]] 113 | name = "serde_json" 114 | version = "1.0.100" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" 117 | dependencies = [ 118 | "itoa", 119 | "ryu", 120 | "serde", 121 | ] 122 | 123 | [[package]] 124 | name = "static_assertions" 125 | version = "1.1.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 128 | 129 | [[package]] 130 | name = "syn" 131 | version = "2.0.23" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" 134 | dependencies = [ 135 | "proc-macro2", 136 | "quote", 137 | "unicode-ident", 138 | ] 139 | 140 | [[package]] 141 | name = "unicode-ident" 142 | version = "1.0.10" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" 145 | 146 | [[package]] 147 | name = "version_check" 148 | version = "0.9.4" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 151 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gameboy-rom" 3 | version = "0.4.0" 4 | authors = ["Mark McCaskey "] 5 | edition = "2018" 6 | description = "Gameboy ROM parser" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/MarkMcCaskey/gameboy-rom-parser" 9 | readme = "README.md" 10 | keywords = ["gameboy", "rom", "parser", "gb", "gbc"] 11 | categories = ["emulators", "encoding"] 12 | 13 | [badges] 14 | travis-ci = { repository = "MarkMcCaskey/gameboy-rom-parser", branch = "master" } 15 | 16 | [dependencies] 17 | nom = "5.0" 18 | serde = {version = "1", features = ["derive"]} 19 | serde_json = {version = "1", optional = true} 20 | 21 | [[bin]] 22 | name = "gb2json" 23 | path = "src/bin/gb2json.rs" 24 | required-features = ["serde_json"] 25 | 26 | [[bin]] 27 | name = "gbstats" 28 | path = "src/bin/gbstats.rs" 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GameBoy ROM parser 2 | 3 | [![Build Status](https://travis-ci.org/MarkMcCaskey/gameboy-rom-parser.svg?branch=master)](https://travis-ci.org/MarkMcCaskey/gameboy-rom-parser) 4 | [![Crates.io Version](https://img.shields.io/crates/v/gameboy-rom.svg)](https://crates.io/crates/gameboy-rom) 5 | 6 | A parser to get data out of GB ROMs and perform basic validation. It provides a streaming opcode parser as well as high-level types for inspecting the Gameboy ROM's header. 7 | 8 | ## Demonstration 9 | 10 | ```shell 11 | cargo run --bin gb2json --features="serde_json" -- /path/to/rom/data 12 | cargo run --bin gbstats -- /path/to/rom/data 13 | ``` 14 | 15 | And [here](https://github.com/MarkMcCaskey/rusty-boy/blob/master/src/cpu/cartridge/mod.rs)'s it in use in a real emulator ([rusty-boy]). 16 | 17 | [rusty-boy]: https://github.com/markmccaskey/rusty-boy 18 | -------------------------------------------------------------------------------- /src/bin/gb2json.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | fn main() { 4 | let mut args = std::env::args(); 5 | args.next().unwrap(); 6 | let rom_file_path = if let Some(arg) = args.next() { 7 | arg 8 | } else { 9 | eprintln!("Must supply a path to a gameboy ROM"); 10 | std::process::exit(-1); 11 | }; 12 | let mut file = std::fs::File::open(rom_file_path).expect("gameboy rom file"); 13 | let mut bytes = vec![]; 14 | file.read_to_end(&mut bytes).expect("read bytes from file"); 15 | let gbr = gameboy_rom::GameBoyRom::new(&bytes[..]); 16 | 17 | match gbr.parse_header() { 18 | Ok(rh) => { 19 | println!("{}", serde_json::to_string_pretty(&rh).unwrap()); 20 | println!( 21 | "ROM passes validation check? {}", 22 | if let Err(err) = rh.validate() { 23 | format!("NO: {:?}", err) 24 | } else { 25 | "YES".to_string() 26 | } 27 | ); 28 | } 29 | Err(e) => { 30 | eprintln!("Failed to parse ROM: {:?}", e); 31 | std::process::exit(-1); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/bin/gbstats.rs: -------------------------------------------------------------------------------- 1 | //! A hacky little program to count the frequency of Gameboy instructions. 2 | //! Please note that this doesn't handle relative jumps, has no awareness of ROM 3 | //! banks, and double counts instructions if they were arrived at through a 4 | //! different entry-point. 5 | //! 6 | //! This program is for API demonstration purposes only. 7 | use std::io::Read; 8 | 9 | use gameboy_rom::{opcodes::Opcode, GameBoyRom}; 10 | use std::collections::*; 11 | 12 | fn main() { 13 | let mut args = std::env::args(); 14 | args.next().unwrap(); 15 | let rom_file_path = if let Some(arg) = args.next() { 16 | arg 17 | } else { 18 | eprintln!("Must supply a path to a gameboy ROM"); 19 | std::process::exit(-1); 20 | }; 21 | let mut file = std::fs::File::open(rom_file_path).expect("gameboy rom file"); 22 | let mut bytes = vec![]; 23 | file.read_to_end(&mut bytes).expect("read bytes from file"); 24 | 25 | let gbr = GameBoyRom::new(bytes.as_slice()); 26 | // seen jump locations 27 | let mut seen_locations: HashSet = HashSet::new(); 28 | // locations we want to analyze 29 | let mut to_inspect: VecDeque = VecDeque::new(); 30 | // instructions we've seen 31 | let mut seen_instructions: HashMap = HashMap::new(); 32 | 33 | // init start location 34 | seen_locations.insert(0x100); 35 | to_inspect.push_back(0x100); 36 | 37 | while let Some(loc) = to_inspect.pop_front() { 38 | for o in gbr.get_instructions_at(loc) { 39 | match o { 40 | Opcode::Call(_, address) | Opcode::Jp(_, address) => { 41 | let adj_adr = address as usize; 42 | if !seen_locations.contains(&adj_adr) { 43 | seen_locations.insert(address as _); 44 | to_inspect.push_back(address as _); 45 | } 46 | } 47 | _ => (), 48 | } 49 | let ent = seen_instructions.entry(o.clone()).or_default(); 50 | *ent += 1; 51 | } 52 | } 53 | 54 | let mut vec = seen_instructions 55 | .into_iter() 56 | .collect::>(); 57 | vec.sort_by(|(_, a), (_, b)| b.cmp(&a)); 58 | 59 | println!("The top 10 most common instructions were:"); 60 | for i in 0..10 { 61 | println!( 62 | "{:>2}. {:?} appearing {} times", 63 | (i + 1), 64 | vec[i].0, 65 | vec[i].1 66 | ); 67 | } 68 | 69 | println!(""); 70 | println!("The top 10 least common instructions were:"); 71 | for i in 0..10 { 72 | let elem = vec[vec.len() - i - 1]; 73 | println!("{:>2}. {:?} appearing {} times", (i + 1), elem.0, elem.1); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | //! Data types related to the ROM header that the parser can produce. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The ROM's declared use of Gameboy Color features 6 | #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] 7 | pub enum GameboyColorCompatibility { 8 | /// The ROM indicates that it does not make use of any Gameboy Color enhancements 9 | Monochrome, 10 | /// The ROM supports but does not require Gameboy Color 11 | ColorOptional, 12 | /// The ROM requires Gameboy Color enhancements 13 | ColorRequired, 14 | } 15 | 16 | impl GameboyColorCompatibility { 17 | /// Whether or not the ROM declares it uses GameBoy Color features 18 | pub const fn supports_color(self) -> bool { 19 | match self { 20 | GameboyColorCompatibility::Monochrome => false, 21 | _ => true, 22 | } 23 | } 24 | } 25 | 26 | /// The ROM type as a convenient enum 27 | #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] 28 | pub enum RomType { 29 | RomOnly, 30 | Mbc1, 31 | Mbc1Ram, 32 | Mbc1RamBattery, 33 | Mbc2, 34 | Mbc2Battery, 35 | RomRam, 36 | RomRamBattery, 37 | Mmm01, 38 | Mmm01Sram, 39 | Mmm01SramBattery, 40 | Mbc3TimerBattery, 41 | Mbc3TimerRamBattery, 42 | Mbc3, 43 | Mbc3Ram, 44 | Mbc3RamBattery, 45 | Mbc5, 46 | Mbc5Ram, 47 | Mbc5RamBattery, 48 | Mbc5Rumble, 49 | Mbc5RumbleSram, 50 | Mbc5RumbleSramBattery, 51 | PocketCamera, 52 | Tama5, 53 | Huc3, 54 | Huc1, 55 | Other(u8), 56 | } 57 | 58 | impl From for RomType { 59 | fn from(byte: u8) -> RomType { 60 | match byte { 61 | 0x00 => RomType::RomOnly, 62 | 0x01 => RomType::Mbc1, 63 | 0x02 => RomType::Mbc1Ram, 64 | 0x03 => RomType::Mbc1RamBattery, 65 | 0x05 => RomType::Mbc2, 66 | 0x06 => RomType::Mbc2Battery, 67 | 0x08 => RomType::RomRam, 68 | 0x09 => RomType::RomRamBattery, 69 | 0x0B => RomType::Mmm01, 70 | 0x0C => RomType::Mmm01Sram, 71 | 0x0D => RomType::Mmm01SramBattery, 72 | 0x0F => RomType::Mbc3TimerBattery, 73 | 0x10 => RomType::Mbc3TimerRamBattery, 74 | 0x11 => RomType::Mbc3, 75 | 0x12 => RomType::Mbc3Ram, 76 | 0x13 => RomType::Mbc3RamBattery, 77 | 0x19 => RomType::Mbc5, 78 | 0x1A => RomType::Mbc5Ram, 79 | 0x1B => RomType::Mbc5RamBattery, 80 | 0x1C => RomType::Mbc5Rumble, 81 | 0x1D => RomType::Mbc5RumbleSram, 82 | 0x1E => RomType::Mbc5RumbleSramBattery, 83 | 0x1F => RomType::PocketCamera, 84 | 0xFD => RomType::Tama5, 85 | 0xFE => RomType::Huc3, 86 | 0xFF => RomType::Huc1, 87 | otherwise => RomType::Other(otherwise), 88 | } 89 | } 90 | } 91 | 92 | /// Metadata about the ROM 93 | #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] 94 | pub struct RomHeader<'a> { 95 | pub begin_code_execution_point: &'a [u8], 96 | /// Logo at the start, should match Nintendo Logo 97 | pub scrolling_graphic: &'a [u8], 98 | /// up to 10 ASCII characters 99 | pub game_title: &'a str, 100 | /// gbc bit 101 | pub gameboy_color: GameboyColorCompatibility, 102 | /// 2 ASCII hex digits or zeros 103 | pub licensee_code_new: [u8; 2], 104 | /// sgb bit 105 | pub super_gameboy: bool, 106 | /// how the data after the header will be parsed 107 | pub rom_type: RomType, 108 | /// How many 16KB ROM banks to use 109 | pub rom_size: u16, 110 | /// How many RAM banks are available on the cart 111 | pub ram_banks: u8, 112 | /// The size of the RAM bank in bytes (normal values are 2kB and 8kB) 113 | pub ram_bank_size: u16, 114 | /// jp bit 115 | pub japanese: bool, 116 | pub licensee_code: u8, 117 | pub mask_rom_version: u8, 118 | pub complement: u8, 119 | /// the sum of all bytes in the ROM except these two bytes, truncated to 2 bytes 120 | pub checksum: u16, 121 | } 122 | 123 | impl<'a> RomHeader<'a> { 124 | /// checks that the ROM header is internally consistent. 125 | /// warning: this doesn't guarantee that the entire ROM header is well formed 126 | /// TODO: consider parsing into unvalidated form (just segmented bytes and then doing a translation step...) 127 | pub fn validate(&self) -> Result<(), HeaderValidationError> { 128 | // TODO: look into if international copyright law actually protects these 48 bytes 129 | // for now just validate proxy metrics of the logo 130 | const XOR_RESULT: u8 = 134; 131 | const SUM_RESULT: u16 = 5446; 132 | const OR_RESULT: u8 = 255; 133 | const AND_RESULT: u8 = 0; 134 | if self 135 | .scrolling_graphic 136 | .iter() 137 | .map(|x| *x as u16) 138 | .sum::() 139 | != SUM_RESULT 140 | || self.scrolling_graphic.iter().fold(0, |a, b| a | b) != OR_RESULT 141 | || self.scrolling_graphic.iter().fold(0, |a, b| a & b) != AND_RESULT 142 | || self.scrolling_graphic.iter().fold(0, |a, b| a ^ b) != XOR_RESULT 143 | { 144 | return Err(HeaderValidationError::ScrollingLogoMismatch); 145 | } 146 | if self.super_gameboy && self.licensee_code != 0x33 { 147 | return Err(HeaderValidationError::SuperGameBoyOldLicenseeCodeMismatch); 148 | } 149 | Ok(()) 150 | } 151 | } 152 | 153 | /// Errors that may occur while attempting to validate a ROM header. 154 | #[derive(Debug, PartialEq, Eq)] 155 | pub enum HeaderValidationError { 156 | /// SGB requires the old licensee code to be 0x33 157 | SuperGameBoyOldLicenseeCodeMismatch, 158 | /// Apparent mismatch on scrolling logo 159 | ScrollingLogoMismatch, 160 | } 161 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A parser for Gameboy ROMS. 2 | //! 3 | //! This crate provides a streaming Gameboy instruction parser as well as some 4 | //! high-level types like `RomHeader` and `RomType`. 5 | //! 6 | //! Basic validation is provided through the `validate` method on `RomHeader`. 7 | //! 8 | //! Header logic based on info from the [GB CPU Manual]. 9 | //! 10 | //! Opcode parsing logic was created with this [opcode table] as a reference. 11 | //! 12 | //! Information from other places is and other places is called out in comments in the relevant files 13 | //! 14 | //! [GB CPU Manual]: http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf 15 | //! [opcode table]: https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html 16 | //! 17 | //! Basic usage: 18 | //! ``` 19 | //! # fn example() -> Result<(), String> { 20 | //! # let rom_buffer = vec![0; 0x100]; 21 | //! let rom = gameboy_rom::GameBoyRom::new(rom_buffer.as_slice()); 22 | //! let rom_header = rom.parse_header()?; 23 | //! # Ok(()) 24 | //! # } 25 | //! ``` 26 | 27 | pub mod header; 28 | pub mod opcodes; 29 | mod parser; 30 | pub mod util; 31 | 32 | pub use crate::header::*; 33 | pub use crate::opcodes::*; 34 | 35 | /// Top level type for dealing with GameBoy ROMs. 36 | #[derive(Debug)] 37 | pub struct GameBoyRom<'rom> { 38 | rom_data: &'rom [u8], 39 | } 40 | 41 | impl<'rom> GameBoyRom<'rom> { 42 | /// Create a new instance of the `GameBoyRom`. 43 | pub fn new(rom_bytes: &'rom [u8]) -> Self { 44 | Self { 45 | rom_data: rom_bytes, 46 | } 47 | } 48 | 49 | /// Parse the ROM header and return a high level type containing its data. 50 | pub fn parse_header(&self) -> Result { 51 | parser::parse_rom_header(self.rom_data) 52 | .map_err(|e| format!("Failed to parse ROM: {:?}", e)) 53 | .map(|(_, rh)| rh) 54 | } 55 | 56 | /// Get an iterator over the instructions starting at the given address. 57 | pub fn get_instructions_at(&self, address: usize) -> OpcodeStreamer { 58 | OpcodeStreamer::new(self.rom_data, address) 59 | } 60 | } 61 | 62 | /// Streaming parser over GameBoy [`Opcode`]s. 63 | pub struct OpcodeStreamer<'rom> { 64 | rom_data: &'rom [u8], 65 | current_index: usize, 66 | } 67 | 68 | impl<'rom> OpcodeStreamer<'rom> { 69 | pub(crate) fn new(rom_bytes: &'rom [u8], start: usize) -> Self { 70 | Self { 71 | rom_data: rom_bytes, 72 | current_index: start, 73 | } 74 | } 75 | } 76 | 77 | impl<'rom> Iterator for OpcodeStreamer<'rom> { 78 | type Item = Opcode; 79 | 80 | fn next(&mut self) -> Option { 81 | match parser::parse_instruction(&self.rom_data[self.current_index..]) { 82 | Ok((i, op)) => { 83 | // Compare the pointers to find out how many bytes we read 84 | let offset = 85 | i.as_ptr() as usize - (&self.rom_data[self.current_index..]).as_ptr() as usize; 86 | self.current_index += offset; 87 | 88 | Some(op) 89 | } 90 | Err(_) => None, 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/opcodes.rs: -------------------------------------------------------------------------------- 1 | //! Types related to the opcodes of the Gameboy parser. 2 | 3 | /// An 8 bit register. 4 | /// 5 | /// Includes the `DerefHL` variant which represents a memory access at the value 6 | /// contained in the `HL` 16 bit register. 7 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 8 | pub enum Register8 { 9 | A, 10 | B, 11 | C, 12 | D, 13 | E, 14 | H, 15 | L, 16 | DerefHL, 17 | } 18 | 19 | /// A 16 bit register. 20 | /// 21 | /// Does not include `PC`, the program counter, used to indicate which 22 | /// instruction is being executed next. 23 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 24 | pub enum Register16 { 25 | /// 16 bit register that's made up of 2 8 bit registers, `B` and `C`. 26 | BC, 27 | /// 16 bit register that's made up of 2 8 bit registers, `D` and `E`. 28 | DE, 29 | /// 16 bit register that's made up of 2 8 bit registers, `H` and `L`. 30 | HL, 31 | /// 16 bit register that's made up of 2 8 bit registers, `A` and `F`. 32 | AF, 33 | /// The stack pointer. 34 | SP, 35 | } 36 | 37 | /// Flags relevant to the operation of the instruction. 38 | /// 39 | /// This flag is used to specify which flag the Opcode cares about, it is not 40 | /// used to indicate which flags an instruction may set. 41 | /// 42 | /// For example, `Opcode::Jp(Some(Flag::NZ), 0x1234)` says that the instruction 43 | /// is a `Jp` (jump) instruction that will jump to location 0x1234, if the `NZ` 44 | /// (not zero) flag is set. 45 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 46 | pub enum Flag { 47 | /// The Carry flag. 48 | C, 49 | /// The Zero Flag. 50 | Z, 51 | /// The inverse of the Carry flag. 52 | NC, 53 | /// The inverse of the Zero flag. 54 | NZ, 55 | } 56 | 57 | /// The instructions of the Gameboy. 58 | /// 59 | /// The naming tends to be of the form: `ActionDestSrc` when there is ambiguity. 60 | /// 61 | /// For example, `Opcode::StoreImm16AddrSp` means that the `SP` register should 62 | /// be stored at the address specified by the immediate 16 bit value. 63 | /// 64 | /// These docs don't intend to include complete explanations of the instructions, 65 | /// though the comments below may provide a basic overview. 66 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 67 | pub enum Opcode { 68 | /// No operation. 69 | Nop, 70 | /// The Gameboy enters a very low-power STOP state, graphics will not continue to draw. 71 | Stop, 72 | /// The Gameboy enters a low-power HALT state. 73 | Halt, 74 | /// Store an immediate value into a 16 bit register. 75 | StoreImm16(Register16, u16), 76 | /// Store an immediate value into an 8 bit register. 77 | StoreImm8(Register8, u8), 78 | /// Store A at (HL) and increment or decrement HL; true means inc 79 | StoreAToHlAddr(bool), 80 | /// Load A from (HL) and increment or decrement HL; true means inc 81 | LoadAFromHlAddr(bool), 82 | /// Store A to the value pointed at by register 16 (must be BC or DE) 83 | StoreATo16(Register16), 84 | /// Loads A from value pointed at by register 16 (must be BC or DE) 85 | LoadAFromReg16Addr(Register16), 86 | Mov8(Register8, Register8), 87 | /// Relative jump based on flag to offset 88 | Jr(Option, u8), 89 | /// Jump based on flag to offset 90 | Jp(Option, u16), 91 | /// Increment an 8 bit regsiter. 92 | Inc8(Register8), 93 | /// Decrement an 8 bit regsiter. 94 | Dec8(Register8), 95 | /// Increment a 16 bit regsiter. 96 | Inc16(Register16), 97 | /// Decrement a 16 bit regsiter. 98 | Dec16(Register16), 99 | /// Push the value in the given register onto the stack. 100 | Push(Register16), 101 | /// Pop a value off the stack and load it into the given register. 102 | Pop(Register16), 103 | /// Add the given regsiter to the A. 104 | Add(Register8), 105 | /// Add the given regsiter to the A with carry. 106 | Adc(Register8), 107 | /// Subtract the given regsiter from the A. 108 | Sub(Register8), 109 | /// Subtract the given regsiter from the A with carry. 110 | Sbc(Register8), 111 | /// Bitwise AND the given register with the A. 112 | And(Register8), 113 | /// Bitwise XOR the given register with the A. 114 | Xor(Register8), 115 | /// Bitwise OR the given register with the A. 116 | Or(Register8), 117 | /// Compare the value of the given register with the A and set flags. 118 | Cp(Register8), 119 | /// Add an immediate value to the A. 120 | Add8(u8), 121 | /// Add an immediate value to the A with carry. 122 | Adc8(u8), 123 | /// Subtract an immediate value from the A. 124 | Sub8(u8), 125 | /// Subtract an immediate value from the A with carry. 126 | Sbc8(u8), 127 | /// Bitwise AND an immediate value with the A. 128 | And8(u8), 129 | /// Bitwise XOR an immediate value with the A. 130 | Xor8(u8), 131 | /// Bitwise OR an immediate value with the A. 132 | Or8(u8), 133 | /// Compare the immediate value with the A and set flags. 134 | Cp8(u8), 135 | /// Add the immediate value to the Program Counter and load it into SP. 136 | /// TODO: check this explanation 137 | AddSp8(u8), 138 | /// Converts the value in A to its BCD form. 139 | /// TODO: double check this 140 | Daa, 141 | /// TODO: document this 142 | Scf, 143 | /// Bitwise negate the value in the A. 144 | Cpl, 145 | /// TODO: document this (inverse of SCF?) 146 | Ccf, 147 | /// Rotate A left. 148 | Rlca, 149 | /// Rotate A left through carry. 150 | Rla, 151 | /// Rotate A right. 152 | Rrca, 153 | /// Rotate A right through carry. 154 | Rra, 155 | /// Stores SP at pointer given by immediate 16. 156 | StoreImm16AddrSp(u16), 157 | /// Adds a value to HL. 158 | AddHl(Register16), 159 | /// Conditionally adjusts the program counter and updates the stack pointer. 160 | Ret(Option), 161 | /// Non-conditional `Ret` that also enables interrupts. 162 | Reti, 163 | /// Disable interrupts. 164 | Di, 165 | /// Enable interrupts. 166 | Ei, 167 | /// Conditionally update push the program counter onto the stack and adjusts 168 | /// the program counter. 169 | Call(Option, u16), 170 | /// Gets the value at memory address HL and jumps to it. 171 | JpHl, 172 | /// Contains eight possible values: between 0-8. Value should be multplied 173 | /// by 8 to determine the reset location. 174 | /// TODO: consider simplifying this 175 | Rst(u8), 176 | /// HL = SP + (PC + i8). 177 | /// TODO: double check behavior of relative parameters. 178 | LdHlSp8(i8), 179 | /// Load the value of HL into SP. 180 | LdSpHl, 181 | /// stores A in (u8) 182 | StoreHA(u8), 183 | /// loads A from (u8) 184 | LoadHA(u8), 185 | /// stores A in (C) 186 | StoreCA, 187 | /// Loads A from (C) 188 | LoadCA, 189 | /// LD (a16), A 190 | StoreAAtAddress(u16), 191 | /// LD A, (a16) 192 | LoadAFromAddress(u16), 193 | /// # 0xCB instructions 194 | /// 195 | /// Rotate register left. 196 | Rlc(Register8), 197 | /// Rotate register right. 198 | Rrc(Register8), 199 | /// Rotate register right through carry. 200 | Rr(Register8), 201 | /// Rotate register left through carry. 202 | Rl(Register8), 203 | /// Arithmetic left shift on given register. 204 | Sla(Register8), 205 | /// Arithmetic right shift on given register. 206 | Sra(Register8), 207 | /// Swap low and high nibble (4 bits). 208 | Swap(Register8), 209 | /// Logical Right shift on given register. 210 | Srl(Register8), 211 | /// Set flags based on the given bit in register. 212 | /// u8 is number between 0 and 7 (inclusive). 213 | Bit(u8, Register8), 214 | /// Reset the given bit in the register. 215 | /// u8 is number between 0 and 7 (inclusive) 216 | Res(u8, Register8), 217 | /// Set the given bit in the register. 218 | /// u8 is number between 0 and 7 (inclusive) 219 | Set(u8, Register8), 220 | } 221 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | //! The logic to transform bytes into GameBoy ROM data types 2 | 3 | use crate::header::*; 4 | use crate::opcodes::*; 5 | use crate::util::*; 6 | 7 | use nom::{ 8 | bytes::complete::take, 9 | combinator::*, 10 | error::{context, make_error, VerboseError}, 11 | number::complete::{be_u16, le_u16}, 12 | sequence::tuple, 13 | Err, IResult, 14 | }; 15 | 16 | pub fn parse_scrolling_graphic<'a>( 17 | input: &'a [u8], 18 | ) -> IResult<&'a [u8], &'a [u8], VerboseError<&'a [u8]>> { 19 | context("scrolling graphic", take(0x30usize))(input) 20 | } 21 | 22 | pub fn parse_game_title<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a str, VerboseError<&'a [u8]>> { 23 | context( 24 | "game title as ASCII", 25 | map_res(take(0xFusize), std::str::from_utf8), 26 | )(input) 27 | } 28 | 29 | pub fn parse_gbc_byte<'a>( 30 | input: &'a [u8], 31 | ) -> IResult<&'a [u8], GameboyColorCompatibility, VerboseError<&'a [u8]>> { 32 | let (i, byte) = take(1usize)(input)?; 33 | 34 | match byte[0] { 35 | 0x80 => Ok((i, GameboyColorCompatibility::ColorOptional)), 36 | 0xC0 => Ok((i, GameboyColorCompatibility::ColorRequired)), 37 | _ => Ok((i, GameboyColorCompatibility::Monochrome)), 38 | } 39 | } 40 | 41 | pub fn parse_rom_type<'a>(input: &'a [u8]) -> IResult<&'a [u8], RomType, VerboseError<&'a [u8]>> { 42 | let (i, byte) = take(1usize)(input)?; 43 | 44 | Ok((i, byte[0].into())) 45 | } 46 | 47 | pub fn parse_new_licensee_code<'a>( 48 | input: &'a [u8], 49 | ) -> IResult<&'a [u8], [u8; 2], VerboseError<&'a [u8]>> { 50 | context( 51 | "new licensee code", 52 | map(take(2usize), |bytes: &'a [u8]| [bytes[0], bytes[1]]), 53 | )(input) 54 | } 55 | 56 | /// 3 is SGB 57 | /// 0 is GB 58 | pub fn parse_sgb_byte<'a>(input: &'a [u8]) -> IResult<&'a [u8], bool, VerboseError<&'a [u8]>> { 59 | let (i, byte) = take(1usize)(input)?; 60 | 61 | Ok((i, byte[0] == 0x03)) 62 | } 63 | 64 | pub fn parse_rom_size<'a>(input: &'a [u8]) -> IResult<&'a [u8], u16, VerboseError<&'a [u8]>> { 65 | context( 66 | "ROM size byte", 67 | map_opt(take(1usize), |bytes: &'a [u8]| translate_rom_size(bytes[0])), 68 | )(input) 69 | } 70 | 71 | pub fn parse_ram_size<'a>(input: &'a [u8]) -> IResult<&'a [u8], (u8, u16), VerboseError<&'a [u8]>> { 72 | context( 73 | "RAM size byte", 74 | map_opt(take(1usize), |bytes: &'a [u8]| translate_ram_size(bytes[0])), 75 | )(input) 76 | } 77 | 78 | pub fn parse_jp_byte<'a>(input: &'a [u8]) -> IResult<&'a [u8], bool, VerboseError<&'a [u8]>> { 79 | let (i, byte) = take(1usize)(input)?; 80 | 81 | Ok((i, byte[0] == 0)) 82 | } 83 | 84 | pub fn parse_byte<'a>(input: &'a [u8]) -> IResult<&'a [u8], u8, VerboseError<&'a [u8]>> { 85 | map(take(1usize), |bytes: &'a [u8]| bytes[0])(input) 86 | } 87 | 88 | pub fn parse_rom_header<'a>( 89 | input: &'a [u8], 90 | ) -> IResult<&'a [u8], RomHeader<'a>, VerboseError<&'a [u8]>> { 91 | map( 92 | tuple(( 93 | context("Rom start", take(0x100usize)), 94 | context("begin code execution point", take(4usize)), 95 | parse_scrolling_graphic, 96 | parse_game_title, 97 | parse_gbc_byte, 98 | parse_new_licensee_code, 99 | parse_sgb_byte, 100 | parse_rom_type, 101 | parse_rom_size, 102 | parse_ram_size, 103 | parse_jp_byte, 104 | context("old licensee code", parse_byte), 105 | context("mask rom version number", parse_byte), 106 | context("complement", parse_byte), 107 | context("checksum", be_u16), 108 | )), 109 | |( 110 | _, 111 | begin_code_execution_point, 112 | scrolling_graphic, 113 | game_title, 114 | gameboy_color, 115 | licensee_code_new, 116 | super_gameboy, 117 | rom_type, 118 | rom_size, 119 | (ram_banks, ram_bank_size), 120 | japanese, 121 | licensee_code, 122 | mask_rom_version, 123 | complement, 124 | checksum, 125 | )| { 126 | RomHeader { 127 | begin_code_execution_point, 128 | scrolling_graphic, 129 | game_title, 130 | gameboy_color, 131 | licensee_code_new, 132 | super_gameboy, 133 | rom_type, 134 | rom_size, 135 | ram_banks, 136 | ram_bank_size, 137 | japanese, 138 | licensee_code, 139 | mask_rom_version, 140 | complement, 141 | checksum, 142 | } 143 | }, 144 | )(input) 145 | } 146 | 147 | pub fn parse_instruction(input: &[u8]) -> IResult<&[u8], Opcode, VerboseError<&[u8]>> { 148 | let (i, byte) = take(1usize)(input)?; 149 | Ok(match byte[0] { 150 | 0x00 => (i, Opcode::Nop), 151 | 0x10 => (i, Opcode::Stop), 152 | 0x20 => { 153 | let (i, bytes) = take(1usize)(i)?; 154 | (i, Opcode::Jr(Some(Flag::NZ), bytes[0])) 155 | } 156 | 0x30 => { 157 | let (i, bytes) = take(1usize)(i)?; 158 | (i, Opcode::Jr(Some(Flag::NC), bytes[0])) 159 | } 160 | 0x18 => { 161 | let (i, bytes) = take(1usize)(i)?; 162 | (i, Opcode::Jr(None, bytes[0])) 163 | } 164 | 0x28 => { 165 | let (i, bytes) = take(1usize)(i)?; 166 | (i, Opcode::Jr(Some(Flag::Z), bytes[0])) 167 | } 168 | 0x38 => { 169 | let (i, bytes) = take(1usize)(i)?; 170 | (i, Opcode::Jr(Some(Flag::C), bytes[0])) 171 | } 172 | 0x01 => { 173 | let (i, short) = le_u16(i)?; 174 | (i, Opcode::StoreImm16(Register16::BC, short)) 175 | } 176 | 0x11 => { 177 | let (i, short) = le_u16(i)?; 178 | (i, Opcode::StoreImm16(Register16::DE, short)) 179 | } 180 | 0x21 => { 181 | let (i, short) = le_u16(i)?; 182 | (i, Opcode::StoreImm16(Register16::HL, short)) 183 | } 184 | 0x31 => { 185 | let (i, short) = le_u16(i)?; 186 | (i, Opcode::StoreImm16(Register16::SP, short)) 187 | } 188 | 0x02 => (i, Opcode::StoreATo16(Register16::BC)), 189 | 0x12 => (i, Opcode::StoreATo16(Register16::DE)), 190 | 0x22 => (i, Opcode::StoreAToHlAddr(true)), 191 | 0x32 => (i, Opcode::StoreAToHlAddr(false)), 192 | 0x03 => (i, Opcode::Inc16(Register16::BC)), 193 | 0x13 => (i, Opcode::Inc16(Register16::DE)), 194 | 0x23 => (i, Opcode::Inc16(Register16::HL)), 195 | 0x33 => (i, Opcode::Inc16(Register16::SP)), 196 | 0x04 => (i, Opcode::Inc8(Register8::B)), 197 | 0x14 => (i, Opcode::Inc8(Register8::D)), 198 | 0x24 => (i, Opcode::Inc8(Register8::H)), 199 | 0x34 => (i, Opcode::Inc8(Register8::DerefHL)), 200 | 0x05 => (i, Opcode::Dec8(Register8::B)), 201 | 0x15 => (i, Opcode::Dec8(Register8::D)), 202 | 0x25 => (i, Opcode::Dec8(Register8::H)), 203 | 0x35 => (i, Opcode::Dec8(Register8::DerefHL)), 204 | 0x0C => (i, Opcode::Inc8(Register8::C)), 205 | 0x1C => (i, Opcode::Inc8(Register8::E)), 206 | 0x2C => (i, Opcode::Inc8(Register8::L)), 207 | 0x3C => (i, Opcode::Inc8(Register8::A)), 208 | 0x0D => (i, Opcode::Dec8(Register8::C)), 209 | 0x1D => (i, Opcode::Dec8(Register8::E)), 210 | 0x2D => (i, Opcode::Dec8(Register8::L)), 211 | 0x3D => (i, Opcode::Dec8(Register8::A)), 212 | 0x06 => { 213 | let (i, bytes) = take(1usize)(i)?; 214 | (i, Opcode::StoreImm8(Register8::B, bytes[0])) 215 | } 216 | 0x16 => { 217 | let (i, bytes) = take(1usize)(i)?; 218 | (i, Opcode::StoreImm8(Register8::D, bytes[0])) 219 | } 220 | 0x26 => { 221 | let (i, bytes) = take(1usize)(i)?; 222 | (i, Opcode::StoreImm8(Register8::H, bytes[0])) 223 | } 224 | 0x36 => { 225 | let (i, bytes) = take(1usize)(i)?; 226 | (i, Opcode::StoreImm8(Register8::DerefHL, bytes[0])) 227 | } 228 | 0x0E => { 229 | let (i, bytes) = take(1usize)(i)?; 230 | (i, Opcode::StoreImm8(Register8::C, bytes[0])) 231 | } 232 | 0x1E => { 233 | let (i, bytes) = take(1usize)(i)?; 234 | (i, Opcode::StoreImm8(Register8::E, bytes[0])) 235 | } 236 | 0x2E => { 237 | let (i, bytes) = take(1usize)(i)?; 238 | (i, Opcode::StoreImm8(Register8::L, bytes[0])) 239 | } 240 | 0x3E => { 241 | let (i, bytes) = take(1usize)(i)?; 242 | (i, Opcode::StoreImm8(Register8::A, bytes[0])) 243 | } 244 | 0x07 => (i, Opcode::Rlca), 245 | 0x17 => (i, Opcode::Rla), 246 | 0x27 => (i, Opcode::Daa), 247 | 0x37 => (i, Opcode::Scf), 248 | 0x0F => (i, Opcode::Rrca), 249 | 0x1F => (i, Opcode::Rra), 250 | 0x2F => (i, Opcode::Cpl), 251 | 0x3F => (i, Opcode::Ccf), 252 | 0x08 => { 253 | let (i, short) = le_u16(i)?; 254 | (i, Opcode::StoreImm16AddrSp(short)) 255 | } 256 | 0x09 => (i, Opcode::AddHl(Register16::BC)), 257 | 0x19 => (i, Opcode::AddHl(Register16::DE)), 258 | 0x29 => (i, Opcode::AddHl(Register16::HL)), 259 | 0x39 => (i, Opcode::AddHl(Register16::SP)), 260 | 0x0A => (i, Opcode::LoadAFromReg16Addr(Register16::BC)), 261 | 0x1A => (i, Opcode::LoadAFromReg16Addr(Register16::DE)), 262 | 0x2A => (i, Opcode::LoadAFromHlAddr(true)), 263 | 0x3A => (i, Opcode::LoadAFromHlAddr(false)), 264 | 0x0B => (i, Opcode::Dec16(Register16::BC)), 265 | 0x1B => (i, Opcode::Dec16(Register16::DE)), 266 | 0x2B => (i, Opcode::Dec16(Register16::HL)), 267 | 0x3B => (i, Opcode::Dec16(Register16::SP)), 268 | 0x76 => (i, Opcode::Halt), 269 | 0x40..=0x75 | 0x77..=0x7F => { 270 | let lo4 = byte[0] & 0b0000_1111; 271 | let hi4 = byte[0] >> 4; 272 | let operand1 = match lo4 { 273 | 0x0..=0x7 if hi4 == 0x4 => Register8::B, 274 | 0x8..=0xF if hi4 == 0x4 => Register8::C, 275 | 0x0..=0x7 if hi4 == 0x5 => Register8::D, 276 | 0x8..=0xF if hi4 == 0x5 => Register8::E, 277 | 0x0..=0x7 if hi4 == 0x6 => Register8::H, 278 | 0x8..=0xF if hi4 == 0x6 => Register8::L, 279 | 0x0..=0x7 if hi4 == 0x7 => Register8::DerefHL, 280 | 0x8..=0xF if hi4 == 0x7 => Register8::A, 281 | _ => unreachable!(), 282 | }; 283 | 284 | let operand2 = match lo4 { 285 | 0x0 | 0x8 => Register8::B, 286 | 0x1 | 0x9 => Register8::C, 287 | 0x2 | 0xA => Register8::D, 288 | 0x3 | 0xB => Register8::E, 289 | 0x4 | 0xC => Register8::H, 290 | 0x5 | 0xD => Register8::L, 291 | 0x6 | 0xE => Register8::DerefHL, 292 | 0x7 | 0xF => Register8::A, 293 | _ => unreachable!(), 294 | }; 295 | 296 | (i, Opcode::Mov8(operand1, operand2)) 297 | } 298 | 0x80..=0xBF => { 299 | let lo4 = byte[0] & 0b0000_1111; 300 | let hi4 = byte[0] >> 4; 301 | 302 | let operand = match lo4 { 303 | 0x0 | 0x8 => Register8::B, 304 | 0x1 | 0x9 => Register8::C, 305 | 0x2 | 0xA => Register8::D, 306 | 0x3 | 0xB => Register8::E, 307 | 0x4 | 0xC => Register8::H, 308 | 0x5 | 0xD => Register8::L, 309 | 0x6 | 0xE => Register8::DerefHL, 310 | 0x7 | 0xF => Register8::A, 311 | _ => unreachable!(), 312 | }; 313 | 314 | ( 315 | i, 316 | match (hi4, lo4) { 317 | (0x8, 0x0..=0x7) => Opcode::Add(operand), 318 | (0x8, 0x8..=0xF) => Opcode::Adc(operand), 319 | (0x9, 0x0..=0x7) => Opcode::Sub(operand), 320 | (0x9, 0x8..=0xF) => Opcode::Sbc(operand), 321 | (0xA, 0x0..=0x7) => Opcode::And(operand), 322 | (0xA, 0x8..=0xF) => Opcode::Xor(operand), 323 | (0xB, 0x0..=0x7) => Opcode::Or(operand), 324 | (0xB, 0x8..=0xF) => Opcode::Cp(operand), 325 | _ => unreachable!(), 326 | }, 327 | ) 328 | } 329 | 0xCB => parse_cb(i)?, 330 | 0xC0 => (i, Opcode::Ret(Some(Flag::NZ))), 331 | 0xD0 => (i, Opcode::Ret(Some(Flag::NC))), 332 | 0xC8 => (i, Opcode::Ret(Some(Flag::Z))), 333 | 0xD8 => (i, Opcode::Ret(Some(Flag::C))), 334 | 0xC9 => (i, Opcode::Ret(None)), 335 | 0xD9 => (i, Opcode::Reti), 336 | // ldh 337 | 0xE0 => { 338 | let (i, bytes) = take(1usize)(i)?; 339 | (i, Opcode::StoreHA(bytes[0])) 340 | } 341 | // ldh 342 | 0xF0 => { 343 | let (i, bytes) = take(1usize)(i)?; 344 | (i, Opcode::LoadHA(bytes[0])) 345 | } 346 | 0xC1 => (i, Opcode::Pop(Register16::BC)), 347 | 0xD1 => (i, Opcode::Pop(Register16::DE)), 348 | 0xE1 => (i, Opcode::Pop(Register16::HL)), 349 | 0xF1 => (i, Opcode::Pop(Register16::AF)), 350 | 0xC5 => (i, Opcode::Push(Register16::BC)), 351 | 0xD5 => (i, Opcode::Push(Register16::DE)), 352 | 0xE5 => (i, Opcode::Push(Register16::HL)), 353 | 0xF5 => (i, Opcode::Push(Register16::AF)), 354 | 0xC2 => { 355 | let (i, short) = le_u16(i)?; 356 | (i, Opcode::Jp(Some(Flag::NZ), short)) 357 | } 358 | 0xD2 => { 359 | let (i, short) = le_u16(i)?; 360 | (i, Opcode::Jp(Some(Flag::NC), short)) 361 | } 362 | 0xC3 => { 363 | let (i, short) = le_u16(i)?; 364 | (i, Opcode::Jp(None, short)) 365 | } 366 | 0xCA => { 367 | let (i, short) = le_u16(i)?; 368 | (i, Opcode::Jp(Some(Flag::Z), short)) 369 | } 370 | 0xDA => { 371 | let (i, short) = le_u16(i)?; 372 | (i, Opcode::Jp(Some(Flag::C), short)) 373 | } 374 | // LD (C), A 375 | 0xE2 => (i, Opcode::StoreCA), 376 | // LD A, (C) 377 | 0xF2 => (i, Opcode::LoadCA), 378 | // LD (a16), A 379 | 0xEA => { 380 | let (i, short) = le_u16(i)?; 381 | (i, Opcode::StoreAAtAddress(short)) 382 | } 383 | // LD A, (a16) 384 | 0xFA => { 385 | let (i, short) = le_u16(i)?; 386 | (i, Opcode::LoadAFromAddress(short)) 387 | } 388 | 0xF3 => (i, Opcode::Di), 389 | 0xFB => (i, Opcode::Ei), 390 | 0xC4 => { 391 | let (i, short) = le_u16(i)?; 392 | (i, Opcode::Call(Some(Flag::NZ), short)) 393 | } 394 | 0xD4 => { 395 | let (i, short) = le_u16(i)?; 396 | (i, Opcode::Call(Some(Flag::NC), short)) 397 | } 398 | 0xCC => { 399 | let (i, short) = le_u16(i)?; 400 | (i, Opcode::Call(Some(Flag::Z), short)) 401 | } 402 | 0xDC => { 403 | let (i, short) = le_u16(i)?; 404 | (i, Opcode::Call(Some(Flag::C), short)) 405 | } 406 | 0xCD => { 407 | let (i, short) = le_u16(i)?; 408 | (i, Opcode::Call(None, short)) 409 | } 410 | 0xC6 => { 411 | let (i, bytes) = take(1usize)(i)?; 412 | (i, Opcode::Add8(bytes[0])) 413 | } 414 | 0xD6 => { 415 | let (i, bytes) = take(1usize)(i)?; 416 | (i, Opcode::Sub8(bytes[0])) 417 | } 418 | 0xE6 => { 419 | let (i, bytes) = take(1usize)(i)?; 420 | (i, Opcode::And8(bytes[0])) 421 | } 422 | 0xF6 => { 423 | let (i, bytes) = take(1usize)(i)?; 424 | (i, Opcode::Or8(bytes[0])) 425 | } 426 | 0xCE => { 427 | let (i, bytes) = take(1usize)(i)?; 428 | (i, Opcode::Adc8(bytes[0])) 429 | } 430 | 0xDE => { 431 | let (i, bytes) = take(1usize)(i)?; 432 | (i, Opcode::Sbc8(bytes[0])) 433 | } 434 | 0xEE => { 435 | let (i, bytes) = take(1usize)(i)?; 436 | (i, Opcode::Xor8(bytes[0])) 437 | } 438 | 0xFE => { 439 | let (i, bytes) = take(1usize)(i)?; 440 | (i, Opcode::Cp8(bytes[0])) 441 | } 442 | 0xC7 => (i, Opcode::Rst(0)), 443 | 0xCF => (i, Opcode::Rst(1)), 444 | 0xD7 => (i, Opcode::Rst(2)), 445 | 0xDF => (i, Opcode::Rst(3)), 446 | 0xE7 => (i, Opcode::Rst(4)), 447 | 0xEF => (i, Opcode::Rst(5)), 448 | 0xF7 => (i, Opcode::Rst(6)), 449 | 0xFF => (i, Opcode::Rst(7)), 450 | 0xE8 => { 451 | let (i, bytes) = take(1usize)(i)?; 452 | (i, Opcode::AddSp8(bytes[0])) 453 | } 454 | 0xF8 => { 455 | let (i, bytes) = take(1usize)(i)?; 456 | (i, Opcode::LdHlSp8(bytes[0] as i8)) 457 | } 458 | 0xE9 => (i, Opcode::JpHl), 459 | 0xF9 => (i, Opcode::LdSpHl), 460 | 0xD3 | 0xE3 | 0xE4 | 0xF4 | 0xDB | 0xDD | 0xEB | 0xEC | 0xED | 0xFC | 0xFD => { 461 | return Err(Err::Error(make_error(i, nom::error::ErrorKind::TagBits))); 462 | } 463 | }) 464 | } 465 | 466 | /// Extra math functions 467 | pub fn parse_cb(input: &[u8]) -> IResult<&[u8], Opcode, VerboseError<&[u8]>> { 468 | const CB_TABLE: [Opcode; 256] = [ 469 | Opcode::Rlc(Register8::B), 470 | Opcode::Rlc(Register8::C), 471 | Opcode::Rlc(Register8::E), 472 | Opcode::Rlc(Register8::D), 473 | Opcode::Rlc(Register8::L), 474 | Opcode::Rlc(Register8::H), 475 | Opcode::Rlc(Register8::DerefHL), 476 | Opcode::Rlc(Register8::A), 477 | Opcode::Rrc(Register8::B), 478 | Opcode::Rrc(Register8::C), 479 | Opcode::Rrc(Register8::E), 480 | Opcode::Rrc(Register8::D), 481 | Opcode::Rrc(Register8::L), 482 | Opcode::Rrc(Register8::H), 483 | Opcode::Rrc(Register8::DerefHL), 484 | Opcode::Rrc(Register8::A), 485 | // 0x1X 486 | Opcode::Rl(Register8::B), 487 | Opcode::Rl(Register8::C), 488 | Opcode::Rl(Register8::D), 489 | Opcode::Rl(Register8::E), 490 | Opcode::Rl(Register8::H), 491 | Opcode::Rl(Register8::L), 492 | Opcode::Rl(Register8::DerefHL), 493 | Opcode::Rl(Register8::A), 494 | Opcode::Rr(Register8::B), 495 | Opcode::Rr(Register8::C), 496 | Opcode::Rr(Register8::D), 497 | Opcode::Rr(Register8::E), 498 | Opcode::Rr(Register8::H), 499 | Opcode::Rr(Register8::L), 500 | Opcode::Rr(Register8::DerefHL), 501 | Opcode::Rr(Register8::A), 502 | // 0x2X 503 | Opcode::Sla(Register8::B), 504 | Opcode::Sla(Register8::C), 505 | Opcode::Sla(Register8::D), 506 | Opcode::Sla(Register8::E), 507 | Opcode::Sla(Register8::H), 508 | Opcode::Sla(Register8::L), 509 | Opcode::Sla(Register8::DerefHL), 510 | Opcode::Sla(Register8::A), 511 | Opcode::Sra(Register8::B), 512 | Opcode::Sra(Register8::C), 513 | Opcode::Sra(Register8::D), 514 | Opcode::Sra(Register8::E), 515 | Opcode::Sra(Register8::H), 516 | Opcode::Sra(Register8::L), 517 | Opcode::Sra(Register8::DerefHL), 518 | Opcode::Sra(Register8::A), 519 | // 0x3X 520 | Opcode::Swap(Register8::B), 521 | Opcode::Swap(Register8::C), 522 | Opcode::Swap(Register8::D), 523 | Opcode::Swap(Register8::E), 524 | Opcode::Swap(Register8::H), 525 | Opcode::Swap(Register8::L), 526 | Opcode::Swap(Register8::DerefHL), 527 | Opcode::Swap(Register8::A), 528 | Opcode::Srl(Register8::B), 529 | Opcode::Srl(Register8::C), 530 | Opcode::Srl(Register8::D), 531 | Opcode::Srl(Register8::E), 532 | Opcode::Srl(Register8::H), 533 | Opcode::Srl(Register8::L), 534 | Opcode::Srl(Register8::DerefHL), 535 | Opcode::Srl(Register8::A), 536 | // 0x4X 537 | Opcode::Bit(0, Register8::B), 538 | Opcode::Bit(0, Register8::C), 539 | Opcode::Bit(0, Register8::D), 540 | Opcode::Bit(0, Register8::E), 541 | Opcode::Bit(0, Register8::H), 542 | Opcode::Bit(0, Register8::L), 543 | Opcode::Bit(0, Register8::DerefHL), 544 | Opcode::Bit(0, Register8::A), 545 | Opcode::Bit(1, Register8::B), 546 | Opcode::Bit(1, Register8::C), 547 | Opcode::Bit(1, Register8::D), 548 | Opcode::Bit(1, Register8::E), 549 | Opcode::Bit(1, Register8::H), 550 | Opcode::Bit(1, Register8::L), 551 | Opcode::Bit(1, Register8::DerefHL), 552 | Opcode::Bit(1, Register8::A), 553 | // 0x5X 554 | Opcode::Bit(2, Register8::B), 555 | Opcode::Bit(2, Register8::C), 556 | Opcode::Bit(2, Register8::D), 557 | Opcode::Bit(2, Register8::E), 558 | Opcode::Bit(2, Register8::H), 559 | Opcode::Bit(2, Register8::L), 560 | Opcode::Bit(2, Register8::DerefHL), 561 | Opcode::Bit(2, Register8::A), 562 | Opcode::Bit(3, Register8::B), 563 | Opcode::Bit(3, Register8::C), 564 | Opcode::Bit(3, Register8::D), 565 | Opcode::Bit(3, Register8::E), 566 | Opcode::Bit(3, Register8::H), 567 | Opcode::Bit(3, Register8::L), 568 | Opcode::Bit(3, Register8::DerefHL), 569 | Opcode::Bit(3, Register8::A), 570 | // 0x6X 571 | Opcode::Bit(4, Register8::B), 572 | Opcode::Bit(4, Register8::C), 573 | Opcode::Bit(4, Register8::D), 574 | Opcode::Bit(4, Register8::E), 575 | Opcode::Bit(4, Register8::H), 576 | Opcode::Bit(4, Register8::L), 577 | Opcode::Bit(4, Register8::DerefHL), 578 | Opcode::Bit(4, Register8::A), 579 | Opcode::Bit(5, Register8::B), 580 | Opcode::Bit(5, Register8::C), 581 | Opcode::Bit(5, Register8::D), 582 | Opcode::Bit(5, Register8::E), 583 | Opcode::Bit(5, Register8::H), 584 | Opcode::Bit(5, Register8::L), 585 | Opcode::Bit(5, Register8::DerefHL), 586 | Opcode::Bit(5, Register8::A), 587 | // 0x7X 588 | Opcode::Bit(6, Register8::B), 589 | Opcode::Bit(6, Register8::C), 590 | Opcode::Bit(6, Register8::D), 591 | Opcode::Bit(6, Register8::E), 592 | Opcode::Bit(6, Register8::H), 593 | Opcode::Bit(6, Register8::L), 594 | Opcode::Bit(6, Register8::DerefHL), 595 | Opcode::Bit(6, Register8::A), 596 | Opcode::Bit(7, Register8::B), 597 | Opcode::Bit(7, Register8::C), 598 | Opcode::Bit(7, Register8::D), 599 | Opcode::Bit(7, Register8::E), 600 | Opcode::Bit(7, Register8::H), 601 | Opcode::Bit(7, Register8::L), 602 | Opcode::Bit(7, Register8::DerefHL), 603 | Opcode::Bit(7, Register8::A), 604 | // 0x8X 605 | Opcode::Res(0, Register8::B), 606 | Opcode::Res(0, Register8::C), 607 | Opcode::Res(0, Register8::D), 608 | Opcode::Res(0, Register8::E), 609 | Opcode::Res(0, Register8::H), 610 | Opcode::Res(0, Register8::L), 611 | Opcode::Res(0, Register8::DerefHL), 612 | Opcode::Res(0, Register8::A), 613 | Opcode::Res(1, Register8::B), 614 | Opcode::Res(1, Register8::C), 615 | Opcode::Res(1, Register8::D), 616 | Opcode::Res(1, Register8::E), 617 | Opcode::Res(1, Register8::H), 618 | Opcode::Res(1, Register8::L), 619 | Opcode::Res(1, Register8::DerefHL), 620 | Opcode::Res(1, Register8::A), 621 | // 0x9X 622 | Opcode::Res(2, Register8::B), 623 | Opcode::Res(2, Register8::C), 624 | Opcode::Res(2, Register8::D), 625 | Opcode::Res(2, Register8::E), 626 | Opcode::Res(2, Register8::H), 627 | Opcode::Res(2, Register8::L), 628 | Opcode::Res(2, Register8::DerefHL), 629 | Opcode::Res(2, Register8::A), 630 | Opcode::Res(3, Register8::B), 631 | Opcode::Res(3, Register8::C), 632 | Opcode::Res(3, Register8::D), 633 | Opcode::Res(3, Register8::E), 634 | Opcode::Res(3, Register8::H), 635 | Opcode::Res(3, Register8::L), 636 | Opcode::Res(3, Register8::DerefHL), 637 | Opcode::Res(3, Register8::A), 638 | // 0xAX 639 | Opcode::Res(4, Register8::B), 640 | Opcode::Res(4, Register8::C), 641 | Opcode::Res(4, Register8::D), 642 | Opcode::Res(4, Register8::E), 643 | Opcode::Res(4, Register8::H), 644 | Opcode::Res(4, Register8::L), 645 | Opcode::Res(4, Register8::DerefHL), 646 | Opcode::Res(4, Register8::A), 647 | Opcode::Res(5, Register8::B), 648 | Opcode::Res(5, Register8::C), 649 | Opcode::Res(5, Register8::D), 650 | Opcode::Res(5, Register8::E), 651 | Opcode::Res(5, Register8::H), 652 | Opcode::Res(5, Register8::L), 653 | Opcode::Res(5, Register8::DerefHL), 654 | Opcode::Res(5, Register8::A), 655 | // 0xBX 656 | Opcode::Res(6, Register8::B), 657 | Opcode::Res(6, Register8::C), 658 | Opcode::Res(6, Register8::D), 659 | Opcode::Res(6, Register8::E), 660 | Opcode::Res(6, Register8::H), 661 | Opcode::Res(6, Register8::L), 662 | Opcode::Res(6, Register8::DerefHL), 663 | Opcode::Res(6, Register8::A), 664 | Opcode::Res(7, Register8::B), 665 | Opcode::Res(7, Register8::C), 666 | Opcode::Res(7, Register8::D), 667 | Opcode::Res(7, Register8::E), 668 | Opcode::Res(7, Register8::H), 669 | Opcode::Res(7, Register8::L), 670 | Opcode::Res(7, Register8::DerefHL), 671 | Opcode::Res(7, Register8::A), 672 | // 0xCX 673 | Opcode::Set(0, Register8::B), 674 | Opcode::Set(0, Register8::C), 675 | Opcode::Set(0, Register8::D), 676 | Opcode::Set(0, Register8::E), 677 | Opcode::Set(0, Register8::H), 678 | Opcode::Set(0, Register8::L), 679 | Opcode::Set(0, Register8::DerefHL), 680 | Opcode::Set(0, Register8::A), 681 | Opcode::Set(1, Register8::B), 682 | Opcode::Set(1, Register8::C), 683 | Opcode::Set(1, Register8::D), 684 | Opcode::Set(1, Register8::E), 685 | Opcode::Set(1, Register8::H), 686 | Opcode::Set(1, Register8::L), 687 | Opcode::Set(1, Register8::DerefHL), 688 | Opcode::Set(1, Register8::A), 689 | // 0xDX 690 | Opcode::Set(2, Register8::B), 691 | Opcode::Set(2, Register8::C), 692 | Opcode::Set(2, Register8::D), 693 | Opcode::Set(2, Register8::E), 694 | Opcode::Set(2, Register8::H), 695 | Opcode::Set(2, Register8::L), 696 | Opcode::Set(2, Register8::DerefHL), 697 | Opcode::Set(2, Register8::A), 698 | Opcode::Set(3, Register8::B), 699 | Opcode::Set(3, Register8::C), 700 | Opcode::Set(3, Register8::D), 701 | Opcode::Set(3, Register8::E), 702 | Opcode::Set(3, Register8::H), 703 | Opcode::Set(3, Register8::L), 704 | Opcode::Set(3, Register8::DerefHL), 705 | Opcode::Set(3, Register8::A), 706 | // 0xEX 707 | Opcode::Set(4, Register8::B), 708 | Opcode::Set(4, Register8::C), 709 | Opcode::Set(4, Register8::D), 710 | Opcode::Set(4, Register8::E), 711 | Opcode::Set(4, Register8::H), 712 | Opcode::Set(4, Register8::L), 713 | Opcode::Set(4, Register8::DerefHL), 714 | Opcode::Set(4, Register8::A), 715 | Opcode::Set(5, Register8::B), 716 | Opcode::Set(5, Register8::C), 717 | Opcode::Set(5, Register8::D), 718 | Opcode::Set(5, Register8::E), 719 | Opcode::Set(5, Register8::H), 720 | Opcode::Set(5, Register8::L), 721 | Opcode::Set(5, Register8::DerefHL), 722 | Opcode::Set(5, Register8::A), 723 | // 0xFX 724 | Opcode::Set(6, Register8::B), 725 | Opcode::Set(6, Register8::C), 726 | Opcode::Set(6, Register8::D), 727 | Opcode::Set(6, Register8::E), 728 | Opcode::Set(6, Register8::H), 729 | Opcode::Set(6, Register8::L), 730 | Opcode::Set(6, Register8::DerefHL), 731 | Opcode::Set(6, Register8::A), 732 | Opcode::Set(7, Register8::B), 733 | Opcode::Set(7, Register8::C), 734 | Opcode::Set(7, Register8::D), 735 | Opcode::Set(7, Register8::E), 736 | Opcode::Set(7, Register8::H), 737 | Opcode::Set(7, Register8::L), 738 | Opcode::Set(7, Register8::DerefHL), 739 | Opcode::Set(7, Register8::A), 740 | ]; 741 | 742 | let (i, byte) = take(1usize)(input)?; 743 | Ok((i, CB_TABLE[byte[0] as usize])) 744 | } 745 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for parsing 2 | // extra info from http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header 3 | 4 | /// Takes in the ROM size byte and outputs the number of ROM banks 5 | pub fn translate_rom_size(input: u8) -> Option { 6 | match input { 7 | 0 => Some(2), 8 | 1 => Some(4), 9 | 2 => Some(8), 10 | 3 => Some(16), 11 | 4 => Some(32), 12 | 5 => Some(64), 13 | 6 => Some(128), 14 | 7 => Some(256), 15 | 8 => Some(512), 16 | 0x52 => Some(72), 17 | 0x53 => Some(80), 18 | 0x54 => Some(96), 19 | _ => None, 20 | } 21 | } 22 | 23 | /// Takes in the RAM size byte and outputs the number of RAM banks 24 | /// and the size of each RAM bank in bytes 25 | /// Standard values for RAM bank size are 2kB and 8kB 26 | pub fn translate_ram_size(input: u8) -> Option<(u8, u16)> { 27 | const TWO_KB: u16 = 2 << 10; 28 | const EIGHT_KB: u16 = 2 << 12; 29 | match input { 30 | 0 => Some((0, 0)), 31 | 1 => Some((1, TWO_KB)), 32 | 2 => Some((1, EIGHT_KB)), 33 | 3 => Some((4, EIGHT_KB)), 34 | 4 => Some((16, EIGHT_KB)), 35 | 5 => Some((8, EIGHT_KB)), 36 | _ => None, 37 | } 38 | } 39 | --------------------------------------------------------------------------------