1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
#include "gb.h"
#include "ppu.h"
#include "timer.h"
#include "cpu.h"
#include <string.h>
#include <stdio.h>
void gb_request_interrupt(GB *gb, u8 flag) {
gb->iff |= flag;
}
// ---------- Joypad ----------
static u8 joyp_read(GB *gb) {
u8 r = 0xCF | gb->joyp_sel;
// sel bit4=directions, bit5=buttons ; 0 = selected. Pressed = 0.
if (!(gb->joyp_sel & 0x10)) { // directions
if (gb->buttons & BTN_RIGHT) r &= ~0x01;
if (gb->buttons & BTN_LEFT) r &= ~0x02;
if (gb->buttons & BTN_UP) r &= ~0x04;
if (gb->buttons & BTN_DOWN) r &= ~0x08;
}
if (!(gb->joyp_sel & 0x20)) { // buttons
if (gb->buttons & BTN_A) r &= ~0x01;
if (gb->buttons & BTN_B) r &= ~0x02;
if (gb->buttons & BTN_SELECT) r &= ~0x04;
if (gb->buttons & BTN_START) r &= ~0x08;
}
return r;
}
// ---------- OAM DMA ----------
static void oam_dma(GB *gb, u8 val) {
gb->dma = val;
u16 src = val << 8;
for (int i = 0; i < 0xA0; i++)
gb->ppu.oam[i] = gb_read(gb, src + i);
}
// ---------- HDMA (CGB) ----------
static void hdma_do_block(GB *gb) {
for (int i = 0; i < 0x10; i++) {
u8 v = gb_read(gb, gb->hdma_src++);
ppu_write(gb, 0x8000 + (gb->hdma_dst++ & 0x1FFF), v);
}
}
// ---------- Bus ----------
u8 gb_read(GB *gb, u16 addr) {
if (addr < 0x8000) return cart_read(&gb->cart, addr);
if (addr < 0xA000) return ppu_read(gb, addr);
if (addr < 0xC000) return cart_read(&gb->cart, addr);
if (addr < 0xD000) return gb->wram[addr - 0xC000];
if (addr < 0xE000) {
int bank = gb->cgb_mode ? (gb->wram_bank ? gb->wram_bank : 1) : 1;
return gb->wram[bank * 0x1000 + (addr - 0xD000)];
}
if (addr < 0xFE00) return gb_read(gb, addr - 0x2000); // echo
if (addr < 0xFEA0) return ppu_read(gb, addr);
if (addr < 0xFF00) return 0xFF; // unusable
// IO
if (addr == 0xFF00) return joyp_read(gb);
if (addr == 0xFF01) return gb->sb;
if (addr == 0xFF02) return gb->sc | 0x7E;
if (addr >= 0xFF04 && addr <= 0xFF07) return timer_read(gb, addr);
if (addr == 0xFF0F) return gb->iff | 0xE0;
if (addr >= 0xFF10 && addr <= 0xFF3F) return 0xFF; // APU (stub)
if (addr >= 0xFF40 && addr <= 0xFF4B) {
if (addr == 0xFF46) return gb->dma;
return ppu_read_reg(gb, addr);
}
if (addr == 0xFF4D) return gb->cgb_mode ? (gb->key1 | 0x7E) : 0xFF;
if (addr == 0xFF4F) return gb->cgb_mode ? (gb->ppu.vram_bank | 0xFE) : 0xFF;
if (addr == 0xFF51 && gb->cgb_mode) return gb->hdma_src >> 8;
if (addr == 0xFF52 && gb->cgb_mode) return gb->hdma_src & 0xFF;
if (addr == 0xFF53 && gb->cgb_mode) return gb->hdma_dst >> 8;
if (addr == 0xFF54 && gb->cgb_mode) return gb->hdma_dst & 0xFF;
if (addr == 0xFF55 && gb->cgb_mode)
return gb->hdma_active ? gb->hdma_len : 0xFF;
if ((addr == 0xFF68 || addr == 0xFF69 || addr == 0xFF6A || addr == 0xFF6B)
&& gb->cgb_mode) return ppu_read_reg(gb, addr);
if (addr == 0xFF70) return gb->cgb_mode ? (gb->wram_bank | 0xF8) : 0xFF;
if (addr >= 0xFF80 && addr < 0xFFFF) return gb->hram[addr - 0xFF80];
if (addr == 0xFFFF) return gb->ie;
return 0xFF;
}
void gb_write(GB *gb, u16 addr, u8 val) {
if (addr < 0x8000) { cart_write(&gb->cart, addr, val); return; }
if (addr < 0xA000) { ppu_write(gb, addr, val); return; }
if (addr < 0xC000) { cart_write(&gb->cart, addr, val); return; }
if (addr < 0xD000) { gb->wram[addr - 0xC000] = val; return; }
if (addr < 0xE000) {
int bank = gb->cgb_mode ? (gb->wram_bank ? gb->wram_bank : 1) : 1;
gb->wram[bank * 0x1000 + (addr - 0xD000)] = val; return;
}
if (addr < 0xFE00) { gb_write(gb, addr - 0x2000, val); return; }
if (addr < 0xFEA0) { ppu_write(gb, addr, val); return; }
if (addr < 0xFF00) return;
if (addr == 0xFF00) { gb->joyp_sel = val & 0x30; return; }
if (addr == 0xFF01) { gb->sb = val; return; }
if (addr == 0xFF02) {
gb->sc = val;
if ((val & 0x81) == 0x81) {
// transfer with internal clock: log & complete instantly
if (gb->serial_log) { fputc(gb->sb, stderr); fflush(stderr); }
gb->sb = 0xFF;
gb->sc &= ~0x80;
gb_request_interrupt(gb, INT_SERIAL);
}
return;
}
if (addr >= 0xFF04 && addr <= 0xFF07) { timer_write(gb, addr, val); return; }
if (addr == 0xFF0F) { gb->iff = val & 0x1F; return; }
if (addr >= 0xFF10 && addr <= 0xFF3F) return; // APU stub
if (addr >= 0xFF40 && addr <= 0xFF4B) {
if (addr == 0xFF46) { oam_dma(gb, val); return; }
ppu_write_reg(gb, addr, val);
return;
}
if (addr == 0xFF4D && gb->cgb_mode) { gb->key1 = (gb->key1 & 0x80) | (val & 1); return; }
if (addr == 0xFF4F && gb->cgb_mode) { gb->ppu.vram_bank = val & 1; return; }
if (addr == 0xFF51 && gb->cgb_mode) { gb->hdma_src = (gb->hdma_src & 0xFF) | (val << 8); return; }
if (addr == 0xFF52 && gb->cgb_mode) { gb->hdma_src = (gb->hdma_src & 0xFF00) | (val & 0xF0); return; }
if (addr == 0xFF53 && gb->cgb_mode) { gb->hdma_dst = (gb->hdma_dst & 0xFF) | ((val & 0x1F) << 8); return; }
if (addr == 0xFF54 && gb->cgb_mode) { gb->hdma_dst = (gb->hdma_dst & 0xFF00) | (val & 0xF0); return; }
if (addr == 0xFF55 && gb->cgb_mode) {
int len = ((val & 0x7F) + 1) * 0x10;
if (val & 0x80) {
// HDMA (mode 1): transfer during hblank
gb->hdma_len = val & 0x7F;
gb->hdma_active = true;
} else {
if (gb->hdma_active) {
// stopping an active hblank dma
gb->hdma_active = false;
gb->hdma_len = 0xFF;
} else {
// GDMA: transfer all now
for (int i = 0; i < len; i++) {
u8 v = gb_read(gb, gb->hdma_src++);
ppu_write(gb, 0x8000 + (gb->hdma_dst++ & 0x1FFF), v);
}
}
}
return;
}
if ((addr >= 0xFF68 && addr <= 0xFF6B) && gb->cgb_mode) { ppu_write_reg(gb, addr, val); return; }
if (addr == 0xFF70 && gb->cgb_mode) { gb->wram_bank = val & 0x07; return; }
if (addr >= 0xFF80 && addr < 0xFFFF) { gb->hram[addr - 0xFF80] = val; return; }
if (addr == 0xFFFF) { gb->ie = val; return; }
}
// advance subsystems by t-cycles
void gb_tick(GB *gb, int tcycles) {
// in double-speed mode CPU/timer run 2x, but PPU stays at normal rate.
int ppu_cycles = gb->double_speed ? tcycles / 2 : tcycles;
timer_tick(gb, tcycles);
ppu_tick(gb, ppu_cycles);
gb->cycles += tcycles;
}
// HDMA one block per hblank; called by ppu when entering hblank
void gb_hdma_hblank(GB *gb) {
if (!gb->hdma_active) return;
hdma_do_block(gb);
if (gb->hdma_len == 0) {
gb->hdma_active = false;
gb->hdma_len = 0xFF;
} else {
gb->hdma_len--;
}
}
void gb_init(GB *gb) {
memset(gb, 0, sizeof(*gb));
gb_reset(gb);
}
void gb_reset(GB *gb) {
CPU *c = &gb->cpu;
gb->cgb_mode = gb->cart.cgb;
// post-boot register state
c->pc = 0x0100;
c->sp = 0xFFFE;
if (gb->cgb_mode) {
c->af = 0x1180; c->bc = 0x0000; c->de = 0xFF56; c->hl = 0x000D;
} else {
c->af = 0x01B0; c->bc = 0x0013; c->de = 0x00D8; c->hl = 0x014D;
}
c->ime = false;
gb->wram_bank = 1;
gb->joyp_sel = 0x30;
gb->iff = 0xE1;
gb->hdma_len = 0xFF;
ppu_reset(gb);
gb->timer.div = 0x1800;
}
|