diff --git a/bsnes/nes/cartridge/board/board.cpp b/bsnes/nes/cartridge/board/board.cpp index 67fd926ff..dc09dd54c 100755 --- a/bsnes/nes/cartridge/board/board.cpp +++ b/bsnes/nes/cartridge/board/board.cpp @@ -3,6 +3,7 @@ #include "nes-axrom.cpp" #include "nes-bnrom.cpp" #include "nes-cnrom.cpp" +#include "nes-exrom.cpp" #include "nes-gxrom.cpp" #include "nes-nrom.cpp" #include "nes-sxrom.cpp" @@ -10,7 +11,15 @@ #include "nes-uxrom.cpp" #include "sunsoft-5b.cpp" -unsigned Board::mirror(unsigned addr, unsigned size) const { +uint8 Board::Memory::read(unsigned addr) const { + return data[mirror(addr, size)]; +} + +void Board::Memory::write(unsigned addr, uint8 byte) { + data[mirror(addr, size)] = byte; +} + +unsigned Board::mirror(unsigned addr, unsigned size) { unsigned base = 0; if(size) { unsigned mask = 1 << 23; @@ -120,6 +129,11 @@ Board* Board::load(const string &markup, const uint8_t *data, unsigned size) { if(type == "NES-CNROM" ) return new NES_CNROM(board, data, size); + if(type == "NES-EKROM" ) return new NES_ExROM(board, data, size); + if(type == "NES-ELROM" ) return new NES_ExROM(board, data, size); + if(type == "NES-ETROM" ) return new NES_ExROM(board, data, size); + if(type == "NES-EWROM" ) return new NES_ExROM(board, data, size); + if(type == "NES-GNROM" ) return new NES_GxROM(board, data, size); if(type == "NES-MHROM" ) return new NES_GxROM(board, data, size); diff --git a/bsnes/nes/cartridge/board/board.hpp b/bsnes/nes/cartridge/board/board.hpp index d55a87560..960b52196 100755 --- a/bsnes/nes/cartridge/board/board.hpp +++ b/bsnes/nes/cartridge/board/board.hpp @@ -2,11 +2,13 @@ struct Board { struct Memory { uint8_t *data; unsigned size; + inline uint8 read(unsigned addr) const; + inline void write(unsigned addr, uint8 data); inline Memory(uint8_t *data, unsigned size) : data(data), size(size) {} inline Memory() : data(nullptr), size(0u) {} }; - unsigned mirror(unsigned addr, unsigned size) const; + static unsigned mirror(unsigned addr, unsigned size); virtual void main(); virtual void tick(); diff --git a/bsnes/nes/cartridge/board/nes-exrom.cpp b/bsnes/nes/cartridge/board/nes-exrom.cpp new file mode 100755 index 000000000..fe97f2f1e --- /dev/null +++ b/bsnes/nes/cartridge/board/nes-exrom.cpp @@ -0,0 +1,49 @@ +struct NES_ExROM : Board { + +enum class Revision : unsigned { + EKROM, + ELROM, + ETROM, + EWROM, +} revision; + +MMC5 mmc5; + +void main() { + mmc5.main(); +} + +uint8 prg_read(unsigned addr) { + return mmc5.prg_read(addr); +} + +void prg_write(unsigned addr, uint8 data) { + mmc5.prg_write(addr, data); +} + +uint8 chr_read(unsigned addr) { + return mmc5.chr_read(addr); +} + +void chr_write(unsigned addr, uint8 data) { + mmc5.chr_write(addr, data); +} + +void power() { + mmc5.power(); +} + +void reset() { + mmc5.reset(); +} + +void serialize(serializer &s) { + Board::serialize(s); + mmc5.serialize(s); +} + +NES_ExROM(BML::Node &board, const uint8_t *data, unsigned size) : Board(board, data, size), mmc5(*this) { + revision = Revision::ELROM; +} + +}; diff --git a/bsnes/nes/cartridge/board/nes-sxrom.cpp b/bsnes/nes/cartridge/board/nes-sxrom.cpp index 299f0ad3a..b9bc02838 100755 --- a/bsnes/nes/cartridge/board/nes-sxrom.cpp +++ b/bsnes/nes/cartridge/board/nes-sxrom.cpp @@ -27,31 +27,44 @@ enum class Revision : unsigned { MMC1 mmc1; -unsigned shiftaddr; -unsigned shiftdata; +unsigned ram_addr(unsigned addr) { + unsigned bank = 0; + if(revision == Revision::SOROM) bank = (mmc1.chr_bank[0] & 0x08) >> 3; + if(revision == Revision::SUROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2; + if(revision == Revision::SXROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2; + return (bank << 13) | (addr & 0x1fff); +} uint8 prg_read(unsigned addr) { - if((addr & 0xe000) == 0x6000) return mmc1.ram_read(addr); - if(addr & 0x8000) return Board::prg_read(mmc1.prg_addr(addr)); + if((addr & 0xe000) == 0x6000) { + if(revision == Revision::SNROM) { + if(mmc1.chr_bank[0] & 0x10) return cpu.mdr(); + } + if(mmc1.ram_disable) return 0x00; + return prgram.read(ram_addr(addr)); + } + + if(addr & 0x8000) { + addr = mmc1.prg_addr(addr); + if(revision == Revision::SXROM) { + addr |= ((mmc1.chr_bank[0] & 0x10) >> 4) << 18; + } + return prgrom.read(addr); + } + return cpu.mdr(); } void prg_write(unsigned addr, uint8 data) { - if((addr & 0xe000) == 0x6000) return mmc1.ram_write(addr, data); - - if(addr & 0x8000) { - if(data & 0x80) { - shiftaddr = 0; - mmc1.prg_size = 1; - mmc1.prg_mode = 1; - } else { - shiftdata = ((data & 1) << 4) | (shiftdata >> 1); - if(++shiftaddr == 5) { - shiftaddr = 0; - reg_write((addr >> 13) & 3, shiftdata); - } + if((addr & 0xe000) == 0x6000) { + if(revision == Revision::SNROM) { + if(mmc1.chr_bank[0] & 0x10) return; } + if(mmc1.ram_disable) return; + return prgram.write(ram_addr(addr), data); } + + if(addr & 0x8000) return mmc1.mmio_write(addr, data); } uint8 chr_read(unsigned addr) { @@ -64,60 +77,6 @@ void chr_write(unsigned addr, uint8 data) { return Board::chr_write(mmc1.chr_addr(addr), data); } -void reg_write(unsigned addr, uint8 data) { - switch(addr) { - case 0: - mmc1.chr_mode = (data & 0x10); - mmc1.prg_size = (data & 0x08); - mmc1.prg_mode = (data & 0x04); - mmc1.mirror = (data & 0x03); - break; - - case 1: - mmc1.chr_bank[0] = (data & 0x1f); - switch(revision) { - case Revision::SNROM: - mmc1.ram_disable[1] = (data & 0x10); - break; - case Revision::SOROM: - mmc1.ram_bank = (data & 0x08) >> 3; - break; - case Revision::SUROM: - mmc1.prg_page = (data & 0x10); - break; - case Revision::SXROM: - mmc1.prg_page = (data & 0x10); - mmc1.ram_bank = (data & 0x0c) >> 2; - break; - } - break; - - case 2: - mmc1.chr_bank[1] = (data & 0x1f); - switch(revision) { - case Revision::SNROM: - mmc1.ram_disable[1] = (data & 0x10); - break; - case Revision::SOROM: - mmc1.ram_bank = (data & 0x08) >> 3; - break; - case Revision::SUROM: - mmc1.prg_page = (data & 0x10); - break; - case Revision::SXROM: - mmc1.prg_page = (data & 0x10); - mmc1.ram_bank = (data & 0x0c) >> 2; - break; - } - break; - - case 3: - mmc1.ram_disable[0] = (data & 0x10); - mmc1.prg_bank = (data & 0x0f); - break; - } -} - Memory memory() { return prgram; } @@ -133,16 +92,10 @@ void reset() { void serialize(serializer &s) { Board::serialize(s); mmc1.serialize(s); - - s.integer(shiftaddr); - s.integer(shiftdata); } NES_SxROM(BML::Node &board, const uint8_t *data, unsigned size) : Board(board, data, size), mmc1(*this) { revision = Revision::SXROM; - - shiftaddr = 0; - shiftdata = 0; } }; diff --git a/bsnes/nes/cartridge/chip/chip.cpp b/bsnes/nes/cartridge/chip/chip.cpp index 51dc1b7e7..5b122c2b7 100755 --- a/bsnes/nes/cartridge/chip/chip.cpp +++ b/bsnes/nes/cartridge/chip/chip.cpp @@ -1,5 +1,6 @@ #include "mmc1.cpp" #include "mmc3.cpp" +#include "mmc5.cpp" #include "vrc6.cpp" void Chip::tick() { diff --git a/bsnes/nes/cartridge/chip/mmc1.cpp b/bsnes/nes/cartridge/chip/mmc1.cpp index 1c6bfe619..c2690ef6b 100755 --- a/bsnes/nes/cartridge/chip/mmc1.cpp +++ b/bsnes/nes/cartridge/chip/mmc1.cpp @@ -9,17 +9,18 @@ enum class Revision : unsigned { MMC1C, } revision; +unsigned shiftaddr; +unsigned shiftdata; + bool chr_mode; bool prg_size; //0 = 32K, 1 = 16K bool prg_mode; uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal uint5 chr_bank[2]; +bool ram_disable; uint4 prg_bank; -bool prg_page; -uint2 ram_bank; -bool ram_disable[2]; -unsigned prg_addr(unsigned addr) const { +unsigned prg_addr(unsigned addr) { bool region = addr & 0x4000; unsigned bank = (prg_bank & ~1) + region; @@ -28,17 +29,17 @@ unsigned prg_addr(unsigned addr) const { if(region != prg_mode) bank = prg_bank; } - return (prg_page << 18) | (bank << 14) | (addr & 0x3fff); + return (bank << 14) | (addr & 0x3fff); } -unsigned chr_addr(unsigned addr) const { +unsigned chr_addr(unsigned addr) { bool region = addr & 0x1000; unsigned bank = chr_bank[region]; if(chr_mode == 0) bank = (chr_bank[0] & ~1) | region; return (bank << 12) | (addr & 0x0fff); } -unsigned ciram_addr(unsigned addr) const { +unsigned ciram_addr(unsigned addr) { switch(mirror) { case 0: return 0x0000 | (addr & 0x03ff); case 1: return 0x0400 | (addr & 0x03ff); @@ -47,15 +48,38 @@ unsigned ciram_addr(unsigned addr) const { } } -uint8 ram_read(unsigned addr) { - addr = (ram_bank * 0x2000) | (addr & 0x1fff); - if(ram_disable[0] == false && ram_disable[1] == false) return board.prgram.data[addr]; - return 0x00; -} +void mmio_write(unsigned addr, uint8 data) { + if(data & 0x80) { + shiftaddr = 0; + prg_size = 1; + prg_mode = 1; + } else { + shiftdata = ((data & 1) << 4) | (shiftdata >> 1); + if(++shiftaddr == 5) { + shiftaddr = 0; + switch((addr >> 13) & 3) { + case 0: + chr_mode = (shiftdata & 0x10); + prg_size = (shiftdata & 0x08); + prg_mode = (shiftdata & 0x04); + mirror = (shiftdata & 0x03); + break; -void ram_write(unsigned addr, uint8 data) { - addr = (ram_bank * 0x2000) | (addr & 0x1fff); - if(ram_disable[0] == false && ram_disable[1] == false) board.prgram.data[addr] = data; + case 1: + chr_bank[0] = (shiftdata & 0x1f); + break; + + case 2: + chr_bank[1] = (shiftdata & 0x1f); + break; + + case 3: + ram_disable = (shiftdata & 0x10); + prg_bank = (shiftdata & 0x0f); + break; + } + } + } } void power() { @@ -63,29 +87,30 @@ void power() { } void reset() { + shiftaddr = 0; + shiftdata = 0; + chr_mode = 0; prg_size = 1; prg_mode = 1; mirror = 0; chr_bank[0] = 0; chr_bank[1] = 1; + ram_disable = 0; prg_bank = 0; - prg_page = 0; - ram_bank = 0; - ram_disable[0] = 0; - ram_disable[1] = 0; } void serialize(serializer &s) { + s.integer(shiftaddr); + s.integer(shiftdata); + s.integer(chr_mode); s.integer(prg_size); s.integer(prg_mode); s.integer(mirror); s.array(chr_bank); + s.integer(ram_disable); s.integer(prg_bank); - s.integer(prg_page); - s.integer(ram_bank); - s.array(ram_disable); } MMC1(Board &board) : Chip(board) { diff --git a/bsnes/nes/cartridge/chip/mmc5.cpp b/bsnes/nes/cartridge/chip/mmc5.cpp new file mode 100755 index 000000000..6db53909b --- /dev/null +++ b/bsnes/nes/cartridge/chip/mmc5.cpp @@ -0,0 +1,453 @@ +struct MMC5 : Chip { + +enum class Revision : unsigned { + MMC5, + MMC5B, +} revision; + +uint8 exram[1024]; + +//programmable registers + +uint2 prg_mode; //$5100 +uint2 chr_mode; //$5101 + +uint2 prgram_write_protect[2]; //$5102,$5103 + +uint2 exram_mode; //$5104 +uint2 nametable_mode[4]; //$5105 +uint8 fillmode_tile; //$5106 +uint2 fillmode_color; //$5107 + +bool ram_select; //$5113 +uint2 ram_bank; //$5113 +uint8 prg_bank[4]; //$5114-5117 +uint10 chr_sprite_bank[8]; //$5120-5127 +uint10 chr_bg_bank[4]; //$5128-512b +uint2 chr_bank_hi; //$5130 + +bool vs_enable; //$5200 +bool vs_side; //$5200 +uint5 vs_tile; //$5200 +uint8 vs_scroll; //$5201 +uint8 vs_bank; //5202 + +uint8 irq_line; //$5203 +bool irq_enable; //$5204 + +uint8 multiplicand; //$5205 +uint8 multiplier; //$5206 + +//status registers + +unsigned cpu_cycle_counter; +unsigned irq_counter; +bool irq_pending; +bool in_frame; + +unsigned vcounter; +unsigned hcounter; +uint16 chr_access[4]; +bool chr_active; +bool sprite_8x16; + +void main() { + while(true) { + if(scheduler.sync == Scheduler::SynchronizeMode::All) { + scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); + } + + //scanline() resets this; if no scanlines detected, enter video blanking period + if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period + + cpu.set_irq_line(irq_enable && irq_pending); + tick(); + } +} + +uint8 prg_access(bool write, unsigned addr, uint8 data = 0x00) { + unsigned bank; + + if((addr & 0xe000) == 0x6000) { + bank = (ram_select << 2) | ram_bank; + addr &= 0x1fff; + } else if(prg_mode == 0) { + bank = prg_bank[3] & ~3; + addr &= 0x7fff; + } else if(prg_mode == 1) { + if((addr & 0xc000) == 0x8000) bank = (prg_bank[1] & ~1); + if((addr & 0xe000) == 0xc000) bank = (prg_bank[3] & ~1); + addr &= 0x3fff; + } else if(prg_mode == 2) { + if((addr & 0xe000) == 0x8000) bank = (prg_bank[1] & ~1) | 0; + if((addr & 0xe000) == 0xa000) bank = (prg_bank[1] & ~1) | 1; + if((addr & 0xe000) == 0xc000) bank = (prg_bank[2]); + if((addr & 0xe000) == 0xe000) bank = (prg_bank[3]); + addr &= 0x1fff; + } else if(prg_mode == 3) { + if((addr & 0xe000) == 0x8000) bank = prg_bank[0]; + if((addr & 0xe000) == 0xa000) bank = prg_bank[1]; + if((addr & 0xe000) == 0xc000) bank = prg_bank[2]; + if((addr & 0xe000) == 0xe000) bank = prg_bank[3]; + addr &= 0x1fff; + } + + if(write == false) { + if(bank & 0x80) { + return board.prgrom.read((bank << 13) | addr); + } else { + board.prgram.read((bank << 13) | addr); + } + } else { + if(bank & 0x80) { + board.prgrom.write((bank << 13) | addr, data); + } else { + if(prgram_write_protect[0] == 2 && prgram_write_protect[1] == 1) { + board.prgram.write((bank << 13) | addr, data); + } + } + return 0x00; + } +} + +uint8 prg_read(unsigned addr) { + if((addr & 0xfc00) == 0x5c00) { + if(exram_mode & 2) return exram[addr & 0x03ff]; + return cpu.mdr(); + } + + if(addr >= 0x6000) { + return prg_access(0, addr); + } + + switch(addr) { + case 0x5204: { + uint8 result = (irq_pending << 7) | (in_frame << 6); + irq_pending = false; + return result; + } + case 0x5205: return (multiplier * multiplicand) >> 0; + case 0x5206: return (multiplier * multiplicand) >> 8; + } +} + +void prg_write(unsigned addr, uint8 data) { + if((addr & 0xfc00) == 0x5c00) { + if(exram_mode == 2) exram[addr & 0x03ff] = data; + return; + } + + if(addr >= 0x6000) { + prg_access(1, addr, data); + return; + } + + switch(addr) { + case 0x2000: + sprite_8x16 = data & 0x20; + break; + + case 0x2001: + //if BG+sprites are disabled; enter video blanking period + if((data & 0x18) == 0) blank(); + break; + + case 0x5100: prg_mode = data & 3; break; + case 0x5101: chr_mode = data & 3; break; + + case 0x5102: prgram_write_protect[0] = data & 3; break; + case 0x5103: prgram_write_protect[1] = data & 3; break; + + case 0x5104: + exram_mode = data & 3; + break; + + case 0x5105: + nametable_mode[0] = (data & 0x03) >> 0; + nametable_mode[1] = (data & 0x0c) >> 2; + nametable_mode[2] = (data & 0x30) >> 4; + nametable_mode[3] = (data & 0xc0) >> 6; + break; + + case 0x5106: + fillmode_tile = data; + break; + + case 0x5107: + fillmode_color = data & 3; + break; + + case 0x5113: + ram_select = data & 0x04; + ram_bank = data & 0x03; + break; + + case 0x5114: prg_bank[0] = data; break; + case 0x5115: prg_bank[1] = data; break; + case 0x5116: prg_bank[2] = data; break; + case 0x5117: prg_bank[3] = data | 0x80; break; + + case 0x5120: chr_sprite_bank[0] = (chr_bank_hi << 8) | data; chr_active = 0; break; + case 0x5121: chr_sprite_bank[1] = (chr_bank_hi << 8) | data; chr_active = 0; break; + case 0x5122: chr_sprite_bank[2] = (chr_bank_hi << 8) | data; chr_active = 0; break; + case 0x5123: chr_sprite_bank[3] = (chr_bank_hi << 8) | data; chr_active = 0; break; + case 0x5124: chr_sprite_bank[4] = (chr_bank_hi << 8) | data; chr_active = 0; break; + case 0x5125: chr_sprite_bank[5] = (chr_bank_hi << 8) | data; chr_active = 0; break; + case 0x5126: chr_sprite_bank[6] = (chr_bank_hi << 8) | data; chr_active = 0; break; + case 0x5127: chr_sprite_bank[7] = (chr_bank_hi << 8) | data; chr_active = 0; break; + + case 0x5128: chr_bg_bank[0] = (chr_bank_hi << 8) | data; chr_active = 1; break; + case 0x5129: chr_bg_bank[1] = (chr_bank_hi << 8) | data; chr_active = 1; break; + case 0x512a: chr_bg_bank[2] = (chr_bank_hi << 8) | data; chr_active = 1; break; + case 0x512b: chr_bg_bank[3] = (chr_bank_hi << 8) | data; chr_active = 1; break; + + case 0x5130: + chr_bank_hi = data & 3; + break; + + case 0x5200: + vs_enable = data & 0x80; + vs_side = data & 0x40; + vs_tile = data & 0x1f; + break; + + case 0x5201: + vs_scroll = data; + break; + + case 0x5202: + vs_bank = data; + break; + + case 0x5203: + irq_line = data; + break; + + case 0x5204: + irq_enable = data & 0x80; + break; + + case 0x5205: + multiplicand = data; + break; + + case 0x5206: + multiplier = data; + break; + } +} + +unsigned chr_sprite_addr(unsigned addr) { + unsigned bank; + + if(chr_mode == 0) { + bank = chr_sprite_bank[7]; + return (bank * 0x2000) + (addr & 0x1fff); + } + + if(chr_mode == 1) { + bank = (addr < 0x1000) ? chr_sprite_bank[3] + :/*addr < 0x2000 */ chr_sprite_bank[7]; + return (bank * 0x1000) + (addr & 0x0fff); + } + + if(chr_mode == 2) { + bank = (addr < 0x0800) ? chr_sprite_bank[1] + : (addr < 0x1000) ? chr_sprite_bank[3] + : (addr < 0x1800) ? chr_sprite_bank[5] + :/*addr < 0x2000 */ chr_sprite_bank[7]; + return (bank * 0x0800) + (addr & 0x07ff); + } + + if(chr_mode == 3) { + bank = (addr < 0x0400) ? chr_sprite_bank[0] + : (addr < 0x0800) ? chr_sprite_bank[1] + : (addr < 0x0c00) ? chr_sprite_bank[2] + : (addr < 0x1000) ? chr_sprite_bank[3] + : (addr < 0x1400) ? chr_sprite_bank[4] + : (addr < 0x1800) ? chr_sprite_bank[5] + : (addr < 0x1c00) ? chr_sprite_bank[6] + :/*addr < 0x2000 */ chr_sprite_bank[7]; + return (bank * 0x0400) + (addr & 0x03ff); + } +} + +unsigned chr_bg_addr(unsigned addr) { + addr &= 0x0fff; + unsigned bank; + + if(chr_mode == 0) { + bank = chr_bg_bank[3]; + return (bank * 0x2000) + (addr & 0x0fff); + } + + if(chr_mode == 1) { + bank = chr_bg_bank[3]; + return (bank * 0x1000) + (addr & 0x0fff); + } + + if(chr_mode == 2) { + bank = (addr < 0x0800) ? chr_bg_bank[1] + :/*addr < 0x1000 */ chr_bg_bank[3]; + return (bank * 0x0800) + (addr & 0x07ff); + } + + if(chr_mode == 3) { + bank = (addr < 0x0400) ? chr_bg_bank[0] + : (addr < 0x0800) ? chr_bg_bank[1] + : (addr < 0x0c00) ? chr_bg_bank[2] + :/*addr < 0x1000 */ chr_bg_bank[3]; + return (bank * 0x0400) + (addr & 0x03ff); + } +} + +void blank() { + in_frame = false; +} + +void scanline() { + hcounter = 0; + + if(in_frame == false) { + in_frame = true; + irq_pending = false; + vcounter = 0; + } else { + if(vcounter == irq_line) irq_pending = true; + vcounter++; + } + + cpu_cycle_counter = 0; +} + +uint8 chr_read(unsigned addr) { + chr_access[0] = chr_access[1]; + chr_access[1] = chr_access[2]; + chr_access[2] = chr_access[3]; + chr_access[3] = addr; + + //detect two unused nametable fetches at end of each scanline + if((chr_access[0] & 0x2000) == 0 + && (chr_access[1] & 0x2000) + && (chr_access[2] & 0x2000) + && (chr_access[3] & 0x2000)) scanline(); + + unsigned lx = hcounter; + hcounter += 2; + + if(addr & 0x2000) { + switch(nametable_mode[(addr >> 10) & 3]) { + case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff)); + case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff)); + case 2: return (exram_mode < 2) ? exram[addr & 0x03ff] : 0x00; + case 3: return fillmode_tile; + } + } + + if(sprite_8x16 == false) { + return board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr)); + } + + if(lx < 256) return board.chrrom.read(chr_bg_addr(addr)); + if(lx < 320) return board.chrrom.read(chr_sprite_addr(addr)); + /* lx < 340*/return board.chrrom.read(chr_bg_addr(addr)); +} + +void chr_write(unsigned addr, uint8 data) { + if(addr & 0x2000) { + switch(nametable_mode[(addr >> 10) & 3]) { + case 0: return ppu.ciram_write(0x0000 | (addr & 0x03ff), data); + case 1: return ppu.ciram_write(0x0400 | (addr & 0x03ff), data); + case 2: if(exram_mode < 2) exram[addr & 0x03ff] = data; return; + case 3: fillmode_tile = data; return; + } + } +} + +void power() { + reset(); +} + +void reset() { + for(auto &n : exram) n = 0xff; + + prg_mode = 3; + chr_mode = 0; + for(auto &n : prgram_write_protect) n = 0; + exram_mode = 0; + for(auto &n : nametable_mode) n = 0; + fillmode_tile = 0; + fillmode_color = 0; + ram_select = 0; + ram_bank = 0; + prg_bank[0] = 0x00; + prg_bank[1] = 0x00; + prg_bank[2] = 0x00; + prg_bank[3] = 0xff; + for(auto &n : chr_sprite_bank) n = 0; + for(auto &n : chr_bg_bank) n = 0; + chr_bank_hi = 0; + vs_enable = 0; + vs_side = 0; + vs_tile = 0; + vs_scroll = 0; + vs_bank = 0; + irq_line = 0; + irq_enable = 0; + multiplicand = 0; + multiplier = 0; + + cpu_cycle_counter = 0; + irq_counter = 0; + irq_pending = 0; + in_frame = 0; + vcounter = 0; + hcounter = 0; + for(auto &n : chr_access) n = 0; + chr_active = 0; + sprite_8x16 = 0; +} + +void serialize(serializer &s) { + s.array(exram); + + s.integer(prg_mode); + s.integer(chr_mode); + for(auto &n : prgram_write_protect) s.integer(n); + s.integer(exram_mode); + for(auto &n : nametable_mode) s.integer(n); + s.integer(fillmode_tile); + s.integer(fillmode_color); + s.integer(ram_select); + s.integer(ram_bank); + for(auto &n : prg_bank) s.integer(n); + for(auto &n : chr_sprite_bank) s.integer(n); + for(auto &n : chr_bg_bank) s.integer(n); + s.integer(chr_bank_hi); + s.integer(vs_enable); + s.integer(vs_side); + s.integer(vs_tile); + s.integer(vs_scroll); + s.integer(vs_bank); + s.integer(irq_line); + s.integer(irq_enable); + s.integer(multiplicand); + s.integer(multiplier); + + s.integer(cpu_cycle_counter); + s.integer(irq_counter); + s.integer(irq_pending); + s.integer(in_frame); + + s.integer(vcounter); + s.integer(hcounter); + for(auto &n : chr_access) s.integer(n); + s.integer(chr_active); + s.integer(sprite_8x16); +} + +MMC5(Board &board) : Chip(board) { + revision = Revision::MMC5; +} + +}; diff --git a/bsnes/nes/cartridge/ines.cpp b/bsnes/nes/cartridge/ines.cpp index 784338ab5..71636c98b 100755 --- a/bsnes/nes/cartridge/ines.cpp +++ b/bsnes/nes/cartridge/ines.cpp @@ -46,6 +46,12 @@ static string iNES(const uint8_t *data, unsigned size) { prgram = 8192; break; + case 5: + output.append("\tboard type:NES-ELROM\n"); + output.append("\t\tchip type:MMC5\n"); + prgram = 65536; + break; + case 7: output.append("\tboard type:NES-AOROM\n"); break; diff --git a/bsnes/nes/cpu/memory/memory.cpp b/bsnes/nes/cpu/memory/memory.cpp index 1078e5aec..3192edd97 100755 --- a/bsnes/nes/cpu/memory/memory.cpp +++ b/bsnes/nes/cpu/memory/memory.cpp @@ -16,8 +16,8 @@ uint8 CPU::op_read(uint16 addr) { } void CPU::op_write(uint16 addr, uint8 data) { - add_clocks(12); bus.write(addr, regs.mdr = data); + add_clocks(12); } // diff --git a/bsnes/nes/ppu/ppu.cpp b/bsnes/nes/ppu/ppu.cpp index 05db6258b..a3233fb64 100755 --- a/bsnes/nes/ppu/ppu.cpp +++ b/bsnes/nes/ppu/ppu.cpp @@ -468,11 +468,11 @@ void PPU::raster_scanline() { } //336-339 - unsigned nametable = chr_load(0x2000 | (status.vaddr & 0x0fff)); + chr_load(0x2000 | (status.vaddr & 0x0fff)); tick(); tick(); - unsigned attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5)); + chr_load(0x2000 | (status.vaddr & 0x0fff)); tick(); tick(); diff --git a/bsnes/ui/config/config.cpp b/bsnes/ui/config/config.cpp index bcbc80760..b3e5731f2 100755 --- a/bsnes/ui/config/config.cpp +++ b/bsnes/ui/config/config.cpp @@ -6,9 +6,12 @@ Config::Config() { attach(video.filter = "None", "Video::Filter"); attach(video.shader = "None", "Video::Shader"); attach(video.synchronize = true, "Video::Synchronize"); - attach(video.enableOverscan = false, "Video::EnableOverscan"); attach(video.correctAspectRatio = true, "Video::CorrectAspectRatio"); + attach(video.maskOverscan = false, "Video::MaskOverscan"); + attach(video.maskOverscanHorizontal = 8, "Video::MaskOverscanHorizontal"); + attach(video.maskOverscanVertical = 8, "Video::MaskOverscanVertical"); + attach(video.brightness = 100, "Video::Brightness"); attach(video.contrast = 100, "Video::Contrast"); attach(video.gamma = 100, "Video::Gamma"); diff --git a/bsnes/ui/config/config.hpp b/bsnes/ui/config/config.hpp index d8ffc3a6c..96e88f4a7 100755 --- a/bsnes/ui/config/config.hpp +++ b/bsnes/ui/config/config.hpp @@ -4,9 +4,12 @@ struct Config : public configuration { string filter; string shader; bool synchronize; - bool enableOverscan; bool correctAspectRatio; + bool maskOverscan; + unsigned maskOverscanHorizontal; + unsigned maskOverscanVertical; + unsigned brightness; unsigned contrast; unsigned gamma; diff --git a/bsnes/ui/general/main-window.cpp b/bsnes/ui/general/main-window.cpp index c9f5ec739..acddd91c1 100755 --- a/bsnes/ui/general/main-window.cpp +++ b/bsnes/ui/general/main-window.cpp @@ -70,10 +70,10 @@ MainWindow::MainWindow() { settingsSynchronizeVideo.setChecked(config->video.synchronize); settingsSynchronizeAudio.setText("Synchronize Audio"); settingsSynchronizeAudio.setChecked(config->audio.synchronize); - settingsEnableOverscan.setText("Enable Overscan"); - settingsEnableOverscan.setChecked(config->video.enableOverscan); settingsCorrectAspectRatio.setText("Correct Aspect Ratio"); settingsCorrectAspectRatio.setChecked(config->video.correctAspectRatio); + settingsMaskOverscan.setText("Mask Overscan"); + settingsMaskOverscan.setChecked(config->video.maskOverscan); settingsMuteAudio.setText("Mute Audio"); settingsMuteAudio.setChecked(config->audio.mute); settingsConfiguration.setText("Configuration ..."); @@ -162,8 +162,8 @@ MainWindow::MainWindow() { settingsMenu.append(settingsSynchronizeVideo); settingsMenu.append(settingsSynchronizeAudio); settingsMenu.append(settingsSeparator2); - settingsMenu.append(settingsEnableOverscan); settingsMenu.append(settingsCorrectAspectRatio); + settingsMenu.append(settingsMaskOverscan); settingsMenu.append(settingsMuteAudio); settingsMenu.append(settingsSeparator3); settingsMenu.append(settingsConfiguration); @@ -278,16 +278,15 @@ MainWindow::MainWindow() { audio.set(Audio::Synchronize, config->audio.synchronize); }; - settingsEnableOverscan.onTick = [&] { - config->video.enableOverscan = settingsEnableOverscan.checked(); - utility->resizeMainWindow(); - }; - settingsCorrectAspectRatio.onTick = [&] { config->video.correctAspectRatio = settingsCorrectAspectRatio.checked(); utility->resizeMainWindow(); }; + settingsMaskOverscan.onTick = [&] { + config->video.maskOverscan = settingsMaskOverscan.checked(); + }; + settingsMuteAudio.onTick = [&] { config->audio.mute = settingsMuteAudio.checked(); dspaudio.setVolume(config->audio.mute == false ? 1.0 : 0.0); diff --git a/bsnes/ui/general/main-window.hpp b/bsnes/ui/general/main-window.hpp index 8ab0372a3..bb003a74e 100755 --- a/bsnes/ui/general/main-window.hpp +++ b/bsnes/ui/general/main-window.hpp @@ -53,8 +53,8 @@ struct MainWindow : Window { CheckItem settingsSynchronizeVideo; CheckItem settingsSynchronizeAudio; Separator settingsSeparator2; - CheckItem settingsEnableOverscan; CheckItem settingsCorrectAspectRatio; + CheckItem settingsMaskOverscan; CheckItem settingsMuteAudio; Separator settingsSeparator3; Item settingsConfiguration; diff --git a/bsnes/ui/interface/gameboy.cpp b/bsnes/ui/interface/gameboy.cpp index 748abed6d..0c8a3d185 100755 --- a/bsnes/ui/interface/gameboy.cpp +++ b/bsnes/ui/interface/gameboy.cpp @@ -1,7 +1,7 @@ bool InterfaceGameBoy::loadCartridge(const string &filename) { uint8_t *data; unsigned size; - if(file::read(filename, data, size) == false) return false; + if(interface->loadFile(filename, data, size) == false) return false; interface->unloadCartridge(); interface->baseName = nall::basename(filename); diff --git a/bsnes/ui/interface/interface.cpp b/bsnes/ui/interface/interface.cpp index a42ae023d..c61036d33 100755 --- a/bsnes/ui/interface/interface.cpp +++ b/bsnes/ui/interface/interface.cpp @@ -192,6 +192,29 @@ Interface::Interface() { //internal +bool Interface::loadFile(const string &filename, uint8_t *&data, unsigned &size) { + if(file::read(filename, data, size) == false) return false; + + string patchname = { nall::basename(filename), ".bps" }; + if(file::exists(patchname) == false) return true; + + bpspatch bps; + bps.modify(patchname); + bps.source(data, size); + unsigned targetSize = bps.size(); + uint8_t *targetData = new uint8_t[targetSize]; + bps.target(targetData, targetSize); + if(bps.apply() != bpspatch::result::success) { + delete[] targetData; + return true; + } + + delete[] data; + data = targetData; + size = targetSize; + return true; +} + //RGB555 input void Interface::videoRefresh(const uint16_t *input, unsigned inputPitch, unsigned width, unsigned height) { uint32_t *output; diff --git a/bsnes/ui/interface/interface.hpp b/bsnes/ui/interface/interface.hpp index 26db893e1..83f37164f 100755 --- a/bsnes/ui/interface/interface.hpp +++ b/bsnes/ui/interface/interface.hpp @@ -46,6 +46,7 @@ struct Interface : property { Interface(); + bool loadFile(const string &filename, uint8_t *&data, unsigned &size); void videoRefresh(const uint16_t *input, unsigned inputPitch, unsigned width, unsigned height); string baseName; // = "/path/to/cartridge" (no extension) diff --git a/bsnes/ui/interface/nes.cpp b/bsnes/ui/interface/nes.cpp index 8c3b6991e..3fff497d4 100755 --- a/bsnes/ui/interface/nes.cpp +++ b/bsnes/ui/interface/nes.cpp @@ -14,8 +14,9 @@ void InterfaceNES::setController(bool port, unsigned device) { } bool InterfaceNES::loadCartridge(const string &filename) { - filemap fp; - if(fp.open(filename, filemap::mode::read) == false) return false; + uint8_t *data; + unsigned size; + if(interface->loadFile(filename, data, size) == false) return false; interface->unloadCartridge(); interface->baseName = nall::basename(filename); @@ -23,10 +24,11 @@ bool InterfaceNES::loadCartridge(const string &filename) { string markup; markup.readfile({ interface->baseName, ".bml" }); - NES::Interface::loadCartridge(markup, fp.data(), fp.size()); - fp.close(); + NES::Interface::loadCartridge(markup, data, size); + delete[] data; if(NES::Interface::memorySize(NES::Interface::Memory::RAM) > 0) { + filemap fp; if(fp.open(string{ interface->baseName, ".sav" }, filemap::mode::read)) { memcpy(NES::Interface::memoryData(NES::Interface::Memory::RAM), fp.data(), min(NES::Interface::memorySize(NES::Interface::Memory::RAM), fp.size()) @@ -80,14 +82,17 @@ void InterfaceNES::videoRefresh(const uint16_t *data) { } } - if(config->video.enableOverscan == false) { + if(config->video.maskOverscan) { + unsigned osw = config->video.maskOverscanHorizontal; + unsigned osh = config->video.maskOverscanVertical; + for(unsigned y = 0; y < 240; y++) { uint16_t *dp = output + y * 256; - if(y < 16 || y >= 224) { + if(y < osh || y >= 240 - osh) { memset(dp, 0, 256 * 2); } else { - memset(dp + 0, 0, 8 * 2); - memset(dp + 248, 0, 8 * 2); + memset(dp + 0, 0, osw * 2); + memset(dp + 256 - osw, 0, osw * 2); } } } diff --git a/bsnes/ui/interface/snes.cpp b/bsnes/ui/interface/snes.cpp index 87baa3b34..64d6637d1 100755 --- a/bsnes/ui/interface/snes.cpp +++ b/bsnes/ui/interface/snes.cpp @@ -24,7 +24,7 @@ void InterfaceSNES::setController(bool port, unsigned device) { bool InterfaceSNES::loadCartridge(const string &basename) { uint8_t *data; unsigned size; - if(file::read(basename, data, size) == false) return false; + if(interface->loadFile(basename, data, size) == false) return false; interface->unloadCartridge(); interface->baseName = nall::basename(basename); @@ -45,8 +45,8 @@ bool InterfaceSNES::loadCartridge(const string &basename) { bool InterfaceSNES::loadSatellaviewSlottedCartridge(const string &basename, const string &slotname) { uint8_t *data[2]; unsigned size[2]; - if(file::read(basename, data[0], size[0]) == false) return false; - file::read(slotname, data[1], size[1]); + if(interface->loadFile(basename, data[0], size[0]) == false) return false; + interface->loadFile(slotname, data[1], size[1]); interface->unloadCartridge(); interface->baseName = nall::basename(basename); @@ -69,8 +69,8 @@ bool InterfaceSNES::loadSatellaviewSlottedCartridge(const string &basename, cons bool InterfaceSNES::loadSatellaviewCartridge(const string &basename, const string &slotname) { uint8_t *data[2]; unsigned size[2]; - if(file::read(basename, data[0], size[0]) == false) return false; - file::read(slotname, data[1], size[1]); + if(interface->loadFile(basename, data[0], size[0]) == false) return false; + interface->loadFile(slotname, data[1], size[1]); interface->unloadCartridge(); interface->baseName = nall::basename(basename); @@ -93,9 +93,9 @@ bool InterfaceSNES::loadSatellaviewCartridge(const string &basename, const strin bool InterfaceSNES::loadSufamiTurboCartridge(const string &basename, const string &slotAname, const string &slotBname) { uint8_t *data[3]; unsigned size[3]; - if(file::read(basename, data[0], size[0]) == false) return false; - file::read(slotAname, data[1], size[1]); - file::read(slotBname, data[2], size[2]); + if(interface->loadFile(basename, data[0], size[0]) == false) return false; + interface->loadFile(slotAname, data[1], size[1]); + interface->loadFile(slotBname, data[2], size[2]); interface->unloadCartridge(); interface->baseName = nall::basename(basename); @@ -121,8 +121,8 @@ bool InterfaceSNES::loadSufamiTurboCartridge(const string &basename, const strin bool InterfaceSNES::loadSuperGameBoyCartridge(const string &basename, const string &slotname) { uint8_t *data[2]; unsigned size[2]; - if(file::read(basename, data[0], size[0]) == false) return false; - file::read(slotname, data[1], size[1]); + if(interface->loadFile(basename, data[0], size[0]) == false) return false; + interface->loadFile(slotname, data[1], size[1]); interface->unloadCartridge(); interface->baseName = nall::basename(basename); @@ -211,11 +211,17 @@ void InterfaceSNES::videoRefresh(const uint32_t *data, bool hires, bool interlac } } - if(config->video.enableOverscan == false) { - unsigned mask = 8 << interlace; + if(config->video.maskOverscan) { + unsigned osw = config->video.maskOverscanHorizontal << hires; + unsigned osh = config->video.maskOverscanVertical << interlace; + for(unsigned y = 0; y < height; y++) { - if(y < mask || y >= height - mask) { - memset(output + y * 512, 0, width * 2); + uint16_t *dp = output + y * 512; + if(y < osh || y >= height - osh) { + memset(dp, 0, width * 2); + } else { + memset(dp + 0, 0, osw * 2); + memset(dp + width - osw, 0, osw * 2); } } } diff --git a/bsnes/ui/main.cpp b/bsnes/ui/main.cpp index 011d538f1..e26c9a29e 100755 --- a/bsnes/ui/main.cpp +++ b/bsnes/ui/main.cpp @@ -49,7 +49,7 @@ Application::Application(int argc, char **argv) { inputManager = new InputManager; utility = new Utility; - title = "bsnes v082.30"; + title = "bsnes v082.31"; string fontFamily = Intrinsics::platform() == Intrinsics::Platform::Windows ? "Tahoma, " : "Sans, "; normalFont = { fontFamily, "8" }; diff --git a/bsnes/ui/settings/video.cpp b/bsnes/ui/settings/video.cpp index 5e50f067c..ce7807535 100755 --- a/bsnes/ui/settings/video.cpp +++ b/bsnes/ui/settings/video.cpp @@ -17,6 +17,12 @@ VideoSettings::VideoSettings() { contrast.name.setText("Contrast:"); gamma.name.setText("Gamma:"); gammaRamp.setText("Enable gamma ramp simulation"); + overscanAdjustment.setFont(application->boldFont); + overscanAdjustment.setText("Overscan mask:"); + overscanHorizontal.name.setText("Horizontal:"); + overscanHorizontal.slider.setLength(17); + overscanVertical.name.setText("Vertical:"); + overscanVertical.slider.setLength(17); fullScreenMode.setFont(application->boldFont); fullScreenMode.setText("Fullscreen mode:"); fullScreen[0].setText("Center"); @@ -30,6 +36,9 @@ VideoSettings::VideoSettings() { append(contrast, ~0, 0); append(gamma, ~0, 0); append(gammaRamp, ~0, 0, 5); + append(overscanAdjustment, ~0, 0); + append(overscanHorizontal, ~0, 0); + append(overscanVertical, ~0, 0, 5); append(fullScreenMode, ~0, 0); append(fullScreenLayout, ~0, 0); fullScreenLayout.append(fullScreen[0], ~0, 0, 5); @@ -40,12 +49,15 @@ VideoSettings::VideoSettings() { contrast.slider.setPosition(config->video.contrast); gamma.slider.setPosition(config->video.gamma); gammaRamp.setChecked(config->video.gammaRamp); + overscanHorizontal.slider.setPosition(config->video.maskOverscanHorizontal); + overscanVertical.slider.setPosition(config->video.maskOverscanVertical); fullScreen[config->video.fullScreenMode].setChecked(); synchronize(); - brightness.slider.onChange = contrast.slider.onChange = gamma.slider.onChange = - gammaRamp.onTick = fullScreen[0].onTick = fullScreen[1].onTick = fullScreen[2].onTick = + brightness.slider.onChange = contrast.slider.onChange = gamma.slider.onChange = gammaRamp.onTick = + overscanHorizontal.slider.onChange = overscanVertical.slider.onChange = + fullScreen[0].onTick = fullScreen[1].onTick = fullScreen[2].onTick = { &VideoSettings::synchronize, this }; } @@ -54,6 +66,8 @@ void VideoSettings::synchronize() { config->video.contrast = contrast.slider.position(); config->video.gamma = gamma.slider.position(); config->video.gammaRamp = gammaRamp.checked(); + config->video.maskOverscanHorizontal = overscanHorizontal.slider.position(); + config->video.maskOverscanVertical = overscanVertical.slider.position(); if(fullScreen[0].checked()) config->video.fullScreenMode = 0; if(fullScreen[1].checked()) config->video.fullScreenMode = 1; if(fullScreen[2].checked()) config->video.fullScreenMode = 2; @@ -62,5 +76,8 @@ void VideoSettings::synchronize() { contrast.value.setText({ config->video.contrast, "%" }); gamma.value.setText({ config->video.gamma, "%" }); + overscanHorizontal.value.setText({ config->video.maskOverscanHorizontal, "px" }); + overscanVertical.value.setText({ config->video.maskOverscanVertical, "px" }); + palette.update(); } diff --git a/bsnes/ui/settings/video.hpp b/bsnes/ui/settings/video.hpp index 55a04d9d9..1164473cf 100755 --- a/bsnes/ui/settings/video.hpp +++ b/bsnes/ui/settings/video.hpp @@ -13,6 +13,9 @@ struct VideoSettings : SettingsLayout { VideoSlider contrast; VideoSlider gamma; CheckBox gammaRamp; + Label overscanAdjustment; + VideoSlider overscanHorizontal; + VideoSlider overscanVertical; Label fullScreenMode; HorizontalLayout fullScreenLayout; RadioBox fullScreen[3];