GameBoy Emulator 1
Game Boy emulator core and tooling
Loading...
Searching...
No Matches
main.cpp
1#include "../include/cartridge.hpp"
2#include "../include/success.hpp"
3#include "../include/readROM.hpp"
4#include "../include/ProcessingUnit.hpp"
5#include "../include/interrupt_controller.hpp"
6#include "../include/timer.hpp"
7#include "../include/mmu.hpp"
8#include "../include/ppu.hpp"
9#include <iostream>
10#include <cstring>
11#include <stdexcept>
12#include <iterator>
13#include <SFML/Graphics.hpp>
14
15static void update_joypad(MMU& mmu, const JoypadState& joy) {
16 u8 action = 0x0F;
17 u8 direction = 0x0F;
18
19 if (joy.right) direction &= ~0x01;
20 if (joy.left) direction &= ~0x02;
21 if (joy.up) direction &= ~0x04;
22 if (joy.down) direction &= ~0x08;
23
24 if (joy.a) action &= ~0x01;
25 if (joy.b) action &= ~0x02;
26 if (joy.select) action &= ~0x04;
27 if (joy.start) action &= ~0x08;
28
29 mmu.set_joypad_state(action, direction);
30}
31
32int main(const int argc, char **argv)
33{
34 bool debug_mode = false;
35 bool fullscreen_mode = false;
36 std::string rom_path;
37 for (int i = 1; i < argc; ++i) {
38 if (std::strcmp(argv[i], "--debug") == 0 || std::strcmp(argv[i], "-d") == 0) {
39 debug_mode = true;
40 } else if (std::strcmp(argv[i], "--fullscreen") == 0 || std::strcmp(argv[i], "-f") == 0) {
41 fullscreen_mode = true;
42 } else {
43 rom_path = argv[i];
44 }
45 }
46
47 if (rom_path.empty()) {
48 std::cerr << "Usage: gb_emu <rom_file> [--debug]\n";
49 return 1;
50 }
51
52 try {
53 const std::vector<u8> rom_data = load_rom(rom_path.c_str());
54 Cartridge cartridge(rom_data);
56 Timer timer(ic);
57 PPU ppu(ic);
58 MMU mmu(cartridge, ppu, timer, ic);
59
61 ppu.set_cpu(&cpu);
62 ppu.set_mmu(&mmu);
63
64 // Populate ROM Info for the debugger panel
65 rom_header header{};
66 std::memcpy(&header, &rom_data[0x100], sizeof(rom_header));
67 char title[17]{};
68 std::memcpy(title, header.title, 16);
69
70 PPU::RomInfo info{};
71 info.title = title;
72 info.type = header.type;
73 info.rom_size = header.rom_size;
74 info.ram_size = header.ram_size;
75 info.rom_bytes = rom_data;
76 ppu.set_rom_info(info);
77
78 ppu.init_window(debug_mode, title, fullscreen_mode);
79 std::cout << "Running in " << (debug_mode ? "Debug/Feature" : "Simple") << " Mode...\n";
80
81 JoypadState joypad;
82 sf::Clock clock;
83 u64 cycles = 0;
84 int frame_count = 0;
85
86 while (ppu.isOpen()) {
87 ppu.handleEvents(joypad);
88 update_joypad(mmu, joypad);
89
90 if (ppu.isResetRequested()) {
91 mmu.reset();
92 cpu.reset();
93 ppu.clearResetRequest();
94 }
95
96 const float dt = clock.restart().asSeconds();
97
98 if (!ppu.isPaused()) {
99 const int loops = ppu.isTurbo() ? 2 : 1;
100 for (int loop = 0; loop < loops; ++loop) {
101 int frame_cycles = 0;
102 static int instr_count = 0;
103 static bool trace_enabled = (std::getenv("EMU_TRACE") != nullptr);
104 static std::ofstream trace_file;
105 if (trace_enabled && !trace_file.is_open()) {
106 trace_file.open("cpu_trace.log");
107 }
108 while (!ppu.is_frame_ready() && ((ppu.read(0xFF40) & 0x80) || frame_cycles < 70224)) {
109 const u16 pc_before = cpu.get_pc();
110 const u8 opcode = mmu.read(pc_before);
111
112 if (trace_enabled && instr_count % 10000 == 0 && instr_count < 1000000) {
113 trace_file << "Instr: " << std::dec << instr_count
114 << " | PC: 0x" << std::hex << pc_before
115 << " | Op: 0x" << (int)opcode
116 << " | AF: 0x" << cpu.get_af()
117 << " | BC: 0x" << cpu.get_bc()
118 << " | DE: 0x" << cpu.get_de()
119 << " | HL: 0x" << cpu.get_hl()
120 << " | SP: 0x" << cpu.get_sp()
121 << " | LY: 0x" << (int)ppu.read(0xFF44)
122 << " | Bank: " << (int)mmu.get_current_rom_bank()
123 << "\n";
124 trace_file.flush();
125 }
126 instr_count++;
127
128 int used = cpu.step(mmu);
129 cycles += static_cast<u64>(used);
130 frame_cycles += used;
131 ppu.step(used);
132 mmu.step_timer(used);
133
134 ppu.recordOpcode(pc_before, opcode);
135 ppu.checkBreakpoint(pc_before);
136
137 if (ppu.isPaused()) {
138 break;
139 }
140 }
141 if (ppu.is_frame_ready()) {
142 ppu.clear_frame_ready();
143 }
144 frame_count++;
145 }
146 char* frame_limit_env = std::getenv("EMU_FRAME_LIMIT");
147 int limit = frame_limit_env ? std::atoi(frame_limit_env) : -1;
148 if (limit > 0 && frame_count >= limit) {
149 std::cout << "Reached frame limit of " << limit << ", exiting cleanly.\n";
150 break;
151 }
152 } else {
153 if (ppu.isStepRequested()) {
154 const u16 pc_before = cpu.get_pc();
155 const u8 opcode = mmu.read(pc_before);
156
157 int used = cpu.step(mmu);
158 cycles += static_cast<u64>(used);
159 ppu.step(used);
160 mmu.step_timer(used);
161
162 ppu.recordOpcode(pc_before, opcode);
163 ppu.checkBreakpoint(pc_before);
164
165 ppu.clearStepRequest();
166 }
167 }
168
169 ppu.update(dt, cycles);
170 ppu.render();
171
172 }
173 if (std::getenv("EMU_SAVE_PNG") != nullptr) {
174 sf::Image img({160, 144}, reinterpret_cast<const uint8_t*>(ppu.get_framebuffer()));
175 (void)img.saveToFile("framebuffer.png");
176 std::cout << "Saved framebuffer.png\n";
177 }
178
179 std::cout << std::dec << "\n" << cycles << " cycles executed" << std::endl;
180 } catch (const std::exception &e) {
181 std::cerr << "Error: " << e.what() << std::endl;
182 return 1;
183 }
184
185 return 0;
186}
Definition mmu.hpp:12
Definition ppu.hpp:16
Definition timer.hpp:7