mirror of https://github.com/bsnes-emu/bsnes.git
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.
This commit is contained in:
parent
69ed35db99
commit
101c9507b1
|
@ -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%
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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%
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace GameBoy {
|
|||
/*
|
||||
bgameboy - Game Boy emulator
|
||||
author: byuu
|
||||
license: GPLv2
|
||||
license: GPLv3
|
||||
project started: 2010-12-27
|
||||
*/
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ struct Cartridge : property<Cartridge> {
|
|||
void load(const string &xml, const uint8_t *data, unsigned size);
|
||||
void unload();
|
||||
|
||||
unsigned ram_size();
|
||||
uint8 *ram_data();
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -358,6 +358,7 @@ struct HexEdit : private nall::base_from_member<pHexEdit&>, Widget {
|
|||
struct HorizontalScrollBar : private nall::base_from_member<pHorizontalScrollBar&>, Widget {
|
||||
nall::function<void ()> onChange;
|
||||
|
||||
unsigned length();
|
||||
unsigned position();
|
||||
void setLength(unsigned length);
|
||||
void setPosition(unsigned position);
|
||||
|
@ -372,6 +373,7 @@ struct HorizontalScrollBar : private nall::base_from_member<pHorizontalScrollBar
|
|||
struct HorizontalSlider : private nall::base_from_member<pHorizontalSlider&>, Widget {
|
||||
nall::function<void ()> onChange;
|
||||
|
||||
unsigned length();
|
||||
unsigned position();
|
||||
void setLength(unsigned length);
|
||||
void setPosition(unsigned position);
|
||||
|
@ -485,6 +487,7 @@ struct TextEdit : private nall::base_from_member<pTextEdit&>, Widget {
|
|||
struct VerticalScrollBar : private nall::base_from_member<pVerticalScrollBar&>, Widget {
|
||||
nall::function<void ()> onChange;
|
||||
|
||||
unsigned length();
|
||||
unsigned position();
|
||||
void setLength(unsigned length);
|
||||
void setPosition(unsigned position);
|
||||
|
@ -499,6 +502,7 @@ struct VerticalScrollBar : private nall::base_from_member<pVerticalScrollBar&>,
|
|||
struct VerticalSlider : private nall::base_from_member<pVerticalSlider&>, Widget {
|
||||
nall::function<void ()> onChange;
|
||||
|
||||
unsigned length();
|
||||
unsigned position();
|
||||
void setLength(unsigned length);
|
||||
void setPosition(unsigned position);
|
||||
|
|
|
@ -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<Layout*>(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<Layout*>(child.sizable)) ((Layout*)child.sizable)->reset();
|
||||
if(window() && dynamic_cast<Widget*>(child.sizable)) window()->remove((Widget&)*child.sizable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ void VerticalLayout::remove(Sizable &sizable) {
|
|||
|
||||
void VerticalLayout::reset() {
|
||||
foreach(child, children) {
|
||||
if(window() && dynamic_cast<Layout*>(child.sizable)) ((Layout*)child.sizable)->reset();
|
||||
if(window() && dynamic_cast<Widget*>(child.sizable)) window()->remove((Widget&)*child.sizable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -243,6 +243,7 @@ struct pWidget : public pSizable {
|
|||
|
||||
pWidget(Widget &widget) : pSizable(widget), widget(widget) {}
|
||||
void constructor();
|
||||
void synchronizeState();
|
||||
void destructor();
|
||||
virtual void orphan();
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ void pCanvas::constructor() {
|
|||
qtWidget = qtCanvas = new QtCanvas(*this);
|
||||
qtImage = new QImage(256, 256, QImage::Format_RGB32);
|
||||
|
||||
pWidget::synchronizeState();
|
||||
update();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ void pLabel::setText(const string &text) {
|
|||
|
||||
void pLabel::constructor() {
|
||||
qtWidget = qtLabel = new QLabel;
|
||||
|
||||
pWidget::synchronizeState();
|
||||
setText(label.state.text);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -11,6 +11,7 @@ void pProgressBar::constructor() {
|
|||
qtProgressBar->setRange(0, 100);
|
||||
qtProgressBar->setTextVisible(false);
|
||||
|
||||
pWidget::synchronizeState();
|
||||
setPosition(progressBar.state.position);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ void pRadioBox::setGroup(const reference_array<RadioBox&> &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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -66,6 +66,14 @@ void Interface::unloadCartridge() {
|
|||
cartridge.unload();
|
||||
}
|
||||
|
||||
Cartridge::Information& Interface::information() {
|
||||
return cartridge.information;
|
||||
}
|
||||
|
||||
linear_vector<Cartridge::NonVolatileRAM>& Interface::memory() {
|
||||
return cartridge.nvram;
|
||||
}
|
||||
|
||||
void Interface::power() {
|
||||
system.power();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ public:
|
|||
virtual void loadSuperGameBoyCartridge(const CartridgeData &base, const CartridgeData &slot);
|
||||
virtual void unloadCartridge();
|
||||
|
||||
Cartridge::Information& information();
|
||||
linear_vector<Cartridge::NonVolatileRAM>& memory();
|
||||
|
||||
virtual void power();
|
||||
virtual void reset();
|
||||
virtual void run();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -2,3 +2,4 @@
|
|||
#include "main-window.cpp"
|
||||
#include "file-browser.cpp"
|
||||
#include "slot-loader.cpp"
|
||||
#include "dip-switches.cpp"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "main-window.hpp"
|
||||
#include "file-browser.hpp"
|
||||
#include "slot-loader.hpp"
|
||||
#include "dip-switches.hpp"
|
||||
|
|
|
@ -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); };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include "palette.hpp"
|
||||
|
||||
#include "nes.hpp"
|
||||
#include "snes.hpp"
|
||||
#include "gameboy.hpp"
|
||||
|
@ -8,6 +10,7 @@ struct Interface : property<Interface> {
|
|||
|
||||
void bindControllers();
|
||||
void setController(unsigned port, unsigned device);
|
||||
void updateDSP();
|
||||
|
||||
bool cartridgeLoaded();
|
||||
void loadCartridge(Mode mode);
|
||||
|
@ -21,15 +24,16 @@ struct Interface : property<Interface> {
|
|||
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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
struct AdvancedSettings : VerticalLayout {
|
||||
struct AdvancedSettings : SettingsLayout {
|
||||
Label title;
|
||||
Label driverLabel;
|
||||
HorizontalLayout driverLayout;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
struct InputSettings : VerticalLayout {
|
||||
struct InputSettings : SettingsLayout {
|
||||
Label title;
|
||||
HorizontalLayout selectionLayout;
|
||||
ComboBox primary;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue