From e72058ec37e8a494c1532115791042035ebb8507 Mon Sep 17 00:00:00 2001 From: Colby Date: Wed, 20 Jul 2016 21:19:04 +1000 Subject: [PATCH] proper MBC1 implementation --- lib/waterfoul/mbc/mbc1.rb | 68 +++++++++++++++++++++++++++++-------- spec/mbc/mbc1_spec.rb | 71 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 16 deletions(-) diff --git a/lib/waterfoul/mbc/mbc1.rb b/lib/waterfoul/mbc/mbc1.rb index 6e6ee0c..00a63c2 100644 --- a/lib/waterfoul/mbc/mbc1.rb +++ b/lib/waterfoul/mbc/mbc1.rb @@ -3,27 +3,67 @@ module Waterfoul class MBC1 < ROM EXTERNAL_RAM_SIZE = 0x2000 - def initialize(program, rom_bank = 1) - @rom_bank = rom_bank - @cartridge_program = program - @rom_offset = 0x4000 - @ram_bank = Array.new EXTERNAL_RAM_SIZE, 0 + attr_accessor :ram_enabled, :mode, :rom_bank, :ram_bank + + def initialize(program) + @rom_bank = 1 + @ram_bank = 1 + @mode = 0 + @ram_enabled = false + @game_program = program + @ram = Array.new EXTERNAL_RAM_SIZE, 0 end def [](i) - case i & 0xE000 - when 0x4000, 0x6000 - relative_addr = (i - 0x4000) + @rom_offset - @cartridge_program[relative_addr] - when 0xA000 - byebug - else - @cartridge_program[i] + case i + when 0x0...0x4000 # ROM Bank 0 + @game_program[i] + when 0x4000...0x8000 # ROM Bank n + addr = i - 0x4000 + offset = @rom_bank * 0x4000 + @game_program[offset + addr] + when 0xA000...0xC000 + fail 'trying to read invalid ram' unless @ram_enabled + addr = i - 0xA000 + if @mode == 0 + @ram[addr] + else + offset = @rom_ram_bank_number * 0x8000 + @ram[offset + addr] + end end end def []=(i,v) - byebug + case i + when 0x0...0x2000 + @ram_enabled = (v & 0xA == 0xA ? true : false) + when 0x2000...0x4000 + if @mode == 1 + @rom_bank = (v & 0x1F) | (@rom_bank & 0xE0) + else + @rom_bank = v & 0x1F + end + when 0x4000...0x6000 + v = v & 0x3 + if @mode == 1 + @ram_bank = v + else + @rom_bank = (@rom_bank & 0x1F) | (v << 5) + @rom_bank += 1 if [0x0, 0x20, 0x40, 0x60].include? @rom_bank + end + when 0x6000...0x8000 + @mode = v & 0x1 + when 0xA000...0xC000 + fail 'trying to write invalid ram' unless @ram_enabled + addr = i - 0xA000 + if @mode == 0 + @ram[addr] = v + else + offset = @ram_bank * 0x8000 + @ram[offset + addr] = v + end + end end end end diff --git a/spec/mbc/mbc1_spec.rb b/spec/mbc/mbc1_spec.rb index a832170..0d83ee7 100644 --- a/spec/mbc/mbc1_spec.rb +++ b/spec/mbc/mbc1_spec.rb @@ -1,13 +1,80 @@ require 'spec_helper' describe Waterfoul::MBC::MBC1 do + let(:program) { double :program } + subject { Waterfoul::MBC::MBC1.new program } + describe '#[]' do - context 'read from addr 0x2000' do - it 'reads bytes from bank 0' do + context 'when reading at 0x0001' do + it 'reads byte from bank 0' do + expect(program).to receive(:[]).with(0x0001) + subject[0x0001] + end + end + + context 'when reading at 0x4001' do + it 'reads from bank 1' do + expect(program).to receive(:[]).with(0x4001) + subject[0x4001] end end end describe '#[]=' do + context 'when writing to 0x1' do + it 'enables the external ram' do + subject[0x1] = 0xA + expect(subject.ram_enabled).to eq true + end + end + + context 'when writing to 4001' do + it 'sets rom bank from 5 bits' do + subject[0x2000] = 0xFF + expect(subject.rom_bank).to eq 0x1F + end + end + + context 'when writing to 0x4000' do + context 'with mode set to 1' do + before { subject[0x6000] = 0x1 } + it 'sets ram bank' do + subject[0x4000] = 0x3 + expect(subject.ram_bank).to eq 0x3 + end + end + + context 'with mode set to 0' do + before { subject[0x6000] = 0x0 } + before { subject[0x2000] = 0x1F } + it 'sets upper 3 bits of rom bank' do + subject[0x4000] = 0x3 + expect(subject.rom_bank).to eq 0x7F + end + end + end + + context 'when writing to 0xA001' do + context 'when RAM is enabled' do + before { subject[0x1] = 0xA } + it 'saves the value to external memory' do + subject[0xA001] = 0x1 + expect(subject[0xA001]).to eq 0x1 + end + end + + context 'when ram is disabled' do + it 'raises an error' do + expect { subject[0xA001] = 0x1 }.to raise_error RuntimeError + end + end + end + + context 'writing to 0x6001' do + it 'sets the ROM mode to 1' do + subject[0x6001] = 0x1 + expect(subject.mode).to eq 1 + end + end end end