From 101c9507b16b69c8f99244ad115cd21f56d8d567 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 21 Sep 2011 00:04:43 +1000 Subject: [PATCH] Update to v082r18 release. byuu says: There we go, the GUI is nearly feature-complete once again. All cores now output their native video format (NES={emphasis}{palette}, SNES=BGR555, GameBoy={ bright, normal, darker, darkest }), and are transformed to RGB555 data that is passed to the video renderer. The video renderer then uses its internal palette to apply brightness/contrast/gamma/ramp adjustments and outputs in RGB888 color space. This does add in another rendering pass, unfortunately, but it's a necessary one for universal support. The plan is to adapt all filters to take RGB555 input, and output RGB555 data as well. By doing this, it will be possible to stack filters. However, it's a bit complicated: I need to plan how the stacking should occur (eg we never want to apply scanlines before HQ2x, etc.) Added input frequency adjustments for all three systems. I can easily get perfect video/audio sync on all three now, hooray. Long-term, it seems like we only really need one, and we can do a video/audio delta to get an adjusted value. But for now, this gets the job done. Added audio volume adjust. I left out the balance for now, since it's obviously impossible to balance the NES' single channel audio (I can duplicate the channel, and do twice the filtering work, but ... why?) I replaced NTSC/PAL TV mode selection with an "Enable Overscan" checkbox. On, you get 240 lines on NES+SNES. Off, you get 224 lines on NES+SNES. Also added aspect correction box back. I don't do that gross PAL distortion shit anymore, sorry PAL people. I just scale up the 54/47*(240/224) aspect correction for overscan off mode. All memory is loaded and saved now, for all three systems (hooray, now you can actually play Zelda 1&2.) Added all of the old bsnes hotkeys, with the exception of capture screenshot. May add again later. May come up with something a bit different for extra features. Re-added the NSS DIP switch setting window. Since geometry is saved, I didn't want to auto-hide rows, so now you'll see all eight possible DIPs, and the ones not used are grayed out. Ultimately, nobody will notice since we only have DIPs for ActRaiser NSS, and nobody's probably even using the XML file for that anyway. Whatever, it's nice to have anyway. Took FitzRoy's advice and single-item combo boxes on the input selection are disabled, so the user doesn't waste time checking them. I wanted to leave text so that you know there's not a problem. Qt disabled radio box items look almost exactly like enabled ones. Fixed lots of issues in phoenix and extended it a bit. But I was still having trouble with radio box grouping, so I said fuck it and made the panels show/hide based instead of append/remove based. That's all for stuff off the checklist, I did a bunch of other things I don't recall. So yeah, I'd say the GUI is 100% usable now. This is my opinion on how multi-platform GUIs should be done =) Oh, I figure I should mention, but the NES core is GPLv3, and all future SNES+GB releases will be as well. It's a move against Microsoft's Metro store. --- bsnes/gameboy/apu/master/master.cpp | 11 +- bsnes/gameboy/apu/noise/noise.cpp | 2 +- bsnes/gameboy/apu/square1/square1.cpp | 2 +- bsnes/gameboy/apu/square2/square2.cpp | 2 +- bsnes/gameboy/apu/wave/wave.cpp | 13 +- bsnes/gameboy/gameboy.hpp | 2 +- bsnes/gameboy/interface/interface.cpp | 10 ++ bsnes/gameboy/interface/interface.hpp | 7 + bsnes/gameboy/lcd/lcd.cpp | 2 +- bsnes/nes/cartridge/cartridge.cpp | 8 ++ bsnes/nes/cartridge/cartridge.hpp | 3 + bsnes/nes/interface/interface.cpp | 10 ++ bsnes/nes/interface/interface.hpp | 7 + bsnes/nes/mapper/mapper.cpp | 8 ++ bsnes/nes/mapper/mapper.hpp | 3 + bsnes/nes/mapper/mmc1/mmc1.cpp | 10 ++ bsnes/nes/mapper/mmc1/mmc1.hpp | 3 + bsnes/nes/nes.hpp | 4 +- bsnes/phoenix/core/core.cpp | 16 +++ bsnes/phoenix/core/core.hpp | 4 + .../phoenix/core/layout/horizontal-layout.cpp | 5 + bsnes/phoenix/core/layout/vertical-layout.cpp | 1 + bsnes/phoenix/gtk/widget/canvas.cpp | 11 +- bsnes/phoenix/qt/font.cpp | 4 +- bsnes/phoenix/qt/platform.moc | 2 +- bsnes/phoenix/qt/platform.moc.hpp | 1 + bsnes/phoenix/qt/widget/button.cpp | 2 + bsnes/phoenix/qt/widget/canvas.cpp | 1 + bsnes/phoenix/qt/widget/check-box.cpp | 1 + bsnes/phoenix/qt/widget/combo-box.cpp | 4 +- bsnes/phoenix/qt/widget/hex-edit.cpp | 1 + .../qt/widget/horizontal-scroll-bar.cpp | 1 + bsnes/phoenix/qt/widget/horizontal-slider.cpp | 1 + bsnes/phoenix/qt/widget/label.cpp | 2 + bsnes/phoenix/qt/widget/line-edit.cpp | 2 + bsnes/phoenix/qt/widget/list-view.cpp | 2 + bsnes/phoenix/qt/widget/progress-bar.cpp | 1 + bsnes/phoenix/qt/widget/radio-box.cpp | 4 +- bsnes/phoenix/qt/widget/text-edit.cpp | 2 + .../phoenix/qt/widget/vertical-scroll-bar.cpp | 1 + bsnes/phoenix/qt/widget/vertical-slider.cpp | 1 + bsnes/phoenix/qt/widget/viewport.cpp | 2 + bsnes/phoenix/qt/widget/widget.cpp | 8 ++ bsnes/snes/chip/icd2/icd2.cpp | 4 +- bsnes/snes/chip/icd2/mmio/mmio.cpp | 4 +- bsnes/snes/interface/interface.cpp | 8 ++ bsnes/snes/interface/interface.hpp | 3 + bsnes/snes/ppu/screen/screen.cpp | 2 +- bsnes/snes/snes.hpp | 4 +- bsnes/ui/config/config.cpp | 15 ++- bsnes/ui/config/config.hpp | 13 ++ bsnes/ui/general/dip-switches.cpp | 69 ++++++++++ bsnes/ui/general/dip-switches.hpp | 23 ++++ bsnes/ui/general/general.cpp | 1 + bsnes/ui/general/general.hpp | 1 + bsnes/ui/general/main-window.cpp | 41 ++++-- bsnes/ui/general/main-window.hpp | 5 +- bsnes/ui/input/input.cpp | 4 - bsnes/ui/input/user-interface.cpp | 59 +++++++++ bsnes/ui/input/user-interface.hpp | 11 ++ bsnes/ui/interface/gameboy.cpp | 42 ++++-- bsnes/ui/interface/interface.cpp | 59 +++++++-- bsnes/ui/interface/interface.hpp | 10 +- bsnes/ui/interface/nes.cpp | 45 +++++-- bsnes/ui/interface/palette.cpp | 67 ++++++++++ bsnes/ui/interface/palette.hpp | 14 ++ bsnes/ui/interface/snes.cpp | 122 +++++++++++++----- bsnes/ui/interface/snes.hpp | 3 + bsnes/ui/main.cpp | 2 + bsnes/ui/settings/advanced.hpp | 2 +- bsnes/ui/settings/audio.cpp | 84 ++++++++++++ bsnes/ui/settings/audio.hpp | 27 ++++ bsnes/ui/settings/input.cpp | 2 + bsnes/ui/settings/input.hpp | 2 +- bsnes/ui/settings/settings.cpp | 48 ++++--- bsnes/ui/settings/settings.hpp | 10 ++ bsnes/ui/settings/video.cpp | 66 ++++++++++ bsnes/ui/settings/video.hpp | 24 ++++ bsnes/ui/utility/utility.cpp | 26 +++- 79 files changed, 957 insertions(+), 152 deletions(-) create mode 100755 bsnes/ui/general/dip-switches.cpp create mode 100755 bsnes/ui/general/dip-switches.hpp create mode 100755 bsnes/ui/interface/palette.cpp create mode 100755 bsnes/ui/interface/palette.hpp create mode 100755 bsnes/ui/settings/audio.cpp create mode 100755 bsnes/ui/settings/audio.hpp create mode 100755 bsnes/ui/settings/video.cpp create mode 100755 bsnes/ui/settings/video.hpp diff --git a/bsnes/gameboy/apu/master/master.cpp b/bsnes/gameboy/apu/master/master.cpp index 02df2dc1..54667357 100755 --- a/bsnes/gameboy/apu/master/master.cpp +++ b/bsnes/gameboy/apu/master/master.cpp @@ -1,6 +1,11 @@ #ifdef APU_CPP void APU::Master::run() { + static int16_t volume[] = { + -16384, -14336, -12288, -10240, -8192, -6144, -4096, -2048, + +2048, +4096, +6144, +8192, +10240, +12288, +14336, +16384, + }; + if(enable == false) { center = 0; left = 0; @@ -14,7 +19,7 @@ void APU::Master::run() { sample += apu.wave.output; sample += apu.noise.output; sample >>= 2; - center = sclamp<16>(sample); + center = volume[sample]; sample = 0; channels = 0; @@ -23,7 +28,7 @@ void APU::Master::run() { if(channel3_left_enable) { sample += apu.wave.output; channels++; } if(channel4_left_enable) { sample += apu.noise.output; channels++; } if(channels) sample /= channels; - left = sclamp<16>(sample); + left = volume[sample]; switch(left_volume) { case 0: left >>= 3; break; // 12.5% @@ -43,7 +48,7 @@ void APU::Master::run() { if(channel3_right_enable) { sample += apu.wave.output; channels++; } if(channel4_right_enable) { sample += apu.noise.output; channels++; } if(channels) sample /= channels; - right = sclamp<16>(sample); + right = volume[sample]; switch(right_volume) { case 0: right >>= 3; break; // 12.5% diff --git a/bsnes/gameboy/apu/noise/noise.cpp b/bsnes/gameboy/apu/noise/noise.cpp index 7a016fae..c0def117 100755 --- a/bsnes/gameboy/apu/noise/noise.cpp +++ b/bsnes/gameboy/apu/noise/noise.cpp @@ -16,7 +16,7 @@ void APU::Noise::run() { uint4 sample = (lfsr & 1) ? (uint4)0 : volume; if(enable == false) sample = 0; - output = (sample * 4369) - 32768; + output = sample; } void APU::Noise::clock_length() { diff --git a/bsnes/gameboy/apu/square1/square1.cpp b/bsnes/gameboy/apu/square1/square1.cpp index e0fb5d20..0a9e0132 100755 --- a/bsnes/gameboy/apu/square1/square1.cpp +++ b/bsnes/gameboy/apu/square1/square1.cpp @@ -19,7 +19,7 @@ void APU::Square1::run() { uint4 sample = (duty_output ? volume : (uint4)0); if(enable == false) sample = 0; - output = (sample * 4369) - 32768; + output = sample; } void APU::Square1::sweep(bool update) { diff --git a/bsnes/gameboy/apu/square2/square2.cpp b/bsnes/gameboy/apu/square2/square2.cpp index e75c6d27..09e50678 100755 --- a/bsnes/gameboy/apu/square2/square2.cpp +++ b/bsnes/gameboy/apu/square2/square2.cpp @@ -19,7 +19,7 @@ void APU::Square2::run() { uint4 sample = (duty_output ? volume : (uint4)0); if(enable == false) sample = 0; - output = (sample * 4369) - 32768; + output = sample; } void APU::Square2::clock_length() { diff --git a/bsnes/gameboy/apu/wave/wave.cpp b/bsnes/gameboy/apu/wave/wave.cpp index 8525d0db..3db04eea 100755 --- a/bsnes/gameboy/apu/wave/wave.cpp +++ b/bsnes/gameboy/apu/wave/wave.cpp @@ -6,11 +6,10 @@ void APU::Wave::run() { pattern_sample = pattern[++pattern_offset]; } - uint4 sample = pattern_sample; + uint4 sample = pattern_sample >> volume_shift; if(enable == false) sample = 0; - output = (sample * 4369) - 32768; - output >>= volume_shift; + output = sample; } void APU::Wave::clock_length() { @@ -31,10 +30,10 @@ void APU::Wave::write(unsigned r, uint8 data) { if(r == 2) { //$ff1c NR32 switch((data >> 5) & 3) { - case 0: volume_shift = 16; break; // 0% - case 1: volume_shift = 0; break; //100% - case 2: volume_shift = 1; break; // 50% - case 3: volume_shift = 2; break; // 25% + case 0: volume_shift = 4; break; // 0% + case 1: volume_shift = 0; break; //100% + case 2: volume_shift = 1; break; // 50% + case 3: volume_shift = 2; break; // 25% } } diff --git a/bsnes/gameboy/gameboy.hpp b/bsnes/gameboy/gameboy.hpp index df2d742b..f32c2547 100755 --- a/bsnes/gameboy/gameboy.hpp +++ b/bsnes/gameboy/gameboy.hpp @@ -12,7 +12,7 @@ namespace GameBoy { /* bgameboy - Game Boy emulator author: byuu - license: GPLv2 + license: GPLv3 project started: 2010-12-27 */ diff --git a/bsnes/gameboy/interface/interface.cpp b/bsnes/gameboy/interface/interface.cpp index 5ab8fc51..333b569e 100755 --- a/bsnes/gameboy/interface/interface.cpp +++ b/bsnes/gameboy/interface/interface.cpp @@ -38,6 +38,16 @@ void Interface::unloadCartridge() { cartridge.unload(); } +unsigned Interface::memorySize(Memory memory) { + if(memory == Memory::RAM) return cartridge.ramsize; + return 0u; +} + +uint8_t* Interface::memoryData(Memory memory) { + if(memory == Memory::RAM) return cartridge.ramdata; + return 0u; +} + void Interface::power() { system.power(); } diff --git a/bsnes/gameboy/interface/interface.hpp b/bsnes/gameboy/interface/interface.hpp index 56dea02e..0d79aa5a 100755 --- a/bsnes/gameboy/interface/interface.hpp +++ b/bsnes/gameboy/interface/interface.hpp @@ -13,6 +13,13 @@ public: virtual void loadCartridge(const string &xml, const uint8_t *data, unsigned size); virtual void unloadCartridge(); + enum class Memory : unsigned { + RAM, + }; + + virtual unsigned memorySize(Memory); + virtual uint8_t* memoryData(Memory); + virtual void power(); virtual void run(); diff --git a/bsnes/gameboy/lcd/lcd.cpp b/bsnes/gameboy/lcd/lcd.cpp index 2e1da7af..5c0a5c72 100755 --- a/bsnes/gameboy/lcd/lcd.cpp +++ b/bsnes/gameboy/lcd/lcd.cpp @@ -78,7 +78,7 @@ void LCD::render() { } uint8_t *output = screen + status.ly * 160; - for(unsigned n = 0; n < 160; n++) output[n] = (3 - line[n]) * 0x55; + for(unsigned n = 0; n < 160; n++) output[n] = line[n]; interface->lcdScanline(); } diff --git a/bsnes/nes/cartridge/cartridge.cpp b/bsnes/nes/cartridge/cartridge.cpp index 256af6b0..82599183 100755 --- a/bsnes/nes/cartridge/cartridge.cpp +++ b/bsnes/nes/cartridge/cartridge.cpp @@ -49,6 +49,14 @@ void Cartridge::unload() { loaded = false; } +unsigned Cartridge::ram_size() { + return mapper->ram_size(); +} + +uint8* Cartridge::ram_data() { + return mapper->ram_data(); +} + void Cartridge::power() { mapper->power(); } diff --git a/bsnes/nes/cartridge/cartridge.hpp b/bsnes/nes/cartridge/cartridge.hpp index b3bb1df6..5aff5514 100755 --- a/bsnes/nes/cartridge/cartridge.hpp +++ b/bsnes/nes/cartridge/cartridge.hpp @@ -2,6 +2,9 @@ struct Cartridge : property { void load(const string &xml, const uint8_t *data, unsigned size); void unload(); + unsigned ram_size(); + uint8 *ram_data(); + void power(); void reset(); diff --git a/bsnes/nes/interface/interface.cpp b/bsnes/nes/interface/interface.cpp index f52d3cb0..16011da6 100755 --- a/bsnes/nes/interface/interface.cpp +++ b/bsnes/nes/interface/interface.cpp @@ -36,6 +36,16 @@ void Interface::unloadCartridge() { cartridge.unload(); } +unsigned Interface::memorySize(Memory memory) { + if(memory == Memory::RAM) return cartridge.ram_size(); + return 0u; +} + +uint8_t* Interface::memoryData(Memory memory) { + if(memory == Memory::RAM) return cartridge.ram_data(); + return 0u; +} + void Interface::power() { system.power(); } diff --git a/bsnes/nes/interface/interface.hpp b/bsnes/nes/interface/interface.hpp index 274f5317..2787f57b 100755 --- a/bsnes/nes/interface/interface.hpp +++ b/bsnes/nes/interface/interface.hpp @@ -11,6 +11,13 @@ struct Interface { virtual void loadCartridge(const string &xml, const uint8_t *data, unsigned size); virtual void unloadCartridge(); + enum class Memory : unsigned { + RAM, + }; + + virtual unsigned memorySize(Memory); + virtual uint8_t* memoryData(Memory); + virtual void power(); virtual void reset(); virtual void run(); diff --git a/bsnes/nes/mapper/mapper.cpp b/bsnes/nes/mapper/mapper.cpp index fe41b5d9..40eaf9fd 100755 --- a/bsnes/nes/mapper/mapper.cpp +++ b/bsnes/nes/mapper/mapper.cpp @@ -21,6 +21,14 @@ namespace Mapper { return base; } + unsigned Mapper::ram_size() { + return 0u; + } + + uint8* Mapper::ram_data() { + return 0; + } + #include "none/none.cpp" #include "aorom/aorom.cpp" #include "bandai-fcg/bandai-fcg.cpp" diff --git a/bsnes/nes/mapper/mapper.hpp b/bsnes/nes/mapper/mapper.hpp index be3ff05f..41748798 100755 --- a/bsnes/nes/mapper/mapper.hpp +++ b/bsnes/nes/mapper/mapper.hpp @@ -11,6 +11,9 @@ namespace Mapper { virtual uint8 ciram_read(uint13 addr) = 0; virtual void ciram_write(uint13 addr, uint8 data) = 0; + virtual unsigned ram_size(); + virtual uint8* ram_data(); + virtual void power() = 0; virtual void reset() = 0; }; diff --git a/bsnes/nes/mapper/mmc1/mmc1.cpp b/bsnes/nes/mapper/mmc1/mmc1.cpp index b2cc578f..84ae7c86 100755 --- a/bsnes/nes/mapper/mmc1/mmc1.cpp +++ b/bsnes/nes/mapper/mmc1/mmc1.cpp @@ -170,6 +170,16 @@ void MMC1::ciram_write(uint13 addr, uint8 data) { // +unsigned MMC1::ram_size() { + return 8192u; +} + +uint8* MMC1::ram_data() { + return prg_ram; +} + +// + void MMC1::power() { reset(); } diff --git a/bsnes/nes/mapper/mmc1/mmc1.hpp b/bsnes/nes/mapper/mmc1/mmc1.hpp index b8b4bee3..3351d616 100755 --- a/bsnes/nes/mapper/mmc1/mmc1.hpp +++ b/bsnes/nes/mapper/mmc1/mmc1.hpp @@ -8,6 +8,9 @@ struct MMC1 : Mapper { uint8 ciram_read(uint13 addr); void ciram_write(uint13 addr, uint8 data); + unsigned ram_size(); + uint8 *ram_data(); + void power(); void reset(); diff --git a/bsnes/nes/nes.hpp b/bsnes/nes/nes.hpp index 40280caa..0b7aa956 100755 --- a/bsnes/nes/nes.hpp +++ b/bsnes/nes/nes.hpp @@ -4,14 +4,14 @@ namespace NES { namespace Info { static const char Name[] = "bnes"; - static const char Version[] = "000.13"; + static const char Version[] = "000.14"; } } /* bnes - NES emulator authors: byuu, Ryphecha - license: GPLv2 + license: GPLv3 project started: 2011-09-05 */ diff --git a/bsnes/phoenix/core/core.cpp b/bsnes/phoenix/core/core.cpp index 204996c3..16a3dc7d 100755 --- a/bsnes/phoenix/core/core.cpp +++ b/bsnes/phoenix/core/core.cpp @@ -782,6 +782,10 @@ HexEdit::~HexEdit() { //HorizontalScrollBar //=================== +unsigned HorizontalScrollBar::length() { + return state.length; +} + unsigned HorizontalScrollBar::position() { return p.position(); } @@ -812,6 +816,10 @@ HorizontalScrollBar::~HorizontalScrollBar() { //HorizontalSlider //================ +unsigned HorizontalSlider::length() { + return state.length; +} + unsigned HorizontalSlider::position() { return p.position(); } @@ -1073,6 +1081,10 @@ TextEdit::~TextEdit() { //VerticalScrollBar //================= +unsigned VerticalScrollBar::length() { + return state.length; +} + unsigned VerticalScrollBar::position() { return p.position(); } @@ -1103,6 +1115,10 @@ VerticalScrollBar::~VerticalScrollBar() { //VerticalSlider //============== +unsigned VerticalSlider::length() { + return state.length; +} + unsigned VerticalSlider::position() { return p.position(); } diff --git a/bsnes/phoenix/core/core.hpp b/bsnes/phoenix/core/core.hpp index 14087c87..a87ad95f 100755 --- a/bsnes/phoenix/core/core.hpp +++ b/bsnes/phoenix/core/core.hpp @@ -358,6 +358,7 @@ struct HexEdit : private nall::base_from_member, Widget { struct HorizontalScrollBar : private nall::base_from_member, Widget { nall::function onChange; + unsigned length(); unsigned position(); void setLength(unsigned length); void setPosition(unsigned position); @@ -372,6 +373,7 @@ struct HorizontalScrollBar : private nall::base_from_member, Widget { nall::function onChange; + unsigned length(); unsigned position(); void setLength(unsigned length); void setPosition(unsigned position); @@ -485,6 +487,7 @@ struct TextEdit : private nall::base_from_member, Widget { struct VerticalScrollBar : private nall::base_from_member, Widget { nall::function onChange; + unsigned length(); unsigned position(); void setLength(unsigned length); void setPosition(unsigned position); @@ -499,6 +502,7 @@ struct VerticalScrollBar : private nall::base_from_member, struct VerticalSlider : private nall::base_from_member, Widget { nall::function onChange; + unsigned length(); unsigned position(); void setLength(unsigned length); void setPosition(unsigned position); diff --git a/bsnes/phoenix/core/layout/horizontal-layout.cpp b/bsnes/phoenix/core/layout/horizontal-layout.cpp index 2d0c008e..9f848224 100755 --- a/bsnes/phoenix/core/layout/horizontal-layout.cpp +++ b/bsnes/phoenix/core/layout/horizontal-layout.cpp @@ -41,6 +41,10 @@ Geometry HorizontalLayout::minimumGeometry() { void HorizontalLayout::remove(Sizable &sizable) { for(unsigned n = 0; n < children.size(); n++) { if(children[n].sizable == &sizable) { + if(dynamic_cast(children[n].sizable)) { + Layout *layout = (Layout*)children[n].sizable; + layout->reset(); + } children.remove(n); Layout::remove(sizable); break; @@ -50,6 +54,7 @@ void HorizontalLayout::remove(Sizable &sizable) { void HorizontalLayout::reset() { foreach(child, children) { + if(window() && dynamic_cast(child.sizable)) ((Layout*)child.sizable)->reset(); if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); } } diff --git a/bsnes/phoenix/core/layout/vertical-layout.cpp b/bsnes/phoenix/core/layout/vertical-layout.cpp index 20e884b3..e34abeca 100755 --- a/bsnes/phoenix/core/layout/vertical-layout.cpp +++ b/bsnes/phoenix/core/layout/vertical-layout.cpp @@ -54,6 +54,7 @@ void VerticalLayout::remove(Sizable &sizable) { void VerticalLayout::reset() { foreach(child, children) { + if(window() && dynamic_cast(child.sizable)) ((Layout*)child.sizable)->reset(); if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); } } diff --git a/bsnes/phoenix/gtk/widget/canvas.cpp b/bsnes/phoenix/gtk/widget/canvas.cpp index 9991f93f..a16b2a2d 100755 --- a/bsnes/phoenix/gtk/widget/canvas.cpp +++ b/bsnes/phoenix/gtk/widget/canvas.cpp @@ -11,11 +11,12 @@ uint32_t* pCanvas::buffer() { } void pCanvas::setGeometry(const Geometry &geometry) { - if(geometry.width == cairo_image_surface_get_width(surface) - && geometry.height == cairo_image_surface_get_height(surface)) return; - - cairo_surface_destroy(surface); - surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, geometry.width, geometry.height); + if(geometry.width != cairo_image_surface_get_width (surface) + || geometry.height != cairo_image_surface_get_height(surface) + ) { + cairo_surface_destroy(surface); + surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, geometry.width, geometry.height); + } pWidget::setGeometry(geometry); update(); diff --git a/bsnes/phoenix/qt/font.cpp b/bsnes/phoenix/qt/font.cpp index 3d9addfd..2a303835 100755 --- a/bsnes/phoenix/qt/font.cpp +++ b/bsnes/phoenix/qt/font.cpp @@ -13,8 +13,8 @@ QFont pFont::create(const string &description) { bool italic = part[2].position("Italic"); QFont qtFont; - qtFont.setFamily(part[0]); - qtFont.setPointSize(decimal(part[1])); + qtFont.setFamily(name); + qtFont.setPointSize(size); if(bold) qtFont.setBold(true); if(italic) qtFont.setItalic(true); return qtFont; diff --git a/bsnes/phoenix/qt/platform.moc b/bsnes/phoenix/qt/platform.moc index dd9ed3e2..4b05f4ef 100755 --- a/bsnes/phoenix/qt/platform.moc +++ b/bsnes/phoenix/qt/platform.moc @@ -1,7 +1,7 @@ /**************************************************************************** ** Meta object code from reading C++ file 'platform.moc.hpp' ** -** Created: Fri Sep 16 21:12:56 2011 +** Created: Mon Sep 19 18:14:48 2011 ** by: The Qt Meta Object Compiler version 62 (Qt 4.7.0) ** ** WARNING! All changes made in this file will be lost! diff --git a/bsnes/phoenix/qt/platform.moc.hpp b/bsnes/phoenix/qt/platform.moc.hpp index d1ecee36..74b629b0 100755 --- a/bsnes/phoenix/qt/platform.moc.hpp +++ b/bsnes/phoenix/qt/platform.moc.hpp @@ -243,6 +243,7 @@ struct pWidget : public pSizable { pWidget(Widget &widget) : pSizable(widget), widget(widget) {} void constructor(); + void synchronizeState(); void destructor(); virtual void orphan(); }; diff --git a/bsnes/phoenix/qt/widget/button.cpp b/bsnes/phoenix/qt/widget/button.cpp index 2be213d5..9f5d5a6a 100755 --- a/bsnes/phoenix/qt/widget/button.cpp +++ b/bsnes/phoenix/qt/widget/button.cpp @@ -10,6 +10,8 @@ void pButton::setText(const string &text) { void pButton::constructor() { qtWidget = qtButton = new QPushButton; connect(qtButton, SIGNAL(released()), SLOT(onTick())); + + pWidget::synchronizeState(); setText(button.state.text); } diff --git a/bsnes/phoenix/qt/widget/canvas.cpp b/bsnes/phoenix/qt/widget/canvas.cpp index cc79fd04..abcae404 100755 --- a/bsnes/phoenix/qt/widget/canvas.cpp +++ b/bsnes/phoenix/qt/widget/canvas.cpp @@ -17,6 +17,7 @@ void pCanvas::constructor() { qtWidget = qtCanvas = new QtCanvas(*this); qtImage = new QImage(256, 256, QImage::Format_RGB32); + pWidget::synchronizeState(); update(); } diff --git a/bsnes/phoenix/qt/widget/check-box.cpp b/bsnes/phoenix/qt/widget/check-box.cpp index f23e9d3b..b62e4384 100755 --- a/bsnes/phoenix/qt/widget/check-box.cpp +++ b/bsnes/phoenix/qt/widget/check-box.cpp @@ -21,6 +21,7 @@ void pCheckBox::constructor() { qtWidget = qtCheckBox = new QCheckBox; connect(qtCheckBox, SIGNAL(stateChanged(int)), SLOT(onTick())); + pWidget::synchronizeState(); setChecked(checkBox.state.checked); setText(checkBox.state.text); } diff --git a/bsnes/phoenix/qt/widget/combo-box.cpp b/bsnes/phoenix/qt/widget/combo-box.cpp index b5e57a93..ddabe344 100755 --- a/bsnes/phoenix/qt/widget/combo-box.cpp +++ b/bsnes/phoenix/qt/widget/combo-box.cpp @@ -32,10 +32,12 @@ void pComboBox::constructor() { qtWidget = qtComboBox = new QComboBox; connect(qtComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onChange())); + pWidget::synchronizeState(); + unsigned selection = comboBox.state.selection; locked = true; foreach(text, comboBox.state.text) append(text); locked = false; - setSelection(comboBox.state.selection); + setSelection(selection); } void pComboBox::destructor() { diff --git a/bsnes/phoenix/qt/widget/hex-edit.cpp b/bsnes/phoenix/qt/widget/hex-edit.cpp index a2f61203..fdeebe4a 100755 --- a/bsnes/phoenix/qt/widget/hex-edit.cpp +++ b/bsnes/phoenix/qt/widget/hex-edit.cpp @@ -81,6 +81,7 @@ void pHexEdit::constructor() { connect(qtScroll, SIGNAL(actionTriggered(int)), SLOT(onScroll())); + pWidget::synchronizeState(); setColumns(hexEdit.state.columns); setRows(hexEdit.state.rows); setLength(hexEdit.state.length); diff --git a/bsnes/phoenix/qt/widget/horizontal-scroll-bar.cpp b/bsnes/phoenix/qt/widget/horizontal-scroll-bar.cpp index 5d770c68..6127c301 100755 --- a/bsnes/phoenix/qt/widget/horizontal-scroll-bar.cpp +++ b/bsnes/phoenix/qt/widget/horizontal-scroll-bar.cpp @@ -22,6 +22,7 @@ void pHorizontalScrollBar::constructor() { qtScrollBar->setPageStep(101 >> 3); connect(qtScrollBar, SIGNAL(valueChanged(int)), SLOT(onChange())); + pWidget::synchronizeState(); setLength(horizontalScrollBar.state.length); setPosition(horizontalScrollBar.state.position); } diff --git a/bsnes/phoenix/qt/widget/horizontal-slider.cpp b/bsnes/phoenix/qt/widget/horizontal-slider.cpp index 4cfa7144..5401aae1 100755 --- a/bsnes/phoenix/qt/widget/horizontal-slider.cpp +++ b/bsnes/phoenix/qt/widget/horizontal-slider.cpp @@ -22,6 +22,7 @@ void pHorizontalSlider::constructor() { qtSlider->setPageStep(101 >> 3); connect(qtSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + pWidget::synchronizeState(); setLength(horizontalSlider.state.length); setPosition(horizontalSlider.state.position); } diff --git a/bsnes/phoenix/qt/widget/label.cpp b/bsnes/phoenix/qt/widget/label.cpp index 98adf71f..8dd86eb6 100755 --- a/bsnes/phoenix/qt/widget/label.cpp +++ b/bsnes/phoenix/qt/widget/label.cpp @@ -9,6 +9,8 @@ void pLabel::setText(const string &text) { void pLabel::constructor() { qtWidget = qtLabel = new QLabel; + + pWidget::synchronizeState(); setText(label.state.text); } diff --git a/bsnes/phoenix/qt/widget/line-edit.cpp b/bsnes/phoenix/qt/widget/line-edit.cpp index 6f992028..a13f3b81 100755 --- a/bsnes/phoenix/qt/widget/line-edit.cpp +++ b/bsnes/phoenix/qt/widget/line-edit.cpp @@ -19,6 +19,8 @@ void pLineEdit::constructor() { qtWidget = qtLineEdit = new QLineEdit; connect(qtLineEdit, SIGNAL(returnPressed()), SLOT(onActivate())); connect(qtLineEdit, SIGNAL(textEdited(const QString&)), SLOT(onChange())); + + pWidget::synchronizeState(); setEditable(lineEdit.state.editable); setText(lineEdit.state.text); } diff --git a/bsnes/phoenix/qt/widget/list-view.cpp b/bsnes/phoenix/qt/widget/list-view.cpp index ae169c7d..9d1c7126 100755 --- a/bsnes/phoenix/qt/widget/list-view.cpp +++ b/bsnes/phoenix/qt/widget/list-view.cpp @@ -102,6 +102,7 @@ void pListView::constructor() { connect(qtListView, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), SLOT(onChange(QTreeWidgetItem*))); connect(qtListView, SIGNAL(itemChanged(QTreeWidgetItem*, int)), SLOT(onTick(QTreeWidgetItem*))); + pWidget::synchronizeState(); setCheckable(listView.state.checkable); setHeaderText(listView.state.headerText.size() ? listView.state.headerText : lstring{ " " }); setHeaderVisible(listView.state.headerVisible); @@ -113,6 +114,7 @@ void pListView::constructor() { } setSelected(listView.state.selected); if(listView.state.selected) setSelection(listView.state.selection); + autoSizeColumns(); } void pListView::destructor() { diff --git a/bsnes/phoenix/qt/widget/progress-bar.cpp b/bsnes/phoenix/qt/widget/progress-bar.cpp index a132f5bd..8178bb66 100755 --- a/bsnes/phoenix/qt/widget/progress-bar.cpp +++ b/bsnes/phoenix/qt/widget/progress-bar.cpp @@ -11,6 +11,7 @@ void pProgressBar::constructor() { qtProgressBar->setRange(0, 100); qtProgressBar->setTextVisible(false); + pWidget::synchronizeState(); setPosition(progressBar.state.position); } diff --git a/bsnes/phoenix/qt/widget/radio-box.cpp b/bsnes/phoenix/qt/widget/radio-box.cpp index f02ae64a..f8e7ae40 100755 --- a/bsnes/phoenix/qt/widget/radio-box.cpp +++ b/bsnes/phoenix/qt/widget/radio-box.cpp @@ -23,7 +23,7 @@ void pRadioBox::setGroup(const reference_array &group) { delete qtGroup; qtGroup = 0; } - if(qtRadioBox == group[0].p.qtRadioBox) { + if(group.size() > 0 && qtRadioBox == group[0].p.qtRadioBox) { qtGroup = new QButtonGroup; foreach(item, group) qtGroup->addButton(item.p.qtRadioBox); setChecked(); @@ -42,6 +42,8 @@ void pRadioBox::constructor() { qtRadioBox->setChecked(true); connect(qtRadioBox, SIGNAL(toggled(bool)), SLOT(onTick())); + pWidget::synchronizeState(); + setGroup(radioBox.state.group); setText(radioBox.state.text); } diff --git a/bsnes/phoenix/qt/widget/text-edit.cpp b/bsnes/phoenix/qt/widget/text-edit.cpp index 67b8578f..ed168866 100755 --- a/bsnes/phoenix/qt/widget/text-edit.cpp +++ b/bsnes/phoenix/qt/widget/text-edit.cpp @@ -24,6 +24,8 @@ string pTextEdit::text() { void pTextEdit::constructor() { qtWidget = qtTextEdit = new QTextEdit; connect(qtTextEdit, SIGNAL(textChanged()), SLOT(onChange())); + + pWidget::synchronizeState(); setEditable(textEdit.state.editable); setText(textEdit.state.text); setWordWrap(textEdit.state.wordWrap); diff --git a/bsnes/phoenix/qt/widget/vertical-scroll-bar.cpp b/bsnes/phoenix/qt/widget/vertical-scroll-bar.cpp index 226a0274..74d68ca6 100755 --- a/bsnes/phoenix/qt/widget/vertical-scroll-bar.cpp +++ b/bsnes/phoenix/qt/widget/vertical-scroll-bar.cpp @@ -22,6 +22,7 @@ void pVerticalScrollBar::constructor() { qtScrollBar->setPageStep(101 >> 3); connect(qtScrollBar, SIGNAL(valueChanged(int)), SLOT(onChange())); + pWidget::synchronizeState(); setLength(verticalScrollBar.state.length); setPosition(verticalScrollBar.state.position); } diff --git a/bsnes/phoenix/qt/widget/vertical-slider.cpp b/bsnes/phoenix/qt/widget/vertical-slider.cpp index a42c4cb8..500adb07 100755 --- a/bsnes/phoenix/qt/widget/vertical-slider.cpp +++ b/bsnes/phoenix/qt/widget/vertical-slider.cpp @@ -22,6 +22,7 @@ void pVerticalSlider::constructor() { qtSlider->setPageStep(101 >> 3); connect(qtSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + pWidget::synchronizeState(); setLength(verticalSlider.state.length); setPosition(verticalSlider.state.position); } diff --git a/bsnes/phoenix/qt/widget/viewport.cpp b/bsnes/phoenix/qt/widget/viewport.cpp index ed03c566..3a6254f9 100755 --- a/bsnes/phoenix/qt/widget/viewport.cpp +++ b/bsnes/phoenix/qt/widget/viewport.cpp @@ -6,6 +6,8 @@ void pViewport::constructor() { qtWidget = new QWidget; qtWidget->setAttribute(Qt::WA_PaintOnScreen, true); qtWidget->setStyleSheet("background: #000000"); + + pWidget::synchronizeState(); } void pViewport::destructor() { diff --git a/bsnes/phoenix/qt/widget/widget.cpp b/bsnes/phoenix/qt/widget/widget.cpp index e05f70a9..0bc4901c 100755 --- a/bsnes/phoenix/qt/widget/widget.cpp +++ b/bsnes/phoenix/qt/widget/widget.cpp @@ -31,6 +31,14 @@ void pWidget::constructor() { if(widget.state.abstract) qtWidget = new QWidget; } +//pWidget::constructor() called before p{Derived}::constructor(); ergo qtWidget is not yet valid +//pWidget::synchronizeState() is called to finish construction of p{Derived}::constructor() +void pWidget::synchronizeState() { + setEnabled(widget.state.enabled); + setFont(widget.state.font); +//setVisible(widget.state.visible); +} + void pWidget::destructor() { if(widget.state.abstract) { delete qtWidget; diff --git a/bsnes/snes/chip/icd2/icd2.cpp b/bsnes/snes/chip/icd2/icd2.cpp index 45208c6b..0d515b5e 100755 --- a/bsnes/snes/chip/icd2/icd2.cpp +++ b/bsnes/snes/chip/icd2/icd2.cpp @@ -59,8 +59,8 @@ void ICD2::reset() { r7800 = 0x0000; mlt_req = 0; - foreach(byte, lcd.buffer) byte = 0xff; - foreach(byte, lcd.output) byte = 0xff; + foreach(byte, lcd.buffer) byte = 0; + foreach(byte, lcd.output) byte = 0; lcd.row = 0; packetsize = 0; diff --git a/bsnes/snes/chip/icd2/mmio/mmio.cpp b/bsnes/snes/chip/icd2/mmio/mmio.cpp index 0ee16ef5..0f6f64e2 100755 --- a/bsnes/snes/chip/icd2/mmio/mmio.cpp +++ b/bsnes/snes/chip/icd2/mmio/mmio.cpp @@ -6,9 +6,7 @@ void ICD2::render(const uint8 *source) { for(unsigned y = 0; y < 8; y++) { for(unsigned x = 0; x < 160; x++) { - unsigned pixel = *source++ / 0x55; - pixel ^= 3; - + unsigned pixel = *source++; unsigned addr = y * 2 + (x / 8 * 16); lcd.output[addr + 0] |= ((pixel & 1) >> 0) << (7 - (x & 7)); lcd.output[addr + 1] |= ((pixel & 2) >> 1) << (7 - (x & 7)); diff --git a/bsnes/snes/interface/interface.cpp b/bsnes/snes/interface/interface.cpp index 5f6fba95..54fa945d 100755 --- a/bsnes/snes/interface/interface.cpp +++ b/bsnes/snes/interface/interface.cpp @@ -66,6 +66,14 @@ void Interface::unloadCartridge() { cartridge.unload(); } +Cartridge::Information& Interface::information() { + return cartridge.information; +} + +linear_vector& Interface::memory() { + return cartridge.nvram; +} + void Interface::power() { system.power(); } diff --git a/bsnes/snes/interface/interface.hpp b/bsnes/snes/interface/interface.hpp index c91c98a6..7f7acb45 100755 --- a/bsnes/snes/interface/interface.hpp +++ b/bsnes/snes/interface/interface.hpp @@ -22,6 +22,9 @@ public: virtual void loadSuperGameBoyCartridge(const CartridgeData &base, const CartridgeData &slot); virtual void unloadCartridge(); + Cartridge::Information& information(); + linear_vector& memory(); + virtual void power(); virtual void reset(); virtual void run(); diff --git a/bsnes/snes/ppu/screen/screen.cpp b/bsnes/snes/ppu/screen/screen.cpp index 04beab96..5de6c33c 100755 --- a/bsnes/snes/ppu/screen/screen.cpp +++ b/bsnes/snes/ppu/screen/screen.cpp @@ -214,7 +214,7 @@ PPU::Screen::Screen(PPU &self) : self(self) { unsigned ar = (luma * r + 0.5); unsigned ag = (luma * g + 0.5); unsigned ab = (luma * b + 0.5); - light_table[l][(r << 10) + (g << 5) + b] = (ab << 10) + (ag << 5) + ar; + light_table[l][(b << 10) + (g << 5) + r] = (ab << 10) + (ag << 5) + ar; } } } diff --git a/bsnes/snes/snes.hpp b/bsnes/snes/snes.hpp index 31871ed9..3e4e6144 100755 --- a/bsnes/snes/snes.hpp +++ b/bsnes/snes/snes.hpp @@ -4,7 +4,7 @@ namespace SNES { namespace Info { static const char Name[] = "bsnes"; - static const char Version[] = "082.17"; + static const char Version[] = "082.18"; static const unsigned SerializerVersion = 22; } } @@ -12,7 +12,7 @@ namespace SNES { /* bsnes - SNES emulator author: byuu - license: GPLv2 + license: GPLv3 project started: 2004-10-14 */ diff --git a/bsnes/ui/config/config.cpp b/bsnes/ui/config/config.cpp index b1b09412..6d3cd7be 100755 --- a/bsnes/ui/config/config.cpp +++ b/bsnes/ui/config/config.cpp @@ -5,11 +5,24 @@ Config::Config() { attach(video.driver = "", "Video::Driver"); attach(video.shader = "", "Video::Shader"); attach(video.synchronize = true, "Video::Synchronize"); - attach(video.smooth = false, "Video::Smooth"); + attach(video.enableOverscan = false, "Video::EnableOverscan"); + attach(video.correctAspectRatio = true, "Video::CorrectAspectRatio"); + attach(video.smooth = true, "Video::Smooth"); + + attach(video.brightness = 100, "Video::Brightness"); + attach(video.contrast = 100, "Video::Contrast"); + attach(video.gamma = 100, "Video::Gamma"); + attach(video.gammaRamp = true, "Video::GammaRamp"); + attach(video.fullScreenMode = 0, "Video::FullScreenMode"); attach(audio.driver = "", "Audio::Driver"); attach(audio.synchronize = true, "Audio::Synchronize"); attach(audio.mute = false, "Audio::Mute"); + attach(audio.volume = 100, "Audio::Volume"); + + attach(audio.frequencyNES = 1789772, "Audio::Frequency::NES"); + attach(audio.frequencySNES = 32000, "Audio::Frequency::SNES"); + attach(audio.frequencyGameBoy = 4194304, "Audio::Frequency::GameBoy"); attach(input.driver = "", "Input::Driver"); attach(input.focusPolicy = 1, "Input::FocusPolicy"); diff --git a/bsnes/ui/config/config.hpp b/bsnes/ui/config/config.hpp index 3bacf588..2d757174 100755 --- a/bsnes/ui/config/config.hpp +++ b/bsnes/ui/config/config.hpp @@ -3,13 +3,26 @@ struct Config : public configuration { string driver; string shader; bool synchronize; + bool enableOverscan; + bool correctAspectRatio; bool smooth; + + unsigned brightness; + unsigned contrast; + unsigned gamma; + bool gammaRamp; + unsigned fullScreenMode; } video; struct Audio { string driver; bool synchronize; bool mute; + unsigned volume; + + unsigned frequencyNES; + unsigned frequencySNES; + unsigned frequencyGameBoy; } audio; struct Input { diff --git a/bsnes/ui/general/dip-switches.cpp b/bsnes/ui/general/dip-switches.cpp new file mode 100755 index 00000000..8920ffbd --- /dev/null +++ b/bsnes/ui/general/dip-switches.cpp @@ -0,0 +1,69 @@ +DipSwitches *dipSwitches = 0; + +DipSwitch::DipSwitch() { + append(name, ~0, 0, 5); + append(value, ~0, 0); +} + +DipSwitches::DipSwitches() { + setTitle("DIP Switches"); + + layout.setMargin(5); + acceptButton.setText("Accept"); + + append(layout); + for(unsigned n = 0; n < 8; n++) + layout.append(dip[n], ~0, 0, 5); + layout.append(controlLayout, ~0, 0, 5); + controlLayout.append(spacer, ~0, 0); + controlLayout.append(acceptButton, 0, 0); + + setGeometry({ 128, 128, 400, layout.minimumGeometry().height }); + windowManager->append(this, "DipSwitches"); + + acceptButton.onTick = { &DipSwitches::accept, this }; +} + +void DipSwitches::load() { + if(interface->mode() != Interface::Mode::SNES || SNES::cartridge.has_nss_dip() == false) return; + application->pause = true; + + auto info = interface->snes.information().nss; + unsigned count = info.setting.size(); + + for(unsigned n = 0; n < min(8, count); n++) { + dip[n].setEnabled(true); + dip[n].name.setText(info.setting[n]); + dip[n].value.reset(); + for(unsigned z = 0; z < min(16, info.option[n].size()); z++) { + lstring part; + part.split<1>(":", info.option[n][z]); + values[n][z] = hex(part[0]); + dip[n].value.append(part[1]); + } + } + + for(unsigned n = count; n < 8; n++) { + dip[n].setEnabled(false); + dip[n].name.setText("(unused)"); + dip[n].value.reset(); + dip[n].value.append("(unused)"); + } + + acceptButton.setFocused(); + setVisible(); +} + +void DipSwitches::accept() { + auto info = interface->snes.information().nss; + unsigned count = info.setting.size(); + + unsigned result = 0x0000; + for(unsigned n = 0; n < min(8, count); n++) { + result |= values[n][dip[n].value.selection()]; + } + + setVisible(false); + SNES::nss.set_dip(result); + application->pause = false; +} diff --git a/bsnes/ui/general/dip-switches.hpp b/bsnes/ui/general/dip-switches.hpp new file mode 100755 index 00000000..2b023738 --- /dev/null +++ b/bsnes/ui/general/dip-switches.hpp @@ -0,0 +1,23 @@ +struct DipSwitch : HorizontalLayout { + Label name; + ComboBox value; + + DipSwitch(); +}; + +struct DipSwitches : Window { + VerticalLayout layout; + DipSwitch dip[8]; + HorizontalLayout controlLayout; + Widget spacer; + Button acceptButton; + + void load(); + void accept(); + DipSwitches(); + +private: + unsigned values[8][16]; +}; + +extern DipSwitches *dipSwitches; diff --git a/bsnes/ui/general/general.cpp b/bsnes/ui/general/general.cpp index 50e7f351..b8c55a30 100755 --- a/bsnes/ui/general/general.cpp +++ b/bsnes/ui/general/general.cpp @@ -2,3 +2,4 @@ #include "main-window.cpp" #include "file-browser.cpp" #include "slot-loader.cpp" +#include "dip-switches.cpp" diff --git a/bsnes/ui/general/general.hpp b/bsnes/ui/general/general.hpp index a0f43444..ab6a06af 100755 --- a/bsnes/ui/general/general.hpp +++ b/bsnes/ui/general/general.hpp @@ -1,3 +1,4 @@ #include "main-window.hpp" #include "file-browser.hpp" #include "slot-loader.hpp" +#include "dip-switches.hpp" diff --git a/bsnes/ui/general/main-window.cpp b/bsnes/ui/general/main-window.cpp index 54010bcb..f4b82fcd 100755 --- a/bsnes/ui/general/main-window.cpp +++ b/bsnes/ui/general/main-window.cpp @@ -2,7 +2,7 @@ MainWindow *mainWindow = 0; MainWindow::MainWindow() { setTitle(application->title); - setGeometry({ 256, 256, 512, 480 }); + setGeometry({ 256, 256, 640, 480 }); setBackgroundColor({ 0, 0, 0 }); windowManager->append(this, "MainWindow"); @@ -65,6 +65,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); settingsSmoothVideo.setText("Smooth Video Output"); settingsSmoothVideo.setChecked(config->video.smooth); settingsMuteAudio.setText("Mute Audio"); @@ -146,9 +150,12 @@ MainWindow::MainWindow() { settingsMenu.append(settingsSeparator1); settingsMenu.append(settingsSynchronizeVideo); settingsMenu.append(settingsSynchronizeAudio); + settingsMenu.append(settingsSeparator2); + settingsMenu.append(settingsEnableOverscan); + settingsMenu.append(settingsCorrectAspectRatio); settingsMenu.append(settingsSmoothVideo); settingsMenu.append(settingsMuteAudio); - settingsMenu.append(settingsSeparator2); + settingsMenu.append(settingsSeparator3); settingsMenu.append(settingsConfiguration); append(toolsMenu); @@ -250,6 +257,16 @@ 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(); + }; + settingsSmoothVideo.onTick = [&] { config->video.smooth = settingsSmoothVideo.checked(); video.set(Video::Filter, config->video.smooth == false ? 0u : 1u); @@ -262,17 +279,17 @@ MainWindow::MainWindow() { settingsConfiguration.onTick = [&] { settingsWindow->setVisible(); }; - toolsStateSave1.onTick = [&] { interface->saveState({ interface->baseName, "-1.bst" }); }; - toolsStateSave2.onTick = [&] { interface->saveState({ interface->baseName, "-2.bst" }); }; - toolsStateSave3.onTick = [&] { interface->saveState({ interface->baseName, "-3.bst" }); }; - toolsStateSave4.onTick = [&] { interface->saveState({ interface->baseName, "-4.bst" }); }; - toolsStateSave5.onTick = [&] { interface->saveState({ interface->baseName, "-5.bst" }); }; + toolsStateSave1.onTick = [&] { interface->saveState(1); }; + toolsStateSave2.onTick = [&] { interface->saveState(2); }; + toolsStateSave3.onTick = [&] { interface->saveState(3); }; + toolsStateSave4.onTick = [&] { interface->saveState(4); }; + toolsStateSave5.onTick = [&] { interface->saveState(5); }; - toolsStateLoad1.onTick = [&] { interface->loadState({ interface->baseName, "-1.bst" }); }; - toolsStateLoad2.onTick = [&] { interface->loadState({ interface->baseName, "-2.bst" }); }; - toolsStateLoad3.onTick = [&] { interface->loadState({ interface->baseName, "-3.bst" }); }; - toolsStateLoad4.onTick = [&] { interface->loadState({ interface->baseName, "-4.bst" }); }; - toolsStateLoad5.onTick = [&] { interface->loadState({ interface->baseName, "-5.bst" }); }; + toolsStateLoad1.onTick = [&] { interface->loadState(1); }; + toolsStateLoad2.onTick = [&] { interface->loadState(2); }; + toolsStateLoad3.onTick = [&] { interface->loadState(3); }; + toolsStateLoad4.onTick = [&] { interface->loadState(4); }; + toolsStateLoad5.onTick = [&] { interface->loadState(5); }; toolsCaptureMouse.onTick = [&] { input.acquire(); }; toolsShrinkWindow.onTick = [&] { utility->resizeMainWindow(true); }; diff --git a/bsnes/ui/general/main-window.hpp b/bsnes/ui/general/main-window.hpp index 5ed765d3..ce6bbf2a 100755 --- a/bsnes/ui/general/main-window.hpp +++ b/bsnes/ui/general/main-window.hpp @@ -45,9 +45,12 @@ struct MainWindow : Window { Separator settingsSeparator1; CheckItem settingsSynchronizeVideo; CheckItem settingsSynchronizeAudio; + Separator settingsSeparator2; + CheckItem settingsEnableOverscan; + CheckItem settingsCorrectAspectRatio; CheckItem settingsSmoothVideo; CheckItem settingsMuteAudio; - Separator settingsSeparator2; + Separator settingsSeparator3; Item settingsConfiguration; Menu toolsMenu; diff --git a/bsnes/ui/input/input.cpp b/bsnes/ui/input/input.cpp index 8f62f022..02724ff1 100755 --- a/bsnes/ui/input/input.cpp +++ b/bsnes/ui/input/input.cpp @@ -177,10 +177,6 @@ void InputManager::scan() { if(mainWindow->focused()) userInterface.inputEvent(n, scancode[activeScancode][n]); } } - - if(scancode[activeScancode][keyboard(0)[Keyboard::Escape]]) { - if(mainWindow->fullScreen() == false && input.acquired()) input.unacquire(); - } } InputManager::InputManager() { diff --git a/bsnes/ui/input/user-interface.cpp b/bsnes/ui/input/user-interface.cpp index 02cc6796..2363cca3 100755 --- a/bsnes/ui/input/user-interface.cpp +++ b/bsnes/ui/input/user-interface.cpp @@ -1,4 +1,28 @@ void HotkeyGeneral::inputEvent(int16_t scancode, int16_t value) { + if(scancode == saveState.scancode && value) { + interface->saveState(activeSlot); + } + + if(scancode == loadState.scancode && value) { + interface->loadState(activeSlot); + } + + if(scancode == decrementSlot.scancode && value) { + if(--activeSlot == 0) activeSlot = 5; + utility->showMessage({ "Selected slot ", activeSlot }); + } + + if(scancode == incrementSlot.scancode && value) { + if(++activeSlot == 6) activeSlot = 1; + utility->showMessage({ "Selected slot ", activeSlot }); + } + + if(scancode == toggleMouseCapture.scancode && value) { + if(mainWindow->fullScreen() == false) { + input.acquired() ? input.unacquire() : input.acquire(); + } + } + if(scancode == toggleFullScreen.scancode && value) { utility->toggleFullScreen(); } @@ -19,22 +43,57 @@ void HotkeyGeneral::inputEvent(int16_t scancode, int16_t value) { audio.set(Audio::Synchronize, Async); } } + + if(scancode == power.scancode && value) { + interface->power(); + } + + if(scancode == reset.scancode && value) { + interface->reset(); + } + + if(scancode == quit.scancode && value) { + application->quit = true; + } } HotkeyGeneral::HotkeyGeneral() { name = "General"; + saveState.name = "Save state"; + loadState.name = "Load state"; + decrementSlot.name = "Decrement state slot"; + incrementSlot.name = "Increment state slot"; + toggleMouseCapture.name = "Toggle mouse capture"; toggleFullScreen.name = "Toggle fullscreen"; pause.name = "Pause emulation"; turboMode.name = "Turbo mode"; + power.name = "Power cycle"; + reset.name = "Reset"; + quit.name = "Close emulator"; + saveState.mapping = "KB0::F5"; + loadState.mapping = "KB0::F7"; + decrementSlot.mapping = "KB0::F6"; + incrementSlot.mapping = "KB0::F8"; + toggleMouseCapture.mapping = "KB0::F12"; toggleFullScreen.mapping = "KB0::F11"; pause.mapping = "KB0::P"; turboMode.mapping = "KB0::Tilde"; + append(saveState); + append(loadState); + append(decrementSlot); + append(incrementSlot); + append(toggleMouseCapture); append(toggleFullScreen); append(pause); append(turboMode); + append(power); + append(reset); + append(quit); + + activeSlot = 1; } // diff --git a/bsnes/ui/input/user-interface.hpp b/bsnes/ui/input/user-interface.hpp index 328b26b9..62134c2d 100755 --- a/bsnes/ui/input/user-interface.hpp +++ b/bsnes/ui/input/user-interface.hpp @@ -1,10 +1,21 @@ struct HotkeyGeneral : TertiaryInput { + DigitalInput saveState; + DigitalInput loadState; + DigitalInput decrementSlot; + DigitalInput incrementSlot; + DigitalInput toggleMouseCapture; DigitalInput toggleFullScreen; DigitalInput pause; DigitalInput turboMode; + DigitalInput power; + DigitalInput reset; + DigitalInput quit; void inputEvent(int16_t scancode, int16_t value); HotkeyGeneral(); + +private: + unsigned activeSlot; }; struct HotkeyInput : SecondaryInput { diff --git a/bsnes/ui/interface/gameboy.cpp b/bsnes/ui/interface/gameboy.cpp index c65377ce..a8bf1bbe 100755 --- a/bsnes/ui/interface/gameboy.cpp +++ b/bsnes/ui/interface/gameboy.cpp @@ -10,11 +10,27 @@ bool InterfaceGameBoy::loadCartridge(const string &filename) { GameBoy::Interface::loadCartridge(info.xml, data, size); delete[] data; + if(GameBoy::Interface::memorySize(GameBoy::Interface::Memory::RAM) > 0) { + filemap fp; + if(fp.open(string{ interface->baseName, ".sav" }, filemap::mode::read)) { + memcpy(GameBoy::Interface::memoryData(GameBoy::Interface::Memory::RAM), fp.data(), + min(GameBoy::Interface::memorySize(GameBoy::Interface::Memory::RAM), fp.size()) + ); + } + } + interface->loadCartridge(::Interface::Mode::GameBoy); return true; } void InterfaceGameBoy::unloadCartridge() { + if(GameBoy::Interface::memorySize(GameBoy::Interface::Memory::RAM) > 0) { + file::write({ interface->baseName, ".sav" }, + GameBoy::Interface::memoryData(GameBoy::Interface::Memory::RAM), + GameBoy::Interface::memorySize(GameBoy::Interface::Memory::RAM) + ); + } + GameBoy::Interface::unloadCartridge(); interface->baseName = ""; } @@ -36,23 +52,21 @@ bool InterfaceGameBoy::loadState(const string &filename) { // void InterfaceGameBoy::videoRefresh(const uint8_t *data) { - interface->videoRefresh(); + static uint16_t output[160 * 144]; + static uint32_t palette[] = { + 0x9bbc0f, 0x8bac0f, 0x306230, 0x0f380f + }; - uint32_t *output; - unsigned outpitch; - if(video.lock(output, outpitch, 160, 144)) { - for(unsigned y = 0; y < 144; y++) { - const uint8_t *sp = data + y * 160; - uint32_t *dp = output + y * (outpitch >> 2); - for(unsigned x = 0; x < 160; x++) { - uint32_t color = *sp++; - *dp++ = (color << 16) | (color << 8) | (color << 0); - } + for(unsigned y = 0; y < 144; y++) { + const uint8_t *sp = data + y * 160; + uint16_t *dp = output + y * 160; + for(unsigned x = 0; x < 160; x++) { + uint32_t color = palette[*sp++]; + *dp++ = ((color & 0xf80000) >> 9) | ((color & 0x00f800) >> 6) | ((color & 0x0000f8) >> 3); } - - video.unlock(); - video.refresh(); } + + interface->videoRefresh(output, 160 * 2, 160, 144); } void InterfaceGameBoy::audioSample(int16_t csample, int16_t lsample, int16_t rsample) { diff --git a/bsnes/ui/interface/interface.cpp b/bsnes/ui/interface/interface.cpp index b36bfee4..d64ba9a5 100755 --- a/bsnes/ui/interface/interface.cpp +++ b/bsnes/ui/interface/interface.cpp @@ -1,4 +1,5 @@ #include "../base.hpp" +#include "palette.cpp" #include "nes.cpp" #include "snes.cpp" #include "gameboy.cpp" @@ -25,6 +26,15 @@ void Interface::setController(unsigned port, unsigned device) { } } +void Interface::updateDSP() { + dspaudio.setVolume((double)config->audio.volume / 100.0); + switch(mode()) { + case Mode::NES: return dspaudio.setFrequency(config->audio.frequencyNES); + case Mode::SNES: return dspaudio.setFrequency(config->audio.frequencySNES); + case Mode::GameBoy: return dspaudio.setFrequency(config->audio.frequencyGameBoy); + } +} + bool Interface::cartridgeLoaded() { switch(mode()) { case Mode::NES: return nes.cartridgeLoaded(); @@ -39,6 +49,7 @@ void Interface::loadCartridge(Mode mode) { bindControllers(); cheatEditor->load({ baseName, ".cht" }); stateManager->load({ baseName, ".bsa" }, 0u); + dipSwitches->load(); utility->showMessage({ "Loaded ", notdir(baseName) }); } @@ -63,23 +74,27 @@ void Interface::unloadCartridge() { case Mode::GameBoy: gameBoy.unloadCartridge(); break; } + interface->baseName = ""; + interface->slotName.reset(); utility->setMode(mode = Mode::None); } void Interface::power() { switch(mode()) { - case Mode::NES: return nes.power(); - case Mode::SNES: return snes.power(); - case Mode::GameBoy: return gameBoy.power(); + case Mode::NES: nes.power(); break; + case Mode::SNES: snes.power(); break; + case Mode::GameBoy: gameBoy.power(); break; } + utility->showMessage("System power was cycled"); } void Interface::reset() { switch(mode()) { - case Mode::NES: return nes.reset(); - case Mode::SNES: return snes.reset(); - case Mode::GameBoy: return gameBoy.power(); //Game Boy lacks reset button + case Mode::NES: nes.reset(); break; + case Mode::SNES: snes.reset(); break; + case Mode::GameBoy: gameBoy.power(); break; //Game Boy lacks reset button } + utility->showMessage("System was reset"); } void Interface::run() { @@ -106,23 +121,25 @@ bool Interface::unserialize(serializer &s) { return false; } -bool Interface::saveState(const string &filename) { +bool Interface::saveState(unsigned slot) { + string filename = { baseName, "-", slot, ".bst" }; bool result = false; switch(mode()) { case Mode::SNES: result = snes.saveState(filename); break; case Mode::GameBoy: result = gameBoy.saveState(filename); break; } - utility->showMessage(result == true ? "Saved state" : "Failed to save state"); + utility->showMessage(result == true ? string{ "Saved state ", slot } : "Failed to save state"); return result; } -bool Interface::loadState(const string &filename) { +bool Interface::loadState(unsigned slot) { + string filename = { baseName, "-", slot, ".bst" }; bool result = false; switch(mode()) { case Mode::SNES: result = snes.loadState(filename); break; case Mode::GameBoy: result = gameBoy.loadState(filename); break; } - utility->showMessage(result == true ? "Loaded state" : "Failed to load state"); + utility->showMessage(result == true ? string{ "Loaded state ", slot } : "Failed to load state"); return result; } @@ -136,6 +153,7 @@ void Interface::setCheatCodes(const lstring &list) { Interface::Interface() { mode = Mode::None; + palette.update(); nes.initialize(&nes); snes.initialize(&snes); gameBoy.initialize(&gameBoy); @@ -143,7 +161,26 @@ Interface::Interface() { //internal -void Interface::videoRefresh() { +//RGB555 input +void Interface::videoRefresh(const uint16_t *input, unsigned inputPitch, unsigned width, unsigned height) { + uint32_t *output; + unsigned outputPitch; + + if(video.lock(output, outputPitch, width, height)) { + inputPitch >>= 1, outputPitch >>= 2; + + for(unsigned y = 0; y < height; y++) { + const uint16_t *sp = input + y * inputPitch; + uint32_t *dp = output + y * outputPitch; + for(unsigned x = 0; x < width; x++) { + *dp++ = palette[*sp++]; + } + } + + video.unlock(); + video.refresh(); + } + static unsigned frameCounter = 0; static time_t previous, current; frameCounter++; diff --git a/bsnes/ui/interface/interface.hpp b/bsnes/ui/interface/interface.hpp index 95c1b258..1b693ad7 100755 --- a/bsnes/ui/interface/interface.hpp +++ b/bsnes/ui/interface/interface.hpp @@ -1,3 +1,5 @@ +#include "palette.hpp" + #include "nes.hpp" #include "snes.hpp" #include "gameboy.hpp" @@ -8,6 +10,7 @@ struct Interface : property { void bindControllers(); void setController(unsigned port, unsigned device); + void updateDSP(); bool cartridgeLoaded(); void loadCartridge(Mode mode); @@ -21,15 +24,16 @@ struct Interface : property { serializer serialize(); bool unserialize(serializer&); - bool saveState(const string &filename); - bool loadState(const string &filename); + bool saveState(unsigned slot); + bool loadState(unsigned slot); void setCheatCodes(const lstring &list = lstring{}); Interface(); - void videoRefresh(); + void videoRefresh(const uint16_t *input, unsigned inputPitch, unsigned width, unsigned height); string baseName; // = "/path/to/cartridge" (no extension) + lstring slotName; InterfaceNES nes; InterfaceSNES snes; diff --git a/bsnes/ui/interface/nes.cpp b/bsnes/ui/interface/nes.cpp index 6487fd4e..68a8c038 100755 --- a/bsnes/ui/interface/nes.cpp +++ b/bsnes/ui/interface/nes.cpp @@ -23,11 +23,26 @@ bool InterfaceNES::loadCartridge(const string &filename) { NES::Interface::loadCartridge("", fp.data(), fp.size()); fp.close(); + if(NES::Interface::memorySize(NES::Interface::Memory::RAM) > 0) { + 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()) + ); + } + } + interface->loadCartridge(::Interface::Mode::NES); return true; } void InterfaceNES::unloadCartridge() { + if(NES::Interface::memorySize(NES::Interface::Memory::RAM) > 0) { + file::write({ interface->baseName, ".sav" }, + NES::Interface::memoryData(NES::Interface::Memory::RAM), + NES::Interface::memorySize(NES::Interface::Memory::RAM) + ); + } + NES::Interface::unloadCartridge(); interface->baseName = ""; } @@ -35,22 +50,24 @@ void InterfaceNES::unloadCartridge() { // void InterfaceNES::videoRefresh(const uint16_t *data) { - interface->videoRefresh(); + static uint16_t output[256 * 240]; - uint32_t *output; - unsigned outpitch; - if(video.lock(output, outpitch, 256, 240)) { - for(unsigned y = 0; y < 240; y++) { - const uint16_t *sp = data + y * 256; - uint32_t *dp = output + y * (outpitch >> 2); - for(unsigned x = 0; x < 256; x++) { - *dp++ = palette[*sp++]; - } - } - - video.unlock(); - video.refresh(); + unsigned height = 240; + if(config->video.enableOverscan == false) { + height = 224; + data += 8 * 256; } + + for(unsigned y = 0; y < height; y++) { + const uint16_t *sp = data + y * 256; + uint16_t *dp = output + y * 256; + for(unsigned x = 0; x < 256; x++) { + uint32_t color = palette[*sp++]; + *dp++ = ((color & 0xf80000) >> 9) | ((color & 0x00f800) >> 6) | ((color & 0x0000f8) >> 3);; + } + } + + interface->videoRefresh(output, 256 * 2, 256, height); } void InterfaceNES::audioSample(int16_t sample) { diff --git a/bsnes/ui/interface/palette.cpp b/bsnes/ui/interface/palette.cpp new file mode 100755 index 00000000..bbb7789b --- /dev/null +++ b/bsnes/ui/interface/palette.cpp @@ -0,0 +1,67 @@ +Palette palette; + +uint32_t Palette::operator[](unsigned n) { + return color[n]; +} + +const uint8_t Palette::gammaRamp[32] = { + 0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c, + 0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78, + 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0, + 0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff, +}; + +uint8_t Palette::contrastAdjust(uint8_t input) { + signed contrast = config->video.contrast - 100; + signed result = input - contrast + (2 * contrast * input + 127) / 255; + return max(0, min(255, result)); +} + +uint8_t Palette::brightnessAdjust(uint8_t input) { + signed brightness = config->video.brightness - 100; + signed result = input + brightness; + return max(0, min(255, result)); +} + +uint8_t Palette::gammaAdjust(uint8_t input) { + signed result = (signed)(pow(((double)input / 255.0), (double)config->video.gamma / 100.0) * 255.0 + 0.5); + return max(0, min(255, result)); +} + +void Palette::update() { + for(unsigned i = 0; i < 32768; i++) { + unsigned r = (i >> 10) & 31; + unsigned g = (i >> 5) & 31; + unsigned b = (i >> 0) & 31; + + r = (r << 3) | (r >> 2); + g = (g << 3) | (g >> 2); + b = (b << 3) | (b >> 2); + + if(config->video.gammaRamp) { + r = gammaRamp[r >> 3]; + g = gammaRamp[g >> 3]; + b = gammaRamp[b >> 3]; + } + + if(config->video.contrast != 100) { + r = contrastAdjust(r); + g = contrastAdjust(g); + b = contrastAdjust(b); + } + + if(config->video.brightness != 100) { + r = brightnessAdjust(r); + g = brightnessAdjust(g); + b = brightnessAdjust(b); + } + + if(config->video.gamma != 100) { + r = gammaAdjust(r); + g = gammaAdjust(g); + b = gammaAdjust(b); + } + + color[i] = (r << 16) | (g << 8) | (b << 0); + } +} diff --git a/bsnes/ui/interface/palette.hpp b/bsnes/ui/interface/palette.hpp new file mode 100755 index 00000000..0579ae4d --- /dev/null +++ b/bsnes/ui/interface/palette.hpp @@ -0,0 +1,14 @@ +struct Palette { + alwaysinline uint32_t operator[](unsigned n); + + uint8_t contrastAdjust(uint8_t); + uint8_t brightnessAdjust(uint8_t); + uint8_t gammaAdjust(uint8_t); + void update(); + +private: + static const uint8_t gammaRamp[32]; + uint32_t color[32768]; +}; + +extern Palette palette; diff --git a/bsnes/ui/interface/snes.cpp b/bsnes/ui/interface/snes.cpp index e554b71d..5354487f 100755 --- a/bsnes/ui/interface/snes.cpp +++ b/bsnes/ui/interface/snes.cpp @@ -21,18 +21,24 @@ void InterfaceSNES::setController(bool port, unsigned device) { } } -bool InterfaceSNES::loadCartridge(const string &filename) { +bool InterfaceSNES::loadCartridge(const string &basename) { uint8_t *data; unsigned size; - if(file::read(filename, data, size) == false) return false; + if(file::read(basename, data, size) == false) return false; interface->unloadCartridge(); - interface->baseName = nall::basename(filename); + interface->baseName = nall::basename(basename); + + string xml; + xml.readfile({ interface->baseName, ".xml" }); + if(xml == "") xml = SNESCartridge(data, size).xmlMemoryMap; - string xml = SNESCartridge(data, size).xmlMemoryMap; SNES::Interface::loadCartridge({ xml, data, size }); delete[] data; + interface->slotName = { nall::basename(basename) }; + + loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); return true; } @@ -47,11 +53,17 @@ bool InterfaceSNES::loadSatellaviewSlottedCartridge(const string &basename, cons interface->baseName = nall::basename(basename); if(data[1]) interface->baseName.append("+", nall::basename(notdir(slotname))); - string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + string xml; + xml.readfile({ interface->baseName, ".xml" }); + if(xml == "") xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + SNES::Interface::loadSatellaviewSlottedCartridge({ xml, data[0], size[0] }, { "", data[1], size[1] }); delete[] data[0]; if(data[1]) delete[] data[1]; + interface->slotName = { nall::basename(basename), nall::basename(slotname) }; + + loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); return true; } @@ -66,11 +78,17 @@ bool InterfaceSNES::loadSatellaviewCartridge(const string &basename, const strin interface->baseName = nall::basename(basename); if(data[1]) interface->baseName.append("+", nall::basename(notdir(slotname))); - string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + string xml; + xml.readfile({ interface->baseName, ".xml" }); + if(xml == "") xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + SNES::Interface::loadSatellaviewCartridge({ xml, data[0], size[0] }, { "", data[1], size[1] }); delete[] data[0]; if(data[1]) delete[] data[1]; + interface->slotName = { nall::basename(basename), nall::basename(slotname) }; + + loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); return true; } @@ -88,12 +106,18 @@ bool InterfaceSNES::loadSufamiTurboCartridge(const string &basename, const strin else if(data[1]) interface->baseName = nall::basename(slotAname); else if(data[2]) interface->baseName = nall::basename(slotBname); - string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + string xml; + xml.readfile({ interface->baseName, ".xml" }); + if(xml == "") xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + SNES::Interface::loadSufamiTurboCartridge({ xml, data[0], size[0] }, { "", data[1], size[1] }, { "", data[2], size[2] }); delete[] data[0]; if(data[1]) delete[] data[1]; if(data[2]) delete[] data[2]; + interface->slotName = { nall::basename(basename), nall::basename(slotAname), nall::basename(slotBname) }; + + loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); return true; } @@ -108,21 +132,56 @@ bool InterfaceSNES::loadSuperGameBoyCartridge(const string &basename, const stri interface->baseName = nall::basename(basename); if(data[1]) interface->baseName = nall::basename(slotname); - string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; - string gbXml = GameBoyCartridge(data[1], size[1]).xml; + string xml; + xml.readfile({ interface->baseName, ".xml" }); + if(xml == "") xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + + string gbXml; + gbXml.readfile({ nall::basename(slotname), ".xml" }); + if(gbXml == "") gbXml = GameBoyCartridge(data[1], size[1]).xml; SNES::Interface::loadSuperGameBoyCartridge({ xml, data[0], size[0] }, { gbXml, data[1], size[1] }); delete[] data[0]; if(data[1]) delete[] data[1]; + interface->slotName = { nall::basename(basename), nall::basename(slotname) }; + + loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); return true; } void InterfaceSNES::unloadCartridge() { + saveMemory(); SNES::Interface::unloadCartridge(); interface->baseName = ""; } +//slot[] array = Cartridge::Slot to slot# conversion: +//{ Base, Bsx, SufamiTurbo, SufamiTurboA, SufamiTurboB, GameBoy } + +void InterfaceSNES::loadMemory() { + static unsigned slot[] = { 0, 0, 0, 1, 2, 1 }; + foreach(memory, SNES::Interface::memory()) { + if(memory.size == 0) continue; + string filename = { interface->slotName[slot[(unsigned)memory.slot]], memory.id }; + uint8_t *data; + unsigned size; + if(file::read(filename, data, size)) { + memcpy(memory.data, data, min(memory.size, size)); + delete[] data; + } + } +} + +void InterfaceSNES::saveMemory() { + static unsigned slot[] = { 0, 0, 0, 1, 2, 1 }; + foreach(memory, SNES::Interface::memory()) { + if(memory.size == 0) continue; + string filename = { interface->slotName[slot[(unsigned)memory.slot]], memory.id }; + file::write(filename, memory.data, memory.size); + } +} + bool InterfaceSNES::saveState(const string &filename) { serializer s = serialize(); return file::write(filename, s.data(), s.size()); @@ -140,39 +199,32 @@ bool InterfaceSNES::loadState(const string &filename) { // void InterfaceSNES::videoRefresh(const uint16_t *data, bool hires, bool interlace, bool overscan) { - interface->videoRefresh(); + static uint16_t output[512 * 478]; - unsigned width = hires ? 512 : 256; - unsigned height = 0 ? 224 : 239; - if(interlace) height <<= 1; - unsigned inpitch = interlace ? 1024 : 2048; + unsigned width = 256 << hires; + unsigned height = (config->video.enableOverscan ? 240 : 224) << interlace; + unsigned pitch = 1024 >> interlace; - if(0) { //NTSC + //data[] = scanline { 8 (blank) + 240 (video) + 8 (blank) } + //first line of video data is not rendered (effectively blank as well) + if(config->video.enableOverscan) { + if(overscan == false) data += 1 * 1024; // 8 + 224 + 8 + if(overscan == true ) data += 9 * 1024; // 0 + 240 + 0 + } else { if(overscan == false) data += 9 * 1024; // 0 + 224 + 0 - if(overscan == true ) data += 16 * 1024; //-7 + 224 + -7 + if(overscan == true ) data += 16 * 1024; //-8 + 224 + -8 } - if(1) { //PAL - if(overscan == false) data += 1 * 1024; // 8 + 224 + 7 - if(overscan == true ) data += 9 * 1024; // 0 + 239 + 0 - } - - uint32_t *output; - unsigned outpitch; - if(video.lock(output, outpitch, width, height)) { - for(unsigned y = 0; y < height; y++) { - const uint16_t *sp = data + y * (inpitch >> 1); - uint32_t *dp = output + y * (outpitch >> 2); - for(unsigned x = 0; x < width; x++) { - uint32_t color = *sp++; - color = ((color & 0x7c00) << 9) | ((color & 0x03e0) << 6) | ((color & 0x001f) << 3); - *dp++ = color | ((color >> 3) & 0x070707); - } + for(unsigned y = 0; y < height; y++) { + const uint16_t *sp = data + y * pitch; + uint16_t *dp = output + y * 512; + for(unsigned x = 0; x < width; x++) { + uint16_t color = *sp++; + *dp++ = ((color & 0x001f) << 10) | (color & 0x03e0) | ((color & 0x7c00) >> 10); } - - video.unlock(); - video.refresh(); } + + interface->videoRefresh(output, 512 * 2, width, height); } void InterfaceSNES::audioSample(int16_t lsample, int16_t rsample) { diff --git a/bsnes/ui/interface/snes.hpp b/bsnes/ui/interface/snes.hpp index 429e2af3..31e9ab3d 100755 --- a/bsnes/ui/interface/snes.hpp +++ b/bsnes/ui/interface/snes.hpp @@ -8,6 +8,9 @@ struct InterfaceSNES : SNES::Interface { bool loadSuperGameBoyCartridge(const string &basename, const string &slotname); void unloadCartridge(); + void loadMemory(); + void saveMemory(); + bool saveState(const string &filename); bool loadState(const string &filename); diff --git a/bsnes/ui/main.cpp b/bsnes/ui/main.cpp index 09bcd265..69e73ef5 100755 --- a/bsnes/ui/main.cpp +++ b/bsnes/ui/main.cpp @@ -57,6 +57,7 @@ Application::Application(int argc, char **argv) { mainWindow = new MainWindow; fileBrowser = new FileBrowser; slotLoader = new SlotLoader; + dipSwitches = new DipSwitches; settingsWindow = new SettingsWindow; cheatEditor = new CheatEditor; stateManager = new StateManager; @@ -104,6 +105,7 @@ Application::~Application() { delete stateManager; delete cheatEditor; delete settingsWindow; + delete dipSwitches; delete slotLoader; delete fileBrowser; delete mainWindow; diff --git a/bsnes/ui/settings/advanced.hpp b/bsnes/ui/settings/advanced.hpp index 036e4148..860527d8 100755 --- a/bsnes/ui/settings/advanced.hpp +++ b/bsnes/ui/settings/advanced.hpp @@ -1,4 +1,4 @@ -struct AdvancedSettings : VerticalLayout { +struct AdvancedSettings : SettingsLayout { Label title; Label driverLabel; HorizontalLayout driverLayout; diff --git a/bsnes/ui/settings/audio.cpp b/bsnes/ui/settings/audio.cpp new file mode 100755 index 00000000..f934c5ea --- /dev/null +++ b/bsnes/ui/settings/audio.cpp @@ -0,0 +1,84 @@ +AudioSettings *audioSettings = 0; + +AudioSlider::AudioSlider() { + append(name, 75, 0); + append(value, 75, 0); + append(slider, ~0, 0); +} + +unsigned AudioSlider::position() { + unsigned value = slider.position(), center = slider.length() >> 1; + if(value > center) return base + (value - center) * step; + if(value < center) return base - (center - value) * step; + return base; +} + +void AudioSlider::setPosition(unsigned position) { + signed value = position - base, center = slider.length() >> 1; + if(value < 0) return slider.setPosition(center - (abs(value) / step)); + if(value > 0) return slider.setPosition((abs(value) / step) + center); + return slider.setPosition(center); +} + +AudioSettings::AudioSettings() { + title.setFont(application->titleFont); + title.setText("Audio Settings"); + frequencyAdjustmentLabel.setFont(application->boldFont); + frequencyAdjustmentLabel.setText("Frequency: (lower to reduce audio crackling; raise to reduce video tearing)"); + + nes.name.setText("NES:"); + nes.slider.setLength(2001); + nes.base = 1789772; + nes.step = 56; + + snes.name.setText("SNES:"); + snes.slider.setLength(2001); + snes.base = 32000; + snes.step = 1; + + gameBoy.name.setText("Game Boy:"); + gameBoy.slider.setLength(2001); + gameBoy.base = 4194304; + gameBoy.step = 131; + + outputAdjustmentLabel.setFont(application->boldFont); + outputAdjustmentLabel.setText("Output:"); + + volume.name.setText("Volume:"); + volume.slider.setLength(201); + volume.base = 100; + volume.step = 1; + + append(title, ~0, 0, 5); + append(frequencyAdjustmentLabel, ~0, 0); + append(nes, ~0, 0); + append(snes, ~0, 0); + append(gameBoy, ~0, 0, 5); + append(outputAdjustmentLabel, ~0, 0); + append(volume, ~0, 0); + + nes.setPosition(config->audio.frequencyNES); + snes.setPosition(config->audio.frequencySNES); + gameBoy.setPosition(config->audio.frequencyGameBoy); + volume.setPosition(config->audio.volume); + + nes.slider.onChange = snes.slider.onChange = gameBoy.slider.onChange = + volume.slider.onChange = + { &AudioSettings::synchronize, this }; + + synchronize(); +} + +void AudioSettings::synchronize() { + config->audio.frequencyNES = nes.position(); + config->audio.frequencySNES = snes.position(); + config->audio.frequencyGameBoy = gameBoy.position(); + config->audio.volume = volume.position(); + + nes.value.setText({ nes.position(), "hz" }); + snes.value.setText({ snes.position(), "hz" }); + gameBoy.value.setText({ gameBoy.position(), "hz" }); + volume.value.setText({ volume.position(), "%" }); + + interface->updateDSP(); +} diff --git a/bsnes/ui/settings/audio.hpp b/bsnes/ui/settings/audio.hpp new file mode 100755 index 00000000..78abc331 --- /dev/null +++ b/bsnes/ui/settings/audio.hpp @@ -0,0 +1,27 @@ +struct AudioSlider : HorizontalLayout { + Label name; + Label value; + HorizontalSlider slider; + + unsigned base; + unsigned step; + + unsigned position(); + void setPosition(unsigned position); + AudioSlider(); +}; + +struct AudioSettings : SettingsLayout { + Label title; + Label frequencyAdjustmentLabel; + AudioSlider nes; + AudioSlider snes; + AudioSlider gameBoy; + Label outputAdjustmentLabel; + AudioSlider volume; + + void synchronize(); + AudioSettings(); +}; + +extern AudioSettings *audioSettings; diff --git a/bsnes/ui/settings/input.cpp b/bsnes/ui/settings/input.cpp index 866e0a12..96704c9c 100755 --- a/bsnes/ui/settings/input.cpp +++ b/bsnes/ui/settings/input.cpp @@ -52,6 +52,7 @@ void InputSettings::primaryChange() { for(unsigned n = 0; n < input.size(); n++) { secondary.append(input[n].name); } + secondary.setEnabled(input.size() > 1); secondaryChange(); } @@ -62,6 +63,7 @@ void InputSettings::secondaryChange() { for(unsigned n = 0; n < input.size(); n++) { tertiary.append(input[n].name); } + tertiary.setEnabled(input.size() > 1); tertiaryChange(); } diff --git a/bsnes/ui/settings/input.hpp b/bsnes/ui/settings/input.hpp index ff35b69f..34d8c11f 100755 --- a/bsnes/ui/settings/input.hpp +++ b/bsnes/ui/settings/input.hpp @@ -1,4 +1,4 @@ -struct InputSettings : VerticalLayout { +struct InputSettings : SettingsLayout { Label title; HorizontalLayout selectionLayout; ComboBox primary; diff --git a/bsnes/ui/settings/settings.cpp b/bsnes/ui/settings/settings.cpp index d4c50ca2..3baa3529 100755 --- a/bsnes/ui/settings/settings.cpp +++ b/bsnes/ui/settings/settings.cpp @@ -1,51 +1,69 @@ #include "../base.hpp" +#include "video.cpp" +#include "audio.cpp" #include "input.cpp" #include "advanced.cpp" SettingsWindow *settingsWindow = 0; +void SettingsLayout::append(Sizable &sizable, unsigned width, unsigned height, unsigned spacing) { + layout.append(sizable, width, height, spacing); +} + +SettingsLayout::SettingsLayout() { + setMargin(5); + HorizontalLayout::append(spacer, 120, ~0, 5); + HorizontalLayout::append(layout, ~0, ~0); +} + SettingsWindow::SettingsWindow() { setTitle("Configuration Settings"); setGeometry({ 128, 128, 640, 360 }); setStatusVisible(); windowManager->append(this, "SettingsWindow"); + layout.setMargin(5); + panelList.setFont(application->boldFont); + panelList.append("Video"); + panelList.append("Audio"); panelList.append("Input"); panelList.append("Advanced"); + videoSettings = new VideoSettings; + audioSettings = new AudioSettings; inputSettings = new InputSettings; advancedSettings = new AdvancedSettings; append(layout); - layout.setMargin(5); - layout.append(panelList, 120, ~0, 5); + layout.append(panelList, 120, ~0, 5); + append(*videoSettings); + append(*audioSettings); + append(*inputSettings); + append(*advancedSettings); panelList.onChange = [&] { setPanel(panelList.selection()); }; - setPanel(0); + setPanel(2); } SettingsWindow::~SettingsWindow() { delete advancedSettings; delete inputSettings; + delete audioSettings; + delete videoSettings; } void SettingsWindow::setPanel(unsigned n) { - //TODO: removing layouts isn't working right, so for now we are hiding them on toggle - - layout.remove(*inputSettings); - layout.remove(*advancedSettings); + panelList.setSelection(n); + videoSettings->setVisible(false); + audioSettings->setVisible(false); inputSettings->setVisible(false); advancedSettings->setVisible(false); switch(n) { - case 0: - layout.append(*inputSettings, ~0, ~0); - inputSettings->setVisible(); - break; - case 1: - layout.append(*advancedSettings, ~0, ~0); - advancedSettings->setVisible(); - break; + case 0: return videoSettings->setVisible(); + case 1: return audioSettings->setVisible(); + case 2: return inputSettings->setVisible(); + case 3: return advancedSettings->setVisible(); } } diff --git a/bsnes/ui/settings/settings.hpp b/bsnes/ui/settings/settings.hpp index bd18e182..5cc3c458 100755 --- a/bsnes/ui/settings/settings.hpp +++ b/bsnes/ui/settings/settings.hpp @@ -1,3 +1,13 @@ +struct SettingsLayout : HorizontalLayout { + Widget spacer; + VerticalLayout layout; + + void append(Sizable &widget, unsigned width, unsigned height, unsigned spacing = 0); + SettingsLayout(); +}; + +#include "video.hpp" +#include "audio.hpp" #include "input.hpp" #include "advanced.hpp" diff --git a/bsnes/ui/settings/video.cpp b/bsnes/ui/settings/video.cpp new file mode 100755 index 00000000..5e50f067 --- /dev/null +++ b/bsnes/ui/settings/video.cpp @@ -0,0 +1,66 @@ +VideoSettings *videoSettings = 0; + +VideoSlider::VideoSlider() { + slider.setLength(201); + + append(name, 75, 0); + append(value, 75, 0); + append(slider, ~0, 0); +} + +VideoSettings::VideoSettings() { + title.setFont(application->titleFont); + title.setText("Video Settings"); + colorAdjustment.setFont(application->boldFont); + colorAdjustment.setText("Color adjustment:"); + brightness.name.setText("Brightness:"); + contrast.name.setText("Contrast:"); + gamma.name.setText("Gamma:"); + gammaRamp.setText("Enable gamma ramp simulation"); + fullScreenMode.setFont(application->boldFont); + fullScreenMode.setText("Fullscreen mode:"); + fullScreen[0].setText("Center"); + fullScreen[1].setText("Scale"); + fullScreen[2].setText("Stretch"); + RadioBox::group(fullScreen[0], fullScreen[1], fullScreen[2]); + + append(title, ~0, 0, 5); + append(colorAdjustment, ~0, 0); + append(brightness, ~0, 0); + append(contrast, ~0, 0); + append(gamma, ~0, 0); + append(gammaRamp, ~0, 0, 5); + append(fullScreenMode, ~0, 0); + append(fullScreenLayout, ~0, 0); + fullScreenLayout.append(fullScreen[0], ~0, 0, 5); + fullScreenLayout.append(fullScreen[1], ~0, 0, 5); + fullScreenLayout.append(fullScreen[2], ~0, 0); + + brightness.slider.setPosition(config->video.brightness); + contrast.slider.setPosition(config->video.contrast); + gamma.slider.setPosition(config->video.gamma); + gammaRamp.setChecked(config->video.gammaRamp); + 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 = + { &VideoSettings::synchronize, this }; +} + +void VideoSettings::synchronize() { + config->video.brightness = brightness.slider.position(); + config->video.contrast = contrast.slider.position(); + config->video.gamma = gamma.slider.position(); + config->video.gammaRamp = gammaRamp.checked(); + if(fullScreen[0].checked()) config->video.fullScreenMode = 0; + if(fullScreen[1].checked()) config->video.fullScreenMode = 1; + if(fullScreen[2].checked()) config->video.fullScreenMode = 2; + + brightness.value.setText({ config->video.brightness, "%" }); + contrast.value.setText({ config->video.contrast, "%" }); + gamma.value.setText({ config->video.gamma, "%" }); + + palette.update(); +} diff --git a/bsnes/ui/settings/video.hpp b/bsnes/ui/settings/video.hpp new file mode 100755 index 00000000..55a04d9d --- /dev/null +++ b/bsnes/ui/settings/video.hpp @@ -0,0 +1,24 @@ +struct VideoSlider : HorizontalLayout { + Label name; + Label value; + HorizontalSlider slider; + + VideoSlider(); +}; + +struct VideoSettings : SettingsLayout { + Label title; + Label colorAdjustment; + VideoSlider brightness; + VideoSlider contrast; + VideoSlider gamma; + CheckBox gammaRamp; + Label fullScreenMode; + HorizontalLayout fullScreenLayout; + RadioBox fullScreen[3]; + + void synchronize(); + VideoSettings(); +}; + +extern VideoSettings *videoSettings; diff --git a/bsnes/ui/utility/utility.cpp b/bsnes/ui/utility/utility.cpp index 130828fd..72ad5761 100755 --- a/bsnes/ui/utility/utility.cpp +++ b/bsnes/ui/utility/utility.cpp @@ -19,23 +19,21 @@ void Utility::setMode(Interface::Mode mode) { mainWindow->setTitle({ notdir(interface->baseName), " - ", NES::Info::Name, " v", NES::Info::Version }); mainWindow->nesMenu.setVisible(true); dspaudio.setChannels(1); - dspaudio.setFrequency(315.0 / 88.0 * 6000000.0 / 12.0); } else if(mode == Interface::Mode::SNES) { mainWindow->setTitle({ notdir(interface->baseName), " - ", SNES::Info::Name, " v", SNES::Info::Version }); mainWindow->snesMenu.setVisible(true); dspaudio.setChannels(2); - dspaudio.setFrequency(32040.0); } else if(mode == Interface::Mode::GameBoy) { mainWindow->setTitle({ notdir(interface->baseName), " - ", GameBoy::Info::Name, " v", GameBoy::Info::Version }); mainWindow->gameBoyMenu.setVisible(true); dspaudio.setChannels(2); - dspaudio.setFrequency(4194304.0); } + interface->updateDSP(); mainWindow->synchronize(); resizeMainWindow(); } @@ -45,11 +43,17 @@ void Utility::resizeMainWindow(bool shrink) { unsigned width = geometry.width, height = geometry.height; switch(interface->mode()) { - case Interface::Mode::NES: width = 256, height = 240; break; - case Interface::Mode::SNES: width = 256, height = 239; break; + case Interface::Mode::NES: width = 256, height = config->video.enableOverscan ? 240 : 224; break; + case Interface::Mode::SNES: width = 256, height = config->video.enableOverscan ? 240 : 224; break; case Interface::Mode::GameBoy: width = 160, height = 144; break; } + if(config->video.correctAspectRatio) { + if(interface->mode() != Interface::Mode::GameBoy) { + width = (double)width * (config->video.enableOverscan ? 1.225 : 1.149); + } + } + unsigned maxW = geometry.width / width; unsigned maxH = geometry.height / height; unsigned maxM = max(1u, min(maxW, maxH)); @@ -57,6 +61,18 @@ void Utility::resizeMainWindow(bool shrink) { width = width * maxM; height = height * maxM; + if(mainWindow->fullScreen() == true) { + if(config->video.fullScreenMode == 1) { //scale + width = (double)width * ((double)geometry.height / height); + height = geometry.height; + } + + if(config->video.fullScreenMode == 2) { //stretch + width = geometry.width; + height = geometry.height; + } + } + if(shrink == false) { if(geometry.width < width ) width = geometry.width; if(geometry.height < height) height = geometry.height;