Skip to content

Commit 11ae10a

Browse files
authored
Merge pull request #18 from toots/mbc2
Implement Memory Bank Controller 2 (MBC2).
2 parents e32febc + 469e118 commit 11ae10a

12 files changed

Lines changed: 194 additions & 23 deletions

File tree

lib/cartridge/detect_cartridge.ml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ let f ~rom_bytes =
88
| MBC1
99
| MBC1_RAM
1010
| MBC1_RAM_BATTERY -> (module Mbc1 : Cartridge_intf.S)
11-
| _ -> assert false
11+
| MBC2
12+
| MBC2_BATTERY -> (module Mbc2 : Cartridge_intf.S)
13+
| other ->
14+
failwith (Printf.sprintf "Unsupported cartridge type: %s" (Cartridge_type.show other))

lib/cartridge/mbc2.ml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
(* MBC2 - Memory Bank Controller 2
2+
3+
Features:
4+
- Up to 256KB ROM (16 banks)
5+
- Built-in 512×4 bits RAM (only lower nibble used)
6+
- RAM at 0xA000-0xA1FF (mirrored in 0xA200-0xBFFF)
7+
8+
Register writes:
9+
- 0x0000-0x3FFF with bit 8 = 0: RAM enable (0x0A enables)
10+
- 0x0000-0x3FFF with bit 8 = 1: ROM bank number (4 bits, 0→1)
11+
12+
Reference: https://gbdev.io/pandocs/MBC2.html *)
13+
14+
open Uints
15+
16+
type t = {
17+
rom_bytes : Bigstringaf.t;
18+
ram_bytes : Bigstringaf.t;
19+
rom_bank_count : int;
20+
mutable ram_enabled : bool;
21+
mutable rom_bank_num : int;
22+
}
23+
24+
let create ~rom_bytes =
25+
let h = Cartridge_header.create ~rom_bytes in
26+
let rom_bank_count = Cartridge_header.get_rom_bank_count h in
27+
(* MBC2 has built-in 512×4 bit RAM *)
28+
let ram_bytes = Bigstringaf.create 512 in
29+
{
30+
rom_bytes;
31+
ram_bytes;
32+
rom_bank_count;
33+
ram_enabled = false;
34+
rom_bank_num = 1;
35+
}
36+
37+
let read_byte t addr =
38+
let addr = Uint16.to_int addr in
39+
match addr with
40+
| _ when addr <= 0x3FFF ->
41+
Bigstringaf.unsafe_get t.rom_bytes addr
42+
|> Uint8.of_char
43+
| _ when addr <= 0x7FFF ->
44+
let offset = 0x4000 * t.rom_bank_num + (addr - 0x4000) in
45+
Bigstringaf.unsafe_get t.rom_bytes offset
46+
|> Uint8.of_char
47+
| _ when 0xA000 <= addr && addr <= 0xBFFF ->
48+
(* RAM - 512 bytes mirrored, only lower 4 bits valid
49+
This memory is at A000–A1FF and mirrored at
50+
A200–BFFF hence the 0x1FF below. *)
51+
if t.ram_enabled then
52+
let ram_addr = (addr - 0xA000) land 0x1FF in
53+
let value = Bigstringaf.unsafe_get t.ram_bytes ram_addr |> Char.code in
54+
Uint8.of_int (value lor 0xF0) (* Upper 4 bits read as 1 *)
55+
else
56+
Uint8.of_int 0xFF
57+
| _ -> assert false
58+
59+
let write_byte t ~addr ~data =
60+
let addr = Uint16.to_int addr in
61+
let data = Uint8.to_int data in
62+
match addr with
63+
| _ when addr <= 0x3FFF ->
64+
if addr land 0x100 = 0 then
65+
t.ram_enabled <- (data land 0x0F) = 0x0A
66+
else begin
67+
let bank = data land 0x0F in
68+
let bank = if bank = 0 then 1 else bank in
69+
t.rom_bank_num <- bank mod t.rom_bank_count
70+
end
71+
| _ when 0x4000 <= addr && addr <= 0x7FFF ->
72+
(* Writes to this area do nothing on MBC2 *)
73+
()
74+
| _ when 0xA000 <= addr && addr <= 0xBFFF ->
75+
(* RAM - only lower 4 bits written *)
76+
if t.ram_enabled then begin
77+
let ram_addr = (addr - 0xA000) land 0x1FF in
78+
Bigstringaf.unsafe_set t.ram_bytes ram_addr (Char.unsafe_chr (data land 0x0F))
79+
end
80+
| _ -> assert false
81+
82+
let accepts _ addr =
83+
let addr = Uint16.to_int addr in
84+
(addr <= 0x7FFF) || (0xA000 <= addr && addr <= 0xBFFF)

lib/cartridge/mbc2.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include Cartridge_intf.S
32 KB
Binary file not shown.
64 KB
Binary file not shown.
256 KB
Binary file not shown.
32 KB
Binary file not shown.
128 KB
Binary file not shown.
256 KB
Binary file not shown.
64 KB
Binary file not shown.

0 commit comments

Comments
 (0)