|
| 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) |
0 commit comments