mirror of https://github.com/bsnes-emu/bsnes.git
Update to v082r31 release.
byuu says: Enable Overscan->Mask Overscan [best I'm doing] Video settings -> Overscan mask: (horizontal, vertical: 0-16 on each side) [only works on NES+SNES] BPS patching works for NES+SNES+GB; note that long-term I want BPS to only patch headerless PRG+CHR files, but we'll need a database / completed board mapping system first. MMC1 splits the board/chip markups a bit better. My attempts to emulate the extra CHR bits per hardware fail repeatedly. Docs do not explain how it works at all. Emulated enough of the MMC5 to play Castlevania 3. The MMC5 is easily the most complicated mapper the NES has to offer, and of course, has the most pitifully vague and difficult documentation of any mapper around. It seems the only way anyone is able to emulate this chip is empirically. Everyone else apparently hooks the MMC5 right into the PPU core, which I of course cannot do. So I had to come up with my own (probably wrong) way to synchronize the PPU simply by observing CHR bus accesses. I must say, I over-estimated how well fleshed out the NES hardware documentation was. Shit hits the fan right after MMC3. It's miles beyond the GB scene, but I find myself wanting for someone with the technical writing ability of anomie. I can't find anything at all on how we're supposed to support the $2007 port reads/writes without it extra-clocking the PPU's bus, which could throw off mapper timing. Absolutely nothing at all on the subject anywhere, something everybody is required to do for all cycle-based emulators and ... nada. Anyway, I'd like to refine the MMC5 a bit, getting Just Breed playable even without sound would be really nice (it's a fun game.) Then we need to get libsnes building again (ugh, getting worn out in backporting changes to it.) Once v083 is public, we can start discussing a new API for multiple emulators.
This commit is contained in:
parent
4cbaf4e4ec
commit
4c47cc203f
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "mmc1.cpp"
|
||||
#include "mmc3.cpp"
|
||||
#include "mmc5.cpp"
|
||||
#include "vrc6.cpp"
|
||||
|
||||
void Chip::tick() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -46,6 +46,7 @@ struct Interface : property<Interface> {
|
|||
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in New Issue