├── .gitignore ├── .travis.yml ├── COPYRIGHT ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE2.0 ├── LICENSE-MIT ├── README.md └── src ├── cpu ├── cpu_tests.rs ├── instructions.rs └── mod.rs ├── main.rs ├── memory.rs └── rom └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /rls 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Barnacleboy is copyright 2017, Wesley Norris 2 | 3 | Licensed under the Apache License, Version 2.0 4 | or the MIT 6 | license , 7 | at your option. All files in the project carrying such 8 | notice may not be copied, modified, or distributed except 9 | according to those terms. -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "barnacleboy" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "barnacleboy" 3 | version = "0.1.0" 4 | authors = ["rep-nop "] 5 | 6 | [dependencies] 7 | 8 | [profile.dev] 9 | overflow-checks = false -------------------------------------------------------------------------------- /LICENSE-APACHE2.0: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2017 Wesley Norris 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Wesley Norris 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Barnacleboy 2 | A Gameboy emulator written in Rust 3 | 4 | _Currenly going through a fresh rewrite with some better knowledge and another brain! Stay tuned!_ 5 | 6 | [![Build Status](https://travis-ci.org/rep-nop/barnacleboy.svg?branch=master)](https://travis-ci.org/rep-nop/barnacleboy) 7 | 8 | ## What is Barnacleboy? 9 | Barnacleboy is an emulator for the original Nintendo(c) Gameboy(tm) 10 | also known as the DMG Gameboy in specification sheets. The goal of this 11 | emulator is to emulate the original Gameboy hardware as accurately as 12 | possible and learn along the way! 13 | 14 | For those wondering about the name, here's the process I went through 15 | when deciding on it: 16 | 17 | Rust -> users are called Rustaceans -> play on crustacean 18 | -> what other crustaceans are there other than crabs? -> 19 | Barnacles -> merge with Gameboy and blam, you got yourself the name 20 | Barnacleboy 21 | 22 | ## Another GB Emulator? Why? 23 | Hardware emulation has been a topic of my interest for quite a long time 24 | and is something I find very exciting to see happen. After watching a lot 25 | of [yupferris](https://github.com/yupferris)'s Rustendo64 streams, I thought 26 | that emulating a simpler piece of hardware would be a great experience for 27 | learning a lot more about Rust while also giving myself a project to work on 28 | in the long term. 29 | 30 | tl;dr: because I can. :) 31 | 32 | ## Why Rust? 33 | I chose to write Barnacleboy in Rust because its been the first programming 34 | language that I've gotten quite excited in the past few years. I think the 35 | combination of a younger systems language that tries to improve on the issues 36 | of the last many years in a fairly unqiue way really piqued my interest in 37 | the language. The fact that it is memory safe helps to ensure there are as 38 | many minimal issues with that as possible and I think will help the project 39 | quite a bit. 40 | 41 | ## License 42 | Barnacleboy is licensed under a dual MIT and Apache 2.0 license. 43 | 44 | ## Thinking About Contributing? 45 | Awesome! The project is still in its infancy stage, so a lot of work needs 46 | to be done. If you're interested in contributing to the project, feel free 47 | to submit an issue or even a pull-request if you find [something isn't quite 48 | right](https://i.imgur.com/18BUBPf.jpg) or shoot me a message on Discord 49 | (you can find me in the [Rust Discord](https://discord.gg/23sA8nR)) 50 | 51 | ## Gameboy Resources 52 | TODO -------------------------------------------------------------------------------- /src/cpu/cpu_tests.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::{ 2 | instructions::{self as ins, u16_add, u16_sub, u8_add, u8_sub, AluResult, OpcodeBits}, 3 | SharpLR35902, 4 | }; 5 | use crate::memory::{MemoryError, MemoryInterface}; 6 | 7 | #[derive(Debug, Default)] 8 | struct DummyMemoryInterface { 9 | mem: [u8; 32], 10 | } 11 | 12 | impl MemoryInterface for DummyMemoryInterface { 13 | type Word = u8; 14 | type Index = u16; 15 | 16 | fn read(&self, address: Self::Index) -> Result { 17 | Ok(self.mem[address as usize]) 18 | } 19 | fn write(&mut self, address: Self::Index, data: Self::Word) -> Result<(), MemoryError> { 20 | self.mem[address as usize] = data; 21 | Ok(()) 22 | } 23 | } 24 | 25 | #[test] 26 | fn flags() { 27 | let dmi = &mut DummyMemoryInterface::default(); 28 | let mut cpu = SharpLR35902::new(dmi); 29 | 30 | cpu.registers.set_c(); 31 | assert!(cpu.registers.c()); 32 | 33 | cpu.registers.set_s(); 34 | assert!(cpu.registers.s()); 35 | 36 | cpu.registers.set_z(); 37 | assert!(cpu.registers.z()); 38 | 39 | cpu.registers.set_h(); 40 | assert!(cpu.registers.h()); 41 | 42 | cpu.registers.clear_c(); 43 | assert!(!cpu.registers.c()); 44 | 45 | cpu.registers.clear_s(); 46 | assert!(!cpu.registers.s()); 47 | 48 | cpu.registers.clear_z(); 49 | assert!(!cpu.registers.z()); 50 | 51 | cpu.registers.clear_h(); 52 | assert!(!cpu.registers.h()); 53 | } 54 | 55 | #[test] 56 | fn registers() { 57 | let dmi = &mut DummyMemoryInterface::default(); 58 | let mut cpu = SharpLR35902::new(dmi); 59 | 60 | cpu.registers.a = 0x11; 61 | cpu.registers.f = 0x22; 62 | cpu.registers.b = 0x33; 63 | cpu.registers.c = 0x44; 64 | cpu.registers.d = 0x55; 65 | cpu.registers.e = 0x66; 66 | cpu.registers.h = 0x77; 67 | cpu.registers.l = 0x88; 68 | 69 | assert_eq!(cpu.registers.as_dwords().af, 0x1122); 70 | assert_eq!(cpu.registers.as_dwords().bc, 0x3344); 71 | assert_eq!(cpu.registers.as_dwords().de, 0x5566); 72 | assert_eq!(cpu.registers.as_dwords().hl, 0x7788); 73 | } 74 | 75 | #[test] 76 | fn opcode_bits() { 77 | let opcode = 0b1010_1010; 78 | let bits = OpcodeBits::from(opcode); 79 | 80 | assert_eq!(bits.x, 0b10); 81 | assert_eq!(bits.y, 0b101); 82 | assert_eq!(bits.z, 0b010); 83 | assert_eq!(bits.p, 0b10); 84 | assert_eq!(bits.q, 0b1); 85 | } 86 | 87 | #[test] 88 | fn ld_at_bc_a() { 89 | let dmi = &mut DummyMemoryInterface::default(); 90 | let mut cpu = SharpLR35902::new(dmi); 91 | cpu.registers.a = 0xFF; 92 | cpu.registers.as_dwords().bc = 0x01; 93 | cpu.current_opcode = 0x02; 94 | assert!(ins::ld_at_bc_a(&mut cpu).is_ok()); 95 | assert_eq!(cpu.read(0x01).unwrap(), 0xFF); 96 | } 97 | 98 | #[test] 99 | fn ld_at_de_a() { 100 | let dmi = &mut DummyMemoryInterface::default(); 101 | let mut cpu = SharpLR35902::new(dmi); 102 | cpu.registers.a = 0xFF; 103 | cpu.registers.as_dwords().de = 0x01; 104 | cpu.current_opcode = 0x12; 105 | assert!(ins::ld_at_de_a(&mut cpu).is_ok()); 106 | assert_eq!(cpu.read(0x01).unwrap(), 0xFF); 107 | } 108 | 109 | #[test] 110 | fn ld_at_hli_a() { 111 | let dmi = &mut DummyMemoryInterface::default(); 112 | let mut cpu = SharpLR35902::new(dmi); 113 | cpu.registers.a = 0xFF; 114 | cpu.registers.as_dwords().hl = 0x01; 115 | cpu.current_opcode = 0x22; 116 | assert!(ins::ld_at_hli_a(&mut cpu).is_ok()); 117 | assert_eq!(cpu.read(0x01).unwrap(), 0xFF); 118 | assert_eq!(cpu.registers.as_dwords().hl, 0x02); 119 | } 120 | 121 | #[test] 122 | fn ld_at_hld_a() { 123 | let dmi = &mut DummyMemoryInterface::default(); 124 | let mut cpu = SharpLR35902::new(dmi); 125 | cpu.registers.a = 0xFF; 126 | cpu.registers.as_dwords().hl = 0x01; 127 | cpu.current_opcode = 0x22; 128 | assert!(ins::ld_at_hld_a(&mut cpu).is_ok()); 129 | assert_eq!(cpu.read(0x01).unwrap(), 0xFF); 130 | assert_eq!(cpu.registers.as_dwords().hl, 0x00); 131 | } 132 | 133 | #[test] 134 | fn ld_r_d8() { 135 | let dmi = &mut DummyMemoryInterface::default(); 136 | let mut cpu = SharpLR35902::new(dmi); 137 | 138 | for i in 0u8..8u8 { 139 | cpu.write(i as u16, i + 1).unwrap(); 140 | } 141 | 142 | cpu.current_opcode = 0x06; 143 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 144 | assert_eq!(cpu.registers.b, 0x01); 145 | 146 | cpu.current_opcode = 0x0E; 147 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 148 | assert_eq!(cpu.registers.c, 0x02); 149 | 150 | cpu.current_opcode = 0x16; 151 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 152 | assert_eq!(cpu.registers.d, 0x03); 153 | 154 | cpu.current_opcode = 0x1E; 155 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 156 | assert_eq!(cpu.registers.e, 0x04); 157 | 158 | cpu.current_opcode = 0x26; 159 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 160 | assert_eq!(cpu.registers.h, 0x05); 161 | 162 | cpu.current_opcode = 0x2E; 163 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 164 | assert_eq!(cpu.registers.l, 0x06); 165 | 166 | cpu.current_opcode = 0x36; 167 | cpu.registers.as_dwords().hl = 0x0000; 168 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 169 | assert_eq!(cpu.read_hl().unwrap(), 0x07); 170 | 171 | cpu.current_opcode = 0x3E; 172 | assert!(ins::ld_r_d8(&mut cpu).is_ok()); 173 | assert_eq!(cpu.registers.a, 0x08); 174 | } 175 | 176 | #[test] 177 | fn ld_r_at_hl() { 178 | let dmi = &mut DummyMemoryInterface::default(); 179 | let mut cpu = SharpLR35902::new(dmi); 180 | 181 | cpu.write(0x0000, 0xFF).unwrap(); 182 | 183 | cpu.current_opcode = 0x46; 184 | assert!(ins::ld_r_at_hl(&mut cpu).is_ok()); 185 | assert_eq!(cpu.registers.b, 0xFF); 186 | 187 | cpu.current_opcode = 0x4E; 188 | assert!(ins::ld_r_at_hl(&mut cpu).is_ok()); 189 | assert_eq!(cpu.registers.c, 0xFF); 190 | 191 | cpu.current_opcode = 0x56; 192 | assert!(ins::ld_r_at_hl(&mut cpu).is_ok()); 193 | assert_eq!(cpu.registers.d, 0xFF); 194 | 195 | cpu.current_opcode = 0x5E; 196 | assert!(ins::ld_r_at_hl(&mut cpu).is_ok()); 197 | assert_eq!(cpu.registers.e, 0xFF); 198 | 199 | cpu.current_opcode = 0x66; 200 | assert!(ins::ld_r_at_hl(&mut cpu).is_ok()); 201 | assert_eq!(cpu.registers.h, 0xFF); 202 | 203 | cpu.current_opcode = 0x6E; 204 | cpu.registers.h = 0x00; 205 | assert!(ins::ld_r_at_hl(&mut cpu).is_ok()); 206 | assert_eq!(cpu.registers.l, 0xFF); 207 | 208 | cpu.current_opcode = 0x7E; 209 | cpu.registers.l = 0x00; 210 | assert!(ins::ld_r_at_hl(&mut cpu).is_ok()); 211 | assert_eq!(cpu.registers.a, 0xFF); 212 | } 213 | 214 | #[test] 215 | fn ld_at_hl_r() { 216 | let dmi = &mut DummyMemoryInterface::default(); 217 | let mut cpu = SharpLR35902::new(dmi); 218 | 219 | cpu.registers.as_dwords().hl = 0x0011; 220 | cpu.registers.b = 0x01; 221 | cpu.registers.c = 0x02; 222 | cpu.registers.d = 0x03; 223 | cpu.registers.e = 0x04; 224 | cpu.registers.a = 0x05; 225 | 226 | cpu.current_opcode = 0x70; 227 | assert!(ins::ld_at_hl_r(&mut cpu).is_ok()); 228 | assert_eq!(cpu.read_hl().unwrap(), 0x01); 229 | 230 | cpu.current_opcode = 0x71; 231 | assert!(ins::ld_at_hl_r(&mut cpu).is_ok()); 232 | assert_eq!(cpu.read_hl().unwrap(), 0x02); 233 | 234 | cpu.current_opcode = 0x72; 235 | assert!(ins::ld_at_hl_r(&mut cpu).is_ok()); 236 | assert_eq!(cpu.read_hl().unwrap(), 0x03); 237 | 238 | cpu.current_opcode = 0x73; 239 | assert!(ins::ld_at_hl_r(&mut cpu).is_ok()); 240 | assert_eq!(cpu.read_hl().unwrap(), 0x04); 241 | 242 | cpu.current_opcode = 0x74; 243 | assert!(ins::ld_at_hl_r(&mut cpu).is_ok()); 244 | assert_eq!(cpu.read_hl().unwrap(), 0x00); 245 | 246 | cpu.current_opcode = 0x75; 247 | assert!(ins::ld_at_hl_r(&mut cpu).is_ok()); 248 | assert_eq!(cpu.read_hl().unwrap(), 0x11); 249 | 250 | // 0x76 is the instruction `HALT` 251 | cpu.current_opcode = 0x77; 252 | assert!(ins::ld_at_hl_r(&mut cpu).is_ok()); 253 | assert_eq!(cpu.read_hl().unwrap(), 0x05); 254 | } 255 | 256 | #[test] 257 | fn ld_at_hl_d8() { 258 | let dmi = &mut DummyMemoryInterface::default(); 259 | let mut cpu = SharpLR35902::new(dmi); 260 | 261 | cpu.registers.as_dwords().hl = 0x0011; 262 | cpu.write(0x00, 0xFF).unwrap(); 263 | 264 | cpu.current_opcode = 0x36; 265 | assert!(ins::ld_at_hl_d8(&mut cpu).is_ok()); 266 | assert_eq!(cpu.read_hl().unwrap(), 0xFF); 267 | } 268 | 269 | #[test] 270 | fn ld_a_at_bc() { 271 | let dmi = &mut DummyMemoryInterface::default(); 272 | let mut cpu = SharpLR35902::new(dmi); 273 | 274 | cpu.write(0x0A, 0xFF).unwrap(); 275 | cpu.registers.as_dwords().bc = 0x0A; 276 | 277 | cpu.current_opcode = 0x0A; 278 | assert!(ins::ld_a_at_bc(&mut cpu).is_ok()); 279 | assert_eq!(cpu.registers.a, 0xFF); 280 | } 281 | 282 | #[test] 283 | fn helper_tests() { 284 | let result = AluResult { 285 | result: 0xFFu8, 286 | zero: false, 287 | carry: false, 288 | half_carry: false, 289 | subtract: false, 290 | }; 291 | assert_eq!(u8_add(0xF0, 0x0F), result); 292 | 293 | let result = AluResult { 294 | result: 0x10u8, 295 | zero: false, 296 | carry: false, 297 | half_carry: true, 298 | subtract: false, 299 | }; 300 | assert_eq!(u8_add(0x0F, 0x01), result); 301 | 302 | let result = AluResult { 303 | result: 0x00u8, 304 | zero: true, 305 | carry: true, 306 | half_carry: true, 307 | subtract: false, 308 | }; 309 | assert_eq!(u8_add(0xFF, 0x01), result); 310 | 311 | let result = AluResult { 312 | result: 0xEFu8, 313 | zero: false, 314 | carry: false, 315 | half_carry: true, 316 | subtract: true, 317 | }; 318 | assert_eq!(u8_sub(0xF0, 0x01), result); 319 | 320 | let result = AluResult { 321 | result: 0xFFu8, 322 | zero: false, 323 | carry: true, 324 | half_carry: true, 325 | subtract: true, 326 | }; 327 | assert_eq!(u8_sub(0x00, 0x01), result); 328 | 329 | let result = AluResult { 330 | result: 0xFFFFu16, 331 | zero: false, 332 | carry: false, 333 | half_carry: false, 334 | subtract: false, 335 | }; 336 | assert_eq!(u16_add(0xFF00, 0x00FF), result); 337 | 338 | let result = AluResult { 339 | result: 0x1000u16, 340 | zero: false, 341 | carry: false, 342 | half_carry: true, 343 | subtract: false, 344 | }; 345 | assert_eq!(u16_add(0x0FFF, 0x0001), result); 346 | 347 | let result = AluResult { 348 | result: 0x00u16, 349 | zero: true, 350 | carry: true, 351 | half_carry: true, 352 | subtract: false, 353 | }; 354 | assert_eq!(u16_add(0xFFFF, 0x0001), result); 355 | 356 | let result = AluResult { 357 | result: 0xEF00u16, 358 | zero: false, 359 | carry: false, 360 | half_carry: true, 361 | subtract: true, 362 | }; 363 | assert_eq!(u16_sub(0xFEFF, 0x0FFF), result); 364 | 365 | let result = AluResult { 366 | result: 0xFFFFu16, 367 | zero: false, 368 | carry: true, 369 | half_carry: true, 370 | subtract: true, 371 | }; 372 | assert_eq!(u16_sub(0x0000, 0x0001), result); 373 | } 374 | -------------------------------------------------------------------------------- /src/cpu/instructions.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::{SharpLR35902, SharpResult, F_CARRY, F_HALFCARRY, F_SUBTRACT, F_ZERO}; 2 | 3 | /// Groups of bits in an opcode. 4 | /// 5 | /// Broken up into the following groups: 6 | ///``` 7 | /// Bits 6-7: x 8 | /// 9 | /// Bits 3-5: y 10 | /// 11 | /// Bits 4-5: p 12 | /// 13 | /// Bit 3: q 14 | /// 15 | /// Bits 0-2: z 16 | /// ``` 17 | pub struct OpcodeBits { 18 | pub x: u8, 19 | pub y: u8, 20 | pub z: u8, 21 | pub p: u8, 22 | pub q: u8, 23 | } 24 | 25 | impl From for OpcodeBits { 26 | fn from(op: u8) -> OpcodeBits { 27 | let x = (op & 0b1100_0000) >> 6; 28 | let y = (op & 0b0011_1000) >> 3; 29 | let z = op & 0b0000_0111; 30 | let p = (y & 0b110) >> 1; 31 | let q = y & 0b001; 32 | 33 | OpcodeBits { x, y, z, p, q } 34 | } 35 | } 36 | 37 | /// Array of instructions for executing individual opcodes. 38 | pub const INSTRUCTIONS: [fn(&mut SharpLR35902) -> SharpResult; 2] = [nop, ld_r_r]; 39 | 40 | /// NOP instruction. Does nothing. 41 | /// 42 | /// Flags affected: none 43 | pub fn nop(_: &mut SharpLR35902) -> SharpResult { 44 | Ok(()) 45 | } 46 | 47 | /// Stops the processor. 48 | /// 49 | /// Flags affected: none 50 | pub fn stop(_: &mut SharpLR35902) -> SharpResult { 51 | // No-Op for now 52 | Ok(()) 53 | } 54 | 55 | // BEGIN 8-BIT TRANSFER INSTRUCTIONS 56 | 57 | /// Loads the value of register `A` into the memory address pointed to by 58 | /// register `BC`. 59 | /// 60 | /// Flags affected: none 61 | pub fn ld_at_bc_a(cpu: &mut SharpLR35902) -> SharpResult { 62 | let addr = cpu.registers.as_dwords().bc; 63 | let a = cpu.registers.a; 64 | 65 | cpu.write(addr, a)?; 66 | Ok(()) 67 | } 68 | 69 | /// Loads the value of register `A` into the memory address pointed to by 70 | /// register `DE`. 71 | /// 72 | /// Flags affected: none 73 | pub fn ld_at_de_a(cpu: &mut SharpLR35902) -> SharpResult { 74 | let addr = cpu.registers.as_dwords().de; 75 | let a = cpu.registers.a; 76 | 77 | cpu.write(addr, a)?; 78 | Ok(()) 79 | } 80 | 81 | /// Loads the value of register `A` into the memory address pointed to by 82 | /// register `HL` then increments `HL`. 83 | /// 84 | /// Flags affected: none 85 | pub fn ld_at_hli_a(cpu: &mut SharpLR35902) -> SharpResult { 86 | let a = cpu.registers.a; 87 | 88 | cpu.write_hl(a)?; 89 | cpu.registers.as_dwords().hl += 1; 90 | 91 | Ok(()) 92 | } 93 | 94 | /// Loads the value of register `A` into the memory address pointed to by 95 | /// register `HL` then decrements `HL`. 96 | /// 97 | /// Flags affected: none 98 | pub fn ld_at_hld_a(cpu: &mut SharpLR35902) -> SharpResult { 99 | let a = cpu.registers.a; 100 | 101 | cpu.write_hl(a)?; 102 | cpu.registers.as_dwords().hl -= 1; 103 | 104 | Ok(()) 105 | } 106 | 107 | /// Loads the value of the memory address pointed to by register `HL` into 108 | /// register `A` into then increments `HL`. 109 | /// 110 | /// Flags affected: none 111 | pub fn ld_a_at_hli(cpu: &mut SharpLR35902) -> SharpResult { 112 | cpu.registers.a = cpu.read_hl()?; 113 | cpu.registers.as_dwords().hl += 1; 114 | 115 | Ok(()) 116 | } 117 | 118 | /// Loads the value of the memory address pointed to by register `HL` into 119 | /// register `A` into then decrements `HL`. 120 | /// 121 | /// Flags affected: none 122 | pub fn ld_a_at_hld(cpu: &mut SharpLR35902) -> SharpResult { 123 | cpu.registers.a = cpu.read_hl()?; 124 | cpu.registers.as_dwords().hl -= 1; 125 | 126 | Ok(()) 127 | } 128 | 129 | /// Reads an immediate 8-bit value into the specified register. 130 | /// 131 | /// Flags affected: none 132 | pub fn ld_r_d8(cpu: &mut SharpLR35902) -> SharpResult { 133 | let bits = OpcodeBits::from(cpu.current_opcode); 134 | 135 | if bits.y != 6 { 136 | cpu.registers[bits.y] = cpu.read_instruction_word()?; 137 | } else { 138 | let data = cpu.read_instruction_word()?; 139 | cpu.write_hl(data)?; 140 | } 141 | 142 | Ok(()) 143 | } 144 | 145 | /// Reads an 8-bit value pointed to by register `HL` into the specified 146 | /// register. 147 | /// 148 | /// Flags affected: none 149 | pub fn ld_r_at_hl(cpu: &mut SharpLR35902) -> SharpResult { 150 | let bits = OpcodeBits::from(cpu.current_opcode); 151 | cpu.registers[bits.y] = cpu.read_hl()?; 152 | 153 | Ok(()) 154 | } 155 | 156 | /// Writes an 8-bit value in the specified register to the memory location 157 | /// pointed to by register `HL`. 158 | /// 159 | /// Flags affected: none 160 | pub fn ld_at_hl_r(cpu: &mut SharpLR35902) -> SharpResult { 161 | let bits = OpcodeBits::from(cpu.current_opcode); 162 | let val = cpu.registers[bits.z]; 163 | 164 | cpu.write_hl(val)?; 165 | 166 | Ok(()) 167 | } 168 | 169 | /// Writes an 8-bit value in the specified register to the memory location 170 | /// pointed to by register `HL`. 171 | /// 172 | /// Flags affected: none 173 | pub fn ld_at_hl_d8(cpu: &mut SharpLR35902) -> SharpResult { 174 | let val = cpu.read_instruction_word()?; 175 | cpu.write_hl(val)?; 176 | 177 | Ok(()) 178 | } 179 | 180 | /// Reads an 8-bit value pointed to by register `BC` into the `A` 181 | /// register. 182 | /// 183 | /// Flags affected: none 184 | pub fn ld_a_at_bc(cpu: &mut SharpLR35902) -> SharpResult { 185 | let addr = cpu.registers.as_dwords().bc; 186 | cpu.registers.a = cpu.read(addr)?; 187 | 188 | Ok(()) 189 | } 190 | 191 | /// Reads an 8-bit value pointed to by register `DE` into the `A` 192 | /// register. 193 | /// 194 | /// Flags affected: none 195 | pub fn ld_a_at_de(cpu: &mut SharpLR35902) -> SharpResult { 196 | let addr = cpu.registers.as_dwords().de; 197 | cpu.registers.a = cpu.read(addr)?; 198 | 199 | Ok(()) 200 | } 201 | 202 | /// Reads an 8-bit value pointed to by register `C` + 0xFF00 into the `A` 203 | /// register. 204 | /// 205 | /// Flags affected: none 206 | pub fn ld_a_at_c(cpu: &mut SharpLR35902) -> SharpResult { 207 | let addr = cpu.registers.c as u16 + 0xFF00; 208 | cpu.registers.a = cpu.read(addr)?; 209 | 210 | Ok(()) 211 | } 212 | 213 | /// Writes the 8-bit contents of the `A` register to the memory location pointed 214 | /// to by register `C` + 0xFF00. 215 | /// 216 | /// Flags affected: none 217 | pub fn ld_at_c_a(cpu: &mut SharpLR35902) -> SharpResult { 218 | let addr = cpu.registers.c as u16 + 0xFF00; 219 | let a = cpu.registers.a; 220 | cpu.write(addr, a)?; 221 | 222 | Ok(()) 223 | } 224 | 225 | /// Reads an 8-bit value pointed to by the immediate 8-bit operand `d8` + 0xFF00 226 | /// into the `A` register. 227 | /// 228 | /// Flags affected: none 229 | pub fn ld_a_at_d8(cpu: &mut SharpLR35902) -> SharpResult { 230 | let addr = cpu.read_instruction_word()? as u16 + 0xFF00; 231 | cpu.registers.a = cpu.read(addr)?; 232 | 233 | Ok(()) 234 | } 235 | 236 | /// Writes the 8-bit contents of the `A` register to the memory location pointed 237 | /// to by the immediate 8-bit operand `d8` + 0xFF00. 238 | /// 239 | /// Flags affected: none 240 | pub fn ld_at_d8_a(cpu: &mut SharpLR35902) -> SharpResult { 241 | let addr = cpu.read_instruction_word()? as u16 + 0xFF00; 242 | let a = cpu.registers.a; 243 | cpu.write(addr, a)?; 244 | 245 | Ok(()) 246 | } 247 | 248 | /// Reads an 8-bit value pointed to by the immediate 816bit operand `d16` 249 | /// into the `A` register. 250 | /// 251 | /// Flags affected: none 252 | pub fn ld_a_at_d16(cpu: &mut SharpLR35902) -> SharpResult { 253 | let addr = cpu.read_instruction_dword()?; 254 | cpu.registers.a = cpu.read(addr)?; 255 | 256 | Ok(()) 257 | } 258 | 259 | /// Writes the 8-bit contents of the `A` register to the memory location pointed 260 | /// to by the immediate 16-bit operand `d16` 261 | /// 262 | /// Flags affected: none 263 | pub fn ld_at_d16_a(cpu: &mut SharpLR35902) -> SharpResult { 264 | let addr = cpu.read_instruction_dword()?; 265 | let a = cpu.registers.a; 266 | cpu.write(addr, a)?; 267 | 268 | Ok(()) 269 | } 270 | 271 | /// Reads the contents of one register into another. 272 | /// 273 | /// Flags affected: none 274 | pub fn ld_r_r(cpu: &mut SharpLR35902) -> SharpResult { 275 | let bits = OpcodeBits::from(cpu.current_opcode); 276 | cpu.registers[bits.y] = cpu.registers[bits.z]; 277 | 278 | Ok(()) 279 | } 280 | 281 | // BEGIN 16-BIT TRANSFER INSTRUCTIONS 282 | 283 | /// Loads a 16-bit immediate value into the register pair `rr`. 284 | /// 285 | /// Flags affected: none 286 | pub fn ld_rr_nn(cpu: &mut SharpLR35902) -> SharpResult { 287 | let bits = OpcodeBits::from(cpu.current_opcode); 288 | let data = cpu.read_instruction_dword()?; 289 | 290 | if bits.p != 0x03 { 291 | cpu.registers.as_dwords()[bits.p] = data; 292 | } else { 293 | cpu.registers.sp = data; 294 | } 295 | 296 | Ok(()) 297 | } 298 | 299 | /// Loads the contents of register `HL` into the stack pointer (`SP`). 300 | /// 301 | /// Flags affected: none 302 | pub fn ld_sp_hl(cpu: &mut SharpLR35902) -> SharpResult { 303 | let hl = cpu.registers.as_dwords().hl; 304 | 305 | cpu.registers.sp = hl; 306 | 307 | Ok(()) 308 | } 309 | 310 | /// Pushes the value of a register pair `rr` onto the stack and subtracts the 311 | /// stack pointer (`SP`) by 2. 312 | /// 313 | /// Flags affected: none 314 | pub fn push_rr(cpu: &mut SharpLR35902) -> SharpResult { 315 | let bits = OpcodeBits::from(cpu.current_opcode); 316 | 317 | let d16 = cpu.registers.as_dwords()[bits.p]; 318 | let sp = cpu.registers.sp; 319 | 320 | cpu.write(sp - 1, (d16 >> 8) as u8)?; 321 | cpu.write(sp - 2, (d16 & 0x00FF) as u8)?; 322 | cpu.registers.sp -= 2; 323 | 324 | Ok(()) 325 | } 326 | 327 | /// Pops a 16-bit value off of the stack into a register pair `rr` and adds 2 to 328 | /// the stack pointer (`SP`). 329 | /// 330 | /// Flags affected: none 331 | pub fn pop_rr(cpu: &mut SharpLR35902) -> SharpResult { 332 | let bits = OpcodeBits::from(cpu.current_opcode); 333 | let sp = cpu.registers.sp; 334 | 335 | let d16 = cpu.read(sp)? as u16 | (cpu.read(sp + 1)? as u16) << 8; 336 | cpu.registers.sp += 2; 337 | 338 | cpu.registers.as_dwords()[bits.p] = d16; 339 | 340 | Ok(()) 341 | } 342 | 343 | /// Adds an immediate 8-bit signed integer to the stack pointer (`SP`) and 344 | /// places the result into register pair `HL`. 345 | /// 346 | /// Flags affected: C - *, H - *, S - 0, Z - 0 347 | pub fn ldhl_sp_e(cpu: &mut SharpLR35902) -> SharpResult { 348 | let e = cpu.read_instruction_word()? as i8 as i16; 349 | let sp = cpu.registers.sp as i16; 350 | 351 | let result = i16_add(sp, e); 352 | 353 | cpu.registers.as_dwords().hl = result.result as u16; 354 | 355 | if result.carry { 356 | cpu.registers.set_c(); 357 | } 358 | 359 | if result.half_carry { 360 | cpu.registers.set_h(); 361 | } 362 | 363 | cpu.registers.clear_s(); 364 | cpu.registers.clear_z(); 365 | 366 | Ok(()) 367 | } 368 | 369 | /// Stores the value of the stack pointer `SP` into the immediate 16-bit 370 | /// address. 371 | /// 372 | /// Flags affected: none 373 | pub fn ld_d16_sp(cpu: &mut SharpLR35902) -> SharpResult { 374 | let d16 = cpu.read_instruction_word()? as u16 | (cpu.read_instruction_word()? as u16) << 8; 375 | let sp = cpu.registers.sp; 376 | 377 | cpu.write(d16, (sp & 0x00FF) as u8)?; 378 | cpu.write(d16 + 1, (sp >> 8) as u8)?; 379 | 380 | Ok(()) 381 | } 382 | 383 | // BEGIN 8-BIT ARITHMETIC AND LOGIC OPERATIONS 384 | 385 | /// Adds the value in the register `r` to register `A` and stores the result in 386 | /// register `A`. 387 | /// 388 | /// Flags affected: C - *, H - *, S - 0, Z - * 389 | pub fn add_a_r(cpu: &mut SharpLR35902) -> SharpResult { 390 | let bits = OpcodeBits::from(cpu.current_opcode); 391 | 392 | let (result, flags) = if bits.z != 0b110 { 393 | u8_add(cpu.registers.a, cpu.registers[bits.z]).into_parts() 394 | } else { 395 | let second = cpu.read_hl()?; 396 | u8_add(cpu.registers.a, second).into_parts() 397 | }; 398 | 399 | cpu.registers.a = result; 400 | cpu.registers.f = flags; 401 | 402 | Ok(()) 403 | } 404 | 405 | /// Adds the value of the immediate 8-bit operand `d8` to register `A` and 406 | /// stores the result in register `A`. 407 | /// 408 | /// Flags affected: C - *, H - *, S - 0, Z - * 409 | pub fn add_a_d8(cpu: &mut SharpLR35902) -> SharpResult { 410 | let d8 = cpu.read_instruction_word()?; 411 | 412 | let (result, flags) = u8_add(cpu.registers.a, d8).into_parts(); 413 | 414 | cpu.registers.a = result; 415 | cpu.registers.f = flags; 416 | 417 | Ok(()) 418 | } 419 | 420 | /// Adds the value in the register `r` to register `A` along with the value of 421 | /// the carry flag then stores the result in register `A`. 422 | /// 423 | /// Flags affected: C - *, H - *, S - 0, Z - * 424 | pub fn adc_a_r(cpu: &mut SharpLR35902) -> SharpResult { 425 | let c = if cpu.registers.f & F_CARRY == F_CARRY { 426 | 1 427 | } else { 428 | 0 429 | }; 430 | 431 | add_a_r(cpu)?; 432 | 433 | let (result, flags) = u8_add(cpu.registers.a, c).into_parts(); 434 | 435 | cpu.registers.a = result; 436 | cpu.registers.f = flags; 437 | 438 | Ok(()) 439 | } 440 | 441 | /// Adds the value of the immediate 8-bit operand to register `A` along with the 442 | /// value of the carry flag then stores the result in register `A`. 443 | /// 444 | /// Flags affected: C - *, H - *, S - 0, Z - * 445 | pub fn adc_a_d8(cpu: &mut SharpLR35902) -> SharpResult { 446 | let c = if cpu.registers.f & F_CARRY == F_CARRY { 447 | 1 448 | } else { 449 | 0 450 | }; 451 | 452 | add_a_d8(cpu)?; 453 | 454 | let (result, flags) = u8_add(cpu.registers.a, c).into_parts(); 455 | 456 | cpu.registers.a = result; 457 | cpu.registers.f = flags; 458 | 459 | Ok(()) 460 | } 461 | 462 | /// Subtracts the value in the register `r` from register `A` and stores the 463 | /// result in register `A`. 464 | /// 465 | /// Flags affected: C - *, H - *, S - 1, Z - * 466 | pub fn sub_a_r(cpu: &mut SharpLR35902) -> SharpResult { 467 | let bits = OpcodeBits::from(cpu.current_opcode); 468 | 469 | let (result, flags) = if bits.z != 0b110 { 470 | u8_sub(cpu.registers.a, cpu.registers[bits.z]).into_parts() 471 | } else { 472 | let second = cpu.read_hl()?; 473 | u8_sub(cpu.registers.a, second).into_parts() 474 | }; 475 | 476 | cpu.registers.a = result; 477 | cpu.registers.f = flags; 478 | 479 | Ok(()) 480 | } 481 | 482 | /// Subtracts the value of the immediate 8-bit operand `d8` from register `A` 483 | /// and stores the result in register `A`. 484 | /// 485 | /// Flags affected: C - *, H - *, S - 1, Z - * 486 | pub fn sub_a_d8(cpu: &mut SharpLR35902) -> SharpResult { 487 | let d8 = cpu.read_instruction_word()?; 488 | 489 | let (result, flags) = u8_sub(cpu.registers.a, d8).into_parts(); 490 | 491 | cpu.registers.a = result; 492 | cpu.registers.f = flags; 493 | 494 | Ok(()) 495 | } 496 | 497 | /// Subtracts the value in the register `r` from register `A` along with the 498 | /// value of the carry flag then stores the result in register `A`. 499 | /// 500 | /// Flags affected: C - *, H - *, S - 1, Z - * 501 | pub fn sbc_a_r(cpu: &mut SharpLR35902) -> SharpResult { 502 | let c = if cpu.registers.f & F_CARRY == F_CARRY { 503 | 1 504 | } else { 505 | 0 506 | }; 507 | 508 | sub_a_r(cpu)?; 509 | 510 | let (result, flags) = u8_sub(cpu.registers.a, c).into_parts(); 511 | 512 | cpu.registers.a = result; 513 | cpu.registers.f = flags; 514 | 515 | Ok(()) 516 | } 517 | 518 | /// Subtracts the value of the immediate 8-bit operand `d8` from register `A` 519 | /// along with the value of the carry flag then stores the result in register 520 | /// `A`. 521 | /// 522 | /// Flags affected: C - *, H - *, S - 1, Z - * 523 | pub fn sbc_a_d8(cpu: &mut SharpLR35902) -> SharpResult { 524 | let c = if cpu.registers.f & F_CARRY == F_CARRY { 525 | 1 526 | } else { 527 | 0 528 | }; 529 | 530 | sub_a_d8(cpu)?; 531 | 532 | let (result, flags) = u8_sub(cpu.registers.a, c).into_parts(); 533 | 534 | cpu.registers.a = result; 535 | cpu.registers.f = flags; 536 | 537 | Ok(()) 538 | } 539 | 540 | // BEGIN HELPER FUNCTIONS 541 | /// Contains the result and flag changes of an ALU operation. 542 | #[derive(Debug, Default, PartialEq)] 543 | pub struct AluResult { 544 | /// Result of the operation. 545 | pub result: T, 546 | /// Whether the zero flag was set or not. 547 | pub zero: bool, 548 | /// Whether the carry flag was set or not. 549 | pub carry: bool, 550 | /// Whether the half-carry flag was set or not. 551 | pub half_carry: bool, 552 | /// Whether the subtract flag was set or not. 553 | pub subtract: bool, 554 | } 555 | 556 | impl AluResult { 557 | /// Returns the result of the operation and the flags in `u8` form. 558 | pub fn into_parts(self) -> (T, u8) { 559 | let res = self.result; 560 | let flags = { 561 | let mut f = 0u8; 562 | 563 | if self.zero { 564 | f |= F_ZERO; 565 | } 566 | 567 | if self.half_carry { 568 | f |= F_HALFCARRY; 569 | } 570 | 571 | if self.carry { 572 | f |= F_CARRY; 573 | } 574 | 575 | if self.subtract { 576 | f | F_SUBTRACT; 577 | } 578 | 579 | f 580 | }; 581 | 582 | (res, flags) 583 | } 584 | } 585 | 586 | /// Helper function. 587 | /// 588 | /// Returns an `AluResult` of the addition of two `u8`s. 589 | pub fn u8_add(first: u8, second: u8) -> AluResult { 590 | let mut chc = AluResult::default(); 591 | 592 | let (res, cry) = first.overflowing_add(second); 593 | chc.result = res; 594 | chc.carry = cry; 595 | 596 | if chc.result == 0 { 597 | chc.zero = true; 598 | } 599 | 600 | if (first & 0x0F) + (second & 0x0F) > 0x0F { 601 | chc.half_carry = true; 602 | } 603 | 604 | chc 605 | } 606 | 607 | /// Helper function. 608 | /// 609 | /// Returns an `AluResult` of the addition of two `u16`s. 610 | pub fn u16_add(first: u16, second: u16) -> AluResult { 611 | let mut chc = AluResult::default(); 612 | 613 | let (res, cry) = first.overflowing_add(second); 614 | chc.result = res; 615 | chc.carry = cry; 616 | 617 | if chc.result == 0 { 618 | chc.zero = true; 619 | } 620 | 621 | if (first & 0xFFF) + (second & 0xFFF) > 0xFFF { 622 | chc.half_carry = true; 623 | } 624 | 625 | chc 626 | } 627 | 628 | /// Helper function. 629 | /// 630 | /// Returns an `AluResult` of the subtraction of two `u8`s. 631 | pub fn u8_sub(first: u8, second: u8) -> AluResult { 632 | let mut chc = AluResult::default(); 633 | 634 | let (res, cry) = first.overflowing_sub(second); 635 | chc.result = res; 636 | chc.carry = cry; 637 | 638 | if chc.result == 0 { 639 | chc.zero = true; 640 | } 641 | 642 | if (first & 0x0F) < (second & 0x0F) { 643 | chc.half_carry = true; 644 | } 645 | 646 | chc.subtract = true; 647 | 648 | chc 649 | } 650 | 651 | /// Helper function. 652 | /// 653 | /// Returns an `AluResult` of the subtraction of two `u16`s. 654 | pub fn u16_sub(first: u16, second: u16) -> AluResult { 655 | let mut chc = AluResult::default(); 656 | 657 | let (res, cry) = first.overflowing_sub(second); 658 | chc.result = res; 659 | chc.carry = cry; 660 | 661 | if chc.result == 0 { 662 | chc.zero = true; 663 | } 664 | 665 | if (first & 0xFFF) < (second & 0xFFF) { 666 | chc.half_carry = true; 667 | } 668 | 669 | chc.subtract = true; 670 | 671 | chc 672 | } 673 | 674 | /// Helper function. 675 | /// 676 | /// Returns an `AluResult` of the addition of two `i16`s. 677 | pub fn i16_add(first: i16, second: i16) -> AluResult { 678 | let mut chc = AluResult::default(); 679 | 680 | let (res, cry) = first.overflowing_add(second); 681 | chc.result = res; 682 | chc.carry = cry; 683 | 684 | if chc.result == 0 { 685 | chc.zero = true; 686 | } 687 | 688 | if (first & 0xFFF) + (second & 0xFFF) > 0xFFF { 689 | chc.half_carry = true; 690 | } 691 | 692 | chc.subtract = false; 693 | 694 | chc 695 | } 696 | -------------------------------------------------------------------------------- /src/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod cpu_tests; 3 | mod instructions; 4 | 5 | use memory::MemoryInterface; 6 | use std::{cell::RefCell, rc::Rc}; 7 | 8 | /// Zero flag. 9 | pub const F_ZERO: u8 = 0b1000_0000; 10 | /// Subtraction flag. 11 | pub const F_SUBTRACT: u8 = 0b0100_0000; 12 | /// Half-carry flag. 13 | pub const F_HALFCARRY: u8 = 0b0010_0000; 14 | /// Carry flag. 15 | pub const F_CARRY: u8 = 0b0001_0000; 16 | 17 | type MemoryController<'a> = &'a mut dyn MemoryInterface; 18 | type SharpResult = Result<(), LRError>; 19 | /// The original Sharp LR35902 processor, a 8080/Z80 derivative with some 20 | /// interesting changes. Most notably, removal of the shadow register set along 21 | /// with various opcode changes. 22 | pub struct SharpLR35902<'a> { 23 | /// CPU registers. 24 | registers: SharpLR35902Registers, 25 | /// Interrupt pending flag. (Do we need this?) 26 | interrupt_pending: bool, 27 | /// Whether the CPU is halted or not. 28 | halted: bool, 29 | /// Current instruction opcode. 30 | current_opcode: u8, 31 | /// `Rc` to the memory controller object. 32 | memory_controller: MemoryController<'a>, 33 | } 34 | 35 | /// The Sharp LR35902 register set. Contains 7 8-bit general purpose registers 36 | /// (`a`, `b`, `c`, `d`, `e`, `h`, `l`), a flag register, program counter, and 37 | /// stack pointer. Each of the 7 general purpose registers plus the flags 38 | /// register can be combined into 4 different 16-bit register pairs: `af`, `bc`, 39 | /// `de`, and `hl`. The first register name denotes the top 8 bits, with the 40 | /// second denoting the bottom 8 bits. 41 | #[cfg(target_endian = "little")] 42 | #[derive(Debug, Default, Clone, Copy)] 43 | #[repr(C, align(2))] 44 | pub struct SharpLR35902Registers { 45 | /// Flags register. 46 | pub f: u8, 47 | /// General purpose `A` register. 48 | pub a: u8, 49 | /// General purpose `C` register. 50 | pub c: u8, 51 | /// General purpose `B` register. 52 | pub b: u8, 53 | /// General purpose `E` register. 54 | pub e: u8, 55 | /// General purpose `D` register. 56 | pub d: u8, 57 | /// General purpose `L` register. 58 | pub l: u8, 59 | /// General purpose `H` register. 60 | pub h: u8, 61 | /// Program counter. 62 | pub pc: u16, 63 | /// Stack pointer. 64 | pub sp: u16, 65 | } 66 | 67 | #[cfg(target_endian = "big")] 68 | #[derive(Debug, Default, Clone, Copy)] 69 | #[repr(C, align(2))] 70 | pub struct SharpLR35902Registers { 71 | /// General purpose `A` register. 72 | pub a: u8, 73 | /// Flags register. 74 | pub f: u8, 75 | /// General purpose `B` register. 76 | pub b: u8, 77 | /// General purpose `C` register. 78 | pub c: u8, 79 | /// General purpose `D` register. 80 | pub d: u8, 81 | /// General purpose `D` register. 82 | pub e: u8, 83 | /// General purpose `H` register. 84 | pub h: u8, 85 | /// General purpose `L` register. 86 | pub l: u8, 87 | /// Program counter. 88 | pub pc: u16, 89 | /// Stack pointer. 90 | pub sp: u16, 91 | } 92 | 93 | impl SharpLR35902Registers { 94 | /// Temporarily transmutes `SharpLR35902Registers` into `DWordRegisters` 95 | /// which contain the 16-bit register pairs for convenience. 96 | fn as_dwords(&mut self) -> &mut DWordRegisters { 97 | unsafe { &mut *(self as *mut SharpLR35902Registers as *mut DWordRegisters) } 98 | } 99 | 100 | /// Sets the zero flag. 101 | fn set_z(&mut self) { 102 | self.f |= F_ZERO; 103 | } 104 | 105 | /// Sets the subtraction flag. 106 | fn set_s(&mut self) { 107 | self.f |= F_SUBTRACT; 108 | } 109 | 110 | /// Sets the half-carry flag. 111 | fn set_h(&mut self) { 112 | self.f |= F_HALFCARRY; 113 | } 114 | 115 | /// Sets the carry flag. 116 | fn set_c(&mut self) { 117 | self.f |= F_CARRY; 118 | } 119 | 120 | /// Clears the zero flag. 121 | fn clear_z(&mut self) { 122 | self.f &= !F_ZERO; 123 | } 124 | 125 | /// Clears the subtraction flag. 126 | fn clear_s(&mut self) { 127 | self.f &= !F_SUBTRACT; 128 | } 129 | 130 | /// Clears the half-carry flag. 131 | fn clear_h(&mut self) { 132 | self.f &= !F_HALFCARRY; 133 | } 134 | 135 | /// Clears the carry flag. 136 | fn clear_c(&mut self) { 137 | self.f &= !F_CARRY; 138 | } 139 | 140 | /// Returns whether or not the zero flag is set. 141 | fn z(&self) -> bool { 142 | self.f & F_ZERO == F_ZERO 143 | } 144 | 145 | /// Returns whether or not the subtraction flag is set. 146 | fn s(&self) -> bool { 147 | self.f & F_SUBTRACT == F_SUBTRACT 148 | } 149 | 150 | /// Returns whether or not the half-carry flag is set. 151 | fn h(&self) -> bool { 152 | self.f & F_HALFCARRY == F_HALFCARRY 153 | } 154 | 155 | /// Returns whether or not the carry flag is set. 156 | fn c(&self) -> bool { 157 | self.f & F_CARRY == F_CARRY 158 | } 159 | } 160 | 161 | /// Representation of the 16-bit register pairs. 162 | #[derive(Clone, Copy, Default, Debug)] 163 | #[repr(C, align(2))] 164 | struct DWordRegisters { 165 | /// Register pair `AF`. 166 | pub af: u16, 167 | /// Register pair `BC`. 168 | pub bc: u16, 169 | /// Register pair `DE`. 170 | pub de: u16, 171 | /// Register pair `HL`. 172 | pub hl: u16, 173 | /// Program counter. 174 | pub pc: u16, 175 | /// Stack pointer. 176 | pub sp: u16, 177 | } 178 | 179 | impl ::std::ops::Index for SharpLR35902Registers { 180 | type Output = u8; 181 | 182 | fn index(&self, index: u8) -> &u8 { 183 | match index { 184 | 0 => &self.b, 185 | 1 => &self.c, 186 | 2 => &self.d, 187 | 3 => &self.e, 188 | 4 => &self.h, 189 | 5 => &self.l, 190 | 6 => &self.f, 191 | 7 => &self.a, 192 | _ => panic!("Register index out of bounds."), 193 | } 194 | } 195 | } 196 | 197 | impl ::std::ops::IndexMut for SharpLR35902Registers { 198 | fn index_mut(&mut self, index: u8) -> &mut u8 { 199 | match index { 200 | 0 => &mut self.b, 201 | 1 => &mut self.c, 202 | 2 => &mut self.d, 203 | 3 => &mut self.e, 204 | 4 => &mut self.h, 205 | 5 => &mut self.l, 206 | 6 => &mut self.f, 207 | 7 => &mut self.a, 208 | _ => panic!("Register index out of bounds."), 209 | } 210 | } 211 | } 212 | 213 | impl ::std::ops::Index for DWordRegisters { 214 | type Output = u16; 215 | 216 | fn index(&self, index: u8) -> &u16 { 217 | match index { 218 | 0 => &self.bc, 219 | 1 => &self.de, 220 | 2 => &self.hl, 221 | 3 => &self.af, 222 | _ => panic!("DWord register index out of bounds."), 223 | } 224 | } 225 | } 226 | 227 | impl ::std::ops::IndexMut for DWordRegisters { 228 | fn index_mut(&mut self, index: u8) -> &mut u16 { 229 | match index { 230 | 0 => &mut self.bc, 231 | 1 => &mut self.de, 232 | 2 => &mut self.hl, 233 | 3 => &mut self.af, 234 | _ => panic!("DWord register index out of bounds."), 235 | } 236 | } 237 | } 238 | 239 | /// CPU errors. 240 | #[derive(Debug)] 241 | pub enum LRError { 242 | InvalidMemoryRead(u16), 243 | InvalidMemoryWrite(u16), 244 | RamDisabled(u16), 245 | InvalidBankRead(u16, usize), 246 | InvalidBankWrite(u16, usize), 247 | } 248 | 249 | impl From for LRError { 250 | fn from(me: crate::memory::MemoryError) -> LRError { 251 | use crate::memory::MemoryError::*; 252 | 253 | match me { 254 | InvalidMemoryRead(addr) => LRError::InvalidMemoryRead(addr), 255 | InvalidMemoryWrite(addr) => LRError::InvalidMemoryWrite(addr), 256 | RamDisabled(addr) => LRError::RamDisabled(addr), 257 | InvalidBankRead(addr, bank) => LRError::InvalidBankRead(addr, bank), 258 | InvalidBankWrite(addr, bank) => LRError::InvalidBankWrite(addr, bank), 259 | } 260 | } 261 | } 262 | 263 | impl ::std::fmt::Display for LRError { 264 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 265 | write!( 266 | f, 267 | "{}", 268 | match self { 269 | LRError::InvalidMemoryRead(addr) => format!("Invalid memory read at {:#X}", addr), 270 | LRError::InvalidMemoryWrite(addr) => format!("Invalid memory write at {:#X}", addr), 271 | LRError::RamDisabled(addr) => { 272 | format!("Attempted read/write at {:#X} while RAM disabled", addr) 273 | } 274 | LRError::InvalidBankRead(addr, bankno) => { 275 | format!("Invalid memory read at {:#X} bank no. {}", addr, bankno) 276 | } 277 | LRError::InvalidBankWrite(addr, bankno) => { 278 | format!("Invalid memory write at {:#X} bank no. {}", addr, bankno) 279 | } 280 | } 281 | ) 282 | } 283 | } 284 | 285 | impl ::std::error::Error for LRError { 286 | fn description(&self) -> &'static str { 287 | match self { 288 | LRError::InvalidMemoryRead(_) => "Invalid memory read", 289 | LRError::InvalidMemoryWrite(_) => "Invalid memory write", 290 | LRError::RamDisabled(_) => "Attempted read/write while RAM is disabled", 291 | LRError::InvalidBankRead(_, _) => "Invalid bank memory read", 292 | LRError::InvalidBankWrite(_, _) => "Invalid bank memory write", 293 | } 294 | } 295 | } 296 | 297 | impl<'a> SharpLR35902<'a> { 298 | /// Creates a new `SharpLR35902` from an `Rc>`. 299 | pub fn new(mi: MemoryController<'a>) -> SharpLR35902<'a> { 300 | Self { 301 | registers: Default::default(), 302 | interrupt_pending: false, 303 | halted: false, 304 | current_opcode: 0x76, 305 | memory_controller: mi, 306 | } 307 | } 308 | 309 | /// Reads a word at the program counter and increments. 310 | fn read_instruction_word(&mut self) -> Result { 311 | let pc = self.registers.pc; 312 | self.registers.pc += 1; 313 | 314 | Ok(self.memory_controller.read(pc)?) 315 | } 316 | 317 | /// Reads a dword at the program counter and increments twice. 318 | fn read_instruction_dword(&mut self) -> Result { 319 | let first_byte = self.read_instruction_word()?; 320 | let second_byte = self.read_instruction_word()?; 321 | 322 | Ok(((second_byte as u16) << 8) | first_byte as u16) 323 | } 324 | 325 | /// Reads a byte at the address `addr`. 326 | fn read(&mut self, addr: u16) -> Result { 327 | Ok(self.memory_controller.read(addr)?) 328 | } 329 | 330 | /// Reads a byte at the address pointed to by `HL`. 331 | fn read_hl(&mut self) -> Result { 332 | let hl = self.registers.as_dwords().hl; 333 | 334 | Ok(self.memory_controller.read(hl)?) 335 | } 336 | 337 | /// Writes a byte at the given address. 338 | fn write(&mut self, addr: u16, data: u8) -> Result<(), LRError> { 339 | self.memory_controller.write(addr, data)?; 340 | 341 | Ok(()) 342 | } 343 | 344 | /// Writes a byte to the address pointed to by `HL`. 345 | fn write_hl(&mut self, data: u8) -> Result<(), LRError> { 346 | let hl = self.registers.as_dwords().hl; 347 | 348 | self.memory_controller.write(hl, data)?; 349 | Ok(()) 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, allow(warnings))] 2 | 3 | mod cpu; 4 | mod memory; 5 | mod rom; 6 | 7 | fn main() { 8 | let file = std::env::args().nth(1).unwrap(); 9 | println!("Reading {:?}...", file); 10 | let cart = rom::GameBoyCartridge::from_file(file).unwrap(); 11 | let contents = cart.contents.clone(); 12 | println!("{:#?}\n\n", cart.header); 13 | let mem_ctrlr = memory::Mbc1::from_rom(cart); 14 | //println!("{:X?}", mem_ctrlr); 15 | } 16 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | use rom::GameBoyCartridge; 2 | 3 | pub trait MemoryInterface { 4 | type Word; 5 | type Index; 6 | 7 | fn read(&self, address: Self::Index) -> Result; 8 | fn write(&mut self, address: Self::Index, data: Self::Word) -> Result<(), MemoryError>; 9 | } 10 | 11 | pub enum MemoryError { 12 | InvalidMemoryRead(u16), 13 | InvalidMemoryWrite(u16), 14 | RamDisabled(u16), 15 | InvalidBankRead(u16, usize), 16 | InvalidBankWrite(u16, usize), 17 | } 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct MemoryRegion { 21 | data: Box<[u8]>, 22 | start: usize, 23 | end: usize, 24 | } 25 | 26 | impl MemoryRegion { 27 | pub fn new(start: usize, end: usize) -> MemoryRegion { 28 | assert!(start < end); 29 | 30 | MemoryRegion { 31 | data: vec![0; end - start + 1].into_boxed_slice(), 32 | start, 33 | end, 34 | } 35 | } 36 | 37 | pub fn from_data(start: usize, end: usize, data: &[u8]) -> MemoryRegion { 38 | let len = end - start + 1; 39 | assert!(len <= data.len()); 40 | 41 | MemoryRegion { 42 | data: data[..len].into(), 43 | start, 44 | end, 45 | } 46 | } 47 | 48 | pub fn get(&self, index: u16) -> Result { 49 | self.data 50 | .get(index as usize - self.start) 51 | .map(|&b| b) 52 | .ok_or(MemoryError::InvalidMemoryRead(index)) 53 | } 54 | 55 | pub fn set(&mut self, index: u16, data: u8) -> Result<(), MemoryError> { 56 | if let Some(byte) = self.data.get_mut(index as usize - self.start) { 57 | *byte = data; 58 | Ok(()) 59 | } else { 60 | Err(MemoryError::InvalidMemoryWrite(index)) 61 | } 62 | } 63 | } 64 | 65 | /// Areas of the memory map that are static between different memory controllers. 66 | #[derive(Debug, Clone)] 67 | pub struct SharedMemoryRegions { 68 | video_ram: MemoryRegion, 69 | work_ram_0: MemoryRegion, 70 | work_ram_1: MemoryRegion, 71 | oam: MemoryRegion, 72 | io: MemoryRegion, 73 | high_ram: MemoryRegion, 74 | } 75 | 76 | impl SharedMemoryRegions { 77 | pub fn new() -> SharedMemoryRegions { 78 | SharedMemoryRegions { 79 | video_ram: MemoryRegion::new(0x8000, 0x9FFF), 80 | work_ram_0: MemoryRegion::new(0xC000, 0xCFFF), 81 | work_ram_1: MemoryRegion::new(0xD000, 0xDFFF), 82 | oam: MemoryRegion::new(0xFE00, 0xFE9F), 83 | io: MemoryRegion::new(0xFF00, 0xFF7F), 84 | high_ram: MemoryRegion::new(0xFF80, 0xFFFF), 85 | } 86 | } 87 | 88 | pub fn get(&self, address: u16) -> Result { 89 | let addr = address as usize; 90 | if addr > self.video_ram.start && addr <= self.video_ram.end { 91 | self.video_ram.get(address) 92 | } else if addr > self.work_ram_0.start && addr <= self.work_ram_0.end { 93 | self.work_ram_0.get(address) 94 | } else if addr > self.work_ram_1.start && addr <= self.work_ram_1.end { 95 | self.work_ram_1.get(address) 96 | } else if addr > self.oam.start && addr <= self.oam.end { 97 | self.oam.get(address) 98 | } else if addr > self.io.start && addr <= self.io.end { 99 | self.io.get(address) 100 | } else if addr > self.high_ram.start && addr <= self.high_ram.end { 101 | self.high_ram.get(address) 102 | } else { 103 | Err(MemoryError::InvalidMemoryRead(address)) 104 | } 105 | } 106 | 107 | pub fn set(&mut self, address: u16, data: u8) -> Result<(), MemoryError> { 108 | let addr = address as usize; 109 | if addr > self.video_ram.start && addr <= self.video_ram.end { 110 | self.video_ram.set(address, data) 111 | } else if addr > self.work_ram_0.start && addr <= self.work_ram_0.end { 112 | self.work_ram_0.set(address, data) 113 | } else if addr > self.work_ram_1.start && addr <= self.work_ram_1.end { 114 | self.work_ram_1.set(address, data) 115 | } else if addr > self.oam.start && addr <= self.oam.end { 116 | self.oam.set(address, data) 117 | } else if addr > self.io.start && addr <= self.io.end { 118 | self.io.set(address, data) 119 | } else if addr > self.high_ram.start && addr <= self.high_ram.end { 120 | self.high_ram.set(address, data) 121 | } else { 122 | Err(MemoryError::InvalidMemoryWrite(address)) 123 | } 124 | } 125 | } 126 | 127 | /// ROM only. Contains 32KiB of ROM mapped to 0x0000 to 0x7FFF, with an optional 128 | /// 8KiB of RAM mapped at 0xA000-0xBFFF. 129 | #[derive(Debug, Clone)] 130 | pub struct RomOnly { 131 | rom: MemoryRegion, 132 | ram: Option, 133 | shared_mem: SharedMemoryRegions, 134 | } 135 | 136 | impl RomOnly { 137 | pub fn blank() -> RomOnly { 138 | RomOnly { 139 | rom: MemoryRegion::new(0x0000, 0x7FFF), 140 | ram: None, 141 | shared_mem: SharedMemoryRegions::new(), 142 | } 143 | } 144 | } 145 | 146 | impl MemoryInterface for RomOnly { 147 | type Word = u8; 148 | type Index = u16; 149 | 150 | fn read(&self, address: Self::Index) -> Result { 151 | match address { 152 | 0x0000...0x7FFF => self.rom.get(address), 153 | 0xA000...0xBFFF => { 154 | if let Some(ram) = &self.ram { 155 | ram.get(address) 156 | } else { 157 | Err(MemoryError::RamDisabled(address)) 158 | } 159 | } 160 | _ => self.shared_mem.get(address), 161 | } 162 | } 163 | 164 | fn write(&mut self, address: Self::Index, data: Self::Word) -> Result<(), MemoryError> { 165 | match address { 166 | 0x0000...0x7FFF => Err(MemoryError::InvalidMemoryWrite(address)), 167 | 0xA000...0xBFFF => { 168 | if let Some(ram) = &mut self.ram { 169 | ram.set(address, data) 170 | } else { 171 | Err(MemoryError::RamDisabled(address)) 172 | } 173 | } 174 | _ => self.shared_mem.set(address, data), 175 | } 176 | } 177 | } 178 | 179 | #[derive(Debug, Clone)] 180 | enum ModeSelect { 181 | Rom, 182 | Ram, 183 | } 184 | 185 | #[derive(Debug, Clone)] 186 | pub struct Mbc1 { 187 | rom_banks: Vec, 188 | rom_bank_select: usize, 189 | ram_banks: Vec, 190 | ram_bank_select: usize, 191 | shared_mem: SharedMemoryRegions, 192 | ram_enabled: bool, 193 | mode: ModeSelect, 194 | } 195 | 196 | impl Mbc1 { 197 | pub fn blank(num_rom_banks: usize, ram_size: usize, num_ram_banks: usize) -> Mbc1 { 198 | Mbc1 { 199 | rom_banks: { 200 | let mut v = Vec::with_capacity(num_rom_banks); 201 | v.push(MemoryRegion::new(0x0000, 0x3FFF)); 202 | 203 | for _ in 1..num_rom_banks { 204 | v.push(MemoryRegion::new(0x4000, 0x7FFF)); 205 | } 206 | 207 | v 208 | }, 209 | rom_bank_select: 1, 210 | ram_banks: vec![MemoryRegion::new(0xA000, 0xA000 + ram_size - 1); num_ram_banks], 211 | ram_bank_select: 0, 212 | shared_mem: SharedMemoryRegions::new(), 213 | ram_enabled: false, 214 | mode: ModeSelect::Rom, 215 | } 216 | } 217 | 218 | pub fn from_rom(cartridge: GameBoyCartridge) -> Mbc1 { 219 | let (ram_size, ram_banks) = if let Some(s) = cartridge.header.ram_size { 220 | s 221 | } else { 222 | (0, 0) 223 | }; 224 | 225 | let num_rom_banks = cartridge.header.rom_size / (16 * 1024); 226 | 227 | Mbc1 { 228 | rom_banks: { 229 | let mut v = Vec::with_capacity(num_rom_banks); 230 | v.push(MemoryRegion::from_data(0x0000, 0x3FFF, &cartridge.contents)); 231 | 232 | for i in 1..num_rom_banks { 233 | v.push(MemoryRegion::from_data( 234 | 0x4000, 235 | 0x7FFF, 236 | &cartridge.contents[i * 16 * 1024..], 237 | )); 238 | } 239 | 240 | v 241 | }, 242 | rom_bank_select: 1, 243 | ram_banks: if ram_size > 0 { 244 | vec![MemoryRegion::new(0xA000, 0xA000 + ram_size - 1); ram_banks] 245 | } else { 246 | Vec::new() 247 | }, 248 | ram_bank_select: 0, 249 | shared_mem: SharedMemoryRegions::new(), 250 | ram_enabled: false, 251 | mode: ModeSelect::Rom, 252 | } 253 | } 254 | } 255 | 256 | // TODO: Bank number in error? 257 | impl MemoryInterface for Mbc1 { 258 | type Word = u8; 259 | type Index = u16; 260 | 261 | fn read(&self, address: Self::Index) -> Result { 262 | match address { 263 | 0x0000...0x3FFF => self.rom_banks[0].get(address), 264 | 0x4000...0x7FFF => self.rom_banks[self.rom_bank_select].get(address), 265 | 0xA000...0xBFFF => { 266 | if self.ram_enabled { 267 | self.ram_banks[self.ram_bank_select].get(address) 268 | } else { 269 | Err(MemoryError::RamDisabled(address)) 270 | } 271 | } 272 | _ => self.shared_mem.get(address), 273 | } 274 | } 275 | 276 | fn write(&mut self, address: Self::Index, data: Self::Word) -> Result<(), MemoryError> { 277 | match address { 278 | 0x0000...0x1FFF => { 279 | if data & 0b1111 == 0xA { 280 | self.ram_enabled = true; 281 | Ok(()) 282 | } else { 283 | self.ram_enabled = false; 284 | Ok(()) 285 | } 286 | } 287 | 0x2000...0x3FFF => { 288 | let mut select = (data & 0b11111) as usize; 289 | 290 | if select == 0 { 291 | select = 1; 292 | } 293 | 294 | self.rom_bank_select = (self.rom_bank_select & !0b11111) | select; 295 | 296 | Ok(()) 297 | } 298 | 0x4000...0x5FFF => { 299 | let select = (data & 0b11) as usize; 300 | 301 | match self.mode { 302 | ModeSelect::Rom => { 303 | self.rom_bank_select = (self.rom_bank_select & !0b110_0000) | (select << 5); 304 | 305 | if self.rom_bank_select == 0x20 306 | || self.rom_bank_select == 0x40 307 | || self.rom_bank_select == 0x60 308 | { 309 | self.rom_bank_select += 1; 310 | } 311 | } 312 | ModeSelect::Ram => { 313 | self.ram_bank_select = select; 314 | } 315 | } 316 | 317 | Ok(()) 318 | } 319 | 0x6000...0x7FFF => { 320 | if data & 0b1 == 0 { 321 | self.mode = ModeSelect::Rom; 322 | } else { 323 | self.mode = ModeSelect::Ram; 324 | } 325 | 326 | Ok(()) 327 | } 328 | 0xA000...0xBFFF => { 329 | if self.ram_enabled { 330 | self.ram_banks[self.ram_bank_select].set(address, data) 331 | } else { 332 | Err(MemoryError::RamDisabled(address)) 333 | } 334 | } 335 | _ => self.shared_mem.set(address, data), 336 | } 337 | } 338 | } 339 | 340 | #[derive(Debug, Clone)] 341 | pub struct Mbc2 { 342 | rom_banks: Vec, 343 | rom_bank_select: usize, 344 | internal_ram: MemoryRegion, 345 | shared_mem: SharedMemoryRegions, 346 | ram_enabled: bool, 347 | } 348 | 349 | impl Mbc2 { 350 | pub fn blank(num_rom_banks: usize, ram_size: usize, num_ram_banks: usize) -> Mbc2 { 351 | Mbc2 { 352 | rom_banks: { 353 | let mut v = Vec::with_capacity(num_rom_banks); 354 | v.push(MemoryRegion::new(0x0000, 0x3FFF)); 355 | 356 | for _ in 1..num_rom_banks { 357 | v.push(MemoryRegion::new(0x4000, 0x7FFF)); 358 | } 359 | 360 | v 361 | }, 362 | rom_bank_select: 1, 363 | internal_ram: MemoryRegion::new(0xA000, 0xA1FF), 364 | shared_mem: SharedMemoryRegions::new(), 365 | ram_enabled: false, 366 | } 367 | } 368 | } 369 | 370 | impl MemoryInterface for Mbc2 { 371 | type Word = u8; 372 | type Index = u16; 373 | 374 | fn read(&self, address: Self::Index) -> Result { 375 | match address { 376 | 0x0000...0x3FFF => self.rom_banks[0].get(address), 377 | 0x4000...0x7FFF => self.rom_banks[self.rom_bank_select].get(address), 378 | 0xA000...0xA1FF => { 379 | if self.ram_enabled { 380 | self.internal_ram.get(address) 381 | } else { 382 | Err(MemoryError::RamDisabled(address)) 383 | } 384 | } 385 | _ => self.shared_mem.get(address), 386 | } 387 | } 388 | 389 | fn write(&mut self, address: Self::Index, data: Self::Word) -> Result<(), MemoryError> { 390 | match address { 391 | 0x0000...0x1FFF => { 392 | if (data & 0x1F) == 0 { 393 | if data & 0b1111 == 0xA { 394 | self.ram_enabled = true; 395 | Ok(()) 396 | } else { 397 | self.ram_enabled = false; 398 | Ok(()) 399 | } 400 | } else { 401 | Ok(()) 402 | } 403 | } 404 | 0x2000...0x3FFF => { 405 | if (data & 0x1F) == 1 { 406 | let mut select = (data & 0b1111) as usize; 407 | 408 | if select == 0 { 409 | select = 1; 410 | } 411 | 412 | self.rom_bank_select = (self.rom_bank_select & !0b1111) | select; 413 | 414 | Ok(()) 415 | } else { 416 | Ok(()) 417 | } 418 | } 419 | 0xA000...0xA1FF => { 420 | if self.ram_enabled { 421 | self.internal_ram.set(address, data & 0x0F) 422 | } else { 423 | Err(MemoryError::RamDisabled(address)) 424 | } 425 | } 426 | _ => self.shared_mem.set(address, data), 427 | } 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/rom/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::Path}; 2 | 3 | #[derive(Debug)] 4 | pub struct GameBoyCartridge { 5 | pub header: CartridgeHeader, 6 | pub contents: Vec, 7 | } 8 | 9 | impl GameBoyCartridge { 10 | pub fn from_file(file: impl AsRef) -> Result> { 11 | const BANK_SIZE: usize = 16 * 1024; 12 | 13 | let bytes = fs::read(file)?; 14 | 15 | let mut title = String::new(); 16 | 17 | for &b in &bytes[0x134..=0x143] { 18 | if b != 0 { 19 | title.push(b as char); 20 | } 21 | } 22 | 23 | let new_licensee_code = [bytes[0x144] as char, bytes[0x145] as char]; 24 | 25 | let sgb_support = if bytes[0x146] == 0x03 { true } else { false }; 26 | 27 | let bank_type = MemoryBankType::from_byte(bytes[0x147]).unwrap(); 28 | 29 | let rom_size = if bytes[0x148] <= 0x07 { 30 | (32 * 1024) << (bytes[0x148] as usize) 31 | } else { 32 | match bytes[0x148] { 33 | 0x52 => 72 * BANK_SIZE, 34 | 0x53 => 80 * BANK_SIZE, 35 | 0x54 => 96 * BANK_SIZE, 36 | _ => panic!("Unknown ROM size"), 37 | } 38 | }; 39 | 40 | let ram_size = match bytes[0x149] { 41 | 0x00 => None, 42 | 0x01 => Some((2 * 1024, 1)), 43 | 0x02 => Some((8 * 1024, 1)), 44 | 0x03 => Some((32 * 1024, 4)), 45 | _ => panic!("Unknown RAM size"), 46 | }; 47 | 48 | let destination = if bytes[0x14A] == 0 { 49 | Destination::Japan 50 | } else { 51 | Destination::AnywhereElse 52 | }; 53 | 54 | let old_licensee_code = bytes[0x14B]; 55 | 56 | let version_number = bytes[0x14C]; 57 | 58 | let header_checksum = bytes[0x14D]; 59 | 60 | let global_checksum = ((bytes[0x14E] as u16) << 8) | bytes[0x14F] as u16; 61 | 62 | Ok(GameBoyCartridge { 63 | header: CartridgeHeader { 64 | title, 65 | new_licensee_code, 66 | sgb_support, 67 | bank_type, 68 | rom_size, 69 | ram_size, 70 | destination, 71 | old_licensee_code, 72 | version_number, 73 | header_checksum, 74 | global_checksum, 75 | }, 76 | contents: bytes, 77 | }) 78 | } 79 | } 80 | 81 | #[derive(Debug)] 82 | pub struct CartridgeHeader { 83 | pub title: String, 84 | pub new_licensee_code: [char; 2], 85 | pub sgb_support: bool, 86 | pub bank_type: MemoryBankType, 87 | pub rom_size: usize, 88 | pub ram_size: Option<(usize, usize)>, 89 | pub destination: Destination, 90 | pub old_licensee_code: u8, 91 | pub version_number: u8, 92 | pub header_checksum: u8, 93 | pub global_checksum: u16, 94 | } 95 | 96 | #[derive(Debug)] 97 | pub enum MemoryBankType { 98 | RomOnly { 99 | ram: bool, 100 | battery: bool, 101 | }, 102 | MBC1 { 103 | ram: bool, 104 | battery: bool, 105 | }, 106 | MBC2 { 107 | battery: bool, 108 | }, 109 | MMM01 { 110 | ram: bool, 111 | battery: bool, 112 | }, 113 | MBC3 { 114 | timer: bool, 115 | ram: bool, 116 | battery: bool, 117 | }, 118 | MBC4 { 119 | ram: bool, 120 | battery: bool, 121 | }, 122 | MBC5 { 123 | ram: bool, 124 | battery: bool, 125 | rumble: bool, 126 | }, 127 | PocketCamera, 128 | BandaiTama5, 129 | HuC3, 130 | HuC1, 131 | } 132 | 133 | impl MemoryBankType { 134 | pub fn from_byte(b: u8) -> Result { 135 | use self::MemoryBankType::*; 136 | 137 | let ty = match b { 138 | 0x00 => RomOnly { 139 | ram: false, 140 | battery: false, 141 | }, 142 | 0x01 => MBC1 { 143 | ram: false, 144 | battery: false, 145 | }, 146 | 0x02 => MBC1 { 147 | ram: true, 148 | battery: false, 149 | }, 150 | 0x03 => MBC1 { 151 | ram: true, 152 | battery: true, 153 | }, 154 | 0x05 => MBC2 { battery: false }, 155 | 0x06 => MBC2 { battery: true }, 156 | 0x08 => RomOnly { 157 | ram: true, 158 | battery: false, 159 | }, 160 | 0x09 => RomOnly { 161 | ram: true, 162 | battery: true, 163 | }, 164 | 0x0B => MMM01 { 165 | ram: false, 166 | battery: false, 167 | }, 168 | 0x0C => MMM01 { 169 | ram: true, 170 | battery: false, 171 | }, 172 | 0x0D => MMM01 { 173 | ram: true, 174 | battery: true, 175 | }, 176 | 0x0F => MBC3 { 177 | ram: false, 178 | battery: true, 179 | timer: true, 180 | }, 181 | 0x10 => MBC3 { 182 | ram: true, 183 | battery: true, 184 | timer: true, 185 | }, 186 | 0x11 => MBC3 { 187 | ram: false, 188 | battery: false, 189 | timer: false, 190 | }, 191 | 0x12 => MBC3 { 192 | ram: true, 193 | battery: false, 194 | timer: false, 195 | }, 196 | 0x13 => MBC3 { 197 | ram: true, 198 | battery: true, 199 | timer: false, 200 | }, 201 | 0x15 => MBC4 { 202 | ram: false, 203 | battery: false, 204 | }, 205 | 0x16 => MBC4 { 206 | ram: true, 207 | battery: false, 208 | }, 209 | 0x17 => MBC4 { 210 | ram: true, 211 | battery: true, 212 | }, 213 | 0x19 => MBC5 { 214 | ram: false, 215 | battery: false, 216 | rumble: false, 217 | }, 218 | 0x1A => MBC5 { 219 | ram: true, 220 | battery: false, 221 | rumble: false, 222 | }, 223 | 0x1B => MBC5 { 224 | ram: true, 225 | battery: true, 226 | rumble: false, 227 | }, 228 | 0x1C => MBC5 { 229 | ram: false, 230 | battery: false, 231 | rumble: true, 232 | }, 233 | 0x1D => MBC5 { 234 | ram: true, 235 | battery: false, 236 | rumble: true, 237 | }, 238 | 0x1E => MBC5 { 239 | ram: true, 240 | battery: true, 241 | rumble: true, 242 | }, 243 | 0xFC => PocketCamera, 244 | 0xFD => BandaiTama5, 245 | 0xFE => HuC3, 246 | 0xFF => HuC1, 247 | _ => return Err(()), 248 | }; 249 | 250 | Ok(ty) 251 | } 252 | } 253 | 254 | #[derive(Debug)] 255 | pub enum Destination { 256 | Japan, 257 | AnywhereElse, 258 | } 259 | --------------------------------------------------------------------------------