From ef85f7ccb08b68453848265a3ee4b35733efea4e Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 12 Oct 2011 23:03:58 +1100 Subject: [PATCH] Update to v082r33 release. byuu says: Added MMC2, MMC4, VRC4, VRC7 (no audio.) Split NES audio code up into individual modules. Fixed libsnes to compile: Themaister, can you please test to make sure it works? I don't have a libsnes client on my work PC to test it. Added about / license information to bottom of advanced settings screen for now (better than nothing, I guess.) Blocked PPU reads/writes while rendering for now, easier than coming up with a bus address locking thing :/ I can't seem to fix MMC5 graphics during the intro to Uchuu Keibitai. Without that, trying to implement vertical-split screen mode doesn't make sense. So as far as special audio chips go ... * VRC6 is completed * Sunsoft 5B has everything the only game to use it uses, but there are more unused channels I'd like to support anyway (they aren't documented, though.) * MMC5 audio unsupported for now * VRC7 audio unsupported, probably for a long time (hardest audio driver of all. More complex than core NES APU.) * audio PCM games (Moero Pro Yakyuu!) I probably won't ever support (they require external WAV packs.) --- bsnes/gameboy/gameboy.hpp | 2 +- bsnes/nes/apu/apu.cpp | 373 +++--------------- bsnes/nes/apu/apu.hpp | 118 +----- bsnes/nes/apu/dmc.cpp | 117 ++++++ bsnes/nes/apu/dmc.hpp | 32 ++ bsnes/nes/apu/envelope.cpp | 39 ++ bsnes/nes/apu/envelope.hpp | 16 + bsnes/nes/apu/noise.cpp | 57 +++ bsnes/nes/apu/noise.hpp | 18 + bsnes/nes/apu/pulse.cpp | 51 +++ bsnes/nes/apu/pulse.hpp | 20 + bsnes/nes/apu/serialization.cpp | 88 +---- bsnes/nes/apu/sweep.cpp | 53 +++ bsnes/nes/apu/sweep.hpp | 16 + bsnes/nes/apu/triangle.cpp | 58 +++ bsnes/nes/apu/triangle.hpp | 21 + bsnes/nes/cartridge/board/bandai-fcg.cpp | 2 +- bsnes/nes/cartridge/board/board.cpp | 33 +- bsnes/nes/cartridge/board/board.hpp | 16 +- bsnes/nes/cartridge/board/konami-vrc4.cpp | 61 +++ bsnes/nes/cartridge/board/konami-vrc6.cpp | 3 +- bsnes/nes/cartridge/board/konami-vrc7.cpp | 47 +++ bsnes/nes/cartridge/board/nes-axrom.cpp | 3 +- bsnes/nes/cartridge/board/nes-bnrom.cpp | 3 +- bsnes/nes/cartridge/board/nes-cnrom.cpp | 3 +- bsnes/nes/cartridge/board/nes-exrom.cpp | 4 + bsnes/nes/cartridge/board/nes-fxrom.cpp | 91 +++++ bsnes/nes/cartridge/board/nes-gxrom.cpp | 3 +- bsnes/nes/cartridge/board/nes-nrom.cpp | 7 +- bsnes/nes/cartridge/board/nes-pxrom.cpp | 97 +++++ bsnes/nes/cartridge/board/nes-sxrom.cpp | 4 - bsnes/nes/cartridge/board/nes-txrom.cpp | 6 +- bsnes/nes/cartridge/board/nes-uxrom.cpp | 5 +- bsnes/nes/cartridge/board/sunsoft-5b.cpp | 4 +- bsnes/nes/cartridge/cartridge.cpp | 8 +- bsnes/nes/cartridge/cartridge.hpp | 4 + bsnes/nes/cartridge/chip/chip.cpp | 2 + bsnes/nes/cartridge/chip/mmc1.cpp | 1 - bsnes/nes/cartridge/chip/mmc3.cpp | 1 - bsnes/nes/cartridge/chip/mmc5.cpp | 8 +- bsnes/nes/cartridge/chip/vrc4.cpp | 183 +++++++++ bsnes/nes/cartridge/chip/vrc6.cpp | 1 - bsnes/nes/cartridge/chip/vrc7.cpp | 154 ++++++++ bsnes/nes/cartridge/ines.cpp | 27 ++ bsnes/nes/cpu/cpu.cpp | 2 - bsnes/nes/nes.hpp | 3 +- bsnes/nes/ppu/ppu.cpp | 6 +- bsnes/nes/system/system.cpp | 1 + bsnes/phoenix/phoenix.cpp | 1 + .../alt/ppu-compatibility/render/line.cpp | 6 +- bsnes/snes/interface/interface.hpp | 2 +- bsnes/snes/snes.hpp | 2 +- bsnes/ui-libsnes/libsnes.cpp | 69 +++- bsnes/ui/interface/snes.cpp | 10 + bsnes/ui/interface/snes.hpp | 4 +- bsnes/ui/main.cpp | 2 +- bsnes/ui/settings/advanced.cpp | 4 + bsnes/ui/settings/advanced.hpp | 2 + 58 files changed, 1378 insertions(+), 596 deletions(-) create mode 100755 bsnes/nes/apu/dmc.cpp create mode 100755 bsnes/nes/apu/dmc.hpp create mode 100755 bsnes/nes/apu/envelope.cpp create mode 100755 bsnes/nes/apu/envelope.hpp create mode 100755 bsnes/nes/apu/noise.cpp create mode 100755 bsnes/nes/apu/noise.hpp create mode 100755 bsnes/nes/apu/pulse.cpp create mode 100755 bsnes/nes/apu/pulse.hpp create mode 100755 bsnes/nes/apu/sweep.cpp create mode 100755 bsnes/nes/apu/sweep.hpp create mode 100755 bsnes/nes/apu/triangle.cpp create mode 100755 bsnes/nes/apu/triangle.hpp create mode 100755 bsnes/nes/cartridge/board/konami-vrc4.cpp create mode 100755 bsnes/nes/cartridge/board/konami-vrc7.cpp create mode 100755 bsnes/nes/cartridge/board/nes-fxrom.cpp create mode 100755 bsnes/nes/cartridge/board/nes-pxrom.cpp create mode 100755 bsnes/nes/cartridge/chip/vrc4.cpp create mode 100755 bsnes/nes/cartridge/chip/vrc7.cpp diff --git a/bsnes/gameboy/gameboy.hpp b/bsnes/gameboy/gameboy.hpp index 0fa43551..f4207e68 100755 --- a/bsnes/gameboy/gameboy.hpp +++ b/bsnes/gameboy/gameboy.hpp @@ -87,7 +87,7 @@ namespace GameBoy { clock = 0; } - inline Processor() : thread(0) {} + inline Processor() : thread(nullptr) {} }; #include diff --git a/bsnes/nes/apu/apu.cpp b/bsnes/nes/apu/apu.cpp index 9bbfa1ce..5a41bcd8 100755 --- a/bsnes/nes/apu/apu.cpp +++ b/bsnes/nes/apu/apu.cpp @@ -2,6 +2,12 @@ namespace NES { +#include "envelope.cpp" +#include "sweep.cpp" +#include "pulse.cpp" +#include "triangle.cpp" +#include "noise.cpp" +#include "dmc.cpp" #include "serialization.cpp" APU apu; @@ -36,17 +42,17 @@ void APU::main() { scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); } - unsigned rectangle_output, triangle_output, noise_output, dmc_output; + unsigned pulse_output, triangle_output, noise_output, dmc_output; - rectangle_output = rectangle[0].clock(); - rectangle_output += rectangle[1].clock(); + pulse_output = pulse[0].clock(); + pulse_output += pulse[1].clock(); triangle_output = triangle.clock(); noise_output = noise.clock(); dmc_output = dmc.clock(); clock_frame_counter_divider(); - signed output = rectangle_dac[rectangle_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output]; + signed output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output]; output = filter.run_hipass_strong(output); output += cartridge_sample; @@ -78,79 +84,21 @@ void APU::power() { filter.hipass_weak = 0; filter.lopass = 0; - for(unsigned n = 0; n < 2; n++) { - rectangle[n].sweep.shift = 0; - rectangle[n].sweep.decrement = 0; - rectangle[n].sweep.period = 0; - rectangle[n].sweep.counter = 1; - rectangle[n].sweep.enable = 0; - rectangle[n].sweep.reload = 0; - rectangle[n].sweep.rectangle_period = 0; - } - - reset(); + pulse[0].power(); + pulse[1].power(); + triangle.power(); + noise.power(); + dmc.power(); } void APU::reset() { Processor::create(APU::Main, 21477272); - for(unsigned n = 0; n < 2; n++) { - rectangle[n].length_counter = 0; - - rectangle[n].envelope.speed = 0; - rectangle[n].envelope.use_speed_as_volume = 0; - rectangle[n].envelope.loop_mode = 0; - rectangle[n].envelope.reload_decay = 0; - rectangle[n].envelope.decay_counter = 0; - rectangle[n].envelope.decay_volume = 0; - - rectangle[n].duty = 0; - rectangle[n].duty_counter = 0; - rectangle[n].period = 0; - rectangle[n].period_counter = 1; - } - - triangle.length_counter = 0; - - triangle.linear_length = 0; - triangle.halt_length_counter = 0; - triangle.period = 0; - triangle.period_counter = 1; - triangle.step_counter = 0; - triangle.linear_length_counter = 0; - triangle.reload_linear = 0; - - noise.length_counter = 0; - - noise.envelope.speed = 0; - noise.envelope.use_speed_as_volume = 0; - noise.envelope.loop_mode = 0; - noise.envelope.reload_decay = 0; - noise.envelope.decay_counter = 0; - noise.envelope.decay_volume = 0; - - noise.period = 0; - noise.period_counter = 1; - noise.short_mode = 0; - noise.lfsr = 1; - - dmc.length_counter = 0; - dmc.irq_pending = 0; - - dmc.period = 0; - dmc.period_counter = ntsc_dmc_period_table[0]; - dmc.irq_enable = 0; - dmc.loop_mode = 0; - dmc.dac_latch = 0; - dmc.addr_latch = 0; - dmc.length_latch = 0; - dmc.read_addr = 0; - dmc.dma_delay_counter = 0; - dmc.bit_counter = 0; - dmc.have_dma_buffer = 0; - dmc.dma_buffer = 0; - dmc.have_sample = 0; - dmc.sample = 0; + pulse[0].reset(); + pulse[1].reset(); + triangle.reset(); + noise.reset(); + dmc.reset(); frame.irq_pending = 0; @@ -167,13 +115,13 @@ void APU::reset() { uint8 APU::read(uint16 addr) { if(addr == 0x4015) { uint8 result = 0x00; - result |= rectangle[0].length_counter ? 0x01 : 0; - result |= rectangle[1].length_counter ? 0x02 : 0; - result |= triangle.length_counter ? 0x04 : 0; - result |= noise.length_counter ? 0x08 : 0; - result |= dmc.length_counter ? 0x10 : 0; - result |= frame.irq_pending ? 0x40 : 0; - result |= dmc.irq_pending ? 0x80 : 0; + result |= pulse[0].length_counter ? 0x01 : 0; + result |= pulse[1].length_counter ? 0x02 : 0; + result |= triangle.length_counter ? 0x04 : 0; + result |= noise.length_counter ? 0x08 : 0; + result |= dmc.length_counter ? 0x10 : 0; + result |= frame.irq_pending ? 0x40 : 0; + result |= dmc.irq_pending ? 0x80 : 0; frame.irq_pending = false; set_irq_line(); @@ -185,42 +133,38 @@ uint8 APU::read(uint16 addr) { } void APU::write(uint16 addr, uint8 data) { - const unsigned r = (addr >> 2) & 1; //rectangle# + const unsigned n = (addr >> 2) & 1; //pulse# switch(addr) { case 0x4000: case 0x4004: - rectangle[r].duty = data >> 6; - rectangle[r].envelope.loop_mode = data & 0x20; - rectangle[r].envelope.use_speed_as_volume = data & 0x10; - rectangle[r].envelope.speed = data & 0x0f; + pulse[n].duty = data >> 6; + pulse[n].envelope.loop_mode = data & 0x20; + pulse[n].envelope.use_speed_as_volume = data & 0x10; + pulse[n].envelope.speed = data & 0x0f; break; case 0x4001: case 0x4005: - rectangle[r].sweep.enable = data & 0x80; - rectangle[r].sweep.period = (data & 0x70) >> 4; - rectangle[r].sweep.decrement = data & 0x08; - rectangle[r].sweep.shift = data & 0x07; - rectangle[r].sweep.reload = true; + pulse[n].sweep.enable = data & 0x80; + pulse[n].sweep.period = (data & 0x70) >> 4; + pulse[n].sweep.decrement = data & 0x08; + pulse[n].sweep.shift = data & 0x07; + pulse[n].sweep.reload = true; break; case 0x4002: case 0x4006: - rectangle[r].period &= 0x700; - rectangle[r].period |= data; - rectangle[r].sweep.rectangle_period &= 0x700; - rectangle[r].sweep.rectangle_period |= data; + pulse[n].period = (pulse[n].period & 0x0700) | (data << 0); + pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x0700) | (data << 0); break; case 0x4003: case 0x4007: - rectangle[r].period &= 0xff; - rectangle[r].period |= (data & 7) << 8; - rectangle[r].sweep.rectangle_period &= 0xff; - rectangle[r].sweep.rectangle_period |= (data & 7) << 8; + pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8); + pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x00ff) | (data << 8); - rectangle[r].duty_counter = 7; - rectangle[r].envelope.reload_decay = true; + pulse[n].duty_counter = 7; + pulse[n].envelope.reload_decay = true; - if(enabled_channels & (1 << r)) { - rectangle[r].length_counter = length_counter_table[(data >> 3) & 0x1f]; + if(enabled_channels & (1 << n)) { + pulse[n].length_counter = length_counter_table[(data >> 3) & 0x1f]; } break; @@ -230,13 +174,11 @@ void APU::write(uint16 addr, uint8 data) { break; case 0x400a: - triangle.period &= 0x700; - triangle.period |= data; + triangle.period = (triangle.period & 0x0700) | (data << 0); break; case 0x400b: - triangle.period &= 0xff; - triangle.period |= (data & 7) << 8; + triangle.period = (triangle.period & 0x00ff) | (data << 8); triangle.reload_linear = true; @@ -286,10 +228,10 @@ void APU::write(uint16 addr, uint8 data) { break; case 0x4015: - if((data & 0x01) == 0) rectangle[0].length_counter = 0; - if((data & 0x02) == 0) rectangle[1].length_counter = 0; - if((data & 0x04) == 0) triangle.length_counter = 0; - if((data & 0x08) == 0) noise.length_counter = 0; + if((data & 0x01) == 0) pulse[0].length_counter = 0; + if((data & 0x02) == 0) pulse[1].length_counter = 0; + if((data & 0x04) == 0) triangle.length_counter = 0; + if((data & 0x08) == 0) noise.length_counter = 0; (data & 0x10) ? dmc.start() : dmc.stop(); dmc.irq_pending = false; @@ -327,217 +269,20 @@ signed APU::Filter::run_lopass(signed sample) { return (lopass >> 32); } -bool APU::Sweep::check_period() { - if(rectangle_period > 0x7ff) return false; - - if(decrement == 0) { - if((rectangle_period + (rectangle_period >> shift)) & 0x800) return false; - } - - return true; -} - -void APU::Sweep::clock(unsigned channel) { - if(--counter == 0) { - counter = period + 1; - if(enable && shift && rectangle_period > 8) { - signed delta = rectangle_period >> shift; - - if(decrement) { - rectangle_period -= delta; - if(channel == 0) rectangle_period--; - } else if((rectangle_period + delta) < 0x800) { - rectangle_period += delta; - } - } - } - - if(reload) { - reload = false; - counter = period + 1; - } -} - -unsigned APU::Envelope::volume() const { - return use_speed_as_volume ? speed : decay_volume; -} - -void APU::Envelope::clock() { - if(reload_decay) { - reload_decay = false; - decay_volume = 0x0f; - decay_counter = speed + 1; - return; - } - - if(--decay_counter == 0) { - decay_counter = speed + 1; - if(decay_volume || loop_mode) decay_volume--; - } -} - -void APU::Rectangle::clock_length() { - if(envelope.loop_mode == 0) { - if(length_counter) length_counter--; - } -} - -uint8 APU::Rectangle::clock() { - if(sweep.check_period() == false) return 0; - if(length_counter == 0) return 0; - - static const unsigned duty_table[] = { 1, 2, 4, 6 }; - uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0; - if(sweep.rectangle_period < 0x008) result = 0; - - if(--period_counter == 0) { - period_counter = (sweep.rectangle_period + 1) * 2; - duty_counter++; - } - - return result; -} - -void APU::Triangle::clock_length() { - if(halt_length_counter == 0) { - if(length_counter > 0) length_counter--; - } -} - -void APU::Triangle::clock_linear_length() { - if(reload_linear) { - linear_length_counter = linear_length; - } else if(linear_length_counter) { - linear_length_counter--; - } - - if(halt_length_counter == 0) reload_linear = false; -} - -uint8 APU::Triangle::clock() { - uint8 result = step_counter & 0x0f; - if((step_counter & 0x10) == 0) result ^= 0x0f; - if(length_counter == 0 || linear_length_counter == 0) return result; - - if(--period_counter == 0) { - step_counter++; - period_counter = period + 1; - } - - return result; -} - -void APU::Noise::clock_length() { - if(envelope.loop_mode == 0) { - if(length_counter > 0) length_counter--; - } -} - -uint8 APU::Noise::clock() { - if(length_counter == 0) return 0; - - uint8 result = (lfsr & 1) ? envelope.volume() : 0; - - if(--period_counter == 0) { - unsigned feedback; - - if(short_mode) { - feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1); - } else { - feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1); - } - - lfsr = (lfsr >> 1) | (feedback << 14); - period_counter = apu.ntsc_noise_period_table[period]; - } - - return result; -} - -void APU::DMC::start() { - if(length_counter == 0) { - read_addr = 0x4000 + (addr_latch << 6); - length_counter = (length_latch << 4) + 1; - } -} - -void APU::DMC::stop() { - length_counter = 0; - dma_delay_counter = 0; - cpu.set_rdy_line(1); - cpu.set_rdy_addr({ false, 0u }); -} - -uint8 APU::DMC::clock() { - uint8 result = dac_latch; - - if(dma_delay_counter > 0) { - dma_delay_counter--; - - if(dma_delay_counter == 1) { - cpu.set_rdy_addr({ true, uint16(0x8000 | read_addr) }); - } else if(dma_delay_counter == 0) { - cpu.set_rdy_line(1); - cpu.set_rdy_addr({ false, 0u }); - - dma_buffer = cpu.mdr(); - have_dma_buffer = true; - length_counter--; - read_addr++; - - if(length_counter == 0) { - if(loop_mode) { - start(); - } else if(irq_enable) { - irq_pending = true; - apu.set_irq_line(); - } - } - } - } - - if(--period_counter == 0) { - if(have_sample) { - signed delta = (((sample >> bit_counter) & 1) << 2) - 2; - unsigned data = dac_latch + delta; - if((data & 0x80) == 0) dac_latch = data; - } - - if(++bit_counter == 0) { - if(have_dma_buffer) { - have_sample = true; - sample = dma_buffer; - have_dma_buffer = false; - } else { - have_sample = false; - } - } - - period_counter = ntsc_dmc_period_table[period]; - } - - if(length_counter > 0 && have_dma_buffer == false && dma_delay_counter == 0) { - cpu.set_rdy_line(0); - dma_delay_counter = 4; - } - - return result; -} - void APU::clock_frame_counter() { frame.counter++; if(frame.counter & 1) { - rectangle[0].clock_length(); - rectangle[0].sweep.clock(0); - rectangle[1].clock_length(); - rectangle[1].sweep.clock(1); + pulse[0].clock_length(); + pulse[0].sweep.clock(0); + pulse[1].clock_length(); + pulse[1].sweep.clock(1); triangle.clock_length(); noise.clock_length(); } - rectangle[0].envelope.clock(); - rectangle[1].envelope.clock(); + pulse[0].envelope.clock(); + pulse[1].envelope.clock(); triangle.clock_linear_length(); noise.envelope.clock(); @@ -561,9 +306,9 @@ void APU::clock_frame_counter_divider() { APU::APU() { for(unsigned amp = 0; amp < 32; amp++) { if(amp == 0) { - rectangle_dac[amp] = 0; + pulse_dac[amp] = 0; } else { - rectangle_dac[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0); + pulse_dac[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0); } } diff --git a/bsnes/nes/apu/apu.hpp b/bsnes/nes/apu/apu.hpp index 0d22850f..9d5edd89 100755 --- a/bsnes/nes/apu/apu.hpp +++ b/bsnes/nes/apu/apu.hpp @@ -27,116 +27,12 @@ struct APU : Processor { void serialize(serializer&); } filter; - struct Envelope { - uint4 speed; - bool use_speed_as_volume; - bool loop_mode; - - bool reload_decay; - uint8 decay_counter; - uint4 decay_volume; - - unsigned volume() const; - void clock(); - void serialize(serializer&); - }; - - struct Sweep { - uint8 shift; - bool decrement; - uint3 period; - uint8 counter; - bool enable; - bool reload; - unsigned rectangle_period; - - bool check_period(); - void clock(unsigned channel); - void serialize(serializer&); - }; - - struct Rectangle { - unsigned length_counter; - - Envelope envelope; - Sweep sweep; - - uint2 duty; - uint3 duty_counter; - - uint11 period; - unsigned period_counter; - - void clock_length(); - bool check_period(); - uint8 clock(); - void serialize(serializer&); - } rectangle[2]; - - struct Triangle { - unsigned length_counter; - - uint8 linear_length; - bool halt_length_counter; - - uint11 period; - unsigned period_counter; - - uint5 step_counter; - uint8 linear_length_counter; - bool reload_linear; - - void clock_length(); - void clock_linear_length(); - uint8 clock(); - void serialize(serializer&); - } triangle; - - struct Noise { - unsigned length_counter; - - Envelope envelope; - - uint4 period; - unsigned period_counter; - - bool short_mode; - uint15 lfsr; - - void clock_length(); - uint8 clock(); - void serialize(serializer&); - } noise; - - struct DMC { - unsigned length_counter; - bool irq_pending; - - uint4 period; - unsigned period_counter; - - bool irq_enable; - bool loop_mode; - - uint8 dac_latch; - uint8 addr_latch; - uint8 length_latch; - - uint15 read_addr; - unsigned dma_delay_counter; - - uint3 bit_counter; - bool have_dma_buffer; - uint8 dma_buffer; - - bool have_sample; - uint8 sample; - - void start(); - void stop(); - uint8 clock(); - void serialize(serializer&); - } dmc; + #include "envelope.hpp" + #include "sweep.hpp" + #include "pulse.hpp" + #include "triangle.hpp" + #include "noise.hpp" + #include "dmc.hpp" struct FrameCounter { enum : unsigned { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz) @@ -156,7 +52,7 @@ struct APU : Processor { uint8 enabled_channels; int16 cartridge_sample; - int16 rectangle_dac[32]; + int16 pulse_dac[32]; int16 dmc_triangle_noise_dac[128][16][16]; static const uint8 length_counter_table[32]; diff --git a/bsnes/nes/apu/dmc.cpp b/bsnes/nes/apu/dmc.cpp new file mode 100755 index 00000000..6e0031b9 --- /dev/null +++ b/bsnes/nes/apu/dmc.cpp @@ -0,0 +1,117 @@ +void APU::DMC::start() { + if(length_counter == 0) { + read_addr = 0x4000 + (addr_latch << 6); + length_counter = (length_latch << 4) + 1; + } +} + +void APU::DMC::stop() { + length_counter = 0; + dma_delay_counter = 0; + cpu.set_rdy_line(1); + cpu.set_rdy_addr({ false, 0u }); +} + +uint8 APU::DMC::clock() { + uint8 result = dac_latch; + + if(dma_delay_counter > 0) { + dma_delay_counter--; + + if(dma_delay_counter == 1) { + cpu.set_rdy_addr({ true, uint16(0x8000 | read_addr) }); + } else if(dma_delay_counter == 0) { + cpu.set_rdy_line(1); + cpu.set_rdy_addr({ false, 0u }); + + dma_buffer = cpu.mdr(); + have_dma_buffer = true; + length_counter--; + read_addr++; + + if(length_counter == 0) { + if(loop_mode) { + start(); + } else if(irq_enable) { + irq_pending = true; + apu.set_irq_line(); + } + } + } + } + + if(--period_counter == 0) { + if(have_sample) { + signed delta = (((sample >> bit_counter) & 1) << 2) - 2; + unsigned data = dac_latch + delta; + if((data & 0x80) == 0) dac_latch = data; + } + + if(++bit_counter == 0) { + if(have_dma_buffer) { + have_sample = true; + sample = dma_buffer; + have_dma_buffer = false; + } else { + have_sample = false; + } + } + + period_counter = ntsc_dmc_period_table[period]; + } + + if(length_counter > 0 && have_dma_buffer == false && dma_delay_counter == 0) { + cpu.set_rdy_line(0); + dma_delay_counter = 4; + } + + return result; +} + +void APU::DMC::power() { +} + +void APU::DMC::reset() { + length_counter = 0; + irq_pending = 0; + + period = 0; + period_counter = ntsc_dmc_period_table[0]; + irq_enable = 0; + loop_mode = 0; + dac_latch = 0; + addr_latch = 0; + length_latch = 0; + read_addr = 0; + dma_delay_counter = 0; + bit_counter = 0; + have_dma_buffer = 0; + dma_buffer = 0; + have_sample = 0; + sample = 0; +} + +void APU::DMC::serialize(serializer &s) { + s.integer(length_counter); + s.integer(irq_pending); + + s.integer(period); + s.integer(period_counter); + + s.integer(irq_enable); + s.integer(loop_mode); + + s.integer(dac_latch); + s.integer(addr_latch); + s.integer(length_latch); + + s.integer(read_addr); + s.integer(dma_delay_counter); + + s.integer(bit_counter); + s.integer(have_dma_buffer); + s.integer(dma_buffer); + + s.integer(have_sample); + s.integer(sample); +} diff --git a/bsnes/nes/apu/dmc.hpp b/bsnes/nes/apu/dmc.hpp new file mode 100755 index 00000000..5fcce929 --- /dev/null +++ b/bsnes/nes/apu/dmc.hpp @@ -0,0 +1,32 @@ +struct DMC { + unsigned length_counter; + bool irq_pending; + + uint4 period; + unsigned period_counter; + + bool irq_enable; + bool loop_mode; + + uint8 dac_latch; + uint8 addr_latch; + uint8 length_latch; + + uint15 read_addr; + unsigned dma_delay_counter; + + uint3 bit_counter; + bool have_dma_buffer; + uint8 dma_buffer; + + bool have_sample; + uint8 sample; + + void start(); + void stop(); + uint8 clock(); + + void power(); + void reset(); + void serialize(serializer&); +} dmc; diff --git a/bsnes/nes/apu/envelope.cpp b/bsnes/nes/apu/envelope.cpp new file mode 100755 index 00000000..08b56ee8 --- /dev/null +++ b/bsnes/nes/apu/envelope.cpp @@ -0,0 +1,39 @@ +unsigned APU::Envelope::volume() const { + return use_speed_as_volume ? speed : decay_volume; +} + +void APU::Envelope::clock() { + if(reload_decay) { + reload_decay = false; + decay_volume = 0x0f; + decay_counter = speed + 1; + return; + } + + if(--decay_counter == 0) { + decay_counter = speed + 1; + if(decay_volume || loop_mode) decay_volume--; + } +} + +void APU::Envelope::power() { +} + +void APU::Envelope::reset() { + speed = 0; + use_speed_as_volume = 0; + loop_mode = 0; + reload_decay = 0; + decay_counter = 0; + decay_volume = 0; +} + +void APU::Envelope::serialize(serializer &s) { + s.integer(speed); + s.integer(use_speed_as_volume); + s.integer(loop_mode); + + s.integer(reload_decay); + s.integer(decay_counter); + s.integer(decay_volume); +} diff --git a/bsnes/nes/apu/envelope.hpp b/bsnes/nes/apu/envelope.hpp new file mode 100755 index 00000000..054d2fef --- /dev/null +++ b/bsnes/nes/apu/envelope.hpp @@ -0,0 +1,16 @@ +struct Envelope { + uint4 speed; + bool use_speed_as_volume; + bool loop_mode; + + bool reload_decay; + uint8 decay_counter; + uint4 decay_volume; + + unsigned volume() const; + void clock(); + + void power(); + void reset(); + void serialize(serializer&); +}; diff --git a/bsnes/nes/apu/noise.cpp b/bsnes/nes/apu/noise.cpp new file mode 100755 index 00000000..74173802 --- /dev/null +++ b/bsnes/nes/apu/noise.cpp @@ -0,0 +1,57 @@ +void APU::Noise::clock_length() { + if(envelope.loop_mode == 0) { + if(length_counter > 0) length_counter--; + } +} + +uint8 APU::Noise::clock() { + if(length_counter == 0) return 0; + + uint8 result = (lfsr & 1) ? envelope.volume() : 0; + + if(--period_counter == 0) { + unsigned feedback; + + if(short_mode) { + feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1); + } else { + feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1); + } + + lfsr = (lfsr >> 1) | (feedback << 14); + period_counter = apu.ntsc_noise_period_table[period]; + } + + return result; +} + +void APU::Noise::power() { +} + +void APU::Noise::reset() { + length_counter = 0; + + envelope.speed = 0; + envelope.use_speed_as_volume = 0; + envelope.loop_mode = 0; + envelope.reload_decay = 0; + envelope.decay_counter = 0; + envelope.decay_volume = 0; + + period = 0; + period_counter = 1; + short_mode = 0; + lfsr = 1; +} + +void APU::Noise::serialize(serializer &s) { + s.integer(length_counter); + + envelope.serialize(s); + + s.integer(period); + s.integer(period_counter); + + s.integer(short_mode); + s.integer(lfsr); +} diff --git a/bsnes/nes/apu/noise.hpp b/bsnes/nes/apu/noise.hpp new file mode 100755 index 00000000..6684893a --- /dev/null +++ b/bsnes/nes/apu/noise.hpp @@ -0,0 +1,18 @@ +struct Noise { + unsigned length_counter; + + Envelope envelope; + + uint4 period; + unsigned period_counter; + + bool short_mode; + uint15 lfsr; + + void clock_length(); + uint8 clock(); + + void power(); + void reset(); + void serialize(serializer&); +} noise; diff --git a/bsnes/nes/apu/pulse.cpp b/bsnes/nes/apu/pulse.cpp new file mode 100755 index 00000000..e8305617 --- /dev/null +++ b/bsnes/nes/apu/pulse.cpp @@ -0,0 +1,51 @@ +void APU::Pulse::clock_length() { + if(envelope.loop_mode == 0) { + if(length_counter) length_counter--; + } +} + +uint8 APU::Pulse::clock() { + if(sweep.check_period() == false) return 0; + if(length_counter == 0) return 0; + + static const unsigned duty_table[] = { 1, 2, 4, 6 }; + uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0; + if(sweep.pulse_period < 0x008) result = 0; + + if(--period_counter == 0) { + period_counter = (sweep.pulse_period + 1) * 2; + duty_counter++; + } + + return result; +} + +void APU::Pulse::power() { + envelope.power(); + sweep.power(); +} + +void APU::Pulse::reset() { + envelope.reset(); + sweep.reset(); + + length_counter = 0; + + duty = 0; + duty_counter = 0; + period = 0; + period_counter = 1; +} + +void APU::Pulse::serialize(serializer &s) { + s.integer(length_counter); + + envelope.serialize(s); + sweep.serialize(s); + + s.integer(duty); + s.integer(duty_counter); + + s.integer(period); + s.integer(period_counter); +} diff --git a/bsnes/nes/apu/pulse.hpp b/bsnes/nes/apu/pulse.hpp new file mode 100755 index 00000000..6c41cea4 --- /dev/null +++ b/bsnes/nes/apu/pulse.hpp @@ -0,0 +1,20 @@ +struct Pulse { + unsigned length_counter; + + Envelope envelope; + Sweep sweep; + + uint2 duty; + uint3 duty_counter; + + uint11 period; + unsigned period_counter; + + void clock_length(); + bool check_period(); + uint8 clock(); + + void power(); + void reset(); + void serialize(serializer&); +} pulse[2]; diff --git a/bsnes/nes/apu/serialization.cpp b/bsnes/nes/apu/serialization.cpp index 4ff24314..7043769f 100755 --- a/bsnes/nes/apu/serialization.cpp +++ b/bsnes/nes/apu/serialization.cpp @@ -3,8 +3,8 @@ void APU::serialize(serializer &s) { filter.serialize(s); - rectangle[0].serialize(s); - rectangle[1].serialize(s); + pulse[0].serialize(s); + pulse[1].serialize(s); triangle.serialize(s); dmc.serialize(s); frame.serialize(s); @@ -19,90 +19,6 @@ void APU::Filter::serialize(serializer &s) { s.integer(lopass); } -void APU::Envelope::serialize(serializer &s) { - s.integer(speed); - s.integer(use_speed_as_volume); - s.integer(loop_mode); - - s.integer(reload_decay); - s.integer(decay_counter); - s.integer(decay_volume); -} - -void APU::Sweep::serialize(serializer &s) { - s.integer(shift); - s.integer(decrement); - s.integer(period); - s.integer(counter); - s.integer(enable); - s.integer(reload); - s.integer(rectangle_period); -} - -void APU::Rectangle::serialize(serializer &s) { - s.integer(length_counter); - - envelope.serialize(s); - sweep.serialize(s); - - s.integer(duty); - s.integer(duty_counter); - - s.integer(period); - s.integer(period_counter); -} - -void APU::Triangle::serialize(serializer &s) { - s.integer(length_counter); - - s.integer(linear_length); - s.integer(halt_length_counter); - - s.integer(period); - s.integer(period_counter); - - s.integer(step_counter); - s.integer(linear_length_counter); - s.integer(reload_linear); -} - -void APU::Noise::serialize(serializer &s) { - s.integer(length_counter); - - envelope.serialize(s); - - s.integer(period); - s.integer(period_counter); - - s.integer(short_mode); - s.integer(lfsr); -} - -void APU::DMC::serialize(serializer &s) { - s.integer(length_counter); - s.integer(irq_pending); - - s.integer(period); - s.integer(period_counter); - - s.integer(irq_enable); - s.integer(loop_mode); - - s.integer(dac_latch); - s.integer(addr_latch); - s.integer(length_latch); - - s.integer(read_addr); - s.integer(dma_delay_counter); - - s.integer(bit_counter); - s.integer(have_dma_buffer); - s.integer(dma_buffer); - - s.integer(have_sample); - s.integer(sample); -} - void APU::FrameCounter::serialize(serializer &s) { s.integer(irq_pending); diff --git a/bsnes/nes/apu/sweep.cpp b/bsnes/nes/apu/sweep.cpp new file mode 100755 index 00000000..8c0211b9 --- /dev/null +++ b/bsnes/nes/apu/sweep.cpp @@ -0,0 +1,53 @@ +bool APU::Sweep::check_period() { + if(pulse_period > 0x7ff) return false; + + if(decrement == 0) { + if((pulse_period + (pulse_period >> shift)) & 0x800) return false; + } + + return true; +} + +void APU::Sweep::clock(unsigned channel) { + if(--counter == 0) { + counter = period + 1; + if(enable && shift && pulse_period > 8) { + signed delta = pulse_period >> shift; + + if(decrement) { + pulse_period -= delta; + if(channel == 0) pulse_period--; + } else if((pulse_period + delta) < 0x800) { + pulse_period += delta; + } + } + } + + if(reload) { + reload = false; + counter = period + 1; + } +} + +void APU::Sweep::power() { + shift = 0; + decrement = 0; + period = 0; + counter = 1; + enable = 0; + reload = 0; + pulse_period = 0; +} + +void APU::Sweep::reset() { +} + +void APU::Sweep::serialize(serializer &s) { + s.integer(shift); + s.integer(decrement); + s.integer(period); + s.integer(counter); + s.integer(enable); + s.integer(reload); + s.integer(pulse_period); +} diff --git a/bsnes/nes/apu/sweep.hpp b/bsnes/nes/apu/sweep.hpp new file mode 100755 index 00000000..69137b5b --- /dev/null +++ b/bsnes/nes/apu/sweep.hpp @@ -0,0 +1,16 @@ +struct Sweep { + uint8 shift; + bool decrement; + uint3 period; + uint8 counter; + bool enable; + bool reload; + uint11 pulse_period; + + bool check_period(); + void clock(unsigned channel); + + void power(); + void reset(); + void serialize(serializer&); +}; diff --git a/bsnes/nes/apu/triangle.cpp b/bsnes/nes/apu/triangle.cpp new file mode 100755 index 00000000..0b0b1358 --- /dev/null +++ b/bsnes/nes/apu/triangle.cpp @@ -0,0 +1,58 @@ +void APU::Triangle::clock_length() { + if(halt_length_counter == 0) { + if(length_counter > 0) length_counter--; + } +} + +void APU::Triangle::clock_linear_length() { + if(reload_linear) { + linear_length_counter = linear_length; + } else if(linear_length_counter) { + linear_length_counter--; + } + + if(halt_length_counter == 0) reload_linear = false; +} + +uint8 APU::Triangle::clock() { + uint8 result = step_counter & 0x0f; + if((step_counter & 0x10) == 0) result ^= 0x0f; + if(length_counter == 0 || linear_length_counter == 0) return result; + + if(--period_counter == 0) { + step_counter++; + period_counter = period + 1; + } + + return result; +} + +void APU::Triangle::power() { + reset(); +} + +void APU::Triangle::reset() { + length_counter = 0; + + linear_length = 0; + halt_length_counter = 0; + period = 0; + period_counter = 1; + step_counter = 0; + linear_length_counter = 0; + reload_linear = 0; +} + +void APU::Triangle::serialize(serializer &s) { + s.integer(length_counter); + + s.integer(linear_length); + s.integer(halt_length_counter); + + s.integer(period); + s.integer(period_counter); + + s.integer(step_counter); + s.integer(linear_length_counter); + s.integer(reload_linear); +} diff --git a/bsnes/nes/apu/triangle.hpp b/bsnes/nes/apu/triangle.hpp new file mode 100755 index 00000000..ef88a5ca --- /dev/null +++ b/bsnes/nes/apu/triangle.hpp @@ -0,0 +1,21 @@ +struct Triangle { + unsigned length_counter; + + uint8 linear_length; + bool halt_length_counter; + + uint11 period; + unsigned period_counter; + + uint5 step_counter; + uint8 linear_length_counter; + bool reload_linear; + + void clock_length(); + void clock_linear_length(); + uint8 clock(); + + void power(); + void reset(); + void serialize(serializer&); +} triangle; diff --git a/bsnes/nes/cartridge/board/bandai-fcg.cpp b/bsnes/nes/cartridge/board/bandai-fcg.cpp index 39399586..f4c69291 100755 --- a/bsnes/nes/cartridge/board/bandai-fcg.cpp +++ b/bsnes/nes/cartridge/board/bandai-fcg.cpp @@ -39,7 +39,7 @@ uint8 prg_read(unsigned addr) { if(addr & 0x8000) { bool region = addr & 0x4000; unsigned bank = (region == 0 ? prg_bank : 0x0f); - return Board::prg_read((bank << 14) | (addr & 0x3fff)); + return prgrom.read((bank << 14) | (addr & 0x3fff)); } return cpu.mdr(); } diff --git a/bsnes/nes/cartridge/board/board.cpp b/bsnes/nes/cartridge/board/board.cpp index dc09dd54..091da556 100755 --- a/bsnes/nes/cartridge/board/board.cpp +++ b/bsnes/nes/cartridge/board/board.cpp @@ -1,11 +1,15 @@ #include "bandai-fcg.cpp" +#include "konami-vrc4.cpp" #include "konami-vrc6.cpp" +#include "konami-vrc7.cpp" #include "nes-axrom.cpp" #include "nes-bnrom.cpp" #include "nes-cnrom.cpp" #include "nes-exrom.cpp" +#include "nes-fxrom.cpp" #include "nes-gxrom.cpp" #include "nes-nrom.cpp" +#include "nes-pxrom.cpp" #include "nes-sxrom.cpp" #include "nes-txrom.cpp" #include "nes-uxrom.cpp" @@ -16,7 +20,7 @@ uint8 Board::Memory::read(unsigned addr) const { } void Board::Memory::write(unsigned addr, uint8 byte) { - data[mirror(addr, size)] = byte; + if(writable) data[mirror(addr, size)] = byte; } unsigned Board::mirror(unsigned addr, unsigned size) { @@ -53,17 +57,9 @@ void Board::tick() { if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread); } -uint8 Board::prg_read(unsigned addr) { - return prgrom.data[mirror(addr, prgrom.size)]; -} - -void Board::prg_write(unsigned addr, uint8 data) { - prgrom.data[mirror(addr, prgrom.size)] = data; -} - uint8 Board::chr_read(unsigned addr) { - if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)]; if(chrram.size) return chrram.data[mirror(addr, chrram.size)]; + if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)]; return 0u; } @@ -71,7 +67,7 @@ void Board::chr_write(unsigned addr, uint8 data) { if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data; } -Board::Memory Board::memory() { +Board::Memory& Board::memory() { return prgram; } @@ -102,13 +98,12 @@ Board::Board(BML::Node &board, const uint8_t *data, unsigned size) { if(prgrom.size) memcpy(prgrom.data, data, prgrom.size); if(chrrom.size) memcpy(chrrom.data, data + prgrom.size, chrrom.size); + + prgram.writable = true; + chrram.writable = true; } Board::~Board() { - if(prgrom.size) delete[] prgrom.data; - if(prgram.size) delete[] prgram.data; - if(chrrom.size) delete[] chrrom.data; - if(chrram.size) delete[] chrram.data; } Board* Board::load(const string &markup, const uint8_t *data, unsigned size) { @@ -118,7 +113,9 @@ Board* Board::load(const string &markup, const uint8_t *data, unsigned size) { if(type == "BANDAI-FCG") return new BandaiFCG(board, data, size); + if(type == "KONAMI-VRC-4") return new KonamiVRC4(board, data, size); if(type == "KONAMI-VRC-6") return new KonamiVRC6(board, data, size); + if(type == "KONAMI-VRC-7") return new KonamiVRC7(board, data, size); if(type == "NES-AMROM" ) return new NES_AxROM(board, data, size); if(type == "NES-ANROM" ) return new NES_AxROM(board, data, size); @@ -134,12 +131,18 @@ Board* Board::load(const string &markup, const uint8_t *data, unsigned 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-FJROM" ) return new NES_FxROM(board, data, size); + if(type == "NES-FKROM" ) return new NES_FxROM(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); if(type == "NES-NROM-128") return new NES_NROM(board, data, size); if(type == "NES-NROM-256") return new NES_NROM(board, data, size); + if(type == "NES-PEEOROM" ) return new NES_PxROM(board, data, size); + if(type == "NES-PNROM" ) return new NES_PxROM(board, data, size); + if(type == "NES-SNROM" ) return new NES_SxROM(board, data, size); if(type == "NES-SXROM" ) return new NES_SxROM(board, data, size); diff --git a/bsnes/nes/cartridge/board/board.hpp b/bsnes/nes/cartridge/board/board.hpp index 960b5219..0cd8ce91 100755 --- a/bsnes/nes/cartridge/board/board.hpp +++ b/bsnes/nes/cartridge/board/board.hpp @@ -2,10 +2,14 @@ struct Board { struct Memory { uint8_t *data; unsigned size; + bool writable; + 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) {} + inline Memory() : data(nullptr), size(0u), writable(false) {} + inline ~Memory() { if(data) delete[] data; } }; static unsigned mirror(unsigned addr, unsigned size); @@ -13,20 +17,22 @@ struct Board { virtual void main(); virtual void tick(); - virtual uint8 prg_read(unsigned addr); - virtual void prg_write(unsigned addr, uint8 data); + virtual uint8 prg_read(unsigned addr) = 0; + virtual void prg_write(unsigned addr, uint8 data) = 0; virtual uint8 chr_read(unsigned addr); virtual void chr_write(unsigned addr, uint8 data); - virtual Memory memory(); + virtual inline void scanline(unsigned y) {} + + virtual Memory& memory(); virtual void power(); virtual void reset(); virtual void serialize(serializer&); Board(BML::Node &board, const uint8_t *data, unsigned size); - ~Board(); + virtual ~Board(); static Board* load(const string &markup, const uint8_t *data, unsigned size); diff --git a/bsnes/nes/cartridge/board/konami-vrc4.cpp b/bsnes/nes/cartridge/board/konami-vrc4.cpp new file mode 100755 index 00000000..fa8fa4b8 --- /dev/null +++ b/bsnes/nes/cartridge/board/konami-vrc4.cpp @@ -0,0 +1,61 @@ +struct KonamiVRC4 : Board { + +struct Settings { + struct Pinout { + unsigned a0; + unsigned a1; + } pinout; +} settings; + +VRC4 vrc4; + +void main() { + return vrc4.main(); +} + +uint8 prg_read(unsigned addr) { + if(addr < 0x6000) return cpu.mdr(); + if(addr < 0x8000) return prgram.read(addr); + return prgrom.read(vrc4.prg_addr(addr)); +} + +void prg_write(unsigned addr, uint8 data) { + if(addr < 0x6000) return; + if(addr < 0x8000) return prgram.write(addr, data); + + bool a0 = (addr & settings.pinout.a0); + bool a1 = (addr & settings.pinout.a1); + addr &= 0xfff0; + addr |= (a1 << 1) | (a0 << 0); + return vrc4.reg_write(addr, data); +} + +uint8 chr_read(unsigned addr) { + if(addr & 0x2000) return ppu.ciram_read(vrc4.ciram_addr(addr)); + return Board::chr_read(vrc4.chr_addr(addr)); +} + +void chr_write(unsigned addr, uint8 data) { + if(addr & 0x2000) return ppu.ciram_write(vrc4.ciram_addr(addr), data); + return Board::chr_write(vrc4.chr_addr(addr), data); +} + +void power() { + vrc4.power(); +} + +void reset() { + vrc4.reset(); +} + +void serialize(serializer &s) { + Board::serialize(s); + vrc4.serialize(s); +} + +KonamiVRC4(BML::Node &board, const uint8_t *data, unsigned size) : Board(board, data, size), vrc4(*this) { + settings.pinout.a0 = 1 << decimal(board["chip"]["pinout"]["a0"].value); + settings.pinout.a1 = 1 << decimal(board["chip"]["pinout"]["a1"].value); +} + +}; diff --git a/bsnes/nes/cartridge/board/konami-vrc6.cpp b/bsnes/nes/cartridge/board/konami-vrc6.cpp index cec08700..6ab17b2e 100755 --- a/bsnes/nes/cartridge/board/konami-vrc6.cpp +++ b/bsnes/nes/cartridge/board/konami-vrc6.cpp @@ -4,7 +4,7 @@ VRC6 vrc6; uint8 prg_read(unsigned addr) { if((addr & 0xe000) == 0x6000) return vrc6.ram_read(addr); - if(addr & 0x8000) return Board::prg_read(vrc6.prg_addr(addr)); + if(addr & 0x8000) return prgrom.read(vrc6.prg_addr(addr)); return cpu.mdr(); } @@ -35,7 +35,6 @@ void serialize(serializer &s) { void main() { vrc6.main(); } void power() { vrc6.power(); } void reset() { vrc6.reset(); } -Memory memory() { return prgram; } KonamiVRC6(BML::Node &board, const uint8_t *data, unsigned size) : Board(board, data, size), vrc6(*this) { } diff --git a/bsnes/nes/cartridge/board/konami-vrc7.cpp b/bsnes/nes/cartridge/board/konami-vrc7.cpp new file mode 100755 index 00000000..b085a3e4 --- /dev/null +++ b/bsnes/nes/cartridge/board/konami-vrc7.cpp @@ -0,0 +1,47 @@ +struct KonamiVRC7 : Board { + +VRC7 vrc7; + +void main() { + return vrc7.main(); +} + +uint8 prg_read(unsigned addr) { + if(addr < 0x6000) return cpu.mdr(); + if(addr < 0x8000) return prgram.read(addr); + return prgrom.read(vrc7.prg_addr(addr)); +} + +void prg_write(unsigned addr, uint8 data) { + if(addr < 0x6000) return; + if(addr < 0x8000) return prgram.write(addr, data); + return vrc7.reg_write(addr, data); +} + +uint8 chr_read(unsigned addr) { + if(addr & 0x2000) return ppu.ciram_read(vrc7.ciram_addr(addr)); + return chrram.read(vrc7.chr_addr(addr)); +} + +void chr_write(unsigned addr, uint8 data) { + if(addr & 0x2000) return ppu.ciram_write(vrc7.ciram_addr(addr), data); + return chrram.write(vrc7.chr_addr(addr), data); +} + +void power() { + vrc7.power(); +} + +void reset() { + vrc7.reset(); +} + +void serialize(serializer &s) { + Board::serialize(s); + vrc7.serialize(s); +} + +KonamiVRC7(BML::Node &board, const uint8_t *data, unsigned size) : Board(board, data, size), vrc7(*this) { +} + +}; diff --git a/bsnes/nes/cartridge/board/nes-axrom.cpp b/bsnes/nes/cartridge/board/nes-axrom.cpp index 376e19a6..379d4ae5 100755 --- a/bsnes/nes/cartridge/board/nes-axrom.cpp +++ b/bsnes/nes/cartridge/board/nes-axrom.cpp @@ -9,7 +9,7 @@ uint4 prg_bank; bool mirror_select; uint8 prg_read(unsigned addr) { - if(addr & 0x8000) return Board::prg_read((prg_bank << 15) | (addr & 0x7fff)); + if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff)); return cpu.mdr(); } @@ -31,7 +31,6 @@ void chr_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/board/nes-bnrom.cpp b/bsnes/nes/cartridge/board/nes-bnrom.cpp index 86c2b0a3..013de500 100755 --- a/bsnes/nes/cartridge/board/nes-bnrom.cpp +++ b/bsnes/nes/cartridge/board/nes-bnrom.cpp @@ -9,7 +9,7 @@ struct Settings { uint2 prg_bank; uint8 prg_read(unsigned addr) { - if(addr & 0x8000) return Board::prg_read((prg_bank << 15) | (addr & 0x7fff)); + if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff)); return cpu.mdr(); } @@ -34,7 +34,6 @@ void chr_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/board/nes-cnrom.cpp b/bsnes/nes/cartridge/board/nes-cnrom.cpp index f3728a34..85ff4ef9 100755 --- a/bsnes/nes/cartridge/board/nes-cnrom.cpp +++ b/bsnes/nes/cartridge/board/nes-cnrom.cpp @@ -9,7 +9,7 @@ struct Settings { uint2 chr_bank; uint8 prg_read(unsigned addr) { - if(addr & 0x8000) return Board::prg_read(addr & 0x7fff); + if(addr & 0x8000) return prgrom.read(addr & 0x7fff); return cpu.mdr(); } @@ -36,7 +36,6 @@ void chr_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/board/nes-exrom.cpp b/bsnes/nes/cartridge/board/nes-exrom.cpp index fe97f2f1..e7d4aac8 100755 --- a/bsnes/nes/cartridge/board/nes-exrom.cpp +++ b/bsnes/nes/cartridge/board/nes-exrom.cpp @@ -29,6 +29,10 @@ void chr_write(unsigned addr, uint8 data) { mmc5.chr_write(addr, data); } +void scanline(unsigned y) { + mmc5.scanline(y); +} + void power() { mmc5.power(); } diff --git a/bsnes/nes/cartridge/board/nes-fxrom.cpp b/bsnes/nes/cartridge/board/nes-fxrom.cpp new file mode 100755 index 00000000..891fef76 --- /dev/null +++ b/bsnes/nes/cartridge/board/nes-fxrom.cpp @@ -0,0 +1,91 @@ +//MMC4 + +struct NES_FxROM : Board { + +enum Revision : unsigned { + FJROM, + FKROM, +} revision; + +uint4 prg_bank; +uint5 chr_bank[2][2]; +bool mirror; +bool latch[2]; + +uint8 prg_read(unsigned addr) { + if(addr < 0x6000) return cpu.mdr(); + if(addr < 0x8000) return prgram.read(addr); + unsigned bank = addr < 0xc000 ? prg_bank : (uint4)0x0f; + return prgrom.read((bank * 0x4000) | (addr & 0x3fff)); +} + +void prg_write(unsigned addr, uint8 data) { + if(addr < 0x6000) return; + if(addr < 0x8000) return prgram.write(addr, data); + + switch(addr & 0xf000) { + case 0xa000: prg_bank = data & 0x0f; break; + case 0xb000: chr_bank[0][0] = data & 0x1f; break; + case 0xc000: chr_bank[0][1] = data & 0x1f; break; + case 0xd000: chr_bank[1][0] = data & 0x1f; break; + case 0xe000: chr_bank[1][1] = data & 0x1f; break; + case 0xf000: mirror = data & 0x01; break; + } +} + +unsigned ciram_addr(unsigned addr) const { + switch(mirror) { + case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring + case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring + } +} + +uint8 chr_read(unsigned addr) { + if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr)); + bool region = addr & 0x1000; + unsigned bank = chr_bank[region][latch[region]]; + if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0; + if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1; + return Board::chr_read((bank * 0x1000) | (addr & 0x0fff)); +} + +void chr_write(unsigned addr, uint8 data) { + if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data); + bool region = addr & 0x1000; + unsigned bank = chr_bank[region][latch[region]]; + if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0; + if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1; + return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data); +} + +void power() { +} + +void reset() { + prg_bank = 0; + chr_bank[0][0] = 0; + chr_bank[0][1] = 0; + chr_bank[1][0] = 0; + chr_bank[1][1] = 0; + mirror = 0; + latch[0] = 0; + latch[1] = 0; +} + +void serialize(serializer &s) { + Board::serialize(s); + + s.integer(prg_bank); + s.integer(chr_bank[0][0]); + s.integer(chr_bank[0][1]); + s.integer(chr_bank[1][0]); + s.integer(chr_bank[1][1]); + s.integer(mirror); + s.array(latch); +} + +NES_FxROM(BML::Node &board, const uint8_t *data, unsigned size) : Board(board, data, size) { + revision = Revision::FKROM; +} + +}; diff --git a/bsnes/nes/cartridge/board/nes-gxrom.cpp b/bsnes/nes/cartridge/board/nes-gxrom.cpp index 1f8b73e7..3d0ecbe3 100755 --- a/bsnes/nes/cartridge/board/nes-gxrom.cpp +++ b/bsnes/nes/cartridge/board/nes-gxrom.cpp @@ -11,7 +11,7 @@ uint2 prg_bank; uint2 chr_bank; uint8 prg_read(unsigned addr) { - if(addr & 0x8000) return Board::prg_read((prg_bank << 15) | (addr & 0x7fff)); + if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff)); return cpu.mdr(); } @@ -41,7 +41,6 @@ void chr_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/board/nes-nrom.cpp b/bsnes/nes/cartridge/board/nes-nrom.cpp index 1442161b..5cf60764 100755 --- a/bsnes/nes/cartridge/board/nes-nrom.cpp +++ b/bsnes/nes/cartridge/board/nes-nrom.cpp @@ -8,7 +8,7 @@ struct Settings { } settings; uint8 prg_read(unsigned addr) { - if(addr & 0x8000) return Board::prg_read(addr); + if(addr & 0x8000) return prgrom.read(addr); return cpu.mdr(); } @@ -20,7 +20,8 @@ uint8 chr_read(unsigned addr) { if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff); return ppu.ciram_read(addr & 0x07ff); } - return Board::chr_read(addr); + if(chrram.size) return chrram.read(addr); + return chrrom.read(addr); } void chr_write(unsigned addr, uint8 data) { @@ -28,7 +29,7 @@ void chr_write(unsigned addr, uint8 data) { if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff); return ppu.ciram_write(addr & 0x07ff, data); } - return Board::chr_write(addr, data); + if(chrram.size) return chrram.write(addr, data); } void serialize(serializer &s) { diff --git a/bsnes/nes/cartridge/board/nes-pxrom.cpp b/bsnes/nes/cartridge/board/nes-pxrom.cpp new file mode 100755 index 00000000..82b065ec --- /dev/null +++ b/bsnes/nes/cartridge/board/nes-pxrom.cpp @@ -0,0 +1,97 @@ +//MMC2 + +struct NES_PxROM : Board { + +enum Revision : unsigned { + PEEOROM, + PNROM, +} revision; + +uint4 prg_bank; +uint5 chr_bank[2][2]; +bool mirror; +bool latch[2]; + +uint8 prg_read(unsigned addr) { + if(addr < 0x6000) return cpu.mdr(); + if(addr < 0x8000) return prgram.read(addr); + unsigned bank = 0; + switch((addr / 0x2000) & 3) { + case 0: bank = prg_bank; break; + case 1: bank = 0x0d; break; + case 2: bank = 0x0e; break; + case 3: bank = 0x0f; break; + } + return prgrom.read((bank * 0x2000) | (addr & 0x1fff)); +} + +void prg_write(unsigned addr, uint8 data) { + if(addr < 0x6000) return; + if(addr < 0x8000) return prgram.write(addr, data); + + switch(addr & 0xf000) { + case 0xa000: prg_bank = data & 0x0f; break; + case 0xb000: chr_bank[0][0] = data & 0x1f; break; + case 0xc000: chr_bank[0][1] = data & 0x1f; break; + case 0xd000: chr_bank[1][0] = data & 0x1f; break; + case 0xe000: chr_bank[1][1] = data & 0x1f; break; + case 0xf000: mirror = data & 0x01; break; + } +} + +unsigned ciram_addr(unsigned addr) const { + switch(mirror) { + case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring + case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring + } +} + +uint8 chr_read(unsigned addr) { + if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr)); + bool region = addr & 0x1000; + unsigned bank = chr_bank[region][latch[region]]; + if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0; + if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1; + return Board::chr_read((bank * 0x1000) | (addr & 0x0fff)); +} + +void chr_write(unsigned addr, uint8 data) { + if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data); + bool region = addr & 0x1000; + unsigned bank = chr_bank[region][latch[region]]; + if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0; + if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1; + return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data); +} + +void power() { +} + +void reset() { + prg_bank = 0; + chr_bank[0][0] = 0; + chr_bank[0][1] = 0; + chr_bank[1][0] = 0; + chr_bank[1][1] = 0; + mirror = 0; + latch[0] = 0; + latch[1] = 0; +} + +void serialize(serializer &s) { + Board::serialize(s); + + s.integer(prg_bank); + s.integer(chr_bank[0][0]); + s.integer(chr_bank[0][1]); + s.integer(chr_bank[1][0]); + s.integer(chr_bank[1][1]); + s.integer(mirror); + s.array(latch); +} + +NES_PxROM(BML::Node &board, const uint8_t *data, unsigned size) : Board(board, data, size) { + revision = Revision::PNROM; +} + +}; diff --git a/bsnes/nes/cartridge/board/nes-sxrom.cpp b/bsnes/nes/cartridge/board/nes-sxrom.cpp index 655485a4..a6d933c3 100755 --- a/bsnes/nes/cartridge/board/nes-sxrom.cpp +++ b/bsnes/nes/cartridge/board/nes-sxrom.cpp @@ -81,10 +81,6 @@ void chr_write(unsigned addr, uint8 data) { return Board::chr_write(mmc1.chr_addr(addr), data); } -Memory memory() { - return prgram; -} - void power() { mmc1.power(); } diff --git a/bsnes/nes/cartridge/board/nes-txrom.cpp b/bsnes/nes/cartridge/board/nes-txrom.cpp index 3db3bce0..66daf49e 100755 --- a/bsnes/nes/cartridge/board/nes-txrom.cpp +++ b/bsnes/nes/cartridge/board/nes-txrom.cpp @@ -26,7 +26,7 @@ void main() { uint8 prg_read(unsigned addr) { if((addr & 0xe000) == 0x6000) return mmc3.ram_read(addr); - if(addr & 0x8000) return Board::prg_read(mmc3.prg_addr(addr)); + if(addr & 0x8000) return prgrom.read(mmc3.prg_addr(addr)); return cpu.mdr(); } @@ -93,10 +93,6 @@ void chr_write(unsigned addr, uint8 data) { return Board::chr_write(mmc3.chr_addr(addr), data); } -Memory memory() { - return prgram; -} - void power() { mmc3.power(); } diff --git a/bsnes/nes/cartridge/board/nes-uxrom.cpp b/bsnes/nes/cartridge/board/nes-uxrom.cpp index da20a16a..4ad0a3d5 100755 --- a/bsnes/nes/cartridge/board/nes-uxrom.cpp +++ b/bsnes/nes/cartridge/board/nes-uxrom.cpp @@ -10,8 +10,8 @@ struct Settings { uint4 prg_bank; uint8 prg_read(unsigned addr) { - if((addr & 0xc000) == 0x8000) return Board::prg_read((prg_bank << 14) | (addr & 0x3fff)); - if((addr & 0xc000) == 0xc000) return Board::prg_read(( 0x0f << 14) | (addr & 0x3fff)); + if((addr & 0xc000) == 0x8000) return prgrom.read((prg_bank << 14) | (addr & 0x3fff)); + if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff)); return cpu.mdr(); } @@ -36,7 +36,6 @@ void chr_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/board/sunsoft-5b.cpp b/bsnes/nes/cartridge/board/sunsoft-5b.cpp index fbcaeec9..6732271a 100755 --- a/bsnes/nes/cartridge/board/sunsoft-5b.cpp +++ b/bsnes/nes/cartridge/board/sunsoft-5b.cpp @@ -94,7 +94,7 @@ uint8 prg_read(unsigned addr) { } addr = (bank << 13) | (addr & 0x1fff); - return Board::prg_read(addr); + return prgrom.read(addr); } void prg_write(unsigned addr, uint8 data) { @@ -184,8 +184,6 @@ void power() { double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n)); dac[n] = volume * 8192.0; } - - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/cartridge.cpp b/bsnes/nes/cartridge/cartridge.cpp index d58a89a8..a88c4950 100755 --- a/bsnes/nes/cartridge/cartridge.cpp +++ b/bsnes/nes/cartridge/cartridge.cpp @@ -20,6 +20,8 @@ void Cartridge::load(const string &markup, const uint8_t *data, unsigned size) { sha256 = nall::sha256(data, size); board = Board::load(markup, data, size); } else { + //unsigned crc32 = crc32_calculate(data + 16, size - 16); + //print(hex<8>(crc32), "\n"); sha256 = nall::sha256(data + 16, size - 16); board = Board::load(markup != "" ? markup : iNES(data, size), data + 16, size - 16); } @@ -43,7 +45,6 @@ uint8* Cartridge::ram_data() { } void Cartridge::power() { - create(Cartridge::Main, 21477272); board->power(); } @@ -72,7 +73,12 @@ void Cartridge::chr_write(unsigned addr, uint8 data) { return board->chr_write(addr, data); } +void Cartridge::scanline(unsigned y) { + return board->scanline(y); +} + void Cartridge::serialize(serializer &s) { + Processor::serialize(s); return board->serialize(s); } diff --git a/bsnes/nes/cartridge/cartridge.hpp b/bsnes/nes/cartridge/cartridge.hpp index f6db4b15..9b1f1db1 100755 --- a/bsnes/nes/cartridge/cartridge.hpp +++ b/bsnes/nes/cartridge/cartridge.hpp @@ -28,6 +28,10 @@ struct Cartridge : Processor, property { uint8 chr_read(unsigned addr); void chr_write(unsigned addr, uint8 data); + + //scanline() is for debugging purposes only: + //boards must detect scanline edges on their own + void scanline(unsigned y); }; extern Cartridge cartridge; diff --git a/bsnes/nes/cartridge/chip/chip.cpp b/bsnes/nes/cartridge/chip/chip.cpp index 5b122c2b..5aa18fde 100755 --- a/bsnes/nes/cartridge/chip/chip.cpp +++ b/bsnes/nes/cartridge/chip/chip.cpp @@ -1,7 +1,9 @@ #include "mmc1.cpp" #include "mmc3.cpp" #include "mmc5.cpp" +#include "vrc4.cpp" #include "vrc6.cpp" +#include "vrc7.cpp" void Chip::tick() { board.tick(); diff --git a/bsnes/nes/cartridge/chip/mmc1.cpp b/bsnes/nes/cartridge/chip/mmc1.cpp index 0bf7934f..15dc3097 100755 --- a/bsnes/nes/cartridge/chip/mmc1.cpp +++ b/bsnes/nes/cartridge/chip/mmc1.cpp @@ -98,7 +98,6 @@ void mmio_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/chip/mmc3.cpp b/bsnes/nes/cartridge/chip/mmc3.cpp index 525f296b..10dbcf18 100755 --- a/bsnes/nes/cartridge/chip/mmc3.cpp +++ b/bsnes/nes/cartridge/chip/mmc3.cpp @@ -90,7 +90,6 @@ void ram_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/chip/mmc5.cpp b/bsnes/nes/cartridge/chip/mmc5.cpp index 28d45d6f..45930cef 100755 --- a/bsnes/nes/cartridge/chip/mmc5.cpp +++ b/bsnes/nes/cartridge/chip/mmc5.cpp @@ -30,7 +30,7 @@ bool vs_enable; //$5200 bool vs_side; //$5200 uint5 vs_tile; //$5200 uint8 vs_scroll; //$5201 -uint8 vs_bank; //5202 +uint8 vs_bank; //$5202 uint8 irq_line; //$5203 bool irq_enable; //$5204 @@ -68,6 +68,11 @@ void main() { } } +void scanline(unsigned y) { +//used for testing only, to verify MMC5 scanline detection is accurate: +//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n"); +} + uint8 prg_access(bool write, unsigned addr, uint8 data = 0x00) { unsigned bank; @@ -396,7 +401,6 @@ void chr_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/chip/vrc4.cpp b/bsnes/nes/cartridge/chip/vrc4.cpp new file mode 100755 index 00000000..0bd6c2a0 --- /dev/null +++ b/bsnes/nes/cartridge/chip/vrc4.cpp @@ -0,0 +1,183 @@ +struct VRC4 : Chip { + +bool prg_mode; +uint5 prg_bank[2]; +uint2 mirror; +uint8 chr_bank[8]; + +uint8 irq_latch; +bool irq_mode; +bool irq_enable; +bool irq_acknowledge; + +uint8 irq_counter; +signed irq_scalar; +bool irq_line; + +void main() { + while(true) { + if(scheduler.sync == Scheduler::SynchronizeMode::All) { + scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); + } + + if(irq_enable) { + if(irq_mode == 0) { + irq_scalar -= 3; + if(irq_scalar <= 0) { + irq_scalar += 341; + if(irq_counter == 0xff) { + irq_counter = irq_latch; + irq_line = 1; + } else { + irq_counter++; + } + } + } + + if(irq_mode == 1) { + if(irq_counter == 0xff) { + irq_counter = irq_latch; + irq_line = 1; + } else { + irq_counter++; + } + } + } + + cpu.set_irq_line(irq_line); + tick(); + } +} + +void reg_write(unsigned addr, uint8 data) { + switch(addr) { + case 0x8000: case 0x8001: case 0x8002: case 0x8003: + prg_bank[0] = data & 0x1f; + break; + + case 0x9000: case 0x9001: + mirror = data & 0x03; + break; + + case 0x9002: case 0x9003: + prg_mode = data & 0x02; + break; + + case 0xa000: case 0xa001: case 0xa002: case 0xa003: + prg_bank[1] = data & 0x1f; + break; + + case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xb002: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xb003: chr_bank[1] = (chr_bank[1] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xc000: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xc001: chr_bank[2] = (chr_bank[2] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xc002: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xc003: chr_bank[3] = (chr_bank[3] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xd000: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xd001: chr_bank[4] = (chr_bank[4] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xd002: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xd003: chr_bank[5] = (chr_bank[5] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xe000: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xe001: chr_bank[6] = (chr_bank[6] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xe002: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break; + case 0xe003: chr_bank[7] = (chr_bank[7] & 0x0f) | ((data & 0x0f) << 4); break; + + case 0xf000: + irq_latch = (irq_latch & 0xf0) | ((data & 0x0f) << 0); + break; + + case 0xf001: + irq_latch = (irq_latch & 0x0f) | ((data & 0x0f) << 4); + break; + + case 0xf002: + irq_mode = data & 0x04; + irq_enable = data & 0x02; + irq_acknowledge = data & 0x01; + if(irq_enable) { + irq_counter = irq_latch; + irq_scalar = 341; + } + irq_line = 0; + break; + + case 0xf003: + irq_enable = irq_acknowledge; + irq_line = 0; + break; + } +} + +unsigned prg_addr(unsigned addr) const { + unsigned bank = 0, banks = board.prgrom.size / 0x2000; + switch((addr / 0x2000) & 3) { + case 0: bank = prg_mode == 0 ? (unsigned)prg_bank[0] : banks - 2; break; + case 1: bank = prg_bank[1]; break; + case 2: bank = prg_mode == 0 ? banks - 2 : (unsigned)prg_bank[0]; break; + case 3: bank = banks - 1; break; + } + return (bank * 0x2000) + (addr & 0x1fff); +} + +unsigned chr_addr(unsigned addr) const { + unsigned bank = chr_bank[addr / 0x0400]; + return (bank * 0x0400) + (addr & 0x03ff); +} + +unsigned ciram_addr(unsigned addr) const { + switch(mirror) { + case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring + case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring + case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first) + case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second) + } +} + +void power() { +} + +void reset() { + prg_mode = 0; + for(auto &n : prg_bank) n = 0; + mirror = 0; + for(auto &n : chr_bank) n = 0; + + irq_latch = 0; + irq_mode = 0; + irq_enable = 0; + irq_acknowledge = 0; + + irq_counter = 0; + irq_scalar = 0; + irq_line = 0; +} + +void serialize(serializer &s) { + s.integer(prg_mode); + for(auto &n : prg_bank) s.integer(n); + s.integer(mirror); + for(auto &n : chr_bank) s.integer(n); + + s.integer(irq_latch); + s.integer(irq_mode); + s.integer(irq_enable); + s.integer(irq_acknowledge); + + s.integer(irq_counter); + s.integer(irq_scalar); + s.integer(irq_line); +} + +VRC4(Board &board) : Chip(board) { +} + +}; diff --git a/bsnes/nes/cartridge/chip/vrc6.cpp b/bsnes/nes/cartridge/chip/vrc6.cpp index f872cf23..ed42a4ee 100755 --- a/bsnes/nes/cartridge/chip/vrc6.cpp +++ b/bsnes/nes/cartridge/chip/vrc6.cpp @@ -243,7 +243,6 @@ void reg_write(unsigned addr, uint8 data) { } void power() { - reset(); } void reset() { diff --git a/bsnes/nes/cartridge/chip/vrc7.cpp b/bsnes/nes/cartridge/chip/vrc7.cpp new file mode 100755 index 00000000..fe777071 --- /dev/null +++ b/bsnes/nes/cartridge/chip/vrc7.cpp @@ -0,0 +1,154 @@ +//Konami VRC7 +//Yamaha YM2413 OPLL audio - not emulated + +struct VRC7 : Chip { + +uint8 prg_bank[3]; +uint8 chr_bank[8]; +uint2 mirror; + +uint8 irq_latch; +bool irq_mode; +bool irq_enable; +bool irq_acknowledge; + +uint8 irq_counter; +signed irq_scalar; +bool irq_line; + +void main() { + while(true) { + if(scheduler.sync == Scheduler::SynchronizeMode::All) { + scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); + } + + if(irq_enable) { + if(irq_mode == 0) { + irq_scalar -= 3; + if(irq_scalar <= 0) { + irq_scalar += 341; + if(irq_counter == 0xff) { + irq_counter = irq_latch; + irq_line = 1; + } else { + irq_counter++; + } + } + } + + if(irq_mode == 1) { + if(irq_counter == 0xff) { + irq_counter = irq_latch; + irq_line = 1; + } else { + irq_counter++; + } + } + } + cpu.set_irq_line(irq_line); + + tick(); + } +} + +void reg_write(unsigned addr, uint8 data) { + switch(addr) { + case 0x8000: prg_bank[0] = data; break; + case 0x8010: prg_bank[1] = data; break; + case 0x9000: prg_bank[2] = data; break; + case 0x9010: break; //APU addr port + case 0x9030: break; //APU data port + case 0xa000: chr_bank[0] = data; break; + case 0xa010: chr_bank[1] = data; break; + case 0xb000: chr_bank[2] = data; break; + case 0xb010: chr_bank[3] = data; break; + case 0xc000: chr_bank[4] = data; break; + case 0xc010: chr_bank[5] = data; break; + case 0xd000: chr_bank[6] = data; break; + case 0xd010: chr_bank[7] = data; break; + case 0xe000: mirror = data & 0x03; break; + + case 0xe010: + irq_latch = data; + break; + + case 0xf000: + irq_mode = data & 0x04; + irq_enable = data & 0x02; + irq_acknowledge = data & 0x01; + if(irq_enable) { + irq_counter = irq_latch; + irq_scalar = 341; + } + irq_line = 0; + break; + + case 0xf010: + irq_enable = irq_acknowledge; + irq_line = 0; + break; + } +} + +unsigned prg_addr(unsigned addr) const { + unsigned bank = 0; + switch(addr & 0xe000) { + case 0x8000: bank = prg_bank[0]; break; + case 0xa000: bank = prg_bank[1]; break; + case 0xc000: bank = prg_bank[2]; break; + case 0xe000: bank = 0xff; break; + } + return (bank * 0x2000) + (addr & 0x1fff); +} + +unsigned chr_addr(unsigned addr) const { + unsigned bank = chr_bank[addr / 0x0400]; + return (bank * 0x0400) + (addr & 0x03ff); +} + +unsigned ciram_addr(unsigned addr) const { + switch(mirror) { + case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring + case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring + case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first) + case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second) + } +} + +void power() { +} + +void reset() { + for(auto &n : prg_bank) n = 0; + for(auto &n : chr_bank) n = 0; + mirror = 0; + + irq_latch = 0; + irq_mode = 0; + irq_enable = 0; + irq_acknowledge = 0; + + irq_counter = 0; + irq_scalar = 0; + irq_line = 0; +} + +void serialize(serializer &s) { + s.array(prg_bank); + s.array(chr_bank); + s.integer(mirror); + + s.integer(irq_latch); + s.integer(irq_mode); + s.integer(irq_enable); + s.integer(irq_acknowledge); + + s.integer(irq_counter); + s.integer(irq_scalar); + s.integer(irq_line); +} + +VRC7(Board &board) : Chip(board) { +} + +}; diff --git a/bsnes/nes/cartridge/ines.cpp b/bsnes/nes/cartridge/ines.cpp index 71636c98..2a68fac4 100755 --- a/bsnes/nes/cartridge/ines.cpp +++ b/bsnes/nes/cartridge/ines.cpp @@ -56,11 +56,32 @@ static string iNES(const uint8_t *data, unsigned size) { output.append("\tboard type:NES-AOROM\n"); break; + case 9: + output.append("\tboard type:NES-PNROM\n"); + output.append("\t\tchip type:MMC2\n"); + prgram = 8192; + break; + + case 10: + output.append("\tboard type:NES-FKROM\n"); + output.append("\t\tchip type:MMC4\n"); + prgram = 8192; + break; + case 16: output.append("\tboard type:BANDAI-FCG\n"); output.append("\t\tchip type:LZ93D50\n"); break; + case 21: + case 23: + case 25: + output.append("\tboard type:KONAMI-VRC-4\n"); + output.append("\t\tchip type:VRC4\n"); + output.append("\t\t\tpinout a0=1 a1=0\n"); + prgram = 8192; + break; + case 24: output.append("\tboard type:KONAMI-VRC-6\n"); output.append("\t\tchip type:VRC6\n"); @@ -87,6 +108,12 @@ static string iNES(const uint8_t *data, unsigned size) { output.append("\t\tchip type:5B\n"); prgram = 8192; break; + + case 85: + output.append("\tboard type:KONAMI-VRC-7\n"); + output.append("\t\tchip type:VRC7\n"); + prgram = 8192; + break; } output.append("\t\tprg rom=", prgrom, " ram=", prgram, "\n"); diff --git a/bsnes/nes/cpu/cpu.cpp b/bsnes/nes/cpu/cpu.cpp index 9cd8a5da..efd7579e 100755 --- a/bsnes/nes/cpu/cpu.cpp +++ b/bsnes/nes/cpu/cpu.cpp @@ -58,8 +58,6 @@ void CPU::power() { ram[0x0009] = 0xef; ram[0x000a] = 0xdf; ram[0x000f] = 0xbf; - - reset(); } void CPU::reset() { diff --git a/bsnes/nes/nes.hpp b/bsnes/nes/nes.hpp index aa94b01b..ec72bac0 100755 --- a/bsnes/nes/nes.hpp +++ b/bsnes/nes/nes.hpp @@ -19,6 +19,7 @@ namespace NES { #include #include +#include #include #include #include @@ -93,7 +94,7 @@ namespace NES { s.integer(clock); } - inline Processor() : thread(0) {} + inline Processor() : thread(nullptr) {} }; #include diff --git a/bsnes/nes/ppu/ppu.cpp b/bsnes/nes/ppu/ppu.cpp index a3233fb6..1b35b47b 100755 --- a/bsnes/nes/ppu/ppu.cpp +++ b/bsnes/nes/ppu/ppu.cpp @@ -43,7 +43,6 @@ void PPU::frame_edge() { } void PPU::power() { - reset(); } void PPU::reset() { @@ -108,6 +107,8 @@ uint8 PPU::read(uint16 addr) { if((status.oam_addr & 3) == 3) result &= 0xe3; break; case 7: //PPUDATA + if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00; + addr = status.vaddr & 0x3fff; if(addr <= 0x1fff) { result = status.bus_data; @@ -175,6 +176,8 @@ void PPU::write(uint16 addr, uint8 data) { status.address_latch ^= 1; return; case 7: //PPUDATA + if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return; + addr = status.vaddr & 0x3fff; if(addr <= 0x1fff) { cartridge.chr_write(addr, data); @@ -252,6 +255,7 @@ void PPU::ly_increment() { frame_edge(); } scanline_edge(); + cartridge.scanline(status.ly); } void PPU::scrollx_increment() { diff --git a/bsnes/nes/system/system.cpp b/bsnes/nes/system/system.cpp index f25bf07a..6bc0da4f 100755 --- a/bsnes/nes/system/system.cpp +++ b/bsnes/nes/system/system.cpp @@ -47,6 +47,7 @@ void System::power() { ppu.power(); input.reset(); scheduler.power(); + reset(); } void System::reset() { diff --git a/bsnes/phoenix/phoenix.cpp b/bsnes/phoenix/phoenix.cpp index 77276e02..1aa709fb 100755 --- a/bsnes/phoenix/phoenix.cpp +++ b/bsnes/phoenix/phoenix.cpp @@ -6,6 +6,7 @@ #define WINVER 0x0501 #define _WIN32_WINNT 0x0501 #define _WIN32_IE 0x0600 + #define __MSVCRT_VERSION__ 0x0601 #define NOMINMAX #include diff --git a/bsnes/snes/alt/ppu-compatibility/render/line.cpp b/bsnes/snes/alt/ppu-compatibility/render/line.cpp index 22766844..c7e870fe 100755 --- a/bsnes/snes/alt/ppu-compatibility/render/line.cpp +++ b/bsnes/snes/alt/ppu-compatibility/render/line.cpp @@ -110,9 +110,9 @@ inline void PPU::render_line_output() { } inline void PPU::render_line_clear() { - uint16 *ptr = (uint16*)output + (line * 1024) + ((interlace() && field()) ? 512 : 0); - uint16 width = (!regs.pseudo_hires && regs.bg_mode != 5 && regs.bg_mode != 6) ? 256 : 512; - memset(ptr, 0, width * 2 * sizeof(uint16)); + uint32 *ptr = (uint32*)output + (line * 1024) + ((interlace() && field()) ? 512 : 0); + unsigned width = (!regs.pseudo_hires && regs.bg_mode != 5 && regs.bg_mode != 6) ? 256 : 512; + memset(ptr, 0, width * 2 * sizeof(uint32)); } #endif diff --git a/bsnes/snes/interface/interface.hpp b/bsnes/snes/interface/interface.hpp index d4be719c..d3ae634e 100755 --- a/bsnes/snes/interface/interface.hpp +++ b/bsnes/snes/interface/interface.hpp @@ -33,8 +33,8 @@ struct Interface { void setCheats(const lstring &list = lstring{}); - virtual void message(const string &text); virtual string path(Cartridge::Slot slot, const string &hint) = 0; + virtual void message(const string &text); }; extern Interface *interface; diff --git a/bsnes/snes/snes.hpp b/bsnes/snes/snes.hpp index cca2bf1c..5b9cefd5 100755 --- a/bsnes/snes/snes.hpp +++ b/bsnes/snes/snes.hpp @@ -118,7 +118,7 @@ namespace SNES { s.integer(clock); } - inline Processor() : thread(0) {} + inline Processor() : thread(nullptr) {} }; struct ChipDebugger { diff --git a/bsnes/ui-libsnes/libsnes.cpp b/bsnes/ui-libsnes/libsnes.cpp index 3a875912..fbb4482c 100755 --- a/bsnes/ui-libsnes/libsnes.cpp +++ b/bsnes/ui-libsnes/libsnes.cpp @@ -11,13 +11,25 @@ struct Interface : public SNES::Interface { snes_input_poll_t pinput_poll; snes_input_state_t pinput_state; string basename; + uint16_t *buffer; + uint32_t *palette; - void videoRefresh(const uint16_t *data, bool hires, bool interlace, bool overscan) { + void videoRefresh(const uint32_t *data, bool hires, bool interlace, bool overscan) { unsigned width = hires ? 512 : 256; unsigned height = overscan ? 239 : 224; + unsigned pitch = 1024 >> interlace; if(interlace) height <<= 1; data += 9 * 1024; //skip front porch - if(pvideo_refresh) pvideo_refresh(data, width, height); + + for(unsigned y = 0; y < height; y++) { + const uint32_t *sp = data + y * pitch; + uint16_t *dp = buffer + y * pitch; + for(unsigned x = 0; x < width; x++) { + *dp++ = palette[*sp++]; + } + } + + if(pvideo_refresh) pvideo_refresh(buffer, width, height); if(pinput_poll) pinput_poll(); } @@ -39,14 +51,35 @@ struct Interface : public SNES::Interface { } Interface() : pvideo_refresh(0), paudio_sample(0), pinput_poll(0), pinput_state(0) { + buffer = new uint16_t[512 * 480]; + palette = new uint32_t[16 * 32768]; + + //{llll bbbbb ggggg rrrrr} -> { rrrrr ggggg bbbbb } + for(unsigned l = 0; l < 16; l++) { + for(unsigned r = 0; r < 32; r++) { + for(unsigned g = 0; g < 32; g++) { + for(unsigned b = 0; b < 32; b++) { + double luma = (double)l / 15.0; + unsigned ar = (luma * r + 0.5); + unsigned ag = (luma * g + 0.5); + unsigned ab = (luma * b + 0.5); + palette[(l << 15) + (r << 10) + (g << 5) + (b << 0)] = (ab << 10) + (ag << 5) + (ar << 0); + } + } + } + } + } + + ~Interface() { + delete[] buffer; + delete[] palette; } }; static Interface interface; const char* snes_library_id(void) { - static string id = { SNES::Info::Name, " v", SNES::Info::Version }; - return (const char*)id; + return "bsnes v083"; } unsigned snes_library_revision_major(void) { @@ -148,7 +181,7 @@ bool snes_load_cartridge_normal( ) { snes_cheat_reset(); if(rom_data) SNES::cartridge.rom.copy(rom_data, rom_size); - string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SNESCartridge(rom_data, rom_size).xmlMemoryMap; + string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SnesCartridge(rom_data, rom_size).markup; SNES::cartridge.load(SNES::Cartridge::Mode::Normal, { xmlrom }); SNES::system.power(); return true; @@ -160,10 +193,10 @@ bool snes_load_cartridge_bsx_slotted( ) { snes_cheat_reset(); if(rom_data) SNES::cartridge.rom.copy(rom_data, rom_size); - string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SNESCartridge(rom_data, rom_size).xmlMemoryMap; + string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SnesCartridge(rom_data, rom_size).markup; if(bsx_data) SNES::bsxflash.memory.copy(bsx_data, bsx_size); - string xmlbsx = (bsx_xml && *bsx_xml) ? string(bsx_xml) : SNESCartridge(bsx_data, bsx_size).xmlMemoryMap; - SNES::cartridge.load(SNES::Cartridge::Mode::BsxSlotted, { xmlrom, xmlbsx }); + string xmlbsx = (bsx_xml && *bsx_xml) ? string(bsx_xml) : SnesCartridge(bsx_data, bsx_size).markup; + SNES::cartridge.load(SNES::Cartridge::Mode::BsxSlotted, xmlrom); SNES::system.power(); return true; } @@ -174,10 +207,10 @@ bool snes_load_cartridge_bsx( ) { snes_cheat_reset(); if(rom_data) SNES::cartridge.rom.copy(rom_data, rom_size); - string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SNESCartridge(rom_data, rom_size).xmlMemoryMap; + string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SnesCartridge(rom_data, rom_size).markup; if(bsx_data) SNES::bsxflash.memory.copy(bsx_data, bsx_size); - string xmlbsx = (bsx_xml && *bsx_xml) ? string(bsx_xml) : SNESCartridge(bsx_data, bsx_size).xmlMemoryMap; - SNES::cartridge.load(SNES::Cartridge::Mode::Bsx, { xmlrom, xmlbsx }); + string xmlbsx = (bsx_xml && *bsx_xml) ? string(bsx_xml) : SnesCartridge(bsx_data, bsx_size).markup; + SNES::cartridge.load(SNES::Cartridge::Mode::Bsx, xmlrom); SNES::system.power(); return true; } @@ -189,12 +222,12 @@ bool snes_load_cartridge_sufami_turbo( ) { snes_cheat_reset(); if(rom_data) SNES::cartridge.rom.copy(rom_data, rom_size); - string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SNESCartridge(rom_data, rom_size).xmlMemoryMap; + string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SnesCartridge(rom_data, rom_size).markup; if(sta_data) SNES::sufamiturbo.slotA.rom.copy(sta_data, sta_size); - string xmlsta = (sta_xml && *sta_xml) ? string(sta_xml) : SNESCartridge(sta_data, sta_size).xmlMemoryMap; + string xmlsta = (sta_xml && *sta_xml) ? string(sta_xml) : SnesCartridge(sta_data, sta_size).markup; if(stb_data) SNES::sufamiturbo.slotB.rom.copy(stb_data, stb_size); - string xmlstb = (stb_xml && *stb_xml) ? string(stb_xml) : SNESCartridge(stb_data, stb_size).xmlMemoryMap; - SNES::cartridge.load(SNES::Cartridge::Mode::SufamiTurbo, { xmlrom, xmlsta, xmlstb }); + string xmlstb = (stb_xml && *stb_xml) ? string(stb_xml) : SnesCartridge(stb_data, stb_size).markup; + SNES::cartridge.load(SNES::Cartridge::Mode::SufamiTurbo, xmlrom); SNES::system.power(); return true; } @@ -205,16 +238,16 @@ bool snes_load_cartridge_super_game_boy( ) { snes_cheat_reset(); if(rom_data) SNES::cartridge.rom.copy(rom_data, rom_size); - string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SNESCartridge(rom_data, rom_size).xmlMemoryMap; + string xmlrom = (rom_xml && *rom_xml) ? string(rom_xml) : SnesCartridge(rom_data, rom_size).markup; if(dmg_data) { //GameBoyCartridge needs to modify dmg_data (for MMM01 emulation); so copy data uint8_t *data = new uint8_t[dmg_size]; memcpy(data, dmg_data, dmg_size); - string xmldmg = (dmg_xml && *dmg_xml) ? string(dmg_xml) : GameBoyCartridge(data, dmg_size).xml; + string xmldmg = (dmg_xml && *dmg_xml) ? string(dmg_xml) : GameBoyCartridge(data, dmg_size).markup; GameBoy::cartridge.load(xmldmg, data, dmg_size); delete[] data; } - SNES::cartridge.load(SNES::Cartridge::Mode::SuperGameBoy, { xmlrom, "" }); + SNES::cartridge.load(SNES::Cartridge::Mode::SuperGameBoy, xmlrom); SNES::system.power(); return true; } diff --git a/bsnes/ui/interface/snes.cpp b/bsnes/ui/interface/snes.cpp index 64d6637d..537d92de 100755 --- a/bsnes/ui/interface/snes.cpp +++ b/bsnes/ui/interface/snes.cpp @@ -200,6 +200,7 @@ void InterfaceSNES::videoRefresh(const uint32_t *data, bool hires, bool interlac unsigned height = 240 << interlace; unsigned pitch = 1024 >> interlace; + //skip first line; as it is always blank (by SNES design) if(overscan == false) data += 1 * 1024; // 8 + 224 + 8 if(overscan == true ) data += 9 * 1024; // 0 + 240 + 0 @@ -275,8 +276,13 @@ string InterfaceSNES::path(SNES::Cartridge::Slot slot, const string &hint) { return { interface->slotName[index[(unsigned)slot]], hint }; } +void InterfaceSNES::message(const string &text) { + MessageWindow::information(*mainWindow, text); +} + InterfaceSNES::InterfaceSNES() { //{llll bbbbb ggggg rrrrr} -> { rrrrr ggggg bbbbb } + palette = new uint32_t[16 * 32 * 32 * 32]; for(unsigned l = 0; l < 16; l++) { for(unsigned r = 0; r < 32; r++) { for(unsigned g = 0; g < 32; g++) { @@ -291,3 +297,7 @@ InterfaceSNES::InterfaceSNES() { } } } + +InterfaceSNES::~InterfaceSNES() { + delete[] palette; +} diff --git a/bsnes/ui/interface/snes.hpp b/bsnes/ui/interface/snes.hpp index b4685301..4c1a0c81 100755 --- a/bsnes/ui/interface/snes.hpp +++ b/bsnes/ui/interface/snes.hpp @@ -19,9 +19,11 @@ struct InterfaceSNES : SNES::Interface { int16_t inputPoll(bool port, SNES::Input::Device device, unsigned index, unsigned id); string path(SNES::Cartridge::Slot slot, const string &hint); + void message(const string &text); InterfaceSNES(); + ~InterfaceSNES(); private: - unsigned palette[16 * 32768]; + unsigned *palette; }; diff --git a/bsnes/ui/main.cpp b/bsnes/ui/main.cpp index 3b7d8431..62da08c8 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.32"; + title = "bsnes v082.34"; string fontFamily = Intrinsics::platform() == Intrinsics::Platform::Windows ? "Tahoma, " : "Sans, "; normalFont = { fontFamily, "8" }; diff --git a/bsnes/ui/settings/advanced.cpp b/bsnes/ui/settings/advanced.cpp index 2066abaf..83d8ae05 100755 --- a/bsnes/ui/settings/advanced.cpp +++ b/bsnes/ui/settings/advanced.cpp @@ -15,6 +15,8 @@ AdvancedSettings::AdvancedSettings() { focusPolicy[2].setText("Pause emulation"); RadioBox::group(focusPolicy[0], focusPolicy[1], focusPolicy[2]); focusPolicy[config->input.focusPolicy].setChecked(); + aboutLabel.setFont(application->boldFont); + aboutLabel.setText("bsnes author: byuu license: GPLv3 website: http://byuu.org/"); lstring list; @@ -53,6 +55,8 @@ AdvancedSettings::AdvancedSettings() { focusPolicyLayout.append(focusPolicy[0], ~0, 0, 5); focusPolicyLayout.append(focusPolicy[1], ~0, 0, 5); focusPolicyLayout.append(focusPolicy[2], ~0, 0); + append(spacer, ~0, ~0); + append(aboutLabel, ~0, 0); videoDriver.onChange = [&] { lstring list; diff --git a/bsnes/ui/settings/advanced.hpp b/bsnes/ui/settings/advanced.hpp index 860527d8..89e0432e 100755 --- a/bsnes/ui/settings/advanced.hpp +++ b/bsnes/ui/settings/advanced.hpp @@ -11,6 +11,8 @@ struct AdvancedSettings : SettingsLayout { Label focusPolicyLabel; HorizontalLayout focusPolicyLayout; RadioBox focusPolicy[3]; + Widget spacer; + Label aboutLabel; AdvancedSettings(); };