diff --git a/lib/cartridge/detect_cartridge.ml b/lib/cartridge/detect_cartridge.ml index e61bbb5..83b53f8 100644 --- a/lib/cartridge/detect_cartridge.ml +++ b/lib/cartridge/detect_cartridge.ml @@ -8,4 +8,7 @@ let f ~rom_bytes = | MBC1 | MBC1_RAM | MBC1_RAM_BATTERY -> (module Mbc1 : Cartridge_intf.S) - | _ -> assert false + | MBC2 + | MBC2_BATTERY -> (module Mbc2 : Cartridge_intf.S) + | other -> + failwith (Printf.sprintf "Unsupported cartridge type: %s" (Cartridge_type.show other)) diff --git a/lib/cartridge/mbc2.ml b/lib/cartridge/mbc2.ml new file mode 100644 index 0000000..f8d61fe --- /dev/null +++ b/lib/cartridge/mbc2.ml @@ -0,0 +1,84 @@ +(* MBC2 - Memory Bank Controller 2 + + Features: + - Up to 256KB ROM (16 banks) + - Built-in 512×4 bits RAM (only lower nibble used) + - RAM at 0xA000-0xA1FF (mirrored in 0xA200-0xBFFF) + + Register writes: + - 0x0000-0x3FFF with bit 8 = 0: RAM enable (0x0A enables) + - 0x0000-0x3FFF with bit 8 = 1: ROM bank number (4 bits, 0→1) + + Reference: https://gbdev.io/pandocs/MBC2.html *) + +open Uints + +type t = { + rom_bytes : Bigstringaf.t; + ram_bytes : Bigstringaf.t; + rom_bank_count : int; + mutable ram_enabled : bool; + mutable rom_bank_num : int; +} + +let create ~rom_bytes = + let h = Cartridge_header.create ~rom_bytes in + let rom_bank_count = Cartridge_header.get_rom_bank_count h in + (* MBC2 has built-in 512×4 bit RAM *) + let ram_bytes = Bigstringaf.create 512 in + { + rom_bytes; + ram_bytes; + rom_bank_count; + ram_enabled = false; + rom_bank_num = 1; + } + +let read_byte t addr = + let addr = Uint16.to_int addr in + match addr with + | _ when addr <= 0x3FFF -> + Bigstringaf.unsafe_get t.rom_bytes addr + |> Uint8.of_char + | _ when addr <= 0x7FFF -> + let offset = 0x4000 * t.rom_bank_num + (addr - 0x4000) in + Bigstringaf.unsafe_get t.rom_bytes offset + |> Uint8.of_char + | _ when 0xA000 <= addr && addr <= 0xBFFF -> + (* RAM - 512 bytes mirrored, only lower 4 bits valid + This memory is at A000–A1FF and mirrored at + A200–BFFF hence the 0x1FF below. *) + if t.ram_enabled then + let ram_addr = (addr - 0xA000) land 0x1FF in + let value = Bigstringaf.unsafe_get t.ram_bytes ram_addr |> Char.code in + Uint8.of_int (value lor 0xF0) (* Upper 4 bits read as 1 *) + else + Uint8.of_int 0xFF + | _ -> assert false + +let write_byte t ~addr ~data = + let addr = Uint16.to_int addr in + let data = Uint8.to_int data in + match addr with + | _ when addr <= 0x3FFF -> + if addr land 0x100 = 0 then + t.ram_enabled <- (data land 0x0F) = 0x0A + else begin + let bank = data land 0x0F in + let bank = if bank = 0 then 1 else bank in + t.rom_bank_num <- bank mod t.rom_bank_count + end + | _ when 0x4000 <= addr && addr <= 0x7FFF -> + (* Writes to this area do nothing on MBC2 *) + () + | _ when 0xA000 <= addr && addr <= 0xBFFF -> + (* RAM - only lower 4 bits written *) + if t.ram_enabled then begin + let ram_addr = (addr - 0xA000) land 0x1FF in + Bigstringaf.unsafe_set t.ram_bytes ram_addr (Char.unsafe_chr (data land 0x0F)) + end + | _ -> assert false + +let accepts _ addr = + let addr = Uint16.to_int addr in + (addr <= 0x7FFF) || (0xA000 <= addr && addr <= 0xBFFF) diff --git a/lib/cartridge/mbc2.mli b/lib/cartridge/mbc2.mli new file mode 100644 index 0000000..c3c85ec --- /dev/null +++ b/lib/cartridge/mbc2.mli @@ -0,0 +1 @@ +include Cartridge_intf.S diff --git a/resource/test_roms/mooneye/mbc2/bits_ramg.gb b/resource/test_roms/mooneye/mbc2/bits_ramg.gb new file mode 100644 index 0000000..887786c Binary files /dev/null and b/resource/test_roms/mooneye/mbc2/bits_ramg.gb differ diff --git a/resource/test_roms/mooneye/mbc2/bits_romb.gb b/resource/test_roms/mooneye/mbc2/bits_romb.gb new file mode 100644 index 0000000..bb7ab4c Binary files /dev/null and b/resource/test_roms/mooneye/mbc2/bits_romb.gb differ diff --git a/resource/test_roms/mooneye/mbc2/bits_unused.gb b/resource/test_roms/mooneye/mbc2/bits_unused.gb new file mode 100644 index 0000000..f25dc01 Binary files /dev/null and b/resource/test_roms/mooneye/mbc2/bits_unused.gb differ diff --git a/resource/test_roms/mooneye/mbc2/ram.gb b/resource/test_roms/mooneye/mbc2/ram.gb new file mode 100644 index 0000000..e570e1c Binary files /dev/null and b/resource/test_roms/mooneye/mbc2/ram.gb differ diff --git a/resource/test_roms/mooneye/mbc2/rom_1Mb.gb b/resource/test_roms/mooneye/mbc2/rom_1Mb.gb new file mode 100644 index 0000000..1d45bf8 Binary files /dev/null and b/resource/test_roms/mooneye/mbc2/rom_1Mb.gb differ diff --git a/resource/test_roms/mooneye/mbc2/rom_2Mb.gb b/resource/test_roms/mooneye/mbc2/rom_2Mb.gb new file mode 100644 index 0000000..d5b4e0d Binary files /dev/null and b/resource/test_roms/mooneye/mbc2/rom_2Mb.gb differ diff --git a/resource/test_roms/mooneye/mbc2/rom_512kb.gb b/resource/test_roms/mooneye/mbc2/rom_512kb.gb new file mode 100644 index 0000000..b8997c7 Binary files /dev/null and b/resource/test_roms/mooneye/mbc2/rom_512kb.gb differ diff --git a/test/rom_test/mooneye/mooneye_utils.ml b/test/rom_test/mooneye/mooneye_utils.ml index d5b6bd5..d7d1257 100644 --- a/test/rom_test/mooneye/mooneye_utils.ml +++ b/test/rom_test/mooneye/mooneye_utils.ml @@ -12,28 +12,25 @@ module Make (Cartridge : Cartridge_intf.S) = struct let run_result = Camlboy.run_instruction camlboy in let prev_instr = Camlboy.For_tests.prev_inst camlboy in match prev_instr, run_result with - | JR (None, i8), Frame_ended famebuffer -> - if Int8.to_int i8 <> -3 then - loop () - else begin - Printf.printf "%s\n" @@ Camlboy.show camlboy; - famebuffer - |> Array.iteri (fun i row -> - if row |> Array.for_all (fun color -> color = `White) then - () - else begin - let show_color = function - | `Black -> '#' - | `Dark_gray -> '@' - | `Light_gray -> 'x' - | `White -> '-' - in - Printf.printf "%03d:" i; - row |> Array.iter (fun color -> show_color color |> print_char); - print_newline () - end - ) - end + | JR (None, i8), Frame_ended famebuffer + when List.mem (Int8.to_int i8) [-2; -3] -> + Printf.printf "%s\n" @@ Camlboy.show camlboy; + famebuffer + |> Array.iteri (fun i row -> + if row |> Array.for_all (fun color -> color = `White) then + () + else begin + let show_color = function + | `Black -> '#' + | `Dark_gray -> '@' + | `Light_gray -> 'x' + | `White -> '-' + in + Printf.printf "%03d:" i; + row |> Array.iter (fun color -> show_color color |> print_char); + print_newline () + end + ) | _, _ -> loop () in loop () diff --git a/test/rom_test/mooneye/test_mooneye_mbc2.ml b/test/rom_test/mooneye/test_mooneye_mbc2.ml new file mode 100644 index 0000000..6810268 --- /dev/null +++ b/test/rom_test/mooneye/test_mooneye_mbc2.ml @@ -0,0 +1,86 @@ +open Camlboy_lib +module M = Mooneye_utils.Make(Mbc2) + +let%expect_test "bits_ramg.gb" = + M.run_test_rom_and_print_framebuffer "mbc2/bits_ramg.gb"; + + [%expect{| + A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4989 + 008:-#######-----------------------------------###---#---#---------------------------------------------------------------------------------------------------------- + 009:----#-----####-----###-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------ + 011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------ + 012:----#----#-----------#-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}] + +let%expect_test "bits_romb.gb" = + M.run_test_rom_and_print_framebuffer "mbc2/bits_romb.gb"; + + [%expect{| + A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4879 + 008:-#######-----------------------------------###---#---#---------------------------------------------------------------------------------------------------------- + 009:----#-----####-----###-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------ + 011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------ + 012:----#----#-----------#-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}] + +let%expect_test "bits_unused.gb" = + M.run_test_rom_and_print_framebuffer "mbc2/bits_unused.gb"; + + [%expect{| + A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4089 + 008:-#######-----------------------------------###---#---#---------------------------------------------------------------------------------------------------------- + 009:----#-----####-----###-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------ + 011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------ + 012:----#----#-----------#-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}] + +let%expect_test "ram.gb" = + M.run_test_rom_and_print_framebuffer "mbc2/ram.gb"; + + [%expect{| + A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4879 + 008:-#######-----------------------------------###---#---#---------------------------------------------------------------------------------------------------------- + 009:----#-----####-----###-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------ + 011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------ + 012:----#----#-----------#-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}] + +let%expect_test "rom_512kb.gb" = + M.run_test_rom_and_print_framebuffer "mbc2/rom_512kb.gb"; + + [%expect{| + A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$487A + 008:-#######-----------------------------------###---#---#---------------------------------------------------------------------------------------------------------- + 009:----#-----####-----###-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------ + 011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------ + 012:----#----#-----------#-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}] + +let%expect_test "rom_1Mb.gb" = + M.run_test_rom_and_print_framebuffer "mbc2/rom_1Mb.gb"; + + [%expect{| + A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$487A + 008:-#######-----------------------------------###---#---#---------------------------------------------------------------------------------------------------------- + 009:----#-----####-----###-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------ + 011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------ + 012:----#----#-----------#-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}] + +let%expect_test "rom_2Mb.gb" = + M.run_test_rom_and_print_framebuffer "mbc2/rom_2Mb.gb"; + + [%expect{| + A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$487A + 008:-#######-----------------------------------###---#---#---------------------------------------------------------------------------------------------------------- + 009:----#-----####-----###-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------ + 011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------ + 012:----#----#-----------#-----#--------------#---#--#--#----------------------------------------------------------------------------------------------------------- + 013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]