├── .gitignore
├── CODE-DMG.csproj
├── LICENSE
├── README.md
├── fall_back.gb
├── git-res
├── Boot.png
├── DMG-GUI.png
├── Demo.mp4
├── DonkeyKongLand.gif
├── Kriby.gif
├── MarioLand2.gif
├── PokemonGold.gif
├── PokemonYellow.gif
├── Screenshots.png
├── TestRoms.png
├── Tetris.gif
├── Zelda.gif
├── boot.gif
└── logo.png
├── icon.png
├── src
├── CPU.cs
├── DMG.cs
├── Helper.cs
├── Joypad.cs
├── MBC.cs
├── MMU.cs
├── PPU.cs
├── Program.cs
├── Test.cs
└── Timer.cs
└── test
└── README.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | obj/
3 | bin/
4 | bulid/
5 | publish/
6 |
7 | extra/
8 | test/v1/
9 |
10 | *.bin
11 | *.gb
12 | *.gbc
13 |
14 | !fall_back.gb
--------------------------------------------------------------------------------
/CODE-DMG.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | CODE_DMG
7 | enable
8 | enable
9 |
10 |
11 |
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Bot Randomness
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CODE-DMG
7 |
8 | CODE-DMG, a Gameboy emulator, written in C#.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | CODE-DMG is a Gameboy emulator, capable of running some of the best games for the Gameboy. A bit of a background: With some of my recent projects being focused on emulation
19 | (CHIP-8, Intel 8080), I really got into low level development. Soon after making my Intel 8080 emulator, GO-8080, I wanted to emulate a system more familiar, something I had experience in. Then it hits me, "I should make a Gameboy emulator!" It's a step up from the Intel-8080, and I thought it would be perfect challenge, to learn, and to experiment. Here is a little background about the Gameboy itself (As if it needs a introduction). The Gameboy was designed by Gunpei Yokoi at Nintendo, which then launched in 1989. The Gameboy hardware is quite simple, but it is utilize in complex ways by games. The base hardware of the Gameboy is as follows: a 8-bit CPU known as the Sharp LR35902 (SM83) (also known as DMG-CPU) is clocked at around 4.19 Mhz, with the internal memory of 8 KB of RAM and 8 KB of VRAM with a 16-bit address bus. Gameboy cartridges also have MBC chips which allows for bigger roms with additional RAM. The Gameboy sports a PPU (Picture Processing Unit, think about like the Gameboy's GPU), which is responsible of rendering on the 160x144 LCD with 4 shades of green. The Gameboy's codename was DMG, Dot Maxtix Game (hence the name of this project), and DMG refers to display technolgy used. The Gameboy is known as one of the best gaming devices to ever release, especially how it revolutionized handheld gaming! Going back to on this emulator, I found it really fun to work on! You may be wondering, "Why use C#, and not like C/C++?" Well C# is one of favorite language, so why it not make my favorite project in my favorite language! You now be saying, "That's cool and all, but why use Raylib-cs, and not like WinForm or something like that?" Because I found Raylib for rendering graphics to be simple, with less overhead, and it's just fun! :)
20 |
21 |
22 |
23 |
24 |
25 |
26 | ## Getting Started
27 | Want to use it, and mess around? Here's how you can get started!
28 | Side Note: You may be wondering why is there a "Core" version and "GUI" version. The Core version is only focused on the main emulation. The GUI version is using that core with a nice GUI, so there is no need to use the terminal. CODE-DMG is mainly focused on the Core. However the main 3 reason for the GUI version is ease of use, providing debug tools, and testing the flexibility of the Core. See "imgui" branch for source code of the GUI version.
29 | ### Download
30 | #### GUI
31 | 1. Download it from [here](https://github.com/BotRandomness/CODE-DMG/releases), or on the releases page. (win-x64, win-x86, osx-x64, osx-arm64, linux-x64)
32 | 2. Unzip the folder
33 | 3. Launch the executable
34 | - On MacOS, there might be a pop up saying "Apple could not verify", this is normal. Simply right click on the app, then open, then open again. You also go to System Settings, then security, then allow. You only need to do this once during the first time.
35 | - You may also need to enable execute permission on "Unix-like Oses"
36 | 5. You are ready to go!
37 | #### Core
38 | 1. Download it from [here](https://github.com/BotRandomness/CODE-DMG/releases), or on the releases page. (win-x64, win-x86, osx-x64, osx-arm64, linux-x64)
39 | 2. Unzip the folder
40 | 3. Open up your terminal at the download location (Can be ran with CLI only for right now) Your current working directory must be at the application location when using!
41 | 4. Optional: Place the bootrom by the executable. Bootrom file should be named `dmg_boot.bin`
42 | 5. Windows: `CODE-DMG --dmg `, Unix-like Oses: `./CODE-DMG --dmg ` (You may also need to enable execute permission on "Unix-like Oses". Signing needed for MacOS use "ad-hoc", GUI version already signed, see Build section for more infomation)
43 | 6. You are ready to go!
44 | ### Controls
45 | - (A) = Z
46 | - (B) = X
47 | - [START] = [ENTER]
48 | - [SELECT] = [RSHIFT]
49 | - D-Pad = ArrowKeys
50 | ### Usage
51 | #### GUI
52 | After launching the executable, you are ready to go. Load a ROM by `File -> Open ROM`. Switch to Game mode (the view without all the debug stuff) by `View -> Game`
53 | #### Core
54 | For the most basics usage: `CODE-DMG --dmg ` or `./CODE-DMG --dmg `. Terminal should be at the location of the application as the current working directory when calling `CODE-DMG`. Save files are supported (experimental), a `.sav` file will be genarated in the same location as the ROM. This `.sav` file can work between emulators like BGB. Currently CODE-DMG support MBC0/ROM Only roms, as well as experimental MBC1, MBC3 (no RTC), and MB5 (no rumble).
55 | #### Flags
56 | CODE-DMG flags when using terminal. Note: these flags can be passed in any order, and in any combination.
57 | - `--dmg `, `--dmg`: Starts up the emulator given a rom file (Default mode. No rom given, fall back is default)
58 | - `--json `: Runs a CPU test for a instruction given a JSON file in test/v1
59 | - `-b `, `--bootrom `: Loads custom bootrom path than default. (dmg_boot.bin is default)
60 | - `-s `, `--scale `: Scale window size by factor (2 is default)
61 | - `-f`, `--fps`: Enables FPS counter (off is default)
62 | - `-rl`, `--raylib-log`: Enables Raylib logs (off is default)
63 | - `-p `, `--palette `: Changes the 2bpp palette given name (dmg is default)
64 | - `-a`, `--about`: Shows about
65 | - `-v`, `--version`: Shows version number
66 | - `-h`, `--help`: Shows help screen
67 | #### Bootrom
68 | The bootrom of the Gameboy is first program that is ran by the Gameboy which sets up register and memory value, and also performs a validity check on the rom. This better known as the Nintendo logo scrolling down when you power on a Gameboy. Optional, but it is recommended you provide the bootrom. It must be named `dmg_boot.bin` and be placed in root of executable for the Core version. For the GUI version, you can go to `File -> Select BOOTROM` However, even without a bootrom, roms will still work.
69 |
70 | #### Fallback ROM
71 | The fallback rom, `fall_back.gb`, is a custom ROM I made in GBZ80 assembly as a fall back rom the emulator can run. This rom purposely has invalid header data, which if provided a bootrom, can emulate what happens when you turn on a Gameboy without a game. If a bootrom is not provided, and the fall back rom is ran, then you will see a screen with a message saying "This should not be seen on a real DMG or emu with bootrom checks"
72 |
73 | ## Screenshots
74 |
75 |
76 |
77 | Showcase of running games
78 |
79 | ### Demo Gameplay
80 |
81 |
82 |  |
83 |  |
84 |
85 |
86 |  |
87 |  |
88 |
89 |
90 |  |
91 |  |
92 |
93 |
94 |  |
95 |  |
96 |
97 |
98 |
99 | ### Palette Showcase
100 | https://github.com/user-attachments/assets/3bcbf186-995e-4dc8-8f52-25cb3de8892e
101 | Pallette Names (Top Left to Bottom Right): dmg, cyber, autumn, paris, emu, coffee, grayscale, early, crow, winter
102 |
103 | ## Compatibility
104 |
105 |
106 |
107 | dmg-acid2 passes. Blargg's cpu_instrs passes except for interrupts, which is because I didn't implement all interrupts yet
108 | When it comes to the Mooneye MBC test, MBC5 passes, and MBC1 mostly passes. (I did say MBCs support is experimental)
109 |
110 | Pretty much every game I tried does work. Do note MBC0/ROM Only should work. MBC1, MBC3, and MBC5 should also work. I don't have a full list of games, but here is a list of games I tried:
111 | ```
112 | Asteriods - Works
113 | Donkey Kong Land - Works
114 | Donkey Kong Land 2 - Works
115 | Donkey Kong Land 3 - Works
116 | Dr.Mario - Works
117 | Dropzone - Works
118 | Kriby's Dream Land - Works
119 | Kriby's Dream Land 2 - Hangs on first frame due to unimplemented timer interrupt
120 | Legend of Zelda, The Link's Awakening - Works
121 | Pokemon Blue - Works
122 | Pokemon Yellow - Works
123 | Pokemon Gold - Works
124 | Prehistorik Man - Playable, but graphical glitches in intro (requires accurate timing)
125 | Super Mario Land - Works
126 | Super Mario Land 2, 6 Golden Coins - Works
127 | Tetris - Works
128 | Tennis - Works
129 | ```
130 |
131 | ## Compile
132 | Want to tinker around, modify, make your own, learn a bit about emulation development, or contribute? Here's how you can get started with the code and compile.
133 |
134 | To get started, you need to have dontnet install. For reference, I used dotnet 6.0
135 |
136 | 1. Download dotnet: https://dotnet.microsoft.com/en-us/
137 | 2. Clone this repository, and point your terminal to the root directory of the repository
138 | 3. Run `dotnet run -- --dmg ` to compile, and it should run right after! For the GUI version, doing a `dotnet run` will compile and run right after!
139 |
140 | Raylib-cs (the C# binding (made by Chris Dill) for Raylib), does not need to be installed, as dotnet will automatically install any dependences from NuGet. For more information on raylib-cs can be found here on Github: https://github.com/chrisdill/raylib-cs. This also applies to [Rlimgui-cs](https://github.com/raylib-extras/rlImGui-cs) and [ImGui.NET](https://github.com/ImGuiNET/ImGui.NET) for the GUI version.
141 |
142 | ### Build
143 | - For your own platform, framework dependent: `dotnet publish`
144 | - For other platform, single file, not framework dependent: `dotnet publish -r --self-contained -o bulid/ /p:PublishSingleFile=true`
145 | - For other platform, single file, framework dependent: `dotnet publish -r --no-self-contained -o bulid/ /p:PublishSingleFile=true`
146 |
147 | The reason to have both dotnet dependent or not is the file size. If the user already has dotnet, the lighter file size is the best option. If the user does not have dotnet, it's more convenient to bundle in the dotnet as self contained even if the file size is larger. It's best to put PublishSingleFile for convenience, especially for self contained dotnet as that will have 224 dll files all in the root of the executable.
148 |
149 | For more see the dotnet publish documentation: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish, RID: https://learn.microsoft.com/en-us/dotnet/core/rid-catalog, SingleFile: https://github.com/dotnet/designs/blob/main/accepted/2020/single-file/design.md
150 |
151 | For MacOS, building requires signing, especially for Apple Silicon. This part is going to be a general note. Dotnet and C# when running and building will automatically sign the binaries with a simple "ad-hoc" (meaning needed or for this) signature. This needs to be done for using and distributing. On other platforms, compiling for Macs would not be signed, since Apple's `codesign` tool is only on Macs (though others have made open source versions of the tool for cross-platform use). If you have a unsigned binary compiled on a non-Mac platform now on a Mac platform, a simple "ad-hoc" signing will do and can done by `codesign -s - `. When it comes `.app` bundles as seen in the GUI version, signing is also required in the same way. You can bulid the `.app` bundle manually since it's just a directory (you can see the GUI version of CODE-DMG as a reference). However, even if the `.app` bundle is made up with already signed binary, you will have to re-sign the whole `.app` bundle. This can be done with `codesign --force --deep -s - `.
152 |
153 | ### Program Architechture
154 | Here's a little information on the program layout!
155 |
156 | Looking over the code, the program is quite simple, don't worry! This portation was written to be simple, so no matter of your skill level, anybody should get the idea of the program works, it's sort of the reason why I write these parts! :)
157 |
158 | C# is a object oriented language. Due to this, we can represent each "component" of the Gameboy as a object. This means we have the following:
159 | - `CPU.cs`: The 8-bit CPU of the Gameboy (Also can be know as SM83)
160 | - `PPU.cs`: The PPU of the Gameboy, responsible for changing the mode depending on rendering state, and rendering to screen
161 | - `MMU.cs`: The Memory Management Unit is reponsible of handling `Read()` and `Write()` to all types memory on the Gameboy, with 16-bit addressing space
162 | - `MBC.cs`: The Memory Bank Controller chip (MBCs) on the cartridge to switch ROM banks and RAM banks to allow ROM size to be greater than 32 KB, and external RAM which be used for save data or extra working RAM
163 | - `Joypad.cs`: Handles input for the Gameboy
164 | - `Timer.cs`: The timer component to update timing values
165 | - `DMG.cs`: Where we "wire up" all the componets together
166 | - `Test.cs`: Used for JSON test for the CPU
167 | - `Helper.cs`: Static class to hold global values
168 | - `Program.cs`: The entry point of the emulator
169 |
170 | Note: This not a cycle accurate emulator. Also when it comes to rendering, this is not FIFO based, but a scanline based renderer.
171 |
172 | Each component sticks to it's task. The core of this emulator is written in plain C#, with the only outside library being raylib-cs which is used very minimally. It would be something I may do where I refactor the code to make the core more independent, which should be easy to do.
173 |
174 | Moving on to the basic flow, in the `DMG.cs` a `Run()` methods "wires up" the compoents together as so:
175 | ```cs
176 | public void Run() {
177 | ...
178 | while (!Raylib.WindowShouldClose()) {
179 | Raylib.BeginDrawing();
180 |
181 | int cycles = 0;
182 | int cycle = 0;
183 |
184 | joypad.HandleInput();
185 |
186 | while (cycles < 70224) { //70224 cycles per frame (59.73 FPS)
187 | cycle = cpu.ExecuteInstruction();
188 | cycles += cycle;
189 | ppu.Step(cycle);
190 | timer.Step(cycle);
191 | }
192 | ...
193 | Raylib.EndDrawing();
194 | }
195 | }
196 | ```
197 | As we can see, the main emulation loop is quite simple, having the "CPU drive". We `ExecuteInstruction()` to then pass the `cycle` taken by that instruction to both the PPU and Timer. We can also see we are tracking the number of total `cycles`, this for to keep count of cycles per frame. On the orignal Gameboy, it ran at a speed of around 59.73 FPS, which is around 70224 T-Cycles (It is important to note we are using T-Cycles, not M-Cycles) The PPU and Timer object both are tracking total cycles by themselves for their own operations. After 70224 T-Cycles, that is consider one frame done. This helps to ensure we keep the CPU and PPU and other components are in sync. We have the outer while loop to loop until closed, with raylib being set to 60 FPS. This ensures the Gameboy components are in sync, and the speed we play at feels like orignal speed. Note we can also set raylib to a fast speed, allowing a "fast forward" type feature, while the main emualtion loop of componets will still be in sync.
198 | All the other components should follows their own sturucture to do it's job. For example the `CPU` will execute a instruction using the `MMU`, reading the opcode to see what instruction to do, then writing to memory if needed or handle a interrupt. The `PPU` will change between for modes `HBLANK`, `VBLANK`, `OAM`, and `VRAM` depending on the cycle count, and perform diffent task during those modes, including rendering a scanline. The `MMU` is not only responsible for reading and writing to memory, but it also houses the MMIO, which are registers stored in memory that is assigned to a specific address, for example the current horizontal line count, `LY`, is stored at `0xFF44`. Using `CPU.cs` as example, a constructor is made to set the default state of the componet:
199 | ```cs
200 | public CPU(MMU mmu) {
201 | A = B = C = D = E = H = L = F = 0;
202 | PC = 0x0000;
203 | SP = 0x0000; //Boot Rom will set this to 0xFFFE
204 | zero = negative = halfCarry = carry = false;
205 | IME = false;
206 |
207 | this.mmu = mmu;
208 |
209 | Console.WriteLine("CPU init");
210 | }
211 | ```
212 | Notice how other component can rely on others, and how they need to be passed in. In this case of the CPU, the MMU is passed in, as many insturction need to access to the memory. Each component then had the "main" method they would be invoking. For PPU and Timer it's called `Step()` as we step every cycle passed in. For the CPU, we have `ExecuteInstruction()`:
213 | ```cs
214 | (A little snippet...)
215 | private byte Fetch() {
216 | return mmu.Read(PC++);
217 | }
218 |
219 | public int ExecuteInstruction() {
220 | ...
221 | byte opcode = Fetch();
222 | switch (opcode) {
223 | case 0x00:
224 | return NOP();
225 | case 0x01:
226 | return LD_RR_U16(ref B, ref C);
227 | ...
228 |
229 | private int LD_RR_U16(ref byte r1, ref byte r2) {
230 | r2 = Fetch();
231 | r1 = Fetch();
232 | return 12;
233 | }
234 | ```
235 | Each component works it small parts, emulating the behavior the Gameboy. Looking though each part, it should be easy enough to follow along.
236 |
237 | ## Credits/Resources
238 | CODE-DMG wouldn't be possible without these resources:
239 | - PanDocs: https://gbdev.io/pandocs/
240 | - Gbops - The Gameboy Opcodes: https://izik1.github.io/gbops/
241 | - Wheremyfoodat's logs: https://github.com/wheremyfoodat/Gameboy-logs
242 | - SingleStepTest JSON Test SM83: https://github.com/SingleStepTests/sm83
243 | - Rodrigo Copetti's GameBoy Architecture A Practical Analysis: https://www.copetti.org/writings/consoles/game-boy/
244 |
245 | These documentations were so useful, I recommend anyone to use them!
246 | Also shoutout to the EmuDev community!
247 |
248 | ## Upcoming Features
249 | - [ ] Debugger
250 | - VRAM viewer, Memory viewer, step mode
251 | - [ ] Add MBC2, MBC6, and MBC7
252 | - [ ] Serial port
253 | - [X] UI
254 | - Working with Dear ImGUI (possbile port to raygui)
255 | - [ ] Audio
256 | - Post any feature request in the Issues tab!
257 |
258 | ## Known issues
259 | - [ ] Finish up interrupts
260 | - [ ] Finish up timers
261 | - `TIMA`, `TMA`, and `TAC`
262 | - [ ] Fix up MBC1
263 | - Banking mode
264 | - If you find other bugs/issues, open up a issue in the Issue tab
265 |
266 | ## Remarks
267 | Working on this emulator has been a fun project, to learn about low level system development, and to experiment. I hope this repository can be used by future Gameboy emulation developers as a reference if they get stuck on something. I tried to keep to code as simple and clear as possible, and hope it can help someone else in the future :)
268 |
269 | If you are starting out making a Gameboy emulator, this is path I did that worked out for me. To get a base minimum Gameboy system running, I went for running the bootrom. The bootrom is one of the simplest Gameboy program to run. The bootrom only uses 49 opcodes, which is like around 25 general functions (depending how you implement it), and setting a stopping point at `PC:0x100` as that's where the bootroms ends and a ROM would take over. I used [wheremyfoodat's bootrom log](https://github.com/wheremyfoodat/Gameboy-logs) to make sure my bootrom logs matched up with theirs. This allows you set up your base CPU and MMU, and then a PPU with background rendering. This sets up your base emulation system, while having a Gameboy program running and rendering! Then I moved on to focus on the CPU with JSON. Persoanlly, I found it easier to do the JSON test per CPU instruction, than run the Blargg test roms first. Setting up the JSON test is quite easy, just load the register and memory value of the before state in the JSON file, run the instruction, then compare your registers and memory values in the after state in the JSON file. Doing the JSON test is pretty easy and should catch most of the bugs. (Also note the JSON test assume you have a flat 64 KB array, so in my MMU I has 2 modes: One for regular Gameboy operation, and a simple mode for JSON Test) Once the CPU is done, I ran the Blargg individual CPU tests, and pass all of them but one, the `SBC` instruction (Not including interrutpts). Even though the JSON test didn't catch this bug in my `SBC` instruction, it was a really simple zero flag fix I had to do. Then I moved on the PPU using [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) to finish up the PPU. When it comes to interrupts, the only thing needed for most ROMS to run is VBLANK interrupt, and the LCD interrutpt, which is needed to get dmg-acid2 rendering right (It uses the LYC==LY coincidence interrupt). After that, pretty much all MBC0/ROM Only ROMs worked. Then I moved to making the MBCs, which currently I have MBC1, MBC3, and MBC5. I hope reading this little snippet remark and looking through this repository was helpful to someone developing thier own Gameboy emulator. Of course, for the best detailed information, [PanDocs](https://gbdev.io/pandocs/) is really useful. Thank you for reading!
270 | ```
271 | __________________
272 | |-|--------------|-|
273 | | ______________ |
274 | | | __________ | |
275 | | | | | | |
276 | | |·| | | |
277 | | | | | | |
278 | | | |__________| | |
279 | | |_____________/ |
280 | | _ GAMEBOY |
281 | | _| |_ () |
282 | ||_ _| () |
283 | | |_| |
284 | | / / \\\ /
285 | |________________/
286 | Bot Randomness
287 |
288 | ^ ASCII Gameboy art I made myself
289 | Free to use. If used, credit is not needed, but is appreciated :)
290 | ```
291 |
292 | ## Contributing
293 |
294 | This project is open-source under the MIT License, meaning your free to do what ever you want with it. This project is freely available for anyone to contribute, emulations experts, Nintendo fans, Gameboy lovers, retro enthusiast, or someone who is new to it all.
295 |
296 | If you plan on contributing, a good place to start is to look at upcoming wanted features, and known issues. If you find a new bug, or have feature ideas of your own, posted first to the Issues tab before hand. You can even fork it and make it your own!
297 |
298 | To get started on contributing:
299 |
300 | 1. Fork or Clone the Project
301 | 2. Once you have your own repository (it can be a public repository) to work in, you can get started on what you want to do!
302 | 3. Make sure you git Add and git Commit your Changes to your repository
303 | 4. Then git push to your repository
304 | 5. Open a Pull Request in this repository, where your changes will be look at to be approved
305 | 6. Once it's approved, it will be in a development branch, soon to be merge to the main branch
306 |
307 |
308 |
309 | ## License
310 |
311 | Distributed under the MIT License. See `LICENSE` for more information.
312 |
--------------------------------------------------------------------------------
/fall_back.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/fall_back.gb
--------------------------------------------------------------------------------
/git-res/Boot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/Boot.png
--------------------------------------------------------------------------------
/git-res/DMG-GUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/DMG-GUI.png
--------------------------------------------------------------------------------
/git-res/Demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/Demo.mp4
--------------------------------------------------------------------------------
/git-res/DonkeyKongLand.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/DonkeyKongLand.gif
--------------------------------------------------------------------------------
/git-res/Kriby.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/Kriby.gif
--------------------------------------------------------------------------------
/git-res/MarioLand2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/MarioLand2.gif
--------------------------------------------------------------------------------
/git-res/PokemonGold.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/PokemonGold.gif
--------------------------------------------------------------------------------
/git-res/PokemonYellow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/PokemonYellow.gif
--------------------------------------------------------------------------------
/git-res/Screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/Screenshots.png
--------------------------------------------------------------------------------
/git-res/TestRoms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/TestRoms.png
--------------------------------------------------------------------------------
/git-res/Tetris.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/Tetris.gif
--------------------------------------------------------------------------------
/git-res/Zelda.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/Zelda.gif
--------------------------------------------------------------------------------
/git-res/boot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/boot.gif
--------------------------------------------------------------------------------
/git-res/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/git-res/logo.png
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BotRandomness/CODE-DMG/c1484171411afd622aced1155b5ac902218a8d7d/icon.png
--------------------------------------------------------------------------------
/src/CPU.cs:
--------------------------------------------------------------------------------
1 | class CPU {
2 | public byte A, B, C, D, E, H, L, F;
3 | public ushort PC, SP;
4 | public bool zero, negative, halfCarry, carry;
5 | public bool IME;
6 | private bool halted;
7 |
8 | private MMU mmu;
9 |
10 | public CPU(MMU mmu) {
11 | A = B = C = D = E = H = L = F = 0;
12 | PC = 0x0000;
13 | SP = 0x0000; //Boot Rom will set this to 0xFFFE
14 | zero = negative = halfCarry = carry = false;
15 | IME = false;
16 |
17 | this.mmu = mmu;
18 |
19 | Console.WriteLine("CPU init");
20 | }
21 |
22 | public void Reset() {
23 | A = 0x01;
24 | F = 0xB0; //Z=1,N=0,H=1,C=1
25 | UpdateFlagsFromF();
26 | B = 0x00;
27 | C = 0x13;
28 | D = 0x00;
29 | E = 0xD8;
30 | H = 0x01;
31 | L = 0x4D;
32 | PC = 0x100;
33 | SP = 0xFFFE;
34 |
35 | mmu.JOYP = 0xCF;
36 | mmu.DIV = 0x18;
37 | mmu.IF = 0xE1;
38 | mmu.LCDC = 0x91;
39 | mmu.STAT = 0x85;
40 | mmu.SCY = 0x00;
41 | mmu.SCX = 0x00;
42 | mmu.LY = 0x00;
43 | mmu.LYC = 0x00;
44 | mmu.BGP = 0xFC;
45 | mmu.Write(0xFF50, A);
46 | }
47 |
48 | public int HandleInterrupts() {
49 | byte interruptFlag = mmu.Read(0xFF0F);
50 | byte interruptEnable = mmu.Read(0xFFFF);
51 | byte interrupts = (byte)(interruptFlag & interruptEnable);
52 |
53 | if (interrupts != 0) {
54 | halted = false;
55 | if (IME) {
56 | IME = false;
57 |
58 | for (int bit = 0; bit < 5; bit++) {
59 | if ((interrupts & (1 << bit)) != 0) {
60 | mmu.Write(0xFF0F, (byte)(interruptFlag & ~(1 << bit)));
61 |
62 | SP--;
63 | mmu.Write(SP, (byte)((PC >> 8) & 0xFF));
64 | SP--;
65 | mmu.Write(SP, (byte)(PC & 0xFF));
66 |
67 | PC = GetInterruptHandlerAddress(bit);
68 | return 20;
69 | }
70 | }
71 | }
72 | return 0;
73 | }
74 |
75 | if (halted && interrupts != 0)
76 | {
77 | halted = false;
78 | }
79 |
80 | return 0;
81 | }
82 |
83 | private ushort GetInterruptHandlerAddress(int bit) {
84 | switch (bit) {
85 | case 0: return 0x40; //VBlank interrupt handler
86 | case 1: return 0x48; //LCD STAT interrupt handler
87 | case 2: return 0x50; //Timer interrupt handler
88 | case 3: return 0x58; //Serial interrupt handler
89 | case 4: return 0x60; //Joypad interrupt handler
90 | default: return 0;
91 | }
92 | }
93 |
94 | private void UpdateFFromFlags() {
95 | F = 0;
96 | if (zero) F |= 0x80; //Set the Zero flag (bit 7)
97 | if (negative) F |= 0x40; //Set the Negative (Subtraction) flag (bit 6)
98 | if (halfCarry) F |= 0x20; //Set the Half Carry flag (bit 5)
99 | if (carry) F |= 0x10; //Set the Carry flag (bit 4)
100 | }
101 |
102 | public void UpdateFlagsFromF() {
103 | zero = (F & 0x80) != 0;
104 | negative = (F & 0x40) != 0;
105 | halfCarry = (F & 0x20) != 0;
106 | carry = (F & 0x10) != 0;
107 | }
108 |
109 | private ushort Get16BitReg(string pair) {
110 | switch (pair.ToLower()) {
111 | case "bc":
112 | return (ushort)((B << 8) | C);
113 | case "de":
114 | return (ushort)((D << 8) | E);
115 | case "hl":
116 | return (ushort)((H << 8) | L);
117 | case "af":
118 | return (ushort)((A << 8) | F);
119 | default:
120 | return 0;
121 | }
122 | }
123 |
124 | private void Load16BitReg(string pair, ushort value) {
125 | switch (pair.ToLower()) {
126 | case "bc":
127 | B = (byte)(value >> 8);
128 | C = (byte)(value & 0xFF);
129 | break;
130 | case "de":
131 | D = (byte)(value >> 8);
132 | E = (byte)(value & 0xFF);
133 | break;
134 | case "hl":
135 | H = (byte)(value >> 8);
136 | L = (byte)(value & 0xFF);
137 | break;
138 | case "af":
139 | A = (byte)(value >> 8);
140 | F = (byte)(value & 0xFF);
141 | break;
142 | default:
143 | break;
144 | }
145 | }
146 |
147 | public void Log() {
148 | UpdateFFromFlags();
149 | ushort op1 = (PC);
150 | ushort op2 = (ushort)(PC + 1);
151 | ushort op3 = (ushort)(PC + 2);
152 | ushort op4 = (ushort)(PC + 3);
153 | Console.WriteLine("A: " + A.ToString("X2") + " F: " + F.ToString("X2") + " B: " + B.ToString("X2") + " C: " + C.ToString("X2") + " D: " + D.ToString("X2") + " E: " + E.ToString("X2") + " H: " + H.ToString("X2") + " L: " + L.ToString("X2") + " SP: " + SP.ToString("X4") + " PC: " + "00:" + PC.ToString("X4") + " (" + mmu.Read(op1).ToString("X2") + " " + mmu.Read(op2).ToString("X2") + " " + mmu.Read(op3).ToString("X2") + " " + mmu.Read(op4).ToString("X2") + ")");
154 | }
155 |
156 | private byte Fetch() {
157 | return mmu.Read(PC++);
158 | }
159 |
160 | public int ExecuteInstruction() {
161 | //Log();
162 | //if(PC>= 0x100) {Log();}
163 |
164 | int interruptCycles = HandleInterrupts();
165 | if (interruptCycles > 0) {
166 | return interruptCycles;
167 | }
168 |
169 | if (halted) {
170 | return 4;
171 | }
172 |
173 | byte opcode = Fetch();
174 |
175 | switch (opcode) {
176 | case 0x00:
177 | return NOP();
178 | case 0x01:
179 | return LD_RR_U16(ref B, ref C);
180 | case 0x02:
181 | return LD_ARR_R(ref A, "bc");
182 | case 0x03:
183 | return INC_RR("bc");
184 | case 0x04:
185 | return INC_R(ref B);
186 | case 0x05:
187 | return DEC_R(ref B);
188 | case 0x06:
189 | return LD_R_U8(ref B);
190 | case 0x07:
191 | return RLCA();
192 | case 0x08:
193 | return LD_AU16_SP();
194 | case 0x09:
195 | return ADD_HL_RR("bc");
196 | case 0x0A:
197 | return LD_R_ARR(ref A, "bc");
198 | case 0x0B:
199 | return DEC_RR("bc");
200 | case 0x0C:
201 | return INC_R(ref C);
202 | case 0x0D:
203 | return DEC_R(ref C);
204 | case 0x0E:
205 | return LD_R_U8(ref C);
206 | case 0x0F:
207 | return RRCA();
208 | case 0x10:
209 | return STOP();
210 | case 0x11:
211 | return LD_RR_U16(ref D, ref E);
212 | case 0x12:
213 | return LD_ARR_R(ref A, "de");
214 | case 0x13:
215 | return INC_RR("de");
216 | case 0x14:
217 | return INC_R(ref D);
218 | case 0x15:
219 | return DEC_R(ref D);
220 | case 0x16:
221 | return LD_R_U8(ref D);
222 | case 0x17:
223 | return RLA();
224 | case 0x18:
225 | return JR_CON_I8(true);
226 | case 0x19:
227 | return ADD_HL_RR("de");
228 | case 0x1A:
229 | return LD_R_ARR(ref A, "de");
230 | case 0x1B:
231 | return DEC_RR("de");
232 | case 0x1C:
233 | return INC_R(ref E);
234 | case 0x1D:
235 | return DEC_R(ref E);
236 | case 0x1E:
237 | return LD_R_U8(ref E);
238 | case 0x1F:
239 | return RRA();
240 | case 0x20:
241 | return JR_CON_I8(!zero);
242 | case 0x21:
243 | return LD_RR_U16(ref H, ref L);
244 | case 0x22:
245 | return LD_AHLI_A();
246 | case 0x23:
247 | return INC_RR("hl");
248 | case 0x24:
249 | return INC_R(ref H);
250 | case 0x25:
251 | return DEC_R(ref H);
252 | case 0x26:
253 | return LD_R_U8(ref H);
254 | case 0x27:
255 | return DAA();
256 | case 0x28:
257 | return JR_CON_I8(zero);
258 | case 0x29:
259 | return ADD_HL_RR("hl");
260 | case 0x2A:
261 | return LD_A_AHLI();
262 | case 0x2B:
263 | return DEC_RR("hl");
264 | case 0x2C:
265 | return INC_R(ref L);
266 | case 0x2D:
267 | return DEC_R(ref L);
268 | case 0x2E:
269 | return LD_R_U8(ref L);
270 | case 0x2F:
271 | return CPL();
272 | case 0x30:
273 | return JR_CON_I8(!carry);
274 | case 0x31:
275 | return LD_SP_U16();
276 | case 0x32:
277 | return LD_AHLM_A();
278 | case 0x33:
279 | return INC_SP();
280 | case 0x34:
281 | return INC_AHL();
282 | case 0x35:
283 | return DEC_AHL();
284 | case 0x36:
285 | return LD_AHL_U8();
286 | case 0x37:
287 | return SCF();
288 | case 0x38:
289 | return JR_CON_I8(carry);
290 | case 0x39:
291 | return ADD_HL_SP();
292 | case 0x3A:
293 | return LD_A_AHLM();
294 | case 0x3B:
295 | return DEC_SP();
296 | case 0x3C:
297 | return INC_R(ref A);
298 | case 0x3D:
299 | return DEC_R(ref A);
300 | case 0x3E:
301 | return LD_R_U8(ref A);
302 | case 0x3F:
303 | return CCF();
304 | case 0x40:
305 | return LD_R1_R2(ref B, ref B);
306 | case 0x41:
307 | return LD_R1_R2(ref B, ref C);
308 | case 0x42:
309 | return LD_R1_R2(ref B, ref D);
310 | case 0x43:
311 | return LD_R1_R2(ref B, ref E);
312 | case 0x44:
313 | return LD_R1_R2(ref B, ref H);
314 | case 0x45:
315 | return LD_R1_R2(ref B, ref L);
316 | case 0x46:
317 | return LD_R_ARR(ref B, "hl");
318 | case 0x47:
319 | return LD_R1_R2(ref B, ref A);
320 | case 0x48:
321 | return LD_R1_R2(ref C, ref B);
322 | case 0x49:
323 | return LD_R1_R2(ref C, ref C);
324 | case 0x4A:
325 | return LD_R1_R2(ref C, ref D);
326 | case 0x4B:
327 | return LD_R1_R2(ref C, ref E);
328 | case 0x4C:
329 | return LD_R1_R2(ref C, ref H);
330 | case 0x4D:
331 | return LD_R1_R2(ref C, ref L);
332 | case 0x4E:
333 | return LD_R_ARR(ref C, "hl");
334 | case 0x4F:
335 | return LD_R1_R2(ref C, ref A);
336 | case 0x50:
337 | return LD_R1_R2(ref D, ref B);
338 | case 0x51:
339 | return LD_R1_R2(ref D, ref C);
340 | case 0x52:
341 | return LD_R1_R2(ref D, ref D);
342 | case 0x53:
343 | return LD_R1_R2(ref D, ref E);
344 | case 0x54:
345 | return LD_R1_R2(ref D, ref H);
346 | case 0x55:
347 | return LD_R1_R2(ref D, ref L);
348 | case 0x56:
349 | return LD_R_ARR(ref D, "hl");
350 | case 0x57:
351 | return LD_R1_R2(ref D, ref A);
352 | case 0x58:
353 | return LD_R1_R2(ref E, ref B);
354 | case 0x59:
355 | return LD_R1_R2(ref E, ref C);
356 | case 0x5A:
357 | return LD_R1_R2(ref E, ref D);
358 | case 0x5B:
359 | return LD_R1_R2(ref E, ref E);
360 | case 0x5C:
361 | return LD_R1_R2(ref E, ref H);
362 | case 0x5D:
363 | return LD_R1_R2(ref E, ref L);
364 | case 0x5E:
365 | return LD_R_ARR(ref E, "hl");
366 | case 0x5F:
367 | return LD_R1_R2(ref E, ref A);
368 | case 0x60:
369 | return LD_R1_R2(ref H, ref B);
370 | case 0x61:
371 | return LD_R1_R2(ref H, ref C);
372 | case 0x62:
373 | return LD_R1_R2(ref H, ref D);
374 | case 0x63:
375 | return LD_R1_R2(ref H, ref E);
376 | case 0x64:
377 | return LD_R1_R2(ref H, ref H);
378 | case 0x65:
379 | return LD_R1_R2(ref H, ref L);
380 | case 0x66:
381 | return LD_R_ARR(ref H, "hl");
382 | case 0x67:
383 | return LD_R1_R2(ref H, ref A);
384 | case 0x68:
385 | return LD_R1_R2(ref L, ref B);
386 | case 0x69:
387 | return LD_R1_R2(ref L, ref C);
388 | case 0x6A:
389 | return LD_R1_R2(ref L, ref D);
390 | case 0x6B:
391 | return LD_R1_R2(ref L, ref E);
392 | case 0x6C:
393 | return LD_R1_R2(ref L, ref H);
394 | case 0x6D:
395 | return LD_R1_R2(ref L, ref L);
396 | case 0x6E:
397 | return LD_R_ARR(ref L, "hl");
398 | case 0x6F:
399 | return LD_R1_R2(ref L, ref A);
400 | case 0x70:
401 | return LD_ARR_R(ref B, "hl");
402 | case 0x71:
403 | return LD_ARR_R(ref C, "hl");
404 | case 0x72:
405 | return LD_ARR_R(ref D, "hl");
406 | case 0x73:
407 | return LD_ARR_R(ref E, "hl");
408 | case 0x74:
409 | return LD_ARR_R(ref H, "hl");
410 | case 0x75:
411 | return LD_ARR_R(ref L, "hl");
412 | case 0x76:
413 | return HALT();
414 | case 0x77:
415 | return LD_ARR_R(ref A, "hl");
416 | case 0x78:
417 | return LD_R1_R2(ref A, ref B);
418 | case 0x79:
419 | return LD_R1_R2(ref A, ref C);
420 | case 0x7A:
421 | return LD_R1_R2(ref A, ref D);
422 | case 0x7B:
423 | return LD_R1_R2(ref A, ref E);
424 | case 0x7C:
425 | return LD_R1_R2(ref A, ref H);
426 | case 0x7D:
427 | return LD_R1_R2(ref A, ref L);
428 | case 0x7E:
429 | return LD_R_ARR(ref A, "hl");
430 | case 0x7F:
431 | return LD_R1_R2(ref A, ref A);
432 | case 0x80:
433 | return ADD_A_R(ref B);
434 | case 0x81:
435 | return ADD_A_R(ref C);
436 | case 0x82:
437 | return ADD_A_R(ref D);
438 | case 0x83:
439 | return ADD_A_R(ref E);
440 | case 0x84:
441 | return ADD_A_R(ref H);
442 | case 0x85:
443 | return ADD_A_R(ref L);
444 | case 0x86:
445 | return ADD_A_ARR("hl");
446 | case 0x87:
447 | return ADD_A_R(ref A);
448 | case 0x88:
449 | return ADC_A_R(ref B);
450 | case 0x89:
451 | return ADC_A_R(ref C);
452 | case 0x8A:
453 | return ADC_A_R(ref D);
454 | case 0x8B:
455 | return ADC_A_R(ref E);
456 | case 0x8C:
457 | return ADC_A_R(ref H);
458 | case 0x8D:
459 | return ADC_A_R(ref L);
460 | case 0x8E:
461 | return ADC_A_ARR("hl");
462 | case 0x8F:
463 | return ADC_A_R(ref A);
464 | case 0x90:
465 | return SUB_A_R(ref B);
466 | case 0x91:
467 | return SUB_A_R(ref C);
468 | case 0x92:
469 | return SUB_A_R(ref D);
470 | case 0x93:
471 | return SUB_A_R(ref E);
472 | case 0x94:
473 | return SUB_A_R(ref H);
474 | case 0x95:
475 | return SUB_A_R(ref L);
476 | case 0x96:
477 | return SUB_A_ARR("hl");
478 | case 0x97:
479 | return SUB_A_R(ref A);
480 | case 0x98:
481 | return SBC_A_R(ref B);
482 | case 0x99:
483 | return SBC_A_R(ref C);
484 | case 0x9A:
485 | return SBC_A_R(ref D);
486 | case 0x9B:
487 | return SBC_A_R(ref E);
488 | case 0x9C:
489 | return SBC_A_R(ref H);
490 | case 0x9D:
491 | return SBC_A_R(ref L);
492 | case 0x9E:
493 | return SBC_A_ARR("hl");
494 | case 0x9F:
495 | return SBC_A_R(ref A);
496 | case 0xA0:
497 | return AND_A_R(ref B);
498 | case 0xA1:
499 | return AND_A_R(ref C);
500 | case 0xA2:
501 | return AND_A_R(ref D);
502 | case 0xA3:
503 | return AND_A_R(ref E);
504 | case 0xA4:
505 | return AND_A_R(ref H);
506 | case 0xA5:
507 | return AND_A_R(ref L);
508 | case 0xA6:
509 | return AND_A_ARR("hl");
510 | case 0xA7:
511 | return AND_A_R(ref A);
512 | case 0xA8:
513 | return XOR_A_R(ref B);
514 | case 0xA9:
515 | return XOR_A_R(ref C);
516 | case 0xAA:
517 | return XOR_A_R(ref D);
518 | case 0xAB:
519 | return XOR_A_R(ref E);
520 | case 0xAC:
521 | return XOR_A_R(ref H);
522 | case 0xAD:
523 | return XOR_A_R(ref L);
524 | case 0xAE:
525 | return XOR_A_ARR("hl");
526 | case 0xAF:
527 | return XOR_A_R(ref A);
528 | case 0xB0:
529 | return OR_A_R(ref B);
530 | case 0xB1:
531 | return OR_A_R(ref C);
532 | case 0xB2:
533 | return OR_A_R(ref D);
534 | case 0xB3:
535 | return OR_A_R(ref E);
536 | case 0xB4:
537 | return OR_A_R(ref H);
538 | case 0xB5:
539 | return OR_A_R(ref L);
540 | case 0xB6:
541 | return OR_A_ARR("hl");
542 | case 0xB7:
543 | return OR_A_R(ref A);
544 | case 0xB8:
545 | return CP_A_R(ref B);
546 | case 0xB9:
547 | return CP_A_R(ref C);
548 | case 0xBA:
549 | return CP_A_R(ref D);
550 | case 0xBB:
551 | return CP_A_R(ref E);
552 | case 0xBC:
553 | return CP_A_R(ref H);
554 | case 0xBD:
555 | return CP_A_R(ref L);
556 | case 0xBE:
557 | return CP_A_ARR("hl");
558 | case 0xBF:
559 | return CP_A_R(ref A);
560 | case 0xC0:
561 | return RET_CON(!zero);
562 | case 0xC1:
563 | return POP_RR("bc");
564 | case 0xC2:
565 | return JP_CON_U16(!zero);
566 | case 0xC3:
567 | return JP_CON_U16(true);
568 | case 0xC4:
569 | return CALL_CON_U16(!zero);
570 | case 0xC5:
571 | return PUSH_RR("bc");
572 | case 0xC6:
573 | return ADD_A_U8();
574 | case 0xC7:
575 | return RST(0x0000);
576 | case 0xC8:
577 | return RET_CON(zero);
578 | case 0xC9:
579 | return RET();
580 | case 0xCA:
581 | return JP_CON_U16(zero);
582 | case 0xCB:
583 | return ExecuteCB();
584 | case 0xCC:
585 | return CALL_CON_U16(zero);
586 | case 0xCD:
587 | return CALL_U16();
588 | case 0xCE:
589 | return ADC_A_U8();
590 | case 0xCF:
591 | return RST(0x0008);
592 | case 0xD0:
593 | return RET_CON(!carry);
594 | case 0xD1:
595 | return POP_RR("de");
596 | case 0xD2:
597 | return JP_CON_U16(!carry);
598 | case 0xD3:
599 | return DMG_EXIT(opcode);
600 | case 0xD4:
601 | return CALL_CON_U16(!carry);
602 | case 0xD5:
603 | return PUSH_RR("de");
604 | case 0xD6:
605 | return SUB_A_U8();
606 | case 0xD7:
607 | return RST(0x0010);
608 | case 0xD8:
609 | return RET_CON(carry);
610 | case 0xD9:
611 | return RETI();
612 | case 0xDA:
613 | return JP_CON_U16(carry);
614 | case 0xDB:
615 | return DMG_EXIT(opcode);
616 | case 0xDC:
617 | return CALL_CON_U16(carry);
618 | case 0xDD:
619 | return DMG_EXIT(opcode);
620 | case 0xDE:
621 | return SBC_A_U8();
622 | case 0xDF:
623 | return RST(0x0018);
624 | case 0xE0:
625 | return LD_FF00_U8_A();
626 | case 0xE1:
627 | return POP_RR("hl");
628 | case 0xE2:
629 | return LD_FF00_C_A();
630 | case 0xE3:
631 | return DMG_EXIT(opcode);
632 | case 0xE4:
633 | return DMG_EXIT(opcode);
634 | case 0xE5:
635 | return PUSH_RR("hl");
636 | case 0xE6:
637 | return AND_A_U8();
638 | case 0xE7:
639 | return RST(0x0020);
640 | case 0xE8:
641 | return ADD_SP_I8();
642 | case 0xE9:
643 | return JP_HL();
644 | case 0xEA:
645 | return LD_AU16_A();
646 | case 0xEB:
647 | return DMG_EXIT(opcode);
648 | case 0xEC:
649 | return DMG_EXIT(opcode);
650 | case 0xED:
651 | return DMG_EXIT(opcode);
652 | case 0xEE:
653 | return XOR_A_U8();
654 | case 0xEF:
655 | return RST(0x0028);
656 | case 0xF0:
657 | return LD_A_FF00_U8();
658 | case 0xF1:
659 | return POP_AF();
660 | case 0xF2:
661 | return LD_A_FF00_C();
662 | case 0xF3:
663 | return DI();
664 | case 0xF4:
665 | return DMG_EXIT(opcode);
666 | case 0xF5:
667 | return PUSH_RR("af");
668 | case 0xF6:
669 | return OR_A_U8();
670 | case 0xF7:
671 | return RST(0x0030);
672 | case 0xF8:
673 | return LD_HL_SP_I8();
674 | case 0xF9:
675 | return LD_SP_HL();
676 | case 0xFA:
677 | return LD_A_AU16();
678 | case 0xFB:
679 | return EI();
680 | case 0xFC:
681 | return DMG_EXIT(opcode);
682 | case 0xFD:
683 | return DMG_EXIT(opcode);
684 | case 0xFE:
685 | return CP_A_U8();
686 | case 0xFF:
687 | return RST(0x0038);
688 | default:
689 | //Console.WriteLine("Unimplemented Opcode: " + opcode.ToString("X2") + " , PC: " + (PC-1).ToString("X4"));
690 | //Environment.Exit(1);
691 | //return 0;
692 | }
693 | }
694 |
695 | public int ExecuteCB() {
696 | byte suffix = Fetch();
697 |
698 | switch (suffix) {
699 | case 0x00:
700 | return RLC_R(ref B);
701 | case 0x01:
702 | return RLC_R(ref C);
703 | case 0x02:
704 | return RLC_R(ref D);
705 | case 0x03:
706 | return RLC_R(ref E);
707 | case 0x04:
708 | return RLC_R(ref H);
709 | case 0x05:
710 | return RLC_R(ref L);
711 | case 0x06:
712 | return RLC_AHL();
713 | case 0x07:
714 | return RLC_R(ref A);
715 | case 0x08:
716 | return RRC_R(ref B);
717 | case 0x09:
718 | return RRC_R(ref C);
719 | case 0x0A:
720 | return RRC_R(ref D);
721 | case 0x0B:
722 | return RRC_R(ref E);
723 | case 0x0C:
724 | return RRC_R(ref H);
725 | case 0x0D:
726 | return RRC_R(ref L);
727 | case 0x0E:
728 | return RRC_AHL();
729 | case 0x0F:
730 | return RRC_R(ref A);
731 | case 0x10:
732 | return RL_R(ref B);
733 | case 0x11:
734 | return RL_R(ref C);
735 | case 0x12:
736 | return RL_R(ref D);
737 | case 0x13:
738 | return RL_R(ref E);
739 | case 0x14:
740 | return RL_R(ref H);
741 | case 0x15:
742 | return RL_R(ref L);
743 | case 0x16:
744 | return RL_AHL();
745 | case 0x17:
746 | return RL_R(ref A);
747 | case 0x18:
748 | return RR_R(ref B);
749 | case 0x19:
750 | return RR_R(ref C);
751 | case 0x1A:
752 | return RR_R(ref D);
753 | case 0x1B:
754 | return RR_R(ref E);
755 | case 0x1C:
756 | return RR_R(ref H);
757 | case 0x1D:
758 | return RR_R(ref L);
759 | case 0x1E:
760 | return RR_AHL();
761 | case 0x1F:
762 | return RR_R(ref A);
763 | case 0x20:
764 | return SLA_R(ref B);
765 | case 0x21:
766 | return SLA_R(ref C);
767 | case 0x22:
768 | return SLA_R(ref D);
769 | case 0x23:
770 | return SLA_R(ref E);
771 | case 0x24:
772 | return SLA_R(ref H);
773 | case 0x25:
774 | return SLA_R(ref L);
775 | case 0x26:
776 | return SLA_AHL();
777 | case 0x27:
778 | return SLA_R(ref A);
779 | case 0x28:
780 | return SRA_R(ref B);
781 | case 0x29:
782 | return SRA_R(ref C);
783 | case 0x2A:
784 | return SRA_R(ref D);
785 | case 0x2B:
786 | return SRA_R(ref E);
787 | case 0x2C:
788 | return SRA_R(ref H);
789 | case 0x2D:
790 | return SRA_R(ref L);
791 | case 0x2E:
792 | return SRA_AHL();
793 | case 0x2F:
794 | return SRA_R(ref A);
795 | case 0x30:
796 | return SWAP_R(ref B);
797 | case 0x31:
798 | return SWAP_R(ref C);
799 | case 0x32:
800 | return SWAP_R(ref D);
801 | case 0x33:
802 | return SWAP_R(ref E);
803 | case 0x34:
804 | return SWAP_R(ref H);
805 | case 0x35:
806 | return SWAP_R(ref L);
807 | case 0x36:
808 | return SWAP_AHL();
809 | case 0x37:
810 | return SWAP_R(ref A);
811 | case 0x38:
812 | return SRL_R(ref B);
813 | case 0x39:
814 | return SRL_R(ref C);
815 | case 0x3A:
816 | return SRL_R(ref D);
817 | case 0x3B:
818 | return SRL_R(ref E);
819 | case 0x3C:
820 | return SRL_R(ref H);
821 | case 0x3D:
822 | return SRL_R(ref L);
823 | case 0x3E:
824 | return SRL_AHL();
825 | case 0x3F:
826 | return SRL_R(ref A);
827 | case 0x40:
828 | return BIT_N_R(0, ref B);
829 | case 0x41:
830 | return BIT_N_R(0, ref C);
831 | case 0x42:
832 | return BIT_N_R(0, ref D);
833 | case 0x43:
834 | return BIT_N_R(0, ref E);
835 | case 0x44:
836 | return BIT_N_R(0, ref H);
837 | case 0x45:
838 | return BIT_N_R(0, ref L);
839 | case 0x46:
840 | return BIT_N_AHL(0);
841 | case 0x47:
842 | return BIT_N_R(0, ref A);
843 | case 0x48:
844 | return BIT_N_R(1, ref B);
845 | case 0x49:
846 | return BIT_N_R(1, ref C);
847 | case 0x4A:
848 | return BIT_N_R(1, ref D);
849 | case 0x4B:
850 | return BIT_N_R(1, ref E);
851 | case 0x4C:
852 | return BIT_N_R(1, ref H);
853 | case 0x4D:
854 | return BIT_N_R(1, ref L);
855 | case 0x4E:
856 | return BIT_N_AHL(1);
857 | case 0x4F:
858 | return BIT_N_R(1, ref A);
859 | case 0x50:
860 | return BIT_N_R(2, ref B);
861 | case 0x51:
862 | return BIT_N_R(2, ref C);
863 | case 0x52:
864 | return BIT_N_R(2, ref D);
865 | case 0x53:
866 | return BIT_N_R(2, ref E);
867 | case 0x54:
868 | return BIT_N_R(2, ref H);
869 | case 0x55:
870 | return BIT_N_R(2, ref L);
871 | case 0x56:
872 | return BIT_N_AHL(2);
873 | case 0x57:
874 | return BIT_N_R(2, ref A);
875 | case 0x58:
876 | return BIT_N_R(3, ref B);
877 | case 0x59:
878 | return BIT_N_R(3, ref C);
879 | case 0x5A:
880 | return BIT_N_R(3, ref D);
881 | case 0x5B:
882 | return BIT_N_R(3, ref E);
883 | case 0x5C:
884 | return BIT_N_R(3, ref H);
885 | case 0x5D:
886 | return BIT_N_R(3, ref L);
887 | case 0x5E:
888 | return BIT_N_AHL(3);
889 | case 0x5F:
890 | return BIT_N_R(3, ref A);
891 | case 0x60:
892 | return BIT_N_R(4, ref B);
893 | case 0x61:
894 | return BIT_N_R(4, ref C);
895 | case 0x62:
896 | return BIT_N_R(4, ref D);
897 | case 0x63:
898 | return BIT_N_R(4, ref E);
899 | case 0x64:
900 | return BIT_N_R(4, ref H);
901 | case 0x65:
902 | return BIT_N_R(4, ref L);
903 | case 0x66:
904 | return BIT_N_AHL(4);
905 | case 0x67:
906 | return BIT_N_R(4, ref A);
907 | case 0x68:
908 | return BIT_N_R(5, ref B);
909 | case 0x69:
910 | return BIT_N_R(5, ref C);
911 | case 0x6A:
912 | return BIT_N_R(5, ref D);
913 | case 0x6B:
914 | return BIT_N_R(5, ref E);
915 | case 0x6C:
916 | return BIT_N_R(5, ref H);
917 | case 0x6D:
918 | return BIT_N_R(5, ref L);
919 | case 0x6E:
920 | return BIT_N_AHL(5);
921 | case 0x6F:
922 | return BIT_N_R(5, ref A);
923 | case 0x70:
924 | return BIT_N_R(6, ref B);
925 | case 0x71:
926 | return BIT_N_R(6, ref C);
927 | case 0x72:
928 | return BIT_N_R(6, ref D);
929 | case 0x73:
930 | return BIT_N_R(6, ref E);
931 | case 0x74:
932 | return BIT_N_R(6, ref H);
933 | case 0x75:
934 | return BIT_N_R(6, ref L);
935 | case 0x76:
936 | return BIT_N_AHL(6);
937 | case 0x77:
938 | return BIT_N_R(6, ref A);
939 | case 0x78:
940 | return BIT_N_R(7, ref B);
941 | case 0x79:
942 | return BIT_N_R(7, ref C);
943 | case 0x7A:
944 | return BIT_N_R(7, ref D);
945 | case 0x7B:
946 | return BIT_N_R(7, ref E);
947 | case 0x7C:
948 | return BIT_N_R(7, ref H);
949 | case 0x7D:
950 | return BIT_N_R(7, ref L);
951 | case 0x7E:
952 | return BIT_N_AHL(7);
953 | case 0x7F:
954 | return BIT_N_R(7, ref A);
955 | case 0x80:
956 | return RES_N_R(0, ref B);
957 | case 0x81:
958 | return RES_N_R(0, ref C);
959 | case 0x82:
960 | return RES_N_R(0, ref D);
961 | case 0x83:
962 | return RES_N_R(0, ref E);
963 | case 0x84:
964 | return RES_N_R(0, ref H);
965 | case 0x85:
966 | return RES_N_R(0, ref L);
967 | case 0x86:
968 | return RES_N_AHL(0);
969 | case 0x87:
970 | return RES_N_R(0, ref A);
971 | case 0x88:
972 | return RES_N_R(1, ref B);
973 | case 0x89:
974 | return RES_N_R(1, ref C);
975 | case 0x8A:
976 | return RES_N_R(1, ref D);
977 | case 0x8B:
978 | return RES_N_R(1, ref E);
979 | case 0x8C:
980 | return RES_N_R(1, ref H);
981 | case 0x8D:
982 | return RES_N_R(1, ref L);
983 | case 0x8E:
984 | return RES_N_AHL(1);
985 | case 0x8F:
986 | return RES_N_R(1, ref A);
987 | case 0x90:
988 | return RES_N_R(2, ref B);
989 | case 0x91:
990 | return RES_N_R(2, ref C);
991 | case 0x92:
992 | return RES_N_R(2, ref D);
993 | case 0x93:
994 | return RES_N_R(2, ref E);
995 | case 0x94:
996 | return RES_N_R(2, ref H);
997 | case 0x95:
998 | return RES_N_R(2, ref L);
999 | case 0x96:
1000 | return RES_N_AHL(2);
1001 | case 0x97:
1002 | return RES_N_R(2, ref A);
1003 | case 0x98:
1004 | return RES_N_R(3, ref B);
1005 | case 0x99:
1006 | return RES_N_R(3, ref C);
1007 | case 0x9A:
1008 | return RES_N_R(3, ref D);
1009 | case 0x9B:
1010 | return RES_N_R(3, ref E);
1011 | case 0x9C:
1012 | return RES_N_R(3, ref H);
1013 | case 0x9D:
1014 | return RES_N_R(3, ref L);
1015 | case 0x9E:
1016 | return RES_N_AHL(3);
1017 | case 0x9F:
1018 | return RES_N_R(3, ref A);
1019 | case 0xA0:
1020 | return RES_N_R(4, ref B);
1021 | case 0xA1:
1022 | return RES_N_R(4, ref C);
1023 | case 0xA2:
1024 | return RES_N_R(4, ref D);
1025 | case 0xA3:
1026 | return RES_N_R(4, ref E);
1027 | case 0xA4:
1028 | return RES_N_R(4, ref H);
1029 | case 0xA5:
1030 | return RES_N_R(4, ref L);
1031 | case 0xA6:
1032 | return RES_N_AHL(4);
1033 | case 0xA7:
1034 | return RES_N_R(4, ref A);
1035 | case 0xA8:
1036 | return RES_N_R(5, ref B);
1037 | case 0xA9:
1038 | return RES_N_R(5, ref C);
1039 | case 0xAA:
1040 | return RES_N_R(5, ref D);
1041 | case 0xAB:
1042 | return RES_N_R(5, ref E);
1043 | case 0xAC:
1044 | return RES_N_R(5, ref H);
1045 | case 0xAD:
1046 | return RES_N_R(5, ref L);
1047 | case 0xAE:
1048 | return RES_N_AHL(5);
1049 | case 0xAF:
1050 | return RES_N_R(5, ref A);
1051 | case 0xB0:
1052 | return RES_N_R(6, ref B);
1053 | case 0xB1:
1054 | return RES_N_R(6, ref C);
1055 | case 0xB2:
1056 | return RES_N_R(6, ref D);
1057 | case 0xB3:
1058 | return RES_N_R(6, ref E);
1059 | case 0xB4:
1060 | return RES_N_R(6, ref H);
1061 | case 0xB5:
1062 | return RES_N_R(6, ref L);
1063 | case 0xB6:
1064 | return RES_N_AHL(6);
1065 | case 0xB7:
1066 | return RES_N_R(6, ref A);
1067 | case 0xB8:
1068 | return RES_N_R(7, ref B);
1069 | case 0xB9:
1070 | return RES_N_R(7, ref C);
1071 | case 0xBA:
1072 | return RES_N_R(7, ref D);
1073 | case 0xBB:
1074 | return RES_N_R(7, ref E);
1075 | case 0xBC:
1076 | return RES_N_R(7, ref H);
1077 | case 0xBD:
1078 | return RES_N_R(7, ref L);
1079 | case 0xBE:
1080 | return RES_N_AHL(7);
1081 | case 0xBF:
1082 | return RES_N_R(7, ref A);
1083 | case 0xC0:
1084 | return SET_N_R(0, ref B);
1085 | case 0xC1:
1086 | return SET_N_R(0, ref C);
1087 | case 0xC2:
1088 | return SET_N_R(0, ref D);
1089 | case 0xC3:
1090 | return SET_N_R(0, ref E);
1091 | case 0xC4:
1092 | return SET_N_R(0, ref H);
1093 | case 0xC5:
1094 | return SET_N_R(0, ref L);
1095 | case 0xC6:
1096 | return SET_N_AHL(0);
1097 | case 0xC7:
1098 | return SET_N_R(0, ref A);
1099 | case 0xC8:
1100 | return SET_N_R(1, ref B);
1101 | case 0xC9:
1102 | return SET_N_R(1, ref C);
1103 | case 0xCA:
1104 | return SET_N_R(1, ref D);
1105 | case 0xCB:
1106 | return SET_N_R(1, ref E);
1107 | case 0xCC:
1108 | return SET_N_R(1, ref H);
1109 | case 0xCD:
1110 | return SET_N_R(1, ref L);
1111 | case 0xCE:
1112 | return SET_N_AHL(1);
1113 | case 0xCF:
1114 | return SET_N_R(1, ref A);
1115 | case 0xD0:
1116 | return SET_N_R(2, ref B);
1117 | case 0xD1:
1118 | return SET_N_R(2, ref C);
1119 | case 0xD2:
1120 | return SET_N_R(2, ref D);
1121 | case 0xD3:
1122 | return SET_N_R(2, ref E);
1123 | case 0xD4:
1124 | return SET_N_R(2, ref H);
1125 | case 0xD5:
1126 | return SET_N_R(2, ref L);
1127 | case 0xD6:
1128 | return SET_N_AHL(2);
1129 | case 0xD7:
1130 | return SET_N_R(2, ref A);
1131 | case 0xD8:
1132 | return SET_N_R(3, ref B);
1133 | case 0xD9:
1134 | return SET_N_R(3, ref C);
1135 | case 0xDA:
1136 | return SET_N_R(3, ref D);
1137 | case 0xDB:
1138 | return SET_N_R(3, ref E);
1139 | case 0xDC:
1140 | return SET_N_R(3, ref H);
1141 | case 0xDD:
1142 | return SET_N_R(3, ref L);
1143 | case 0xDE:
1144 | return SET_N_AHL(3);
1145 | case 0xDF:
1146 | return SET_N_R(3, ref A);
1147 | case 0xE0:
1148 | return SET_N_R(4, ref B);
1149 | case 0xE1:
1150 | return SET_N_R(4, ref C);
1151 | case 0xE2:
1152 | return SET_N_R(4, ref D);
1153 | case 0xE3:
1154 | return SET_N_R(4, ref E);
1155 | case 0xE4:
1156 | return SET_N_R(4, ref H);
1157 | case 0xE5:
1158 | return SET_N_R(4, ref L);
1159 | case 0xE6:
1160 | return SET_N_AHL(4);
1161 | case 0xE7:
1162 | return SET_N_R(4, ref A);
1163 | case 0xE8:
1164 | return SET_N_R(5, ref B);
1165 | case 0xE9:
1166 | return SET_N_R(5, ref C);
1167 | case 0xEA:
1168 | return SET_N_R(5, ref D);
1169 | case 0xEB:
1170 | return SET_N_R(5, ref E);
1171 | case 0xEC:
1172 | return SET_N_R(5, ref H);
1173 | case 0xED:
1174 | return SET_N_R(5, ref L);
1175 | case 0xEE:
1176 | return SET_N_AHL(5);
1177 | case 0xEF:
1178 | return SET_N_R(5, ref A);
1179 | case 0xF0:
1180 | return SET_N_R(6, ref B);
1181 | case 0xF1:
1182 | return SET_N_R(6, ref C);
1183 | case 0xF2:
1184 | return SET_N_R(6, ref D);
1185 | case 0xF3:
1186 | return SET_N_R(6, ref E);
1187 | case 0xF4:
1188 | return SET_N_R(6, ref H);
1189 | case 0xF5:
1190 | return SET_N_R(6, ref L);
1191 | case 0xF6:
1192 | return SET_N_AHL(6);
1193 | case 0xF7:
1194 | return SET_N_R(6, ref A);
1195 | case 0xF8:
1196 | return SET_N_R(7, ref B);
1197 | case 0xF9:
1198 | return SET_N_R(7, ref C);
1199 | case 0xFA:
1200 | return SET_N_R(7, ref D);
1201 | case 0xFB:
1202 | return SET_N_R(7, ref E);
1203 | case 0xFC:
1204 | return SET_N_R(7, ref H);
1205 | case 0xFD:
1206 | return SET_N_R(7, ref L);
1207 | case 0xFE:
1208 | return SET_N_AHL(7);
1209 | case 0xFF:
1210 | return SET_N_R(7, ref A);
1211 | default:
1212 | //Console.WriteLine("Unimplemented CB Opcode: " + suffix.ToString("X2") + " , PC: " + (PC-1).ToString("X4"));
1213 | //Environment.Exit(1);
1214 | //return 0;
1215 | }
1216 | }
1217 |
1218 | //R = 8bit reg, RR = 16bit reg, U8 = byte, U16 = ushort, I8, sbyte, AXX = (XX) Value in memory at XX
1219 |
1220 | //x8 LSM
1221 | private int LD_ARR_R(ref byte r, string regPair) {
1222 | ushort addr = Get16BitReg(regPair);
1223 | mmu.Write(addr, r);
1224 | return 8;
1225 | }
1226 |
1227 | private int LD_AHLM_A() {
1228 | ushort hlm = Get16BitReg("hl");
1229 | mmu.Write(hlm, A);
1230 | hlm = (ushort)(hlm - 1);
1231 | Load16BitReg("hl", hlm);
1232 | return 8;
1233 | }
1234 |
1235 | private int LD_AHLI_A() {
1236 | ushort hli = Get16BitReg("hl");
1237 | mmu.Write(hli, A);
1238 | hli = (ushort)(hli + 1);
1239 | Load16BitReg("hl", hli);
1240 | return 8;
1241 | }
1242 |
1243 | private int LD_R_U8(ref byte r) {
1244 | byte u8 = Fetch();
1245 | r = u8;
1246 | return 8;
1247 | }
1248 |
1249 | private int LD_AHL_U8() {
1250 | ushort addr = Get16BitReg("hl");
1251 | byte u8 = Fetch();
1252 | mmu.Write(addr, u8);
1253 | return 12;
1254 | }
1255 |
1256 | private int LD_R_ARR(ref byte r, string regPair) {
1257 | ushort addr = Get16BitReg(regPair);
1258 | r = mmu.Read(addr);
1259 | return 8;
1260 | }
1261 |
1262 | private int LD_A_AHLM() {
1263 | ushort hlm = Get16BitReg("hl");
1264 | A = mmu.Read(hlm);
1265 | hlm = (ushort)(hlm - 1);
1266 | Load16BitReg("hl", hlm);
1267 | return 8;
1268 | }
1269 |
1270 | private int LD_A_AHLI() {
1271 | ushort hli = Get16BitReg("hl");
1272 | A = mmu.Read(hli);
1273 | hli = (ushort)(hli + 1);
1274 | Load16BitReg("hl", hli);
1275 | return 8;
1276 | }
1277 |
1278 | private int LD_R1_R2(ref byte r1, ref byte r2) {
1279 | r1 = r2;
1280 | return 4;
1281 | }
1282 |
1283 | private int LD_FF00_U8_A() {
1284 | byte u8 = Fetch();
1285 | ushort addr = (ushort)(0xFF00 + u8);
1286 | mmu.Write(addr, A);
1287 | return 12;
1288 | }
1289 |
1290 | private int LD_A_FF00_U8() {
1291 | byte u8 = Fetch();
1292 | ushort addr = (ushort)(0xFF00 + u8);
1293 | A = mmu.Read(addr);
1294 | return 12;
1295 | }
1296 |
1297 | private int LD_FF00_C_A() {
1298 | ushort addr = (ushort)(0xFF00 + C);
1299 | mmu.Write(addr, A);
1300 | return 8;
1301 | }
1302 |
1303 | private int LD_A_FF00_C() {
1304 | ushort addr = (ushort)(0xFF00 + C);
1305 | A = mmu.Read(addr);
1306 | return 8;
1307 | }
1308 |
1309 | private int LD_AU16_A() {
1310 | byte lower = Fetch();
1311 | byte upper = Fetch();
1312 | ushort value = (ushort)((upper << 8) | lower);
1313 | mmu.Write(value, A);
1314 | return 16;
1315 | }
1316 |
1317 | private int LD_A_AU16() {
1318 | byte lower = Fetch();
1319 | byte upper = Fetch();
1320 | ushort value = (ushort)((upper << 8) | lower);
1321 | A = mmu.Read(value);
1322 | return 16;
1323 | }
1324 |
1325 | //x16 LSM
1326 | private int LD_SP_U16() {
1327 | byte lower = Fetch();
1328 | byte upper = Fetch();
1329 | ushort value = (ushort)((upper << 8) | lower);
1330 |
1331 | SP = value;
1332 | return 12;
1333 | }
1334 |
1335 | private int LD_RR_U16(ref byte r1, ref byte r2) {
1336 | r2 = Fetch();
1337 | r1 = Fetch();
1338 | return 12;
1339 | }
1340 |
1341 | private int LD_AU16_SP() {
1342 | byte lower = Fetch();
1343 | byte upper = Fetch();
1344 | ushort address = (ushort)((upper << 8) | lower);
1345 |
1346 | byte spLower = (byte)(SP & 0xFF);
1347 | byte spUpper = (byte)((SP >> 8) & 0xFF);
1348 | mmu.Write(address, spLower);
1349 | mmu.Write((ushort)(address + 1), spUpper);
1350 |
1351 | return 20;
1352 | }
1353 |
1354 | private int PUSH_RR(string regPair) {
1355 | ushort regVal = Get16BitReg(regPair);
1356 | SP--;
1357 | mmu.Write(SP, (byte)(regVal >> 8));
1358 | SP--;
1359 | mmu.Write(SP, (byte)(regVal & 0xFF));
1360 | return 16;
1361 | }
1362 |
1363 | private int POP_RR(string regPair) {
1364 | byte lowByte = mmu.Read(SP);
1365 | SP++;
1366 | byte highByte = mmu.Read(SP);
1367 | ushort regVal = (ushort)((highByte << 8) | lowByte);
1368 | Load16BitReg(regPair, regVal);
1369 | SP++;
1370 | return 12;
1371 | }
1372 |
1373 | private int POP_AF() {
1374 | byte lowByte = mmu.Read(SP);
1375 | SP++;
1376 | byte highByte = mmu.Read(SP);
1377 | A = highByte;
1378 | F = (byte)(lowByte & 0xF0);
1379 | UpdateFlagsFromF();
1380 | SP++;
1381 | return 12;
1382 | }
1383 |
1384 | private int LD_SP_HL() {
1385 | SP = Get16BitReg("hl");
1386 | return 8;
1387 | }
1388 |
1389 | //x8 ALU
1390 | private int INC_R(ref byte r) {
1391 | int val = r + 1;
1392 |
1393 | zero = (val & 0xFF) == 0;
1394 | negative = false;
1395 | halfCarry = (val & 0x0F) == 0;
1396 | UpdateFFromFlags();
1397 |
1398 | r = (byte)val;
1399 | return 4;
1400 | }
1401 |
1402 | private int INC_AHL() {
1403 | ushort addr = Get16BitReg("hl");
1404 | byte val = mmu.Read(addr);
1405 | int result = val + 1;
1406 |
1407 | zero = (result & 0xFF) == 0;
1408 | negative = false;
1409 | halfCarry = (result & 0x0F) == 0;
1410 | UpdateFFromFlags();
1411 |
1412 | mmu.Write(addr, (byte)result);
1413 | return 12;
1414 | }
1415 |
1416 | private int DEC_R(ref byte r) {
1417 | int val = r - 1;
1418 |
1419 | zero = (val & 0xFF) == 0;
1420 | negative = true;
1421 | halfCarry = (r & 0x0F) == 0;
1422 | UpdateFFromFlags();
1423 |
1424 | r = (byte)val;
1425 | return 4;
1426 | }
1427 |
1428 | private int DEC_AHL() {
1429 | ushort addr = Get16BitReg("hl");
1430 | byte val = mmu.Read(addr);
1431 | int result = val - 1;
1432 |
1433 | zero = (result & 0xFF) == 0;
1434 | negative = true;
1435 | halfCarry = (val & 0x0F) == 0;
1436 | UpdateFFromFlags();
1437 |
1438 | mmu.Write(addr, (byte)result);
1439 | return 12;
1440 | }
1441 |
1442 | private int ADD_A_R(ref byte r) {
1443 | int val = A + r;
1444 |
1445 | zero = (val & 0xFF) == 0;
1446 | negative = false;
1447 | halfCarry = ((A & 0xF) + (r & 0xF)) > 0xF;
1448 | carry = val > 0xFF;
1449 | UpdateFFromFlags();
1450 |
1451 | A = (byte)(val & 0xFF);
1452 | return 4;
1453 | }
1454 |
1455 | private int ADD_A_ARR(string regPair) {
1456 | ushort addr = Get16BitReg(regPair);
1457 | byte val = mmu.Read(addr);
1458 | int result = A + val;
1459 |
1460 | zero = (result & 0xFF) == 0;
1461 | negative = false;
1462 | halfCarry = ((A & 0xF) + (val & 0xF)) > 0xF;
1463 | carry = result > 0xFF;
1464 | UpdateFFromFlags();
1465 |
1466 | A = (byte)result;
1467 | return 8;
1468 | }
1469 |
1470 | private int ADD_A_U8() {
1471 | byte val = Fetch();
1472 | int result = A + val;
1473 |
1474 | zero = (result & 0xFF) == 0;
1475 | negative = false;
1476 | halfCarry = ((A & 0xF) + (val & 0xF)) > 0xF;
1477 | carry = result > 0xFF;
1478 | UpdateFFromFlags();
1479 |
1480 | A = (byte)(result & 0xFF);
1481 | return 8;
1482 | }
1483 |
1484 | private int ADC_A_R(ref byte r) {
1485 | int val = A + r + (carry ? 1 : 0);
1486 |
1487 | zero = (val & 0xFF) == 0;
1488 | negative = false;
1489 | halfCarry = ((A & 0xF) + (r & 0xF) + (carry ? 1 : 0)) > 0xF;
1490 | carry = val > 0xFF;
1491 | UpdateFFromFlags();
1492 |
1493 | A = (byte)(val & 0xFF);
1494 | return 4;
1495 | }
1496 |
1497 | private int ADC_A_ARR(string regPair) {
1498 | ushort addr = Get16BitReg(regPair);
1499 | byte val = mmu.Read(addr);
1500 | int result = A + val + (carry ? 1 : 0);
1501 |
1502 | zero = (result & 0xFF) == 0;
1503 | negative = false;
1504 | halfCarry = ((A & 0xF) + (val & 0xF) + (carry ? 1 : 0)) > 0xF;
1505 | carry = result > 0xFF;
1506 | UpdateFFromFlags();
1507 |
1508 | A = (byte)result;
1509 | return 8;
1510 | }
1511 |
1512 | private int ADC_A_U8() {
1513 | byte val = Fetch();
1514 | int result = A + val + (carry ? 1 : 0);
1515 |
1516 | zero = (result & 0xFF) == 0;
1517 | negative = false;
1518 | halfCarry = ((A & 0xF) + (val & 0xF) + (carry ? 1 : 0)) > 0xF;
1519 | carry = result > 0xFF;
1520 | UpdateFFromFlags();
1521 |
1522 | A = (byte)(result & 0xFF);
1523 | return 8;
1524 | }
1525 |
1526 | private int SUB_A_R(ref byte r) {
1527 | int val = A - r;
1528 |
1529 | zero = val == 0;
1530 | negative = true;
1531 | halfCarry = (A & 0xF) < (r & 0xF);
1532 | carry = A < r;
1533 | UpdateFFromFlags();
1534 |
1535 | A = (byte)val;
1536 | return 4;
1537 | }
1538 |
1539 | private int SUB_A_ARR(string regPair) {
1540 | ushort addr = Get16BitReg(regPair);
1541 | byte val = mmu.Read(addr);
1542 | int result = A - val;
1543 |
1544 | zero = (result & 0xFF) == 0;
1545 | negative = true;
1546 | halfCarry = (A & 0xF) < (val & 0xF);
1547 | carry = result < 0;
1548 | UpdateFFromFlags();
1549 |
1550 | A = (byte)result;
1551 | return 8;
1552 | }
1553 |
1554 | private int SUB_A_U8() {
1555 | byte val = Fetch();
1556 | int result = A - val;
1557 |
1558 | zero = result == 0;
1559 | negative = true;
1560 | halfCarry = (A & 0xF) < (val & 0xF);
1561 | carry = A < val;
1562 | UpdateFFromFlags();
1563 |
1564 | A = (byte)result;
1565 | return 8;
1566 | }
1567 |
1568 | private int SBC_A_R(ref byte r) {
1569 | int val = A - r - (carry ? 1 : 0);
1570 |
1571 | zero = (val & 0xFF) == 0;
1572 | negative = true;
1573 | halfCarry = ((A & 0xF) - (r & 0xF) - (carry ? 1 : 0)) < 0;
1574 | carry = val < 0;
1575 | UpdateFFromFlags();
1576 |
1577 | A = (byte)val;
1578 | return 4;
1579 | }
1580 |
1581 | private int SBC_A_ARR(string regPair) {
1582 | ushort addr = Get16BitReg(regPair);
1583 | byte val = mmu.Read(addr);
1584 | int result = A - val - (carry ? 1 : 0);
1585 |
1586 | zero = (result & 0xFF) == 0;
1587 | negative = true;
1588 | halfCarry = ((A & 0xF) - (val & 0xF) - (carry ? 1 : 0)) < 0;
1589 | carry = result < 0;
1590 | UpdateFFromFlags();
1591 |
1592 | A = (byte)result;
1593 | return 8;
1594 | }
1595 |
1596 | private int SBC_A_U8() {
1597 | byte val = Fetch();
1598 | int result = A - val - (carry ? 1 : 0);
1599 |
1600 | zero = (result & 0xFF) == 0;
1601 | negative = true;
1602 | halfCarry = ((A & 0xF) - (val & 0xF) - (carry ? 1 : 0)) < 0;
1603 | carry = result < 0;
1604 | UpdateFFromFlags();
1605 |
1606 | A = (byte)result;
1607 | return 8;
1608 | }
1609 |
1610 | private int AND_A_R(ref byte r) {
1611 | int val = A & r;
1612 |
1613 | zero = (val & 0xFF) == 0;
1614 | negative = false;
1615 | halfCarry = true;
1616 | carry = false;
1617 | UpdateFFromFlags();
1618 |
1619 | A = (byte)val;
1620 | return 4;
1621 | }
1622 |
1623 | private int AND_A_ARR(string regPair) {
1624 | ushort addr = Get16BitReg(regPair);
1625 | byte val = mmu.Read(addr);
1626 | int result = A & val;
1627 |
1628 | zero = (result & 0xFF) == 0;
1629 | negative = false;
1630 | halfCarry = true;
1631 | carry = false;
1632 | UpdateFFromFlags();
1633 |
1634 | A = (byte)result;
1635 | return 8;
1636 | }
1637 |
1638 | private int AND_A_U8() {
1639 | byte val = Fetch();
1640 | int result = A & val;
1641 |
1642 | zero = (result & 0xFF) == 0;
1643 | negative = false;
1644 | halfCarry = true;
1645 | carry = false;
1646 | UpdateFFromFlags();
1647 |
1648 | A = (byte)result;
1649 | return 8;
1650 | }
1651 |
1652 | private int XOR_A_R(ref byte r) {
1653 | int val = A ^ r;
1654 |
1655 | //zero = A == 0;
1656 | zero = (val & 0xFF) == 0;
1657 | negative = false;
1658 | halfCarry = false;
1659 | carry = false;
1660 | UpdateFFromFlags();
1661 |
1662 | A = (byte)val;
1663 | return 4;
1664 | }
1665 |
1666 | private int XOR_A_ARR(string regPair) {
1667 | ushort addr = Get16BitReg(regPair);
1668 | byte val = mmu.Read(addr);
1669 | int result = A ^ val;
1670 |
1671 | zero = (result & 0xFF) == 0;
1672 | negative = false;
1673 | halfCarry = false;
1674 | carry = false;
1675 | UpdateFFromFlags();
1676 |
1677 | A = (byte)result;
1678 | return 8;
1679 | }
1680 |
1681 | private int XOR_A_U8() {
1682 | byte val = Fetch();
1683 | int result = A ^ val;
1684 |
1685 | zero = (result & 0xFF) == 0;
1686 | negative = false;
1687 | halfCarry = false;
1688 | carry = false;
1689 | UpdateFFromFlags();
1690 |
1691 | A = (byte)result;
1692 | return 8;
1693 | }
1694 |
1695 | private int OR_A_R(ref byte r) {
1696 | int val = A | r;
1697 |
1698 | zero = (val & 0xFF) == 0;
1699 | negative = false;
1700 | halfCarry = false;
1701 | carry = false;
1702 | UpdateFFromFlags();
1703 |
1704 | A = (byte)val;
1705 | return 4;
1706 | }
1707 |
1708 | private int OR_A_ARR(string regPair) {
1709 | ushort addr = Get16BitReg(regPair);
1710 | byte val = mmu.Read(addr);
1711 | int result = A | val;
1712 |
1713 | zero = (result & 0xFF) == 0;
1714 | negative = false;
1715 | halfCarry = false;
1716 | carry = false;
1717 | UpdateFFromFlags();
1718 |
1719 | A = (byte)result;
1720 | return 8;
1721 | }
1722 |
1723 | private int OR_A_U8() {
1724 | byte val = Fetch();
1725 | int result = A | val;
1726 |
1727 | zero = (result & 0xFF) == 0;
1728 | negative = false;
1729 | halfCarry = false;
1730 | carry = false;
1731 | UpdateFFromFlags();
1732 |
1733 | A = (byte)result;
1734 | return 8;
1735 | }
1736 |
1737 | private int CP_A_R(ref byte r) {
1738 | int result = A - r;
1739 |
1740 | zero = (result & 0xFF) == 0;
1741 | negative = true;
1742 | halfCarry = (A & 0xF) < (r & 0xF);
1743 | carry = result < 0;
1744 | UpdateFFromFlags();
1745 |
1746 | return 4;
1747 | }
1748 |
1749 | private int CP_A_ARR(string regPair) {
1750 | ushort addr = Get16BitReg(regPair);
1751 | byte val = mmu.Read(addr);
1752 | int result = A - val;
1753 |
1754 | zero = result == 0;
1755 | negative = true;
1756 | halfCarry = (A & 0xF) < (val & 0xF);
1757 | carry = A < val;
1758 | UpdateFFromFlags();
1759 |
1760 | return 8;
1761 | }
1762 |
1763 | private int CP_A_U8() {
1764 | byte val = Fetch();
1765 | int result = A - val;
1766 |
1767 | zero = result == 0;
1768 | negative = true;
1769 | halfCarry = (A & 0xF) < (val & 0xF);
1770 | carry = A < val;
1771 | UpdateFFromFlags();
1772 |
1773 | return 8;
1774 | }
1775 |
1776 | private int CPL() {
1777 | A = (byte)~A;
1778 |
1779 | negative = true;
1780 | halfCarry = true;
1781 | UpdateFFromFlags();
1782 |
1783 | return 4;
1784 | }
1785 |
1786 | private int SCF() {
1787 | carry = true;
1788 | negative = false;
1789 | halfCarry = false;
1790 | UpdateFFromFlags();
1791 |
1792 | return 4;
1793 | }
1794 |
1795 | private int CCF() {
1796 | carry = !carry;
1797 | negative = false;
1798 | halfCarry = false;
1799 | UpdateFFromFlags();
1800 |
1801 | return 4;
1802 | }
1803 |
1804 | private int DAA() {
1805 | byte adjust = (byte)(carry ? 0x60 : 0x00);
1806 | if (halfCarry) {
1807 | adjust |= 0x06;
1808 | }
1809 | if (negative) {
1810 | A -= adjust;
1811 | } else {
1812 | if ((A & 0x0F) > 0x09) {
1813 | adjust |= 0x06;
1814 | }
1815 | if (A > 0x99) {
1816 | adjust |= 0x60;
1817 | }
1818 |
1819 | A += adjust;
1820 | }
1821 |
1822 | zero = A == 0;
1823 | halfCarry = false;
1824 | carry = adjust >= 0x60;
1825 | UpdateFFromFlags();
1826 |
1827 | return 4;
1828 | }
1829 |
1830 | //x16 ALU
1831 | private int INC_RR(string regPair) {
1832 | ushort regVal = Get16BitReg(regPair);
1833 | regVal++;
1834 | Load16BitReg(regPair, regVal);
1835 | return 8;
1836 | }
1837 |
1838 | private int INC_SP() {
1839 | SP++;
1840 | return 8;
1841 | }
1842 |
1843 | private int DEC_RR(string regPair) {
1844 | ushort regVal = Get16BitReg(regPair);
1845 | regVal--;
1846 | Load16BitReg(regPair, regVal);
1847 | return 8;
1848 | }
1849 |
1850 | private int DEC_SP() {
1851 | SP--;
1852 | return 8;
1853 | }
1854 |
1855 | private int ADD_HL_RR(string regPair) {
1856 | ushort hl = Get16BitReg("hl");
1857 | ushort regVal = Get16BitReg(regPair);
1858 | int result = hl + regVal;
1859 |
1860 | negative = false;
1861 | halfCarry = ((hl & 0x0FFF) + (regVal & 0x0FFF)) > 0x0FFF;
1862 | carry = result > 0xFFFF;
1863 | UpdateFFromFlags();
1864 |
1865 | Load16BitReg("hl", (ushort)result);
1866 |
1867 | return 8;
1868 | }
1869 |
1870 | private int ADD_HL_SP() {
1871 | ushort hl = Get16BitReg("hl");
1872 | int result = hl + SP;
1873 |
1874 | negative = false;
1875 | halfCarry = ((hl & 0x0FFF) + (SP & 0x0FFF)) > 0x0FFF;
1876 | carry = result > 0xFFFF;
1877 | UpdateFFromFlags();
1878 |
1879 | Load16BitReg("hl", (ushort)result);
1880 |
1881 | return 8;
1882 | }
1883 |
1884 | private int ADD_SP_I8() {
1885 | byte lower = (byte)(SP & 0xFF);
1886 | byte high = (byte)((SP >> 8) & 0xFF);
1887 | sbyte sb = (sbyte)Fetch();
1888 | int result = SP + (ushort)sb;
1889 |
1890 | zero = false;
1891 | negative = false;
1892 | halfCarry = ((SP ^ sb ^ (result & 0xFFFF)) & 0x10) == 0x10;
1893 | carry = ((SP ^ sb ^ (result & 0xFFFF)) & 0x100) == 0x100;
1894 | UpdateFFromFlags();
1895 |
1896 | SP = (ushort)result;
1897 |
1898 | return 16;
1899 | }
1900 |
1901 | private int LD_HL_SP_I8() {
1902 | sbyte sb = (sbyte)Fetch();
1903 | int result = SP + sb;
1904 |
1905 | zero = false;
1906 | negative = false;
1907 | halfCarry = ((SP ^ sb ^ (result & 0xFFFF)) & 0x10) == 0x10;
1908 | carry = ((SP ^ sb ^ (result & 0xFFFF)) & 0x100) == 0x100;
1909 | UpdateFFromFlags();
1910 |
1911 | Load16BitReg("hl", (ushort)result);
1912 |
1913 | return 12;
1914 | }
1915 |
1916 | //x8 RSB
1917 | private int RLCA() {
1918 | byte wrap = (byte)(A & 0x80);
1919 | byte result = (byte)((A << 1) | (wrap >> 7));
1920 |
1921 | zero = false;
1922 | negative = false;
1923 | halfCarry = false;
1924 | carry = (A & 0x80) != 0;
1925 | UpdateFFromFlags();
1926 |
1927 | A = result;
1928 |
1929 | return 4;
1930 | }
1931 |
1932 | private int RRCA() {
1933 | byte wrap = (byte)(A & 0x01);
1934 | byte result = (byte)((A >> 1) | (wrap << 7));
1935 |
1936 | zero = false;
1937 | negative = false;
1938 | halfCarry = false;
1939 | carry = (A & 0x01) != 0;
1940 | UpdateFFromFlags();
1941 |
1942 | A = result;
1943 |
1944 | return 4;
1945 | }
1946 |
1947 | private int RLA() {
1948 | byte carryRLA = (byte)(carry ? 1 : 0);
1949 | byte result = (byte)((A << 1) | carryRLA);
1950 |
1951 | zero = false;
1952 | negative = false;
1953 | halfCarry = false;
1954 | carry = (A & 0x80) != 0;
1955 | UpdateFFromFlags();
1956 |
1957 | A = result;
1958 | return 4;
1959 | }
1960 |
1961 | private int RRA() {
1962 | byte carryRRA = (byte)(carry ? 1 : 0);
1963 | byte result = (byte)((A >> 1) | (carryRRA << 7));
1964 |
1965 | zero = false;
1966 | negative = false;
1967 | halfCarry = false;
1968 | carry = (A & 0x01) != 0;
1969 | UpdateFFromFlags();
1970 |
1971 | A = result;
1972 | return 4;
1973 | }
1974 |
1975 | private int RLC_R(ref byte r) {
1976 | byte wrap = (byte)(r & 0x80);
1977 | byte result = (byte)((r << 1) | (wrap >> 7));
1978 |
1979 | zero = result == 0;
1980 | negative = false;
1981 | halfCarry = false;
1982 | carry = (r & 0x80) != 0;
1983 | UpdateFFromFlags();
1984 |
1985 | r = result;
1986 |
1987 | return 8;
1988 | }
1989 |
1990 | private int RLC_AHL() {
1991 | ushort address = Get16BitReg("hl");
1992 | byte val = mmu.Read(address);
1993 | byte wrap = (byte)(val & 0x80);
1994 | byte result = (byte)((val << 1) | (wrap >> 7));
1995 |
1996 | zero = result == 0;
1997 | negative = false;
1998 | halfCarry = false;
1999 | carry = (val & 0x80) != 0;
2000 | UpdateFFromFlags();
2001 |
2002 | mmu.Write(address, result);
2003 |
2004 | return 16;
2005 | }
2006 |
2007 | private int RRC_R(ref byte r) {
2008 | byte wrap = (byte)(r & 0x01);
2009 | byte result = (byte)((r >> 1) | (wrap << 7));
2010 |
2011 | zero = result == 0;
2012 | negative = false;
2013 | halfCarry = false;
2014 | carry = (r & 0x01) != 0;
2015 | UpdateFFromFlags();
2016 |
2017 | r = result;
2018 |
2019 | return 8;
2020 | }
2021 |
2022 | private int RRC_AHL() {
2023 | ushort address = Get16BitReg("hl");
2024 | byte val = mmu.Read(address);
2025 | byte wrap = (byte)(val & 0x01);
2026 | byte result = (byte)((val >> 1) | (wrap << 7));
2027 |
2028 | zero = result == 0;
2029 | negative = false;
2030 | halfCarry = false;
2031 | carry = (val & 0x01) != 0;
2032 | UpdateFFromFlags();
2033 |
2034 | mmu.Write(address, result);
2035 |
2036 | return 16;
2037 | }
2038 |
2039 | private int RL_R(ref byte r) {
2040 | byte carryRL = (byte)(carry ? 1 : 0);
2041 | byte result = (byte)((r << 1) | carryRL);
2042 |
2043 | zero = result == 0;
2044 | negative = false;
2045 | halfCarry = false;
2046 | carry = (r & 0x80) != 0;
2047 | UpdateFFromFlags();
2048 |
2049 | r = result;
2050 |
2051 | return 8;
2052 | }
2053 |
2054 | private int RL_AHL() {
2055 | ushort address = Get16BitReg("hl");
2056 | byte val = mmu.Read(address);
2057 | byte carryRL = (byte)(carry ? 1 : 0);
2058 | byte result = (byte)((val << 1) | carryRL);
2059 |
2060 | zero = result == 0;
2061 | negative = false;
2062 | halfCarry = false;
2063 | carry = (val & 0x80) != 0;
2064 | UpdateFFromFlags();
2065 |
2066 | mmu.Write(address, result);
2067 |
2068 | return 16;
2069 | }
2070 |
2071 | private int RR_R(ref byte r) {
2072 | byte carryRR = (byte)(carry ? 1 : 0);
2073 | byte result = (byte)((r >> 1) | (carryRR << 7));
2074 |
2075 | zero = result == 0;
2076 | negative = false;
2077 | halfCarry = false;
2078 | carry = (r & 0x01) != 0;
2079 | UpdateFFromFlags();
2080 |
2081 | r = result;
2082 |
2083 | return 8;
2084 | }
2085 |
2086 | private int RR_AHL() {
2087 | ushort address = Get16BitReg("hl");
2088 | byte val = mmu.Read(address);
2089 | byte carryRR = (byte)(carry ? 1 : 0);
2090 | byte result = (byte)((val >> 1) | (carryRR << 7));
2091 |
2092 | zero = result == 0;
2093 | negative = false;
2094 | halfCarry = false;
2095 | carry = (val & 0x01) != 0;
2096 | UpdateFFromFlags();
2097 |
2098 | mmu.Write(address, result);
2099 |
2100 | return 16;
2101 | }
2102 |
2103 | private int SLA_R(ref byte r) {
2104 | byte msb = (byte)(r & 0x80);
2105 | byte result = (byte)(r << 1);
2106 |
2107 | zero = result == 0;
2108 | negative = false;
2109 | halfCarry = false;
2110 | carry = msb != 0;
2111 | UpdateFFromFlags();
2112 |
2113 | r = result;
2114 |
2115 | return 8;
2116 | }
2117 |
2118 | private int SLA_AHL() {
2119 | ushort address = Get16BitReg("hl");
2120 | byte val = mmu.Read(address);
2121 | byte msb = (byte)(val & 0x80);
2122 | byte result = (byte)(val << 1);
2123 |
2124 | zero = result == 0;
2125 | negative = false;
2126 | halfCarry = false;
2127 | carry = msb != 0;
2128 | UpdateFFromFlags();
2129 |
2130 | mmu.Write(address, result);
2131 |
2132 | return 16;
2133 | }
2134 |
2135 | private int SRA_R(ref byte r) {
2136 | byte msb = (byte)(r & 0x80);
2137 | byte lsb = (byte)(r & 0x01);
2138 | byte result = (byte)((r >> 1) | msb);
2139 |
2140 | zero = result == 0;
2141 | negative = false;
2142 | halfCarry = false;
2143 | carry = lsb != 0;
2144 | UpdateFFromFlags();
2145 |
2146 | r = result;
2147 |
2148 | return 8;
2149 | }
2150 |
2151 | private int SRA_AHL() {
2152 | ushort address = Get16BitReg("hl");
2153 | byte val = mmu.Read(address);
2154 | byte msb = (byte)(val & 0x80);
2155 | byte lsb = (byte)(val & 0x01);
2156 | byte result = (byte)((val >> 1) | msb);
2157 |
2158 | zero = result == 0;
2159 | negative = false;
2160 | halfCarry = false;
2161 | carry = lsb != 0;
2162 | UpdateFFromFlags();
2163 |
2164 | mmu.Write(address, result);
2165 |
2166 | return 16;
2167 | }
2168 |
2169 | private int SRL_R(ref byte r) {
2170 | byte lsb = (byte)(r & 0x01);
2171 | byte result = (byte)(r >> 1);
2172 |
2173 | zero = result == 0;
2174 | negative = false;
2175 | halfCarry = false;
2176 | carry = lsb != 0;
2177 | UpdateFFromFlags();
2178 |
2179 | r = result;
2180 |
2181 | return 8;
2182 | }
2183 |
2184 | private int SRL_AHL() {
2185 | ushort address = Get16BitReg("hl");
2186 | byte val = mmu.Read(address);
2187 | byte lsb = (byte)(val & 0x01);
2188 | byte result = (byte)(val >> 1);
2189 |
2190 | zero = result == 0;
2191 | negative = false;
2192 | halfCarry = false;
2193 | carry = lsb != 0;
2194 | UpdateFFromFlags();
2195 |
2196 | mmu.Write(address, result);
2197 |
2198 | return 16;
2199 | }
2200 |
2201 | private int SWAP_R(ref byte r) {
2202 | byte upper = (byte)(r & 0xF0);
2203 | byte lower = (byte)(r & 0x0F);
2204 | byte result = (byte)((lower << 4) | (upper >> 4));
2205 |
2206 | zero = result == 0;
2207 | negative = false;
2208 | halfCarry = false;
2209 | carry = false;
2210 | UpdateFFromFlags();
2211 |
2212 | r = result;
2213 |
2214 | return 8;
2215 | }
2216 |
2217 | private int SWAP_AHL() {
2218 | ushort address = Get16BitReg("hl");
2219 | byte val = mmu.Read(address);
2220 | byte upper = (byte)(val & 0xF0);
2221 | byte lower = (byte)(val & 0x0F);
2222 | byte result = (byte)((lower << 4) | (upper >> 4));
2223 |
2224 | zero = result == 0;
2225 | negative = false;
2226 | halfCarry = false;
2227 | carry = false;
2228 | UpdateFFromFlags();
2229 |
2230 | mmu.Write(address, result);
2231 |
2232 | return 16;
2233 | }
2234 |
2235 | private int BIT_N_R(byte n, ref byte r) {
2236 | byte bitTest = (byte)(r & (1 << n));
2237 |
2238 | zero = bitTest == 0;
2239 | negative = false;
2240 | halfCarry = true;
2241 | UpdateFFromFlags();
2242 |
2243 | return 8;
2244 | }
2245 |
2246 | private int BIT_N_AHL(byte n) {
2247 | ushort address = Get16BitReg("hl");
2248 | byte val = mmu.Read(address);
2249 | byte bitTest = (byte)(val & (1 << n));
2250 |
2251 | zero = bitTest == 0;
2252 | negative = false;
2253 | halfCarry = true;
2254 | UpdateFFromFlags();
2255 |
2256 | return 12;
2257 | }
2258 |
2259 | private int RES_N_R(byte n, ref byte r) {
2260 | byte mask = (byte)~(1 << n);
2261 | r &= mask;
2262 |
2263 | return 8;
2264 | }
2265 |
2266 | private int RES_N_AHL(byte n) {
2267 | ushort address = Get16BitReg("hl");
2268 | byte val = mmu.Read(address);
2269 | byte mask = (byte)~(1 << n);
2270 | val &= mask;
2271 |
2272 | mmu.Write(address, val);
2273 |
2274 | return 16;
2275 | }
2276 |
2277 | private int SET_N_R(byte n, ref byte r) {
2278 | byte mask = (byte)(1 << n);
2279 | r |= mask;
2280 |
2281 | return 8;
2282 | }
2283 |
2284 | private int SET_N_AHL(byte n) {
2285 | ushort address = Get16BitReg("hl");
2286 | byte val = mmu.Read(address);
2287 | byte mask = (byte)(1 << n);
2288 | val |= mask;
2289 |
2290 | mmu.Write(address, val);
2291 |
2292 | return 16;
2293 | }
2294 |
2295 | //control
2296 | private int JR_CON_I8(bool flag) {
2297 | byte rel = Fetch();
2298 |
2299 | if (flag) {
2300 | sbyte offset = (sbyte)rel;
2301 | PC = (ushort)(PC + offset);
2302 | return 12;
2303 | } else {
2304 | return 8;
2305 | }
2306 | }
2307 |
2308 | private int CALL_U16() {
2309 | byte lower = Fetch();
2310 | byte upper = Fetch();
2311 | ushort address = (ushort)((upper << 8) | lower);
2312 |
2313 | ushort returnAddress = (ushort)(PC);
2314 | SP--;
2315 | mmu.Write(SP, (byte)(returnAddress >> 8));
2316 | SP--;
2317 | mmu.Write(SP, (byte)(returnAddress & 0xFF));
2318 |
2319 | PC = address;
2320 | return 24;
2321 | }
2322 |
2323 | private int CALL_CON_U16(bool flag) {
2324 | byte lower = Fetch();
2325 | byte upper = Fetch();
2326 | ushort address = (ushort)((upper << 8) | lower);
2327 |
2328 | if (flag) {
2329 | ushort returnAddress = (ushort)(PC);
2330 | SP--;
2331 | mmu.Write(SP, (byte)(returnAddress >> 8));
2332 | SP--;
2333 | mmu.Write(SP, (byte)(returnAddress & 0xFF));
2334 |
2335 | PC = address;
2336 | return 24;
2337 | } else {
2338 | return 12;
2339 | }
2340 | }
2341 |
2342 | private int RET() {
2343 | byte lowByte = mmu.Read(SP);
2344 | SP++;
2345 | byte highByte = mmu.Read(SP);
2346 | SP++;
2347 |
2348 | ushort returnAddress = (ushort)((highByte << 8) | lowByte);
2349 |
2350 | PC = returnAddress;
2351 | return 16;
2352 | }
2353 |
2354 | private int RET_CON(bool flag) {
2355 | if (flag) {
2356 | byte lowByte = mmu.Read(SP);
2357 | SP++;
2358 | byte highByte = mmu.Read(SP);
2359 | SP++;
2360 |
2361 | ushort returnAddress = (ushort)((highByte << 8) | lowByte);
2362 |
2363 | PC = returnAddress;
2364 | return 20;
2365 | } else {
2366 | return 8;
2367 | }
2368 | }
2369 |
2370 | private int RETI() {
2371 | IME = true;
2372 |
2373 | byte lowByte = mmu.Read(SP);
2374 | SP++;
2375 | byte highByte = mmu.Read(SP);
2376 | SP++;
2377 |
2378 | ushort returnAddress = (ushort)((highByte << 8) | lowByte);
2379 |
2380 | PC = returnAddress;
2381 | return 16;
2382 | }
2383 |
2384 | private int JP_CON_U16(bool flag) {
2385 | byte lower = Fetch();
2386 | byte upper = Fetch();
2387 | ushort address = (ushort)((upper << 8) | lower);
2388 |
2389 | if (flag) {
2390 | PC = address;
2391 | return 16;
2392 | } else {
2393 | return 12;
2394 | }
2395 | }
2396 |
2397 | private int JP_HL() {
2398 | ushort address = Get16BitReg("hl");
2399 | PC = address;
2400 | return 4;
2401 | }
2402 |
2403 | private int RST(ushort vector) {
2404 | ushort returnAddress = (ushort)(PC);
2405 | SP--;
2406 | mmu.Write(SP, (byte)(returnAddress >> 8));
2407 | SP--;
2408 | mmu.Write(SP, (byte)(returnAddress & 0xFF));
2409 |
2410 | PC = vector;
2411 | return 16;
2412 | }
2413 |
2414 | //misc
2415 | private int NOP() {
2416 | return 4;
2417 | }
2418 |
2419 | private int DI() {
2420 | IME = false;
2421 | return 4;
2422 | }
2423 |
2424 | private int EI() {
2425 | IME = true;
2426 | return 4;
2427 | }
2428 |
2429 | private int HALT() {
2430 | halted = true;
2431 | return 4;
2432 | }
2433 |
2434 | private int STOP() {
2435 | return 4;
2436 | }
2437 |
2438 | private int DMG_EXIT(byte op) {
2439 | Console.WriteLine("Unimplemented Opcode: " + op.ToString("X2") + " , PC: " + (PC-1).ToString("X4") + " (In opcode switch)");
2440 | Environment.Exit(1);
2441 | return 0;
2442 | }
2443 | }
--------------------------------------------------------------------------------
/src/DMG.cs:
--------------------------------------------------------------------------------
1 | using Raylib_cs;
2 |
3 | class DMG {
4 | public byte[] gameRom;
5 | public byte[] bootRom;
6 |
7 | public MMU mmu;
8 | public CPU cpu;
9 | public PPU ppu;
10 | public Joypad joypad;
11 | public Timer timer;
12 |
13 | Image screenImage;
14 | Texture2D screenTexture;
15 |
16 | Image icon;
17 |
18 | public DMG(string gameRomPath, string bootRomDataPath) {
19 | gameRom = File.ReadAllBytes(gameRomPath);
20 | if (File.Exists(bootRomDataPath)){
21 | bootRom = File.ReadAllBytes(bootRomDataPath);
22 | } else {
23 | bootRom = new byte[256];
24 | }
25 |
26 |
27 | if (!Helper.raylibLog) Raylib.SetTraceLogLevel(TraceLogLevel.None);
28 |
29 | Raylib.InitWindow(160*Helper.scale, 144*Helper.scale, "DMG");
30 | Raylib.SetTargetFPS(60);
31 |
32 | icon = Raylib.LoadImage("icon.png");
33 | Raylib.SetWindowIcon(icon);
34 |
35 | screenImage = Raylib.GenImageColor(160, 144, Color.Black);
36 | screenTexture = Raylib.LoadTextureFromImage(screenImage);
37 |
38 | mmu = new MMU(gameRom, bootRom, false);
39 | cpu = new CPU(mmu);
40 | ppu = new PPU(mmu, screenImage, screenTexture);
41 | joypad = new Joypad(mmu);
42 | timer = new Timer(mmu);
43 |
44 | if (!File.Exists(bootRomDataPath)) cpu.Reset();
45 |
46 | Console.WriteLine("DMG");
47 | }
48 |
49 | public void Run() {
50 | Console.WriteLine("\n" + mmu.HeaderInfo() + "\n");
51 | mmu.Load(Path.Combine(Path.GetDirectoryName(Helper.rom) ?? string.Empty, Path.GetFileNameWithoutExtension(Helper.rom) + ".sav"));
52 |
53 | while (!Raylib.WindowShouldClose()) {
54 | Raylib.BeginDrawing();
55 | //Raylib.ClearBackground(Color.White);
56 |
57 | int cycles = 0;
58 | int cycle = 0;
59 |
60 | joypad.HandleInput();
61 |
62 | while (cycles < 70224) {
63 | cycle = cpu.ExecuteInstruction();
64 | cycles += cycle;
65 | ppu.Step(cycle);
66 | timer.Step(cycle);
67 |
68 | if (cpu.PC == 0x100) {
69 | Console.WriteLine("Made it to PC: 0x100");
70 | }
71 | }
72 |
73 | if (Helper.fpsEnable) Raylib.DrawFPS(0, 0);
74 |
75 | Raylib.EndDrawing();
76 | }
77 |
78 | mmu.Save(Path.Combine(Path.GetDirectoryName(Helper.rom) ?? string.Empty, Path.GetFileNameWithoutExtension(Helper.rom) + ".sav"));
79 | Console.Write("Closing Window\n");
80 |
81 | Raylib.UnloadImage(screenImage);
82 | Raylib.UnloadTexture(screenTexture);
83 | Raylib.UnloadImage(icon);
84 | Raylib.CloseWindow();
85 | }
86 | }
--------------------------------------------------------------------------------
/src/Helper.cs:
--------------------------------------------------------------------------------
1 | using Raylib_cs;
2 |
3 | class Helper {
4 | public static int scale = 2;
5 | public static string rom = "fall_back.gb";
6 | public static string bootrom = "dmg_boot.bin";
7 | public static string jsonPath = "";
8 | public static bool fpsEnable = false;
9 | public static string paletteName = "dmg";
10 | public static int mode = 0;
11 | public static bool raylibLog = false;
12 | public static Version version = new Version(1, 0, 0);
13 | public static Dictionary palettes = new Dictionary() {
14 | {"dmg", new Color[]{new Color(155, 188, 15, 255), //Lightest
15 | new Color(139, 172, 15, 255), //Light
16 | new Color(48, 98, 48, 255), //Dark
17 | new Color(15, 56, 15, 255), //Darkest
18 | new Color(255, 255, 255, 255)}}, //Transparent
19 | {"cyber", new Color[]{new Color(50, 153, 180, 255),
20 | new Color(46, 116, 134, 255),
21 | new Color(2, 70, 88, 255),
22 | new Color(2, 49, 61, 255),
23 | new Color(255, 255, 255, 255)}},
24 | {"emu", new Color[]{new Color(224, 248, 208, 255),
25 | new Color(136, 192, 112, 255),
26 | new Color(52, 104, 86, 255),
27 | new Color(8, 24, 32, 255),
28 | new Color(255, 255, 255, 255)}},
29 | {"autumn", new Color[]{new Color(255, 246, 211, 255),
30 | new Color(249, 168, 117, 255),
31 | new Color(235, 107, 111, 255),
32 | new Color(124, 63, 88, 255),
33 | new Color(255, 255, 255, 255)}},
34 | {"paris", new Color[]{new Color(218, 112, 214, 255),
35 | new Color(186, 85, 211, 255),
36 | new Color(153, 50, 204, 255),
37 | new Color(75, 0, 130, 255),
38 | new Color(255, 255, 255, 255)}},
39 | {"grayscale", new Color[]{Color.White,
40 | Color.LightGray,
41 | Color.DarkGray,
42 | Color.Black,
43 | new Color(255, 255, 255, 255)}},
44 | {"early", new Color[]{Color.Black,
45 | Color.DarkGray,
46 | Color.LightGray,
47 | Color.White,
48 | new Color(255, 255, 255, 255)}},
49 | {"crow", new Color[]{new Color(204, 61, 80, 255),
50 | new Color(153, 31, 39, 255),
51 | new Color(89, 22, 22, 255),
52 | new Color(38, 15, 13, 255),
53 | new Color(255, 255, 255, 255)}},
54 | {"coffee", new Color[]{new Color(204, 158, 122, 255),
55 | new Color(153, 116, 92, 255),
56 | new Color(115, 77, 69, 255),
57 | new Color(77, 48, 46, 255),
58 | new Color(255, 255, 255, 255)}},
59 | {"winter", new Color[]{new Color(159, 244, 229, 255),
60 | new Color(0, 185, 190, 255),
61 | new Color(0, 95, 140, 255),
62 | new Color(0, 43, 89, 255),
63 | new Color(255, 255, 255, 255)}}
64 | };
65 |
66 | public static void Flags(string[] args) {
67 | if (args.Length >= 1) {
68 | for (int i = 0; i < args.Length; i++) {
69 | if (args[i] == "--dmg") {
70 | if (args[i] != args[^1] && args[i + 1].IndexOf('-') != 0) {
71 | rom = args[i + 1];
72 | i += 1;
73 | }
74 | mode = 0;
75 | if (!File.Exists(rom)) {
76 | Console.WriteLine("ROM \"" + rom + "\" not found!");
77 | Console.WriteLine("Error: Provide ROM --dmg ");
78 | Environment.Exit(1);
79 | }
80 | }
81 | if (args[i] == "--json") {
82 | jsonPath = Path.Combine("test", "v1", args[i + 1]);
83 | mode = 1;
84 | if (!File.Exists(jsonPath)) {
85 | Console.WriteLine("JSON Test \"" + args[i + 1] + "\" not found!");
86 | Console.WriteLine("Error: Provide Test --json ");
87 | Environment.Exit(1);
88 | }
89 | i += 1;
90 | }
91 | if (args[i] == "-b" || args[i] == "--bootrom") {
92 | bootrom = args[i + 1];
93 | if (!File.Exists(bootrom)) {
94 | Console.WriteLine("Error: Custom bootrom file \""+ bootrom +"\" not found!");
95 | Environment.Exit(1);
96 | }
97 | i += 1;
98 | }
99 | if (args[i] == "-s" || args[i] == "--scale") {
100 | scale = int.Parse(args[i + 1]);
101 | i += 1;
102 | }
103 | if (args[i] == "-f" || args[i] == "--fps") {
104 | fpsEnable = true;
105 | }
106 | if (args[i] == "-rl" || args[i] == "--raylib-log") {
107 | raylibLog = true;
108 | }
109 | if (args[i] == "-p" || args[i] == "--palette") {
110 | foreach (KeyValuePair palette in palettes) {
111 | if (args[i + 1].ToLower().Equals(palette.Key)) {
112 | paletteName = args[i + 1].ToLower();
113 | break;
114 | }
115 | }
116 | i += 1;
117 | }
118 | if (args[i] == "-v" || args[i] == "--version") {
119 | Console.WriteLine(version);
120 | Console.WriteLine("Made by Bot Randomness :)");
121 | ASCII_DMG();
122 | Environment.Exit(1);
123 | }
124 | if (args[i] == ":)" || args[i] == "-a" || args[i] == "--about") {
125 | Console.WriteLine("Made by Bot Randomness :)");
126 | Console.WriteLine(version);
127 | ASCII_DMG();
128 | Environment.Exit(1);
129 | }
130 | if (args[i] == "-h" || args[i] == "--help") {
131 | Console.WriteLine("DMG Help:");
132 | Console.WriteLine("--dmg , --dmg: Starts up the emulator given a rom file (Default mode. No rom given, fall back is default)");
133 | Console.WriteLine("--json : Runs a CPU test for a instruction given a JSON file in test/v1");
134 | Console.WriteLine("-b , --bootrom : Loads custom bootrom path than default. (dmg_boot.bin is default)");
135 | Console.WriteLine("-s , --scale : Scale window size by factor (2 is default)");
136 | Console.WriteLine("-f, --fps: Enables FPS counter (off is default)");
137 | Console.WriteLine("-rl, --raylib-log: Enables Raylib logs (off is default)");
138 | Console.WriteLine("-p , --palette : Changes the 2bpp palette given name (dmg is default)");
139 | Console.WriteLine("-a, --about: Shows about");
140 | Console.WriteLine("-v, --version: Shows version number");
141 | Console.WriteLine("-h, --help: Shows help screen (What you are reading right now)\n");
142 | Console.WriteLine("Pallette names: dmg, cyber, emu, autumn, paris, grayscale, early, crow, coffee, winter");
143 | Console.WriteLine("Controls: (A)=Z, (B)=X, [START]=[ENTER], [SELECT]=[RSHIFT], D-Pad=ArrowKeys");
144 | Console.WriteLine("Note: Keep bootrom file (if provided) and fallback rom must be by the excutable!");
145 | Console.WriteLine("Your current working directory must be at the application location when using!");
146 | Console.WriteLine("Bootrom is not needed, but it is recommended. It must be named \"dmg_boot.bin\" and be placed in root of executable");
147 | Environment.Exit(1);
148 | }
149 | }
150 | } else {
151 | Console.WriteLine("Error: No mode passed in");
152 | Console.WriteLine("Mode: --dmg , --json ");
153 | Console.WriteLine("Use -h or --help to bring up help options. Run in terminal (CLI only for now).");
154 | if (!File.Exists(rom)) Environment.Exit(1);
155 | }
156 | Console.WriteLine("");
157 | }
158 |
159 | public static void ASCII_DMG() {
160 | Console.WriteLine(" __________________ ");
161 | Console.WriteLine("|-|--------------|-|");
162 | Console.WriteLine("| ______________ |");
163 | Console.WriteLine("| | __________ | |");
164 | Console.WriteLine("| | | | | |");
165 | Console.WriteLine("| |·| | | |");
166 | Console.WriteLine("| | | | | |");
167 | Console.WriteLine("| | |__________| | |");
168 | Console.WriteLine("| |_____________/ |");
169 | Console.WriteLine("| _ GAMEBOY |");
170 | Console.WriteLine("| _| |_ () |");
171 | Console.WriteLine("||_ _| () |");
172 | Console.WriteLine("| |_| |");
173 | Console.WriteLine("| / / \\\\\\ / ");
174 | Console.WriteLine("|________________/ ");
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/Joypad.cs:
--------------------------------------------------------------------------------
1 | using Raylib_cs;
2 |
3 | class Joypad {
4 | private MMU mmu;
5 |
6 | public Joypad(MMU mmu)
7 | {
8 | this.mmu = mmu;
9 | }
10 |
11 | private void SetButton(uint button, bool pressed) {
12 | //0 = pressed, 1 = released
13 | if (pressed) {
14 | mmu.joypadState &= (byte)~button;
15 | } else {
16 | mmu.joypadState |= (byte)button;
17 | }
18 | }
19 |
20 | public void HandleInput() {
21 | //Action buttons
22 | SetButton(0x01, Raylib.IsKeyDown(KeyboardKey.Z)); //A
23 | SetButton(0x02, Raylib.IsKeyDown(KeyboardKey.X)); //B
24 | SetButton(0x04, Raylib.IsKeyDown(KeyboardKey.RightShift)); //Select
25 | SetButton(0x08, Raylib.IsKeyDown(KeyboardKey.Enter)); //Start
26 |
27 | //D-Pad buttons
28 | SetButton(0x10, Raylib.IsKeyDown(KeyboardKey.Right)); //Right
29 | SetButton(0x20, Raylib.IsKeyDown(KeyboardKey.Left)); //Left
30 | SetButton(0x40, Raylib.IsKeyDown(KeyboardKey.Up)); //Up
31 | SetButton(0x80, Raylib.IsKeyDown(KeyboardKey.Down)); //Down
32 |
33 | //Console.WriteLine(mmu.Read(0xFF00).ToString("X4"));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/MBC.cs:
--------------------------------------------------------------------------------
1 | class MBC {
2 | //MBC is currently only experimental
3 | //MBC0/ROM Only should be good
4 | //MBC1, MBC3, MBC5 are currently basic and experimental
5 | private byte[] rom; //Game ROM
6 | public byte[] ramBanks; //Cartridge RAM
7 | private int romBank = 1; //Current ROM bank
8 | private int ramBank = 0; //Current RAM bank
9 | private bool ramEnabled = false; //RAM enable flag
10 | public int mbcType; //MBC type
11 | int romSize;
12 | int ramSize;
13 | int romBankCount;
14 | int ramBankCount;
15 |
16 | public MBC(byte[] romData)
17 | {
18 | romSize = CalculateRomSize(romData[0x0148]);
19 | romBankCount = romSize / (16 * 1024);
20 | rom = romData;
21 |
22 | switch (rom[0x0147]) {
23 | case 0x00:
24 | mbcType = 0;
25 | break;
26 | case 0x01:
27 | mbcType = 1;
28 | break;
29 | case 0x02:
30 | mbcType = 1;
31 | break;
32 | case 0x03:
33 | mbcType = 1;
34 | break;
35 | case 0x0F:
36 | mbcType = 3;
37 | break;
38 | case 0x10:
39 | mbcType = 3;
40 | break;
41 | case 0x11:
42 | mbcType = 3;
43 | break;
44 | case 0x12:
45 | mbcType = 3;
46 | break;
47 | case 0x13:
48 | mbcType = 3;
49 | break;
50 | case 0x19:
51 | mbcType = 5;
52 | break;
53 | case 0x1A:
54 | mbcType = 5;
55 | break;
56 | case 0x1B:
57 | mbcType = 5;
58 | break;
59 | case 0x1C:
60 | mbcType = 5;
61 | break;
62 | case 0x1D:
63 | mbcType = 5;
64 | break;
65 | case 0x1E:
66 | mbcType = 5;
67 | break;
68 | default:
69 | mbcType = 0;
70 | Console.WriteLine("Error: Unknown/Unsupported MBC, using MBC0/ROM Only");
71 | break;
72 | }
73 |
74 | switch (rom[0x0149]) {
75 | case 0x01:
76 | ramSize = 2 * 1024; //2 KB (Unoffical?)
77 | ramBankCount = 1;
78 | break;
79 | case 0x02:
80 | ramSize = 8 * 1024; //8 KB
81 | ramBankCount = 1;
82 | break;
83 | case 0x03:
84 | ramSize = 32 * 1024; //32 KB
85 | ramBankCount = 4;
86 | break;
87 | case 0x04:
88 | ramSize = 128 * 1024; //128 KB
89 | ramBankCount = 16;
90 | break;
91 | case 0x05:
92 | ramSize = 64 * 1024; //64 KB
93 | ramBankCount = 8;
94 | break;
95 | default:
96 | ramSize = 0; //No RAM
97 | ramBankCount = 0;
98 | break;
99 | }
100 |
101 | ramBanks = new byte[ramSize];
102 | }
103 |
104 | private int CalculateRomSize(byte headerValue) {
105 | return 32 * 1024 * (1 << headerValue); //32 KiB × (1 << )
106 | }
107 |
108 | public string GetTitle() {
109 | byte[] titleBytes = new byte[16];
110 | Array.Copy(rom, 0x0134, titleBytes, 0, 16);
111 |
112 | string title = System.Text.Encoding.ASCII.GetString(titleBytes).TrimEnd(null);
113 |
114 | if (title.Length > 15)
115 | {
116 | title = title.Substring(0, 15);
117 | }
118 |
119 | return "Title: " + title;
120 | }
121 |
122 | public string GetCartridgeType() {
123 | byte cartridgeTypeByte = rom[0x0147];
124 | string cartridgeType;
125 |
126 | switch (cartridgeTypeByte) {
127 | case 0x00:
128 | cartridgeType = "MBC0/ROM ONLY";
129 | break;
130 | case 0x01:
131 | cartridgeType = "MBC1";
132 | break;
133 | case 0x02:
134 | cartridgeType = "MBC1+RAM";
135 | break;
136 | case 0x03:
137 | cartridgeType = "MBC1+RAM+BATTERY";
138 | break;
139 | case 0x05:
140 | cartridgeType = "MBC2";
141 | break;
142 | case 0x06:
143 | cartridgeType = "MBC2+BATTERY";
144 | break;
145 | case 0x08:
146 | cartridgeType = "ROM+RAM";
147 | break;
148 | case 0x09:
149 | cartridgeType = "ROM+RAM+BATTERY";
150 | break;
151 | case 0x0B:
152 | cartridgeType = "MMM01";
153 | break;
154 | case 0x0C:
155 | cartridgeType = "MMM01+RAM";
156 | break;
157 | case 0x0D:
158 | cartridgeType = "MMM01+RAM+BATTERY";
159 | break;
160 | case 0x0F:
161 | cartridgeType = "MBC3+TIMER+BATTERY";
162 | break;
163 | case 0x10:
164 | cartridgeType = "MBC3+TIMER+RAM+BATTERY";
165 | break;
166 | case 0x11:
167 | cartridgeType = "MBC3";
168 | break;
169 | case 0x12:
170 | cartridgeType = "MBC3+RAM";
171 | break;
172 | case 0x13:
173 | cartridgeType = "MBC3+RAM+BATTERY";
174 | break;
175 | case 0x19:
176 | cartridgeType = "MBC5";
177 | break;
178 | case 0x1A:
179 | cartridgeType = "MBC5+RAM";
180 | break;
181 | case 0x1B:
182 | cartridgeType = "MBC5+RAM+BATTERY";
183 | break;
184 | case 0x1C:
185 | cartridgeType = "MBC5+RUMBLE";
186 | break;
187 | case 0x1D:
188 | cartridgeType = "MBC5+RUMBLE+RAM";
189 | break;
190 | case 0x1E:
191 | cartridgeType = "MBC5+RUMBLE+RAM+BATTERY";
192 | break;
193 | case 0x20:
194 | cartridgeType = "MBC6";
195 | break;
196 | case 0x22:
197 | cartridgeType = "MBC7+SENSOR+RUMBLE+RAM+BATTERY";
198 | break;
199 | case 0xFC:
200 | cartridgeType = "POCKET CAMERA";
201 | break;
202 | case 0xFD:
203 | cartridgeType = "BANDAI TAMA5";
204 | break;
205 | case 0xFE:
206 | cartridgeType = "HuC3";
207 | break;
208 | case 0xFF:
209 | cartridgeType = "HuC1+RAM+BATTERY";
210 | break;
211 | default:
212 | cartridgeType = "Unknown cartridge type";
213 | break;
214 | }
215 | return "Cartridge Type: " + cartridgeType;
216 | }
217 |
218 | public string GetRomSize() {
219 | byte romSizeByte = rom[0x0148];
220 | string romSizeName;
221 |
222 | switch (romSizeByte) {
223 | case 0x00:
224 | romSizeName = "32 KiB (2 ROM banks, No Banking)";
225 | break;
226 | case 0x01:
227 | romSizeName = "64 KiB (4 ROM banks)";
228 | break;
229 | case 0x02:
230 | romSizeName = "128 KiB (8 ROM banks)";
231 | break;
232 | case 0x03:
233 | romSizeName = "256 KiB (16 ROM banks)";
234 | break;
235 | case 0x04:
236 | romSizeName = "512 KiB (32 ROM banks)";
237 | break;
238 | case 0x05:
239 | romSizeName = "1 MiB (64 ROM banks)";
240 | break;
241 | case 0x06:
242 | romSizeName = "2 MiB (128 ROM banks)";
243 | break;
244 | case 0x07:
245 | romSizeName = "4 MiB (256 ROM banks)";
246 | break;
247 | case 0x08:
248 | romSizeName = "8 MiB (512 ROM banks)";
249 | break;
250 | default:
251 | romSizeName = "Unknown ROM size";
252 | break;
253 | }
254 | return "ROM Size: " + romSizeName;
255 | }
256 |
257 | public string GetRamSize() {
258 | byte ramSizeByte = rom[0x0149];
259 | string ramSizeName;
260 |
261 | switch (ramSizeByte) {
262 | case 0x00:
263 | ramSizeName = "No RAM";
264 | break;
265 | case 0x01:
266 | ramSizeName = "Unused (2 KB?)";
267 | break;
268 | case 0x02:
269 | ramSizeName = "8 KiB (1 bank)";
270 | break;
271 | case 0x03:
272 | ramSizeName = "32 KiB (4 banks of 8 KiB each)";
273 | break;
274 | case 0x04:
275 | ramSizeName = "128 KiB (16 banks of 8 KiB each)";
276 | break;
277 | case 0x05:
278 | ramSizeName = "64 KiB (8 banks of 8 KiB each)";
279 | break;
280 | default:
281 | ramSizeName = "Unknown RAM size";
282 | break;
283 | }
284 | return "RAM Size: " + ramSizeName;
285 | }
286 |
287 | public string GetChecksum() {
288 | return "Checksum: " + rom[0x014D].ToString("X2");
289 | }
290 |
291 | public byte Read(ushort address) {
292 | if (address < 0x4000) {
293 | //Fixed ROM Bank 0 (common)
294 | return rom[address];
295 | } else if (address < 0x8000) {
296 | //Switchable ROM Bank (common)
297 | int bankOffset = (romBank % romBankCount) * 0x4000;
298 | return rom[bankOffset + (address - 0x4000)];
299 | } else if (address >= 0xA000 && address < 0xC000) {
300 | //RAM Access (common)
301 | if (ramEnabled) {
302 | int ramOffset = (ramBank % ramBankCount) * 0x2000;
303 | return ramBanks[ramOffset + (address - 0xA000)];
304 | }
305 | return 0xFF;
306 | }
307 | return 0xFF;
308 | }
309 |
310 | public void Write(ushort address, byte value) {
311 | if (address < 0x2000) {
312 | //Enable or disable RAM (common)
313 | ramEnabled = (value & 0x0F) == 0x0A;
314 | } else if (address < 0x4000) {
315 | //ROM Bank Switching
316 | if (mbcType == 1) {
317 | romBank = value & 0x1F;
318 | if (romBank == 0) romBank = 1;
319 | } else if (mbcType == 3) {
320 | romBank = value & 0x7F;
321 | if (romBank == 0) romBank = 1;
322 | } else if (mbcType == 5) {
323 | if (address < 0x3000) {
324 | romBank = (romBank & 0x100) | value;
325 | } else {
326 | romBank = (romBank & 0xFF) | ((value & 0x01) << 8);
327 | }
328 | }
329 | } else if (address < 0x6000) {
330 | //RAM Bank Switching
331 | if (mbcType == 1) {
332 | ramBank = value & 0x03;
333 | } else if (mbcType == 5 || mbcType == 3) {
334 | //ramBank = value & 0x03; MBC3 2 bits for no RTC
335 | ramBank = value & 0x0F;
336 | }
337 | //No banking mode for MBC1
338 | } else if (address >= 0xA000 && address < 0xC000) {
339 | //RAM Write (common)
340 | if (ramEnabled) {
341 | int ramOffset = (ramBank % ramBankCount) * 0x2000;
342 | ramBanks[ramOffset + (address - 0xA000)] = value;
343 | }
344 | }
345 | }
346 | }
347 |
--------------------------------------------------------------------------------
/src/MMU.cs:
--------------------------------------------------------------------------------
1 | public class MMU {
2 | private byte[] rom; //ROM
3 | private byte[] wram; //Work RAM
4 | private byte[] vram; //Video RAM
5 | private byte[] oam; //Object Attribute Memory
6 | private byte[] hram; //High RAM
7 | private byte[] io; //I/O Registers
8 | private byte[] bootRom; //Boot ROM
9 | private bool bootEnabled; //Boot ROM enabled flag
10 |
11 | private MBC mbc;
12 |
13 | private const int ROM_SIZE = 0x8000; //32KB
14 | private const int BOOT_ROM_SIZE = 0x0100; //256 bytes
15 | private const int WRAM_SIZE = 0x2000; //8KB
16 | private const int VRAM_SIZE = 0x2000; //8KB
17 | private const int OAM_SIZE = 0x00A0; //160 bytes
18 | private const int HRAM_SIZE = 0x007F; //127 bytes
19 | private const int IO_SIZE = 0x0080; //128 bytes
20 |
21 | public byte IE; //0xFFFF
22 | public byte IF; //0xFF0F
23 | public byte JOYP; //0xFF00
24 | public byte DIV; //0xFF04
25 | public byte TIMA; //0xFF05
26 | public byte TMA; //0xFF06
27 | public byte TAC; //0xFF07
28 | public byte LCDC; //0xFF40
29 | public byte STAT; //0xFF41
30 | public byte SCY; //0xFF42
31 | public byte SCX; //0xFF43
32 | public byte LY; //0xFF44
33 | public byte LYC; //0xFF54
34 | public byte BGP; //0xFF47
35 | public byte OBP0; //0xFF48
36 | public byte OBP1; //0xFF49
37 | public byte WY; //0xFF4A
38 | public byte WX; //0xFF4B
39 |
40 | public byte joypadState = 0xFF; //Raw inputs
41 |
42 | public byte[] ram; //64 KB RAM
43 | public bool mode;
44 |
45 | public MMU(byte[] gameRom, byte[] bootRomData, bool mode)
46 | {
47 | rom = gameRom;
48 | bootRom = bootRomData;
49 | wram = new byte[WRAM_SIZE];
50 | vram = new byte[VRAM_SIZE];
51 | oam = new byte[OAM_SIZE];
52 | hram = new byte[HRAM_SIZE];
53 | io = new byte[IO_SIZE];
54 | bootEnabled = true;
55 |
56 | ram = new byte[65536];
57 | this.mode = mode;
58 |
59 | mbc = new MBC(rom);
60 |
61 | Console.WriteLine("MMU init");
62 | }
63 |
64 | public void Save(string path) {
65 | if (mbc.mbcType != 0) {
66 | Console.WriteLine("Writing to save to: " + path);
67 | File.WriteAllBytes(path, mbc.ramBanks);
68 | }
69 | }
70 |
71 | public void Load(string path) {
72 | if (File.Exists(path) && mbc.mbcType != 0) {
73 | Console.WriteLine("Loading save: " + path);
74 | mbc.ramBanks = File.ReadAllBytes(path);
75 | } else if (mbc.mbcType != 0) {
76 | Console.WriteLine("Save not found at: " + path);
77 | }
78 | }
79 |
80 | public string HeaderInfo() {
81 | return mbc.GetTitle() + "\n" + mbc.GetCartridgeType() + "\n" + mbc.GetRomSize() + "\n" + mbc.GetRamSize() + "\n" + mbc.GetChecksum();
82 | }
83 |
84 | public byte Read(ushort address) {
85 | if (mode == false) {
86 | return Read1(address);
87 | } else if (mode == true) {
88 | return Read2(address);
89 | }
90 | return 0xFF;
91 | }
92 | public void Write(ushort address, byte value) {
93 | if (mode == false) {
94 | Write1(address, value);
95 | } else if (mode == true) {
96 | Write2(address, value);
97 | }
98 | }
99 |
100 | public void Write2(ushort address, byte value) {
101 | ram[address] = value;
102 | }
103 | public byte Read2(ushort address) {
104 | return ram[address];
105 | }
106 |
107 | public byte Read1(ushort address) {
108 | if (bootEnabled && address < BOOT_ROM_SIZE) {
109 | return bootRom[address]; //Boot ROM
110 | }
111 |
112 | if (address < 0x8000 || (address >= 0xA000 && address < 0xC000)) {
113 | return mbc.Read(address); //Delegate to MBC
114 | }
115 |
116 | switch (address) {
117 | case 0xFF00:
118 | //if action or direction buttons are selected
119 | if ((JOYP & 0x10) == 0) { //Action buttons selected
120 | return (byte)((joypadState >> 4) | 0x20);
121 | }
122 | else if ((JOYP & 0x20) == 0) { //Direction buttons selected
123 | return (byte)((joypadState & 0x0F) | 0x10);
124 | }
125 | return (byte)(JOYP | 0xFF);
126 | case 0xFF04:
127 | return DIV;
128 | case 0xFF40:
129 | return LCDC;
130 | case 0xFF41:
131 | return STAT;
132 | case 0xFF42:
133 | return SCY;
134 | case 0xFF43:
135 | return SCX;
136 | case 0xFF44:
137 | return LY;
138 | case 0xFF45:
139 | return LYC;
140 | case 0xFF47:
141 | return BGP;
142 | case 0xFF48:
143 | return OBP0;
144 | case 0xFF49:
145 | return OBP1;
146 | case 0xFF4A:
147 | return WY;
148 | case 0xFF4B:
149 | return WX;
150 | case 0xFF0F:
151 | return IF;
152 | case 0xFFFF:
153 | return IE;
154 | }
155 |
156 | /*
157 | if (address < ROM_SIZE) {
158 | return rom[address];
159 | }
160 | */
161 | if (address >= 0xC000 && address < 0xE000) {
162 | return wram[address - 0xC000];
163 | }
164 | else if (address >= 0x8000 && address < 0xA000) {
165 | return vram[address - 0x8000];
166 | }
167 | else if (address >= 0xFE00 && address < 0xFEA0) {
168 | return oam[address - 0xFE00];
169 | }
170 | else if (address >= 0xFF80 && address < 0xFFFF) {
171 | return hram[address - 0xFF80];
172 | }
173 | else if (address >= 0xFF00 && address < 0xFF80) {
174 | return io[address - 0xFF00]; //Mostly as a fallback
175 | }
176 | return 0xFF; //Default values of unknown reads
177 | }
178 |
179 | public void Write1(ushort address, byte value) {
180 | if (address == 0xFF50) {
181 | //Disable boot ROM if written to
182 | bootEnabled = false;
183 | return;
184 | }
185 |
186 | if (address < 0x8000 || (address >= 0xA000 && address < 0xC000)) {
187 | mbc.Write(address, value); //Delegate to MBC
188 | return;
189 | }
190 |
191 | switch (address) {
192 | case 0xFF00:
193 | JOYP = (byte)(value & 0x30);
194 | break;
195 | case 0xFF04:
196 | DIV = value;
197 | break;
198 | case 0xFF40:
199 | LCDC = value;
200 | if ((value & 0x80) == 0) {
201 | STAT &= 0x7C;
202 | LY = 0x00;
203 | }
204 | break;
205 | case 0xFF46: //DMA
206 | ushort sourceAddress = (ushort)(value << 8);
207 | for (ushort i = 0; i < 0xA0; i++)
208 | {
209 | Write((ushort)(0xFE00 + i), Read((ushort)(sourceAddress + i)));
210 | }
211 | break;
212 | case 0xFF41:
213 | STAT = value;
214 | break;
215 | case 0xFF42:
216 | SCY = value;
217 | break;
218 | case 0xFF43:
219 | SCX = value;
220 | break;
221 | case 0xFF44:
222 | LY = value;
223 | break;
224 | case 0xFF45:
225 | LYC = value;
226 | break;
227 | case 0xFF47:
228 | BGP = value;
229 | break;
230 | case 0xFF48:
231 | OBP0 = value;
232 | break;
233 | case 0xFF49:
234 | OBP1 = value;
235 | break;
236 | case 0xFF4A:
237 | WY = value;
238 | break;
239 | case 0xFF4B:
240 | WX = value;
241 | break;
242 | case 0xFF0F:
243 | IF = value;
244 | break;
245 | case 0xFFFF:
246 | IE = value;
247 | break;
248 | }
249 |
250 | if (address >= 0xC000 && address < 0xE000) {
251 | wram[address - 0xC000] = value;
252 | }
253 | else if (address >= 0x8000 && address < 0xA000) {
254 | vram[address - 0x8000] = value;
255 | }
256 | else if (address >= 0xFE00 && address < 0xFEA0) {
257 | oam[address - 0xFE00] = value;
258 | }
259 | else if (address >= 0xFF80 && address < 0xFFFF) {
260 | hram[address - 0xFF80] = value;
261 | }
262 | else if (address >= 0xFF00 && address < 0xFF80) {
263 | io[address - 0xFF00] = value; //Mostly as a fallback
264 | }
265 | else if (address == 0xFFFF) {
266 | //IE accounted for in switch statement, else if here to prevement "OUT OF RANGE" message
267 | }
268 | else {
269 | Console.WriteLine(address.ToString("X4") + " - OUT OF RANGE WRITE");
270 | }
271 | }
272 | }
--------------------------------------------------------------------------------
/src/PPU.cs:
--------------------------------------------------------------------------------
1 | using Raylib_cs;
2 |
3 | public class PPU {
4 | private const int HBLANK = 0;
5 | private const int VBLANK = 1;
6 | private const int OAM = 2;
7 | private const int VRAM = 3;
8 |
9 | private const int SCANLINE_CYCLES = 456;
10 |
11 | private int mode;
12 | private int cycles;
13 |
14 | private MMU mmu;
15 |
16 | private Color[] frameBuffer;
17 | private Color[] scanlineBuffer = new Color[ScreenWidth];
18 | private const int ScreenWidth = 160;
19 | private const int ScreenHeight = 144;
20 |
21 | Image screenImage;
22 | Texture2D screenTexture;
23 |
24 | private bool vblankTriggered = false;
25 | private int windowLineCounter = 0;
26 | private bool lcdPreviouslyOff = false;
27 |
28 | public PPU(MMU mmu, Image screenImage, Texture2D screenTexture) {
29 | this.mmu = mmu;
30 | mode = OAM;
31 | cycles = 0;
32 | frameBuffer = new Color[ScreenWidth * ScreenHeight];
33 | this.screenImage = screenImage;
34 | this.screenTexture = screenTexture;
35 |
36 | Console.WriteLine("PPU init");
37 | }
38 |
39 | public void Step(int elapsedCycles) {
40 | cycles += elapsedCycles;
41 |
42 | if ((mmu.LCDC & 0x80) != 0 && lcdPreviouslyOff) {
43 | //mode = OAM;
44 | cycles = 0;
45 | lcdPreviouslyOff = false;
46 | } else if ((mmu.LCDC & 0x80) == 0) {
47 | lcdPreviouslyOff = true;
48 | //mmu.LY = 0;
49 | //mmu.STAT &= 0xFB;
50 | mode = HBLANK;
51 | return;
52 | }
53 |
54 | switch (mode) {
55 | case OAM:
56 | if (cycles >= 80) {
57 | cycles -= 80;
58 | mode = VRAM;
59 |
60 | mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode);
61 | }
62 | break;
63 |
64 | case VRAM:
65 | if (cycles >= 172) {
66 | cycles -= 172;
67 | mode = HBLANK;
68 |
69 | mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode);
70 |
71 | if (mmu.LY < 144) {
72 | RenderScanline();
73 | }
74 |
75 | if ((mmu.STAT & 0x08) != 0) {
76 | mmu.IF = (byte)(mmu.IF | 0x02);
77 | }
78 | }
79 | break;
80 |
81 | case HBLANK:
82 | if (cycles >= 204) {
83 | cycles -= 204;
84 | mmu.LY++;
85 |
86 | setLYCFlag();
87 |
88 | if (mmu.LY == 144) {
89 | mode = VBLANK;
90 | vblankTriggered = false;
91 | DrawFrame(ref screenImage);
92 | if ((mmu.STAT & 0x10) != 0) {
93 | mmu.IF = (byte)(mmu.IF | 0x02);
94 | }
95 | } else {
96 | if ((mmu.STAT & 0x20) != 0) {
97 | mmu.IF = (byte)(mmu.IF | 0x02);
98 | }
99 | mode = OAM;
100 | }
101 | mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode);
102 | }
103 | break;
104 |
105 | case VBLANK:
106 | if (!vblankTriggered && mmu.LY == 144)
107 | {
108 | if ((mmu.LCDC & 0x80) != 0) {
109 | mmu.IF = (byte)(mmu.IF | 0x01);
110 | vblankTriggered = true;
111 | }
112 | }
113 |
114 | if (cycles >= SCANLINE_CYCLES) {
115 | cycles -= SCANLINE_CYCLES;
116 | mmu.LY++;
117 |
118 | setLYCFlag();
119 |
120 | if (mmu.LY == 153) {
121 | mmu.LY = 0;
122 | mode = OAM;
123 | vblankTriggered = false;
124 |
125 | mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode);
126 |
127 | if ((mmu.STAT & 0x20) != 0) {
128 | mmu.IF = (byte)(mmu.IF | 0x02);
129 | }
130 | }
131 | }
132 | break;
133 | }
134 | }
135 |
136 | private void RenderScanline() {
137 | RenderBackground();
138 |
139 | RenderWindow();
140 |
141 | RenderSprites();
142 |
143 | Array.Copy(scanlineBuffer, 0, frameBuffer, mmu.LY * ScreenWidth, ScreenWidth);
144 | }
145 |
146 | private void RenderBackground() {
147 | int currentScanline = mmu.LY;
148 | int scrollX = mmu.SCX;
149 | int scrollY = mmu.SCY;
150 |
151 | if ((mmu.LCDC & 0x01) == 0) return;
152 |
153 | for (int x = 0; x < ScreenWidth; x++) {
154 | int bgX = (scrollX + x) % 256;
155 | int bgY = (scrollY + currentScanline) % 256;
156 |
157 | int tileX = bgX / 8;
158 | int tileY = bgY / 8;
159 |
160 | int tileIndex = tileY * 32 + tileX;
161 |
162 | ushort tileMapBase = (mmu.LCDC & 0x08) != 0 ? (ushort)0x9C00 : (ushort)0x9800;
163 | byte tileNumber = mmu.Read((ushort)(tileMapBase + tileIndex));
164 |
165 | ushort tileDataBase = (mmu.LCDC & 0x10) != 0 || tileNumber >= 128 ? (ushort)0x8000 : (ushort)0x9000;
166 | ushort tileAddress = (ushort)(tileDataBase + tileNumber * 16);
167 |
168 | int lineInTile = bgY % 8;
169 |
170 | byte tileLow = mmu.Read((ushort)(tileAddress + lineInTile * 2));
171 | byte tileHigh = mmu.Read((ushort)(tileAddress + lineInTile * 2 + 1));
172 |
173 | int bitIndex = 7 - (bgX % 8);
174 | int colorBit = ((tileHigh >> bitIndex) & 0b1) << 1 | ((tileLow >> bitIndex) & 0b1);
175 |
176 | byte bgp = mmu.BGP;
177 | int paletteShift = colorBit * 2;
178 | int paletteColor = (bgp >> paletteShift) & 0b11;
179 |
180 | scanlineBuffer[x] = ConvertPaletteColor(paletteColor);
181 | }
182 | }
183 |
184 |
185 | private void RenderWindow()
186 | {
187 | if ((mmu.LCDC & (1 << 5)) == 0) return;
188 |
189 | int currentScanline = mmu.LY;
190 | int windowX = mmu.WX - 7;
191 | int windowY = mmu.WY;
192 |
193 | if (currentScanline < windowY) return;
194 |
195 | if (currentScanline == windowY) {
196 | windowLineCounter = 0;
197 | }
198 |
199 | ushort tileMapBase = (mmu.LCDC & (1 << 6)) != 0 ? (ushort)0x9C00 : (ushort)0x9800;
200 |
201 | bool windowRendered = false;
202 |
203 | for (int x = 0; x < ScreenWidth; x++)
204 | {
205 | if (x < windowX) continue;
206 |
207 | windowRendered = true;
208 |
209 | int windowColumn = x - windowX;
210 |
211 | int tileX = windowColumn / 8;
212 | int tileY = windowLineCounter / 8;
213 |
214 | int tileIndex = tileY * 32 + tileX;
215 |
216 | byte tileNumber = mmu.Read((ushort)(tileMapBase + tileIndex));
217 |
218 | ushort tileDataBase = (mmu.LCDC & (1 << 4)) != 0 || tileNumber >= 128 ? (ushort)0x8000 : (ushort)0x9000;
219 | ushort tileAddress = (ushort)(tileDataBase + tileNumber * 16);
220 |
221 | int lineInTile = windowLineCounter % 8;
222 |
223 | byte tileLow = mmu.Read((ushort)(tileAddress + lineInTile * 2));
224 | byte tileHigh = mmu.Read((ushort)(tileAddress + lineInTile * 2 + 1));
225 |
226 | int bitIndex = 7 - (windowColumn % 8);
227 | int colorBit = ((tileHigh >> bitIndex) & 1) << 1 | ((tileLow >> bitIndex) & 1);
228 |
229 | byte bgp = mmu.BGP;
230 | int paletteShift = colorBit * 2;
231 | int paletteColor = (bgp >> paletteShift) & 0b11;
232 | //return ConvertPaletteColor(paletteColor);
233 |
234 | scanlineBuffer[x] = ConvertPaletteColor(paletteColor);
235 | }
236 |
237 | if (windowRendered) {
238 | windowLineCounter++;
239 | }
240 | }
241 |
242 | private void RenderSprites() {
243 | int currentScanline = mmu.LY;
244 | if ((mmu.LCDC & (1 << 1)) == 0) return;
245 |
246 | int renderedSprites = 0;
247 | int[] pixelOwner = new int[ScreenWidth];
248 | Array.Fill(pixelOwner, -1);
249 |
250 | for (int i = 0; i < 40; i++) {
251 | if (renderedSprites >= 10) break;
252 |
253 | int spriteIndex = i * 4;
254 | int yPos = mmu.Read((ushort)(0xFE00 + spriteIndex)) - 16;
255 | int xPos = mmu.Read((ushort)(0xFE00 + spriteIndex + 1)) - 8;
256 | byte tileIndex = mmu.Read((ushort)(0xFE00 + spriteIndex + 2));
257 | byte attributes = mmu.Read((ushort)(0xFE00 + spriteIndex + 3));
258 |
259 | int spriteHeight = (mmu.LCDC & (1 << 2)) != 0 ? 16 : 8;
260 | if (currentScanline < yPos || currentScanline >= yPos + spriteHeight) {
261 | continue;
262 | }
263 |
264 | int lineInSprite = currentScanline - yPos;
265 | if ((attributes & (1 << 6)) != 0) {
266 | lineInSprite = spriteHeight - 1 - lineInSprite;
267 | }
268 |
269 | if (spriteHeight == 16) {
270 | tileIndex &= 0xFE;
271 | if (lineInSprite >= 8) {
272 | tileIndex += 1;
273 | lineInSprite -= 8;
274 | }
275 | }
276 |
277 | ushort tileAddress = (ushort)(0x8000 + tileIndex * 16 + lineInSprite * 2);
278 | byte tileLow = mmu.Read(tileAddress);
279 | byte tileHigh = mmu.Read((ushort)(tileAddress + 1));
280 |
281 | for (int x = 0; x < 8; x++) {
282 | int bitIndex = (attributes & (1 << 5)) != 0 ? x : 7 - x;
283 | int colorBit = ((tileHigh >> bitIndex) & 1) << 1 | ((tileLow >> bitIndex) & 1);
284 |
285 | if (colorBit == 0) continue;
286 |
287 | int screenX = xPos + x;
288 | if (screenX < 0 || screenX >= ScreenWidth) continue;
289 |
290 | bool bgOverObj = (attributes & (1 << 7)) != 0;
291 | if (bgOverObj && !scanlineBuffer[screenX].Equals(ConvertPaletteColor(0))) {
292 | continue;
293 | }
294 |
295 | if (pixelOwner[screenX] == -1 || xPos < pixelOwner[screenX]) {
296 | pixelOwner[screenX] = xPos;
297 | bool isSpritePalette1 = (attributes & (1 << 4)) != 0;
298 |
299 | byte spritePalette = isSpritePalette1 ? mmu.OBP1 : mmu.OBP0;
300 | int paletteShift = colorBit * 2;
301 | int paletteColor = (spritePalette >> paletteShift) & 0b11;
302 |
303 | scanlineBuffer[screenX] = ConvertPaletteColor(paletteColor);
304 | }
305 | }
306 | renderedSprites++;
307 | }
308 | }
309 |
310 | private Color ConvertPaletteColor(int paletteColor) {
311 | return Helper.palettes[Helper.paletteName][paletteColor];
312 | }
313 |
314 | private void setLYCFlag() {
315 | if (mmu.LY == mmu.LYC) {
316 | //Console.WriteLine(mmu.LY);
317 | //Set the LYC=LY flag
318 | mmu.STAT = (byte)(mmu.STAT | 0x04);
319 | if ((mmu.STAT & 0x40) != 0) {//If the LYC=LY interrupt is enabled set the flag in the IF registers
320 | mmu.IF = (byte)(mmu.IF | 0x02);
321 | }
322 | } else {
323 | mmu.STAT = (byte)(mmu.STAT & 0xFB); //Clear the LYC=LY flag
324 | }
325 | }
326 |
327 | public void DrawFrame(ref Image image) {
328 | for (int y = 0; y < ScreenHeight; y++) {
329 | for (int x = 0; x < ScreenWidth; x++) {
330 | Color color = frameBuffer[y * ScreenWidth + x];
331 | //Raylib.DrawRectangle(x*2, y*2, 1*2, 1*2, color);
332 | Raylib.ImageDrawPixel(ref image, x, y, color);
333 | }
334 | }
335 |
336 | unsafe {
337 | Raylib.UpdateTexture(screenTexture, screenImage.Data);
338 | }
339 | Raylib.DrawTextureEx(screenTexture, new System.Numerics.Vector2(0, 0), 0.0f, Helper.scale, Color.White);
340 | }
341 | }
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
1 | class Program {
2 | public static void Main(string[] args) {
3 | Console.WriteLine("CODE-DMG");
4 |
5 | Helper.Flags(args);
6 |
7 | if (Helper.mode == 0) {
8 | DMG dmg = new DMG(Helper.rom, Helper.bootrom);
9 |
10 | dmg.Run();
11 | } else if (Helper.mode == 1) {
12 | JSONTest jsonTest = new JSONTest();
13 |
14 | jsonTest.Run(Helper.jsonPath);
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Test.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | class JSONTest {
4 | public class ProcessorState {
5 | public int pc { get; set; }
6 | public int sp { get; set; }
7 | public int a { get; set; }
8 | public int b { get; set; }
9 | public int c { get; set; }
10 | public int d { get; set; }
11 | public int e { get; set; }
12 | public int f { get; set; }
13 | public int h { get; set; }
14 | public int l { get; set; }
15 | public int ime { get; set; }
16 | public int ei { get; set; }
17 |
18 | public List> ram { get; set; } = new List>();
19 | }
20 | public class Test {
21 | public string name { get; set; } = "";
22 | public ProcessorState initial { get; set; } = new ProcessorState();
23 | public ProcessorState final { get; set; } = new ProcessorState();
24 | }
25 |
26 | public MMU mmu;
27 | public CPU cpu;
28 |
29 | public JSONTest() {
30 | mmu = new MMU(new byte[32768], new byte[1], true);
31 | cpu = new CPU(mmu);
32 | }
33 |
34 | public void Run(string jsonPath) {
35 | string filePath = jsonPath;
36 |
37 | var json = File.ReadAllText(filePath);
38 | var tests = JsonConvert.DeserializeObject>(json) ?? new List();
39 | foreach (var test in tests) {
40 | Console.WriteLine(test.name);
41 |
42 | cpu.PC = (ushort)test.initial.pc;
43 | cpu.SP = (ushort)test.initial.sp;
44 | cpu.A = (byte)test.initial.a;
45 | cpu.B = (byte)test.initial.b;
46 | cpu.C = (byte)test.initial.c;
47 | cpu.D = (byte)test.initial.d;
48 | cpu.E = (byte)test.initial.e;
49 | cpu.F = (byte)test.initial.f;
50 | cpu.UpdateFlagsFromF();
51 | cpu.H = (byte)test.initial.h;
52 | cpu.L = (byte)test.initial.l;
53 |
54 | string initCPU16Reg = $"PC: {cpu.PC}, SP: {cpu.SP}";
55 | string initCPUReg = $"A: {cpu.A}, B: {cpu.B}, C: {cpu.C}, D: {cpu.D}, E: {cpu.E}, F: {cpu.F}, H: {cpu.H}, L: {cpu.L}";
56 | string initRAM = "";
57 |
58 | foreach (var entry in test.initial.ram) {
59 | mmu.Write((ushort)entry[0], (byte)entry[1]);
60 | initRAM += $"Address: {entry[0]}, Value: {entry[1]}\n";
61 | }
62 |
63 | cpu.ExecuteInstruction();
64 |
65 | string finalCPU16Reg = $"PC: {cpu.PC}, SP: {cpu.SP}";
66 | string finalCPUReg = $"A: {cpu.A}, B: {cpu.B}, C: {cpu.C}, D: {cpu.D}, E: {cpu.E}, F: {cpu.F}, H: {cpu.H}, L: {cpu.L}";
67 | string finalRAM = "";
68 |
69 | bool isMismatch = false;
70 | if (cpu.A != test.final.a) { Console.WriteLine($"Mismatch in A: Expected {test.final.a}, Found {cpu.A}"); isMismatch = true; }
71 | if (cpu.B != test.final.b) { Console.WriteLine($"Mismatch in B: Expected {test.final.b}, Found {cpu.B}"); isMismatch = true; }
72 | if (cpu.C != test.final.c) { Console.WriteLine($"Mismatch in C: Expected {test.final.c}, Found {cpu.C}"); isMismatch = true; }
73 | if (cpu.D != test.final.d) { Console.WriteLine($"Mismatch in D: Expected {test.final.d}, Found {cpu.D}"); isMismatch = true; }
74 | if (cpu.E != test.final.e) { Console.WriteLine($"Mismatch in E: Expected {test.final.e}, Found {cpu.E}"); isMismatch = true; }
75 | if (cpu.F != test.final.f) { Console.WriteLine($"Mismatch in F: Expected {test.final.f}, Found {cpu.F}"); isMismatch = true; }
76 | if (cpu.H != test.final.h) { Console.WriteLine($"Mismatch in H: Expected {test.final.h}, Found {cpu.H}"); isMismatch = true; }
77 | if (cpu.L != test.final.l) { Console.WriteLine($"Mismatch in L: Expected {test.final.l}, Found {cpu.L}"); isMismatch = true; }
78 | if (cpu.PC != test.final.pc) { Console.WriteLine($"Mismatch in Pc: Expected {test.final.pc}, Found {cpu.PC}"); isMismatch = true; }
79 | if (cpu.SP != test.final.sp) { Console.WriteLine($"Mismatch in Sp: Expected {test.final.sp}, Found {cpu.SP}"); isMismatch = true; }
80 |
81 | foreach (var entry in test.final.ram) {
82 | int valueInMMU = mmu.Read((ushort)entry[0]);
83 | finalRAM += $"Address: {entry[0]}, Value: {entry[1]}\n";
84 |
85 | if (valueInMMU != entry[1]) {
86 | Console.WriteLine($"Mismatch in MMU at Address {entry[0]}: Expected {entry[1]}, Found {valueInMMU}");
87 | isMismatch = true;
88 | }
89 | }
90 |
91 | if (isMismatch) {
92 | //To compare init and final values to JSON for full detail if init properly or anyother
93 | Console.WriteLine("\nCPU and RAM init:");
94 | Console.WriteLine(initCPU16Reg);
95 | Console.WriteLine(initCPUReg);
96 | Console.WriteLine(initRAM);
97 |
98 | Console.WriteLine("CPU and RAM final:");
99 | Console.WriteLine(finalCPU16Reg);
100 | Console.WriteLine(finalCPUReg);
101 | Console.WriteLine(finalRAM);
102 |
103 | Console.WriteLine("JSON Test:");
104 | string testJson = JsonConvert.SerializeObject(test, Formatting.Indented);
105 | Console.WriteLine(testJson);
106 |
107 | Environment.Exit(1);
108 | }
109 | }
110 |
111 | Console.WriteLine("All tests passed!");
112 | }
113 | }
--------------------------------------------------------------------------------
/src/Timer.cs:
--------------------------------------------------------------------------------
1 | class Timer {
2 | private MMU mmu;
3 |
4 | int cycles;
5 |
6 | public Timer(MMU mmu) {
7 | this.mmu = mmu;
8 | cycles = 0;
9 | }
10 |
11 | public void Step(int elapsedCycles) {
12 | cycles += elapsedCycles;
13 |
14 | if (cycles >= 256) {
15 | cycles -= 256;
16 | mmu.DIV++;
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/test/README.txt:
--------------------------------------------------------------------------------
1 | JSON test goes here.
2 | test/v1/XX.json
3 |
4 | JSON test repo used from SingleStepTest on GitHub for SM83: https://github.com/SingleStepTests/sm83
--------------------------------------------------------------------------------