Added compatibility option to disable accurate CPU ALU emulation
Refactored settings panels
Added dialog to choose whether IPS patches are for headered ROMs
Disabled extended SNES header decoding thanks to ROM hacks ignoring them
This commit is contained in:
byuu 2019-09-03 12:01:45 +09:00
parent 08e5e81609
commit 2bb1606552
21 changed files with 446 additions and 326 deletions

View File

@ -29,7 +29,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "bsnes"; static const string Name = "bsnes";
static const string Version = "108.15"; static const string Version = "108.16";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "https://byuu.org"; static const string Website = "https://byuu.org";

View File

@ -140,6 +140,11 @@ auto SuperFamicom::manifest() const -> string {
} }
auto SuperFamicom::region() const -> string { auto SuperFamicom::region() const -> string {
//Unlicensed software (homebrew, ROM hacks, etc) often change the standard region code,
//and then neglect to change the extended header region code. Thanks to that, we can't
//decode and display the full game serial + region code.
return videoRegion();
string region; string region;
char A = data[headerAddress + 0x02]; //game type char A = data[headerAddress + 0x02]; //game type

View File

@ -96,12 +96,14 @@ auto CPU::power(bool reset) -> void {
if(!reset) random.array(wram, sizeof(wram)); if(!reset) random.array(wram, sizeof(wram));
if(configuration.hacks.hotfixes) {
//Dirt Racer (Europe) relies on uninitialized memory containing certain values to boot without freezing. //Dirt Racer (Europe) relies on uninitialized memory containing certain values to boot without freezing.
//the game itself is broken and will fail to run sometimes on real hardware, but for the sake of expedience, //the game itself is broken and will fail to run sometimes on real hardware, but for the sake of expedience,
//WRAM is initialized to a constant value that will allow this game to always boot in successfully. //WRAM is initialized to a constant value that will allow this game to always boot in successfully.
if(cartridge.headerTitle() == "DIRT RACER") { if(cartridge.headerTitle() == "DIRT RACER") {
for(auto& byte : wram) byte = 0xff; for(auto& byte : wram) byte = 0xff;
} }
}
for(uint n : range(8)) { for(uint n : range(8)) {
channels[n] = {}; channels[n] = {};

View File

@ -151,8 +151,12 @@ auto CPU::writeCPU(uint addr, uint8 data) -> void {
io.wrmpyb = data; io.wrmpyb = data;
io.rddiv = io.wrmpyb << 8 | io.wrmpya; io.rddiv = io.wrmpyb << 8 | io.wrmpya;
if(!configuration.hacks.cpu.fastMath) {
alu.mpyctr = 8; //perform multiplication over the next eight cycles alu.mpyctr = 8; //perform multiplication over the next eight cycles
alu.shift = io.wrmpyb; alu.shift = io.wrmpyb;
} else {
io.rdmpy = io.wrmpya * io.wrmpyb;
}
return; return;
case 0x4204: //WRDIVL case 0x4204: //WRDIVL
@ -169,8 +173,18 @@ auto CPU::writeCPU(uint addr, uint8 data) -> void {
io.wrdivb = data; io.wrdivb = data;
if(!configuration.hacks.cpu.fastMath) {
alu.divctr = 16; //perform division over the next sixteen cycles alu.divctr = 16; //perform division over the next sixteen cycles
alu.shift = io.wrdivb << 16; alu.shift = io.wrdivb << 16;
} else {
if(io.wrdivb) {
io.rddiv = io.wrdiva / io.wrdivb;
io.rdmpy = io.wrdiva % io.wrdivb;
} else {
io.rddiv = 0xffff;
io.rdmpy = io.wrdiva;
}
}
return; return;
case 0x4207: //HTIMEL case 0x4207: //HTIMEL

View File

@ -16,8 +16,10 @@ auto Configuration::process(Markup::Node document, bool load) -> void {
bind(boolean, "Video/BlurEmulation", video.blurEmulation); bind(boolean, "Video/BlurEmulation", video.blurEmulation);
bind(boolean, "Video/ColorEmulation", video.colorEmulation); bind(boolean, "Video/ColorEmulation", video.colorEmulation);
bind(boolean, "Hacks/Hotfixes", hacks.hotfixes);
bind(text, "Hacks/Entropy", hacks.entropy); bind(text, "Hacks/Entropy", hacks.entropy);
bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock); bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock);
bind(boolean, "Hacks/CPU/FastMath", hacks.cpu.fastMath);
bind(boolean, "Hacks/PPU/Fast", hacks.ppu.fast); bind(boolean, "Hacks/PPU/Fast", hacks.ppu.fast);
bind(boolean, "Hacks/PPU/Deinterlace", hacks.ppu.deinterlace); bind(boolean, "Hacks/PPU/Deinterlace", hacks.ppu.deinterlace);
bind(natural, "Hacks/PPU/RenderCycle", hacks.ppu.renderCycle); bind(natural, "Hacks/PPU/RenderCycle", hacks.ppu.renderCycle);

View File

@ -25,9 +25,11 @@ struct Configuration {
} video; } video;
struct Hacks { struct Hacks {
bool hotfixes = true;
string entropy = "Low"; string entropy = "Low";
struct CPU { struct CPU {
uint overclock = 100; uint overclock = 100;
bool fastMath = false;
} cpu; } cpu;
struct PPU { struct PPU {
bool fast = true; bool fast = true;

View File

@ -106,9 +106,10 @@ auto Presentation::create() -> void {
inputSettings.setIcon(Icon::Device::Joypad).setText("Input ...").onActivate([&] { settingsWindow.show(2); }); inputSettings.setIcon(Icon::Device::Joypad).setText("Input ...").onActivate([&] { settingsWindow.show(2); });
hotkeySettings.setIcon(Icon::Device::Keyboard).setText("Hotkeys ...").onActivate([&] { settingsWindow.show(3); }); hotkeySettings.setIcon(Icon::Device::Keyboard).setText("Hotkeys ...").onActivate([&] { settingsWindow.show(3); });
pathSettings.setIcon(Icon::Emblem::Folder).setText("Paths ...").onActivate([&] { settingsWindow.show(4); }); pathSettings.setIcon(Icon::Emblem::Folder).setText("Paths ...").onActivate([&] { settingsWindow.show(4); });
speedSettings.setIcon(Icon::Device::Clock).setText("Speed ...").onActivate([&] { settingsWindow.show(5); }); emulatorSettings.setIcon(Icon::Action::Settings).setText("Emulator ...").onActivate([&] { settingsWindow.show(5); });
emulatorSettings.setIcon(Icon::Action::Settings).setText("Emulator ...").onActivate([&] { settingsWindow.show(6); }); enhancementSettings.setIcon(Icon::Action::Add).setText("Enhancements ...").onActivate([&] { settingsWindow.show(6); });
driverSettings.setIcon(Icon::Place::Settings).setText("Drivers ...").onActivate([&] { settingsWindow.show(7); }); compatibilitySettings.setIcon(Icon::Action::Remove).setText("Compatibility ...").onActivate([&] { settingsWindow.show(7); });
driverSettings.setIcon(Icon::Place::Settings).setText("Drivers ...").onActivate([&] { settingsWindow.show(8); });
toolsMenu.setText(tr("Tools")).setVisible(false); toolsMenu.setText(tr("Tools")).setVisible(false);
saveState.setIcon(Icon::Media::Record).setText("Save State"); saveState.setIcon(Icon::Media::Record).setText("Save State");
@ -180,18 +181,6 @@ auto Presentation::create() -> void {
documentation.setIcon(Icon::Application::Browser).setText({tr("Documentation"), " ..."}).onActivate([&] { documentation.setIcon(Icon::Application::Browser).setText({tr("Documentation"), " ..."}).onActivate([&] {
invoke("https://doc.byuu.org/bsnes"); invoke("https://doc.byuu.org/bsnes");
}); });
about.setIcon(Icon::Prompt::Question).setText({tr("About bsnes"), " ..."}).onActivate([&] {
AboutDialog()
.setName(Emulator::Name)
.setLogo(Resource::Logo)
.setDescription("Super Nintendo emulator")
.setVersion(Emulator::Version)
.setAuthor("byuu")
.setLicense("GPLv3")
.setWebsite("https://byuu.org")
.setAlignment(*this)
.show();
});
aboutSameBoy.setIcon(Icon::Prompt::Question).setText({tr("About SameBoy"), " ..."}).onActivate([&] { aboutSameBoy.setIcon(Icon::Prompt::Question).setText({tr("About SameBoy"), " ..."}).onActivate([&] {
AboutDialog() AboutDialog()
.setName("SameBoy") .setName("SameBoy")
@ -204,6 +193,18 @@ auto Presentation::create() -> void {
.setAlignment(*this) .setAlignment(*this)
.show(); .show();
}); });
about.setIcon(Icon::Prompt::Question).setText({tr("About bsnes"), " ..."}).onActivate([&] {
AboutDialog()
.setName(Emulator::Name)
.setLogo(Resource::Logo)
.setDescription("Super Nintendo emulator")
.setVersion(Emulator::Version)
.setAuthor("byuu")
.setLicense("GPLv3")
.setWebsite("https://byuu.org")
.setAlignment(*this)
.show();
});
viewport.setFocusable(); viewport.setFocusable();
viewport.setDroppable(); viewport.setDroppable();

View File

@ -85,8 +85,9 @@ struct Presentation : Window {
MenuItem inputSettings{&settingsMenu}; MenuItem inputSettings{&settingsMenu};
MenuItem hotkeySettings{&settingsMenu}; MenuItem hotkeySettings{&settingsMenu};
MenuItem pathSettings{&settingsMenu}; MenuItem pathSettings{&settingsMenu};
MenuItem speedSettings{&settingsMenu};
MenuItem emulatorSettings{&settingsMenu}; MenuItem emulatorSettings{&settingsMenu};
MenuItem enhancementSettings{&settingsMenu};
MenuItem compatibilitySettings{&settingsMenu};
MenuItem driverSettings{&settingsMenu}; MenuItem driverSettings{&settingsMenu};
Menu toolsMenu{&menuBar}; Menu toolsMenu{&menuBar};
Menu saveState{&toolsMenu}; Menu saveState{&toolsMenu};
@ -118,8 +119,8 @@ struct Presentation : Window {
Menu helpMenu{&menuBar}; Menu helpMenu{&menuBar};
MenuItem documentation{&helpMenu}; MenuItem documentation{&helpMenu};
MenuSeparator helpSeparator{&helpMenu}; MenuSeparator helpSeparator{&helpMenu};
MenuItem about{&helpMenu};
MenuItem aboutSameBoy{&helpMenu}; MenuItem aboutSameBoy{&helpMenu};
MenuItem about{&helpMenu};
VerticalLayout layout{this}; VerticalLayout layout{this};
HorizontalLayout viewportLayout{&layout, Size{~0, ~0}, 0}; HorizontalLayout viewportLayout{&layout, Size{~0, ~0}, 0};

View File

@ -1,8 +1,10 @@
auto Program::load() -> void { auto Program::load() -> void {
unload(); unload();
emulator->configure("Hacks/Hotfixes", settings.emulator.hack.hotfixes);
emulator->configure("Hacks/Entropy", settings.emulator.hack.entropy); emulator->configure("Hacks/Entropy", settings.emulator.hack.entropy);
emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock); emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock);
emulator->configure("Hacks/CPU/FastMath", settings.emulator.hack.cpu.fastMath);
emulator->configure("Hacks/PPU/Fast", settings.emulator.hack.ppu.fast); emulator->configure("Hacks/PPU/Fast", settings.emulator.hack.ppu.fast);
emulator->configure("Hacks/PPU/Deinterlace", settings.emulator.hack.ppu.deinterlace); emulator->configure("Hacks/PPU/Deinterlace", settings.emulator.hack.ppu.deinterlace);
emulator->configure("Hacks/PPU/NoSpriteLimit", settings.emulator.hack.ppu.noSpriteLimit); emulator->configure("Hacks/PPU/NoSpriteLimit", settings.emulator.hack.ppu.noSpriteLimit);
@ -115,14 +117,13 @@ auto Program::loadSuperFamicom(string location) -> bool {
} }
if(rom.size() < 0x8000) return false; if(rom.size() < 0x8000) return false;
//assume ROM and IPS agree on whether a copier header is present
superFamicom.patched = applyPatchIPS(rom, location);
if((rom.size() & 0x7fff) == 512) { if((rom.size() & 0x7fff) == 512) {
//remove copier header //remove copier header
memory::move(&rom[0], &rom[512], rom.size() - 512); memory::move(&rom[0], &rom[512], rom.size() - 512);
rom.resize(rom.size() - 512); rom.resize(rom.size() - 512);
} }
//assume BPS is made against a ROM without a copier header
if(!superFamicom.patched) superFamicom.patched = applyPatchIPS(rom, location);
if(!superFamicom.patched) superFamicom.patched = applyPatchBPS(rom, location); if(!superFamicom.patched) superFamicom.patched = applyPatchBPS(rom, location);
auto heuristics = Heuristics::SuperFamicom(rom, location); auto heuristics = Heuristics::SuperFamicom(rom, location);
auto sha256 = Hash::SHA256(rom).digest(); auto sha256 = Hash::SHA256(rom).digest();

View File

@ -1,8 +1,8 @@
auto Program::hackCompatibility() -> void { auto Program::hackCompatibility() -> void {
bool fastPPU = emulatorSettings.fastPPU.checked(); bool fastPPU = settings.emulator.hack.ppu.fast;
bool fastPPUNoSpriteLimit = emulatorSettings.noSpriteLimit.checked(); bool fastPPUNoSpriteLimit = settings.emulator.hack.ppu.noSpriteLimit;
bool fastDSP = emulatorSettings.fastDSP.checked(); bool fastDSP = settings.emulator.hack.dsp.fast;
bool coprocessorDelayedSync = emulatorSettings.coprocessorDelayedSyncOption.checked(); bool coprocessorDelayedSync = settings.emulator.hack.coprocessor.delayedSync;
uint renderCycle = 512; uint renderCycle = 512;
auto title = superFamicom.title; auto title = superFamicom.title;

View File

@ -29,6 +29,15 @@ auto Program::applyPatchIPS(vector<uint8_t>& data, string location) -> bool {
} }
if(!patch) return false; if(!patch) return false;
bool headered = false;
if(MessageDialog().setAlignment(*presentation).setTitle({Location::prefix(location), ".ips"}).setText({
"(You're seeing this prompt because IPS is a terrible patch file format,\n"
" and nobody can agree on whether SNES ROMs should be headered or not.\n"
" Please consider asking the patch author to use BPS patches instead.)\n\n"
"Does this IPS patch expect to be applied to a headered ROM?\n"
"If you're not sure, try 'No', and if it fails to work, try again with 'Yes'."
}).question() == "Yes") headered = true;
//sanity checks //sanity checks
if(patch.size() < 8) return false; if(patch.size() < 8) return false;
if(patch[0] != 'P') return false; if(patch[0] != 'P') return false;
@ -57,10 +66,12 @@ auto Program::applyPatchIPS(vector<uint8_t>& data, string location) -> bool {
if(index >= patch.size()) break; if(index >= patch.size()) break;
uint32_t offset = 0; int32_t offset = 0;
offset |= patch(index++, 0) << 16; offset |= patch(index++, 0) << 16;
offset |= patch(index++, 0) << 8; offset |= patch(index++, 0) << 8;
offset |= patch(index++, 0) << 0; offset |= patch(index++, 0) << 0;
if(headered) offset -= 512;
if(offset < 0 || offset > 16_MiB) return false; //sanity check
uint16_t length = 0; uint16_t length = 0;
length |= patch(index++, 0) << 8; length |= patch(index++, 0) << 8;

View File

@ -30,8 +30,9 @@ auto Program::create() -> void {
inputSettings.create(); inputSettings.create();
hotkeySettings.create(); hotkeySettings.create();
pathSettings.create(); pathSettings.create();
speedSettings.create();
emulatorSettings.create(); emulatorSettings.create();
enhancementSettings.create();
compatibilitySettings.create();
driverSettings.create(); driverSettings.create();
toolsWindow.create(); toolsWindow.create();

View File

@ -0,0 +1,60 @@
auto CompatibilitySettings::create() -> void {
setCollapsible();
setVisible(false);
entropyLabel.setText("Entropy (randomization)").setFont(Font().setBold());
entropyNone.setText("None").setToolTip(
"All memory and registers are initialized to constant values at startup.\n"
"Use this for compatibility with very old demoscene homebrew games."
).onActivate([&] {
settings.emulator.hack.entropy = "None";
});
entropyLow.setText("Low").setToolTip(
"All memory is randomized with repeating patterns, all registers are randomized at startup.\n"
"Use this for the most accurate representation of a real SNES."
).onActivate([&] {
settings.emulator.hack.entropy = "Low";
});
entropyHigh.setText("High").setToolTip(
"All memory and registers are randomized as much as possible.\n"
"Use this when developing new SNES software to ensure maximum compatibility with real hardware."
).onActivate([&] {
settings.emulator.hack.entropy = "High";
});
if(settings.emulator.hack.entropy == "None") entropyNone.setChecked();
if(settings.emulator.hack.entropy == "Low" ) entropyLow.setChecked();
if(settings.emulator.hack.entropy == "High") entropyHigh.setChecked();
cpuLabel.setFont(Font().setBold()).setText("CPU (processor)");
fastMath.setText("Fast math").setToolTip(
"CPU multiplication and division take time to complete on a real SNES.\n"
"Older emulators did not simulate these delays, and provided results immediately.\n"
"Some older ROM hacks do not wait for math operations to complete and need this hack."
).setChecked(settings.emulator.hack.cpu.fastMath).onToggle([&] {
settings.emulator.hack.cpu.fastMath = fastMath.checked();
emulator->configure("Hacks/CPU/FastMath", settings.emulator.hack.cpu.fastMath);
});
ppuLabel.setFont(Font().setBold()).setText("PPU (video)");
noVRAMBlocking.setText("No VRAM blocking").setToolTip(
"This option emulates a bug in older releases of ZSNES and Snes9X where VRAM blocking was not emulated.\n"
"A few older ROM hacks relied on this behavior, and will render graphics incorrectly if not enabled.\n"
"Not only is this extremely inaccurate to real hardware, it also hurts the speed of the fast PPU.\n"
"Do not enable this option unless you need to play a game that is incompatible with bsnes otherwise."
).setChecked(settings.emulator.hack.ppu.noVRAMBlocking).onToggle([&] {
settings.emulator.hack.ppu.noVRAMBlocking = noVRAMBlocking.checked();
emulator->configure("Hacks/PPU/NoVRAMBlocking", settings.emulator.hack.ppu.noVRAMBlocking);
});
dspLabel.setFont(Font().setBold()).setText("DSP (audio)");
echoShadow.setText("Echo shadow RAM").setToolTip(
"This option emulates a bug in ZSNES where echo RAM was treated as separate from APU RAM.\n"
"Many older ROM hacks for Super Mario World relied on this behavior, and will crash without enabling this.\n"
"It is, however, extremely inaccurate to real hardware and should not be enabled unless required."
).setChecked(settings.emulator.hack.dsp.echoShadow).onToggle([&] {
settings.emulator.hack.dsp.echoShadow = echoShadow.checked();
//not a run-time setting: do not call emulator->configure() here.
});
note.setText("Note: some settings do not take effect until after reloading games.");
}

View File

@ -22,114 +22,107 @@ auto EmulatorSettings::create() -> void {
}); });
optionsSpacer.setColor({192, 192, 192}); optionsSpacer.setColor({192, 192, 192});
entropyLabel.setText("Entropy (randomness)").setFont(Font().setBold()); fastForwardLabel.setText("Fast Forward").setFont(Font().setBold());
entropyNone.setText("None").setToolTip(
"All memory and registers are initialized to constant values at startup.\n"
"Use this for compatibility with very old demoscene homebrew games."
).onActivate([&] {
settings.emulator.hack.entropy = "None";
});
entropyLow.setText("Low").setToolTip(
"All memory is randomized with repeating patterns, all registers are randomized at startup.\n"
"Use this for the most accurate representation of a real SNES."
).onActivate([&] {
settings.emulator.hack.entropy = "Low";
});
entropyHigh.setText("High").setToolTip(
"All memory and registers are randomized as much as possible.\n"
"Use this when developing new SNES software to ensure maximum compatibility with real hardware."
).onActivate([&] {
settings.emulator.hack.entropy = "High";
});
if(settings.emulator.hack.entropy == "None") entropyNone.setChecked();
if(settings.emulator.hack.entropy == "Low") entropyLow.setChecked();
if(settings.emulator.hack.entropy == "High") entropyHigh.setChecked();
ppuLabel.setText("PPU (video)").setFont(Font().setBold()); frameSkipLabel.setText("Frame skip:").setToolTip({
fastPPU.setText("Fast mode").setChecked(settings.emulator.hack.ppu.fast).onToggle([&] { "Set how many frames to skip while fast forwarding.\n"
settings.emulator.hack.ppu.fast = fastPPU.checked(); "Frame skipping allows a higher maximum fast forwarding frame rate."
if(!fastPPU.checked()) {
noSpriteLimit.setEnabled(false);
deinterlace.setEnabled(false);
mode7Layout.setEnabled(false);
} else {
noSpriteLimit.setEnabled(true);
deinterlace.setEnabled(true);
mode7Layout.setEnabled(true);
}
}).doToggle();
deinterlace.setText("Deinterlace").setChecked(settings.emulator.hack.ppu.deinterlace).onToggle([&] {
settings.emulator.hack.ppu.deinterlace = deinterlace.checked();
emulator->configure("Hacks/PPU/Deinterlace", settings.emulator.hack.ppu.deinterlace);
}); });
noSpriteLimit.setText("No sprite limit").setChecked(settings.emulator.hack.ppu.noSpriteLimit).onToggle([&] {
settings.emulator.hack.ppu.noSpriteLimit = noSpriteLimit.checked(); frameSkipAmount.append(ComboButtonItem().setText("None"));
frameSkipAmount.append(ComboButtonItem().setText("1 frame"));
frameSkipAmount.append(ComboButtonItem().setText("2 frames"));
frameSkipAmount.append(ComboButtonItem().setText("3 frames"));
frameSkipAmount.append(ComboButtonItem().setText("4 frames"));
frameSkipAmount.append(ComboButtonItem().setText("5 frames"));
frameSkipAmount.append(ComboButtonItem().setText("6 frames"));
frameSkipAmount.append(ComboButtonItem().setText("7 frames"));
frameSkipAmount.append(ComboButtonItem().setText("8 frames"));
frameSkipAmount.append(ComboButtonItem().setText("9 frames"));
frameSkipAmount.item(settings.fastForward.frameSkip).setSelected();
frameSkipAmount.onChange([&] {
settings.fastForward.frameSkip = frameSkipAmount.selected().offset();
}); });
noVRAMBlocking.setText("No VRAM blocking").setToolTip(
"This option emulates a bug in older releases of ZSNES and Snes9X where VRAM blocking was not emulated.\n" limiterLabel.setText("Limiter:").setToolTip({
"A few older ROM hacks relied on this behavior, and will render graphics incorrectly if not enabled.\n" "Set the maximum speed when fast forwarding."
"Not only is this extremely inaccurate to real hardware, it also hurts the speed of the fast PPU.\n"
"Do not enable this option unless you need to play a game that is incompatible with bsnes otherwise."
).setChecked(settings.emulator.hack.ppu.noVRAMBlocking).onToggle([&] {
settings.emulator.hack.ppu.noVRAMBlocking = noVRAMBlocking.checked();
emulator->configure("Hacks/PPU/NoVRAMBlocking", settings.emulator.hack.ppu.noVRAMBlocking);
}); });
mode7Label.setText("HD Mode 7 (fast PPU only)").setFont(Font().setBold());
mode7ScaleLabel.setText("Scale:"); limiterAmount.append(ComboButtonItem().setText("None"));
mode7Scale.append(ComboButtonItem().setText( "240p").setProperty("multiplier", 1)); limiterAmount.append(ComboButtonItem().setText("200%"));
mode7Scale.append(ComboButtonItem().setText( "480p").setProperty("multiplier", 2)); limiterAmount.append(ComboButtonItem().setText("300%"));
mode7Scale.append(ComboButtonItem().setText( "720p").setProperty("multiplier", 3)); limiterAmount.append(ComboButtonItem().setText("400%"));
mode7Scale.append(ComboButtonItem().setText( "960p").setProperty("multiplier", 4)); limiterAmount.append(ComboButtonItem().setText("500%"));
mode7Scale.append(ComboButtonItem().setText("1200p").setProperty("multiplier", 5)); limiterAmount.append(ComboButtonItem().setText("600%"));
mode7Scale.append(ComboButtonItem().setText("1440p").setProperty("multiplier", 6)); limiterAmount.append(ComboButtonItem().setText("700%"));
mode7Scale.append(ComboButtonItem().setText("1680p").setProperty("multiplier", 7)); limiterAmount.append(ComboButtonItem().setText("800%"));
mode7Scale.append(ComboButtonItem().setText("1920p").setProperty("multiplier", 8)); if(settings.fastForward.limiter == 0) limiterAmount.item(0).setSelected();
mode7Scale.append(ComboButtonItem().setText("2160p").setProperty("multiplier", 9)); if(settings.fastForward.limiter == 2) limiterAmount.item(1).setSelected();
for(uint n = 1; n <= 9; n++) { if(settings.fastForward.limiter == 3) limiterAmount.item(2).setSelected();
if(settings.emulator.hack.ppu.mode7.scale == n) mode7Scale.item(n - 1).setSelected(); if(settings.fastForward.limiter == 4) limiterAmount.item(3).setSelected();
} if(settings.fastForward.limiter == 5) limiterAmount.item(4).setSelected();
mode7Scale.onChange([&] { if(settings.fastForward.limiter == 6) limiterAmount.item(5).setSelected();
settings.emulator.hack.ppu.mode7.scale = mode7Scale.selected().property("multiplier").natural(); if(settings.fastForward.limiter == 7) limiterAmount.item(6).setSelected();
emulator->configure("Hacks/PPU/Mode7/Scale", settings.emulator.hack.ppu.mode7.scale); if(settings.fastForward.limiter == 8) limiterAmount.item(7).setSelected();
limiterAmount.onChange([&] {
auto index = limiterAmount.selected().offset();
if(index == 0) settings.fastForward.limiter = 0;
if(index == 1) settings.fastForward.limiter = 2;
if(index == 2) settings.fastForward.limiter = 3;
if(index == 3) settings.fastForward.limiter = 4;
if(index == 4) settings.fastForward.limiter = 5;
if(index == 5) settings.fastForward.limiter = 6;
if(index == 6) settings.fastForward.limiter = 7;
if(index == 7) settings.fastForward.limiter = 8;
}); });
mode7Perspective.setText("Perspective correction").setChecked(settings.emulator.hack.ppu.mode7.perspective).onToggle([&] {
settings.emulator.hack.ppu.mode7.perspective = mode7Perspective.checked(); fastForwardMute.setText("Mute while fast forwarding").setChecked(settings.fastForward.mute).onToggle([&] {
emulator->configure("Hacks/PPU/Mode7/Perspective", settings.emulator.hack.ppu.mode7.perspective); settings.fastForward.mute = fastForwardMute.checked();
}); });
mode7Supersample.setText("Supersampling").setChecked(settings.emulator.hack.ppu.mode7.supersample).onToggle([&] {
settings.emulator.hack.ppu.mode7.supersample = mode7Supersample.checked(); fastForwardSpacer.setColor({192, 192, 192});
emulator->configure("Hacks/PPU/Mode7/Supersample", settings.emulator.hack.ppu.mode7.supersample);
rewindLabel.setText("Rewind").setFont(Font().setBold());
rewindFrequencyLabel.setText("Frequency:");
rewindFrequencyOption.append(ComboButtonItem().setText("Disabled"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 10 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 20 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 30 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 40 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 50 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 60 frames"));
if(settings.rewind.frequency == 0) rewindFrequencyOption.item(0).setSelected();
if(settings.rewind.frequency == 10) rewindFrequencyOption.item(1).setSelected();
if(settings.rewind.frequency == 20) rewindFrequencyOption.item(2).setSelected();
if(settings.rewind.frequency == 30) rewindFrequencyOption.item(3).setSelected();
if(settings.rewind.frequency == 40) rewindFrequencyOption.item(4).setSelected();
if(settings.rewind.frequency == 50) rewindFrequencyOption.item(5).setSelected();
if(settings.rewind.frequency == 60) rewindFrequencyOption.item(6).setSelected();
rewindFrequencyOption.onChange([&] {
settings.rewind.frequency = rewindFrequencyOption.selected().offset() * 10;
program.rewindReset();
}); });
mode7Mosaic.setText("HD->SD Mosaic").setChecked(settings.emulator.hack.ppu.mode7.mosaic).onToggle([&] {
settings.emulator.hack.ppu.mode7.mosaic = mode7Mosaic.checked(); rewindLengthLabel.setText("Length:");
emulator->configure("Hacks/PPU/Mode7/Mosaic", settings.emulator.hack.ppu.mode7.mosaic); rewindLengthOption.append(ComboButtonItem().setText( "10 states"));
rewindLengthOption.append(ComboButtonItem().setText( "20 states"));
rewindLengthOption.append(ComboButtonItem().setText( "40 states"));
rewindLengthOption.append(ComboButtonItem().setText( "80 states"));
rewindLengthOption.append(ComboButtonItem().setText("160 states"));
rewindLengthOption.append(ComboButtonItem().setText("320 states"));
if(settings.rewind.length == 10) rewindLengthOption.item(0).setSelected();
if(settings.rewind.length == 20) rewindLengthOption.item(1).setSelected();
if(settings.rewind.length == 40) rewindLengthOption.item(2).setSelected();
if(settings.rewind.length == 80) rewindLengthOption.item(3).setSelected();
if(settings.rewind.length == 160) rewindLengthOption.item(4).setSelected();
if(settings.rewind.length == 320) rewindLengthOption.item(5).setSelected();
rewindLengthOption.onChange([&] {
settings.rewind.length = 10 << rewindLengthOption.selected().offset();
program.rewindReset();
}); });
dspLabel.setText("DSP (audio)").setFont(Font().setBold());
fastDSP.setText("Fast mode").setChecked(settings.emulator.hack.dsp.fast).onToggle([&] { rewindMute.setText("Mute while rewinding").setChecked(settings.rewind.mute).onToggle([&] {
settings.emulator.hack.dsp.fast = fastDSP.checked(); settings.rewind.mute = rewindMute.checked();
emulator->configure("Hacks/DSP/Fast", settings.emulator.hack.dsp.fast);
}); });
cubicInterpolation.setText("Cubic interpolation").setChecked(settings.emulator.hack.dsp.cubic).onToggle([&] {
settings.emulator.hack.dsp.cubic = cubicInterpolation.checked();
emulator->configure("Hacks/DSP/Cubic", settings.emulator.hack.dsp.cubic);
});
echoShadow.setText("Echo shadow RAM").setToolTip(
"This option emulates a bug in ZSNES where echo RAM was treated as separate from APU RAM.\n"
"Many older ROM hacks for Super Mario World relied on this behavior, and will crash without enabling this.\n"
"It is, however, extremely inaccurate to real hardware and should not be enabled unless required."
).setChecked(settings.emulator.hack.dsp.echoShadow).onToggle([&] {
settings.emulator.hack.dsp.echoShadow = echoShadow.checked();
//not a run-time setting: do not call emulator->configure() here.
});
coprocessorLabel.setText("Coprocessors").setFont(Font().setBold());
coprocessorDelayedSyncOption.setText("Fast mode").setChecked(settings.emulator.hack.coprocessor.delayedSync).onToggle([&] {
settings.emulator.hack.coprocessor.delayedSync = coprocessorDelayedSyncOption.checked();
});
coprocessorPreferHLEOption.setText("Prefer HLE").setChecked(settings.emulator.hack.coprocessor.preferHLE).setToolTip(
"When checked, less accurate HLE emulation will always be used when available.\n"
"When unchecked, HLE will only be used when LLE firmware is missing."
).onToggle([&] {
settings.emulator.hack.coprocessor.preferHLE = coprocessorPreferHLEOption.checked();
});
hacksNote.setText("Note: some hack setting changes do not take effect until after reloading games.");
} }

View File

@ -0,0 +1,117 @@
auto EnhancementSettings::create() -> void {
setCollapsible();
setVisible(false);
overclockingLabel.setText("Overclocking").setFont(Font().setBold());
overclockingLayout.setSize({3, 3});
overclockingLayout.column(0).setAlignment(1.0);
overclockingLayout.column(1).setAlignment(0.5);
cpuLabel.setText("CPU:");
cpuClock.setLength(301).setPosition((settings.emulator.hack.cpu.overclock - 100)).onChange([&] {
settings.emulator.hack.cpu.overclock = cpuClock.position() + 100;
emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock);
cpuValue.setText({settings.emulator.hack.cpu.overclock, "%"});
}).doChange();
sa1Label.setText("SA-1:");
sa1Clock.setLength(301).setPosition((settings.emulator.hack.sa1.overclock - 100)).onChange([&] {
settings.emulator.hack.sa1.overclock = sa1Clock.position() + 100;
emulator->configure("Hacks/SA1/Overclock", settings.emulator.hack.sa1.overclock);
sa1Value.setText({settings.emulator.hack.sa1.overclock, "%"});
}).doChange();
sfxLabel.setText("SuperFX:");
sfxClock.setLength(141).setPosition((settings.emulator.hack.superfx.overclock - 100) / 5).onChange([&] {
settings.emulator.hack.superfx.overclock = sfxClock.position() * 5 + 100;
emulator->configure("Hacks/SuperFX/Overclock", settings.emulator.hack.superfx.overclock);
sfxValue.setText({settings.emulator.hack.superfx.overclock, "%"});
}).doChange();
overclockingSpacer.setColor({192, 192, 192});
ppuLabel.setText("PPU (video)").setFont(Font().setBold());
fastPPU.setText("Fast mode").setChecked(settings.emulator.hack.ppu.fast).onToggle([&] {
settings.emulator.hack.ppu.fast = fastPPU.checked();
if(!fastPPU.checked()) {
noSpriteLimit.setEnabled(false);
deinterlace.setEnabled(false);
mode7Layout.setEnabled(false);
} else {
noSpriteLimit.setEnabled(true);
deinterlace.setEnabled(true);
mode7Layout.setEnabled(true);
}
}).doToggle();
deinterlace.setText("Deinterlace").setChecked(settings.emulator.hack.ppu.deinterlace).onToggle([&] {
settings.emulator.hack.ppu.deinterlace = deinterlace.checked();
emulator->configure("Hacks/PPU/Deinterlace", settings.emulator.hack.ppu.deinterlace);
});
noSpriteLimit.setText("No sprite limit").setChecked(settings.emulator.hack.ppu.noSpriteLimit).onToggle([&] {
settings.emulator.hack.ppu.noSpriteLimit = noSpriteLimit.checked();
});
mode7Label.setText("HD Mode 7 (fast PPU only)").setFont(Font().setBold());
mode7ScaleLabel.setText("Scale:");
mode7Scale.append(ComboButtonItem().setText( "240p").setProperty("multiplier", 1));
mode7Scale.append(ComboButtonItem().setText( "480p").setProperty("multiplier", 2));
mode7Scale.append(ComboButtonItem().setText( "720p").setProperty("multiplier", 3));
mode7Scale.append(ComboButtonItem().setText( "960p").setProperty("multiplier", 4));
mode7Scale.append(ComboButtonItem().setText("1200p").setProperty("multiplier", 5));
mode7Scale.append(ComboButtonItem().setText("1440p").setProperty("multiplier", 6));
mode7Scale.append(ComboButtonItem().setText("1680p").setProperty("multiplier", 7));
mode7Scale.append(ComboButtonItem().setText("1920p").setProperty("multiplier", 8));
mode7Scale.append(ComboButtonItem().setText("2160p").setProperty("multiplier", 9));
for(uint n = 1; n <= 9; n++) {
if(settings.emulator.hack.ppu.mode7.scale == n) mode7Scale.item(n - 1).setSelected();
}
mode7Scale.onChange([&] {
settings.emulator.hack.ppu.mode7.scale = mode7Scale.selected().property("multiplier").natural();
emulator->configure("Hacks/PPU/Mode7/Scale", settings.emulator.hack.ppu.mode7.scale);
});
mode7Perspective.setText("Perspective correction").setChecked(settings.emulator.hack.ppu.mode7.perspective).onToggle([&] {
settings.emulator.hack.ppu.mode7.perspective = mode7Perspective.checked();
emulator->configure("Hacks/PPU/Mode7/Perspective", settings.emulator.hack.ppu.mode7.perspective);
});
mode7Supersample.setText("Supersampling").setChecked(settings.emulator.hack.ppu.mode7.supersample).onToggle([&] {
settings.emulator.hack.ppu.mode7.supersample = mode7Supersample.checked();
emulator->configure("Hacks/PPU/Mode7/Supersample", settings.emulator.hack.ppu.mode7.supersample);
});
mode7Mosaic.setText("HD->SD Mosaic").setChecked(settings.emulator.hack.ppu.mode7.mosaic).onToggle([&] {
settings.emulator.hack.ppu.mode7.mosaic = mode7Mosaic.checked();
emulator->configure("Hacks/PPU/Mode7/Mosaic", settings.emulator.hack.ppu.mode7.mosaic);
});
dspLabel.setText("DSP (audio)").setFont(Font().setBold());
fastDSP.setText("Fast mode").setChecked(settings.emulator.hack.dsp.fast).onToggle([&] {
settings.emulator.hack.dsp.fast = fastDSP.checked();
emulator->configure("Hacks/DSP/Fast", settings.emulator.hack.dsp.fast);
});
cubicInterpolation.setText("Cubic interpolation").setChecked(settings.emulator.hack.dsp.cubic).onToggle([&] {
settings.emulator.hack.dsp.cubic = cubicInterpolation.checked();
emulator->configure("Hacks/DSP/Cubic", settings.emulator.hack.dsp.cubic);
});
coprocessorLabel.setText("Coprocessors").setFont(Font().setBold());
coprocessorDelayedSyncOption.setText("Fast mode").setChecked(settings.emulator.hack.coprocessor.delayedSync).onToggle([&] {
settings.emulator.hack.coprocessor.delayedSync = coprocessorDelayedSyncOption.checked();
});
coprocessorPreferHLEOption.setText("Prefer HLE").setChecked(settings.emulator.hack.coprocessor.preferHLE).setToolTip(
"When checked, less accurate HLE emulation will always be used when available.\n"
"When unchecked, HLE will only be used when LLE firmware is missing."
).onToggle([&] {
settings.emulator.hack.coprocessor.preferHLE = coprocessorPreferHLEOption.checked();
});
coprocessorSpacer.setColor({192, 192, 192});
gameLabel.setText("Game Enhancements").setFont(Font().setBold());
hotfixes.setText("Hotfixes").setToolTip({
"Even commercially licensed and officially released software sometimes shipped with bugs.\n"
"This option will correct certain issues that occurred even on real hardware."
}).setChecked(settings.emulator.hack.hotfixes).onToggle([&] {
settings.emulator.hack.hotfixes = hotfixes.checked();
});
note.setText("Note: some settings do not take effect until after reloading games.");
}

View File

@ -4,8 +4,9 @@
#include "input.cpp" #include "input.cpp"
#include "hotkeys.cpp" #include "hotkeys.cpp"
#include "paths.cpp" #include "paths.cpp"
#include "speed.cpp"
#include "emulator.cpp" #include "emulator.cpp"
#include "enhancements.cpp"
#include "compatibility.cpp"
#include "drivers.cpp" #include "drivers.cpp"
Settings settings; Settings settings;
VideoSettings videoSettings; VideoSettings videoSettings;
@ -13,8 +14,9 @@ AudioSettings audioSettings;
InputSettings inputSettings; InputSettings inputSettings;
HotkeySettings hotkeySettings; HotkeySettings hotkeySettings;
PathSettings pathSettings; PathSettings pathSettings;
SpeedSettings speedSettings;
EmulatorSettings emulatorSettings; EmulatorSettings emulatorSettings;
EnhancementSettings enhancementSettings;
CompatibilitySettings compatibilitySettings;
DriverSettings driverSettings; DriverSettings driverSettings;
namespace Instances { Instance<SettingsWindow> settingsWindow; } namespace Instances { Instance<SettingsWindow> settingsWindow; }
SettingsWindow& settingsWindow = Instances::settingsWindow(); SettingsWindow& settingsWindow = Instances::settingsWindow();
@ -112,8 +114,10 @@ auto Settings::process(bool load) -> void {
bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval); bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval);
bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload); bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload);
bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad); bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad);
bind(boolean, "Emulator/Hack/Hotfixes", emulator.hack.hotfixes);
bind(text, "Emulator/Hack/Entropy", emulator.hack.entropy); bind(text, "Emulator/Hack/Entropy", emulator.hack.entropy);
bind(natural, "Emulator/Hack/CPU/Overclock", emulator.hack.cpu.overclock); bind(natural, "Emulator/Hack/CPU/Overclock", emulator.hack.cpu.overclock);
bind(boolean, "Emulator/Hack/CPU/FastMath", emulator.hack.cpu.fastMath);
bind(boolean, "Emulator/Hack/PPU/Fast", emulator.hack.ppu.fast); bind(boolean, "Emulator/Hack/PPU/Fast", emulator.hack.ppu.fast);
bind(boolean, "Emulator/Hack/PPU/Deinterlace", emulator.hack.ppu.deinterlace); bind(boolean, "Emulator/Hack/PPU/Deinterlace", emulator.hack.ppu.deinterlace);
bind(boolean, "Emulator/Hack/PPU/NoSpriteLimit", emulator.hack.ppu.noSpriteLimit); bind(boolean, "Emulator/Hack/PPU/NoSpriteLimit", emulator.hack.ppu.noSpriteLimit);
@ -172,8 +176,9 @@ auto SettingsWindow::create() -> void {
panelList.append(ListViewItem().setText("Input").setIcon(Icon::Device::Joypad)); panelList.append(ListViewItem().setText("Input").setIcon(Icon::Device::Joypad));
panelList.append(ListViewItem().setText("Hotkeys").setIcon(Icon::Device::Keyboard)); panelList.append(ListViewItem().setText("Hotkeys").setIcon(Icon::Device::Keyboard));
panelList.append(ListViewItem().setText("Paths").setIcon(Icon::Emblem::Folder)); panelList.append(ListViewItem().setText("Paths").setIcon(Icon::Emblem::Folder));
panelList.append(ListViewItem().setText("Speed").setIcon(Icon::Device::Clock));
panelList.append(ListViewItem().setText("Emulator").setIcon(Icon::Action::Settings)); panelList.append(ListViewItem().setText("Emulator").setIcon(Icon::Action::Settings));
panelList.append(ListViewItem().setText("Enhancements").setIcon(Icon::Action::Add));
panelList.append(ListViewItem().setText("Compatibility").setIcon(Icon::Action::Remove));
panelList.append(ListViewItem().setText("Drivers").setIcon(Icon::Place::Settings)); panelList.append(ListViewItem().setText("Drivers").setIcon(Icon::Place::Settings));
panelList.onChange([&] { panelList.onChange([&] {
if(auto item = panelList.selected()) { if(auto item = panelList.selected()) {
@ -188,13 +193,14 @@ auto SettingsWindow::create() -> void {
panelContainer.append(inputSettings, Size{~0, ~0}); panelContainer.append(inputSettings, Size{~0, ~0});
panelContainer.append(hotkeySettings, Size{~0, ~0}); panelContainer.append(hotkeySettings, Size{~0, ~0});
panelContainer.append(pathSettings, Size{~0, ~0}); panelContainer.append(pathSettings, Size{~0, ~0});
panelContainer.append(speedSettings, Size{~0, ~0});
panelContainer.append(emulatorSettings, Size{~0, ~0}); panelContainer.append(emulatorSettings, Size{~0, ~0});
panelContainer.append(enhancementSettings, Size{~0, ~0});
panelContainer.append(compatibilitySettings, Size{~0, ~0});
panelContainer.append(driverSettings, Size{~0, ~0}); panelContainer.append(driverSettings, Size{~0, ~0});
statusBar.setFont(Font().setBold()); statusBar.setFont(Font().setBold());
setTitle("Settings"); setTitle("Settings");
setSize({680_sx, 400_sx}); setSize({680_sx, 400_sy});
setAlignment({0.0, 1.0}); setAlignment({0.0, 1.0});
setDismissable(); setDismissable();
@ -221,8 +227,9 @@ auto SettingsWindow::show(int index) -> void {
inputSettings.setVisible(false); inputSettings.setVisible(false);
hotkeySettings.setVisible(false); hotkeySettings.setVisible(false);
pathSettings.setVisible(false); pathSettings.setVisible(false);
speedSettings.setVisible(false);
emulatorSettings.setVisible(false); emulatorSettings.setVisible(false);
enhancementSettings.setVisible(false);
compatibilitySettings.setVisible(false);
driverSettings.setVisible(false); driverSettings.setVisible(false);
panelList.item(index).setSelected(); panelList.item(index).setSelected();
if(index ==-1) settingsHome.setVisible(true); if(index ==-1) settingsHome.setVisible(true);
@ -231,9 +238,10 @@ auto SettingsWindow::show(int index) -> void {
if(index == 2) inputSettings.setVisible(true); if(index == 2) inputSettings.setVisible(true);
if(index == 3) hotkeySettings.setVisible(true); if(index == 3) hotkeySettings.setVisible(true);
if(index == 4) pathSettings.setVisible(true); if(index == 4) pathSettings.setVisible(true);
if(index == 5) speedSettings.setVisible(true); if(index == 5) emulatorSettings.setVisible(true);
if(index == 6) emulatorSettings.setVisible(true); if(index == 6) enhancementSettings.setVisible(true);
if(index == 7) driverSettings.setVisible(true); if(index == 7) compatibilitySettings.setVisible(true);
if(index == 8) driverSettings.setVisible(true);
panelContainer.resize(); panelContainer.resize();
setVisible(); setVisible();
setFocused(); setFocused();

View File

@ -95,9 +95,11 @@ struct Settings : Markup::Node {
bool autoSaveStateOnUnload = false; bool autoSaveStateOnUnload = false;
bool autoLoadStateOnLoad = false; bool autoLoadStateOnLoad = false;
struct Hack { struct Hack {
bool hotfixes = true;
string entropy = "Low"; string entropy = "Low";
struct CPU { struct CPU {
uint overclock = 100; uint overclock = 100;
bool fastMath = false;
} cpu; } cpu;
struct PPU { struct PPU {
bool fast = true; bool fast = true;
@ -290,39 +292,6 @@ public:
Button screenshotsReset{&layout, Size{80_sx, 0}}; Button screenshotsReset{&layout, Size{80_sx, 0}};
}; };
struct SpeedSettings : VerticalLayout {
auto create() -> void;
public:
Label overclockingLabel{this, Size{~0, 0}, 2};
TableLayout overclockingLayout{this, Size{~0, 0}};
Label cpuLabel{&overclockingLayout, Size{0, 0}};
Label cpuValue{&overclockingLayout, Size{50_sx, 0}};
HorizontalSlider cpuClock{&overclockingLayout, Size{~0, 0}};
//
Label sa1Label{&overclockingLayout, Size{0, 0}};
Label sa1Value{&overclockingLayout, Size{50_sx, 0}};
HorizontalSlider sa1Clock{&overclockingLayout, Size{~0, 0}};
//
Label sfxLabel{&overclockingLayout, Size{0, 0}};
Label sfxValue{&overclockingLayout, Size{50_sx, 0}};
HorizontalSlider sfxClock{&overclockingLayout, Size{~0, 0}};
Label fastForwardLabel{this, Size{~0, 0}, 2};
HorizontalLayout fastForwardLayout{this, Size{~0, 0}};
Label frameSkipLabel{&fastForwardLayout, Size{0, 0}};
ComboButton frameSkipAmount{&fastForwardLayout, Size{0, 0}};
Label limiterLabel{&fastForwardLayout, Size{0, 0}};
ComboButton limiterAmount{&fastForwardLayout, Size{0, 0}};
CheckLabel fastForwardMute{this, Size{0, 0}};
Label rewindLabel{this, Size{~0, 0}, 2};
HorizontalLayout rewindLayout{this, Size{~0, 0}};
Label rewindFrequencyLabel{&rewindLayout, Size{0, 0}};
ComboButton rewindFrequencyOption{&rewindLayout, Size{0, 0}};
Label rewindLengthLabel{&rewindLayout, Size{0, 0}};
ComboButton rewindLengthOption{&rewindLayout, Size{0, 0}};
CheckLabel rewindMute{this, Size{0, 0}};
};
struct EmulatorSettings : VerticalLayout { struct EmulatorSettings : VerticalLayout {
auto create() -> void; auto create() -> void;
@ -334,18 +303,50 @@ public:
CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}}; CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}};
CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}}; CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}};
Canvas optionsSpacer{this, Size{~0, 1}}; Canvas optionsSpacer{this, Size{~0, 1}};
Label entropyLabel{this, Size{~0, 0}, 2}; //
HorizontalLayout entropyLayout{this, Size{~0, 0}}; Label fastForwardLabel{this, Size{~0, 0}, 2};
RadioLabel entropyNone{&entropyLayout, Size{0, 0}}; HorizontalLayout fastForwardLayout{this, Size{~0, 0}};
RadioLabel entropyLow{&entropyLayout, Size{0, 0}}; Label frameSkipLabel{&fastForwardLayout, Size{0, 0}};
RadioLabel entropyHigh{&entropyLayout, Size{0, 0}}; ComboButton frameSkipAmount{&fastForwardLayout, Size{0, 0}};
Group entropyGroup{&entropyNone, &entropyLow, &entropyHigh}; Label limiterLabel{&fastForwardLayout, Size{0, 0}};
ComboButton limiterAmount{&fastForwardLayout, Size{0, 0}};
CheckLabel fastForwardMute{this, Size{0, 0}};
Canvas fastForwardSpacer{this, Size{~0, 1}};
//
Label rewindLabel{this, Size{~0, 0}, 2};
HorizontalLayout rewindLayout{this, Size{~0, 0}};
Label rewindFrequencyLabel{&rewindLayout, Size{0, 0}};
ComboButton rewindFrequencyOption{&rewindLayout, Size{0, 0}};
Label rewindLengthLabel{&rewindLayout, Size{0, 0}};
ComboButton rewindLengthOption{&rewindLayout, Size{0, 0}};
CheckLabel rewindMute{this, Size{0, 0}};
};
struct EnhancementSettings : VerticalLayout {
auto create() -> void;
private:
Label overclockingLabel{this, Size{~0, 0}, 2};
TableLayout overclockingLayout{this, Size{~0, 0}};
Label cpuLabel{&overclockingLayout, Size{0, 0}};
Label cpuValue{&overclockingLayout, Size{50_sx, 0}};
HorizontalSlider cpuClock{&overclockingLayout, Size{~0, 0}};
Canvas overclockingSpacer{this, Size{~0, 1}};
//
Label sa1Label{&overclockingLayout, Size{0, 0}};
Label sa1Value{&overclockingLayout, Size{50_sx, 0}};
HorizontalSlider sa1Clock{&overclockingLayout, Size{~0, 0}};
//
Label sfxLabel{&overclockingLayout, Size{0, 0}};
Label sfxValue{&overclockingLayout, Size{50_sx, 0}};
HorizontalSlider sfxClock{&overclockingLayout, Size{~0, 0}};
//
Label ppuLabel{this, Size{~0, 0}, 2}; Label ppuLabel{this, Size{~0, 0}, 2};
HorizontalLayout ppuLayout{this, Size{~0, 0}}; HorizontalLayout ppuLayout{this, Size{~0, 0}};
CheckLabel fastPPU{&ppuLayout, Size{0, 0}}; CheckLabel fastPPU{&ppuLayout, Size{0, 0}};
CheckLabel deinterlace{&ppuLayout, Size{0, 0}}; CheckLabel deinterlace{&ppuLayout, Size{0, 0}};
CheckLabel noSpriteLimit{&ppuLayout, Size{0, 0}}; CheckLabel noSpriteLimit{&ppuLayout, Size{0, 0}};
CheckLabel noVRAMBlocking{&ppuLayout, Size{0, 0}}; //
Label mode7Label{this, Size{~0, 0}, 2}; Label mode7Label{this, Size{~0, 0}, 2};
HorizontalLayout mode7Layout{this, Size{~0, 0}}; HorizontalLayout mode7Layout{this, Size{~0, 0}};
Label mode7ScaleLabel{&mode7Layout, Size{0, 0}}; Label mode7ScaleLabel{&mode7Layout, Size{0, 0}};
@ -353,16 +354,47 @@ public:
CheckLabel mode7Perspective{&mode7Layout, Size{0, 0}}; CheckLabel mode7Perspective{&mode7Layout, Size{0, 0}};
CheckLabel mode7Supersample{&mode7Layout, Size{0, 0}}; CheckLabel mode7Supersample{&mode7Layout, Size{0, 0}};
CheckLabel mode7Mosaic{&mode7Layout, Size{0, 0}}; CheckLabel mode7Mosaic{&mode7Layout, Size{0, 0}};
//
Label dspLabel{this, Size{~0, 0}, 2}; Label dspLabel{this, Size{~0, 0}, 2};
HorizontalLayout dspLayout{this, Size{~0, 0}}; HorizontalLayout dspLayout{this, Size{~0, 0}};
CheckLabel fastDSP{&dspLayout, Size{0, 0}}; CheckLabel fastDSP{&dspLayout, Size{0, 0}};
CheckLabel cubicInterpolation{&dspLayout, Size{0, 0}}; CheckLabel cubicInterpolation{&dspLayout, Size{0, 0}};
CheckLabel echoShadow{&dspLayout, Size{0, 0}}; //
Label coprocessorLabel{this, Size{~0, 0}, 2}; Label coprocessorLabel{this, Size{~0, 0}, 2};
HorizontalLayout coprocessorLayout{this, Size{~0, 0}}; HorizontalLayout coprocessorLayout{this, Size{~0, 0}};
CheckLabel coprocessorDelayedSyncOption{&coprocessorLayout, Size{0, 0}}; CheckLabel coprocessorDelayedSyncOption{&coprocessorLayout, Size{0, 0}};
CheckLabel coprocessorPreferHLEOption{&coprocessorLayout, Size{0, 0}}; CheckLabel coprocessorPreferHLEOption{&coprocessorLayout, Size{0, 0}};
Label hacksNote{this, Size{~0, 0}}; Canvas coprocessorSpacer{this, Size{~0, 1}};
//
Label gameLabel{this, Size{~0, 0}, 2};
CheckLabel hotfixes{this, Size{0, 0}};
//
Widget spacer{this, Size{~0, ~0}};
Label note{this, Size{~0, 0}};
};
struct CompatibilitySettings : VerticalLayout {
auto create() -> void;
private:
Label entropyLabel{this, Size{~0, 0}, 2};
HorizontalLayout entropyLayout{this, Size{~0, 0}};
RadioLabel entropyNone{&entropyLayout, Size{0, 0}};
RadioLabel entropyLow{&entropyLayout, Size{0, 0}};
RadioLabel entropyHigh{&entropyLayout, Size{0, 0}};
Group entropyGroup{&entropyNone, &entropyLow, &entropyHigh};
//
Label cpuLabel{this, Size{~0, 0}, 2};
CheckLabel fastMath{this, Size{0, 0}};
//
Label ppuLabel{this, Size{~0, 0}, 2};
CheckLabel noVRAMBlocking{this, Size{0, 0}};
//
Label dspLabel{this, Size{~0, 0}, 2};
CheckLabel echoShadow{this, Size{0, 0}};
//
Widget spacer{this, Size{~0, ~0}};
Label note{this, Size{~0, 0}};
}; };
struct DriverSettings : VerticalLayout { struct DriverSettings : VerticalLayout {
@ -402,6 +434,7 @@ public:
CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}};
CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}};
Canvas videoSpacer{this, Size{~0, 1}}; Canvas videoSpacer{this, Size{~0, 1}};
//
Label audioLabel{this, Size{~0, 0}, 2}; Label audioLabel{this, Size{~0, 0}, 2};
VerticalLayout audioLayout{this, Size{~0, 0}}; VerticalLayout audioLayout{this, Size{~0, 0}};
HorizontalLayout audioDriverLayout{&audioLayout, Size{~0, 0}}; HorizontalLayout audioDriverLayout{&audioLayout, Size{~0, 0}};
@ -421,6 +454,7 @@ public:
CheckLabel audioBlockingToggle{&audioToggleLayout, Size{0, 0}}; CheckLabel audioBlockingToggle{&audioToggleLayout, Size{0, 0}};
CheckLabel audioDynamicToggle{&audioToggleLayout, Size{0, 0}}; CheckLabel audioDynamicToggle{&audioToggleLayout, Size{0, 0}};
Canvas audioSpacer{this, Size{~0, 1}}; Canvas audioSpacer{this, Size{~0, 1}};
//
Label inputLabel{this, Size{~0, 0}, 2}; Label inputLabel{this, Size{~0, 0}, 2};
VerticalLayout inputLayout{this, Size{~0, 0}}; VerticalLayout inputLayout{this, Size{~0, 0}};
HorizontalLayout inputDriverLayout{&inputLayout, Size{~0, 0}}; HorizontalLayout inputDriverLayout{&inputLayout, Size{~0, 0}};
@ -438,7 +472,7 @@ struct SettingsWindow : Window, Lock {
public: public:
VerticalLayout layout{this}; VerticalLayout layout{this};
HorizontalLayout panelLayout{&layout, Size{~0, ~0}}; HorizontalLayout panelLayout{&layout, Size{~0, ~0}};
ListView panelList{&panelLayout, Size{120_sx, ~0}}; ListView panelList{&panelLayout, Size{125_sx, ~0}};
VerticalLayout panelContainer{&panelLayout, Size{~0, ~0}}; VerticalLayout panelContainer{&panelLayout, Size{~0, ~0}};
StatusBar statusBar{this}; StatusBar statusBar{this};
}; };
@ -449,8 +483,9 @@ extern AudioSettings audioSettings;
extern InputSettings inputSettings; extern InputSettings inputSettings;
extern HotkeySettings hotkeySettings; extern HotkeySettings hotkeySettings;
extern PathSettings pathSettings; extern PathSettings pathSettings;
extern SpeedSettings speedSettings;
extern EmulatorSettings emulatorSettings; extern EmulatorSettings emulatorSettings;
extern EnhancementSettings enhancementSettings;
extern CompatibilitySettings compatibilitySettings;
extern DriverSettings driverSettings; extern DriverSettings driverSettings;
namespace Instances { extern Instance<SettingsWindow> settingsWindow; } namespace Instances { extern Instance<SettingsWindow> settingsWindow; }
extern SettingsWindow& settingsWindow; extern SettingsWindow& settingsWindow;

View File

@ -1,133 +0,0 @@
auto SpeedSettings::create() -> void {
setCollapsible();
setVisible(false);
overclockingLabel.setText("Overclocking").setFont(Font().setBold());
overclockingLayout.setSize({3, 3});
overclockingLayout.column(0).setAlignment(1.0);
overclockingLayout.column(1).setAlignment(0.5);
cpuLabel.setText("CPU:");
cpuClock.setLength(301).setPosition((settings.emulator.hack.cpu.overclock - 100)).onChange([&] {
settings.emulator.hack.cpu.overclock = cpuClock.position() + 100;
emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock);
cpuValue.setText({settings.emulator.hack.cpu.overclock, "%"});
}).doChange();
sa1Label.setText("SA-1:");
sa1Clock.setLength(301).setPosition((settings.emulator.hack.sa1.overclock - 100)).onChange([&] {
settings.emulator.hack.sa1.overclock = sa1Clock.position() + 100;
emulator->configure("Hacks/SA1/Overclock", settings.emulator.hack.sa1.overclock);
sa1Value.setText({settings.emulator.hack.sa1.overclock, "%"});
}).doChange();
sfxLabel.setText("SuperFX:");
sfxClock.setLength(141).setPosition((settings.emulator.hack.superfx.overclock - 100) / 5).onChange([&] {
settings.emulator.hack.superfx.overclock = sfxClock.position() * 5 + 100;
emulator->configure("Hacks/SuperFX/Overclock", settings.emulator.hack.superfx.overclock);
sfxValue.setText({settings.emulator.hack.superfx.overclock, "%"});
}).doChange();
fastForwardLabel.setText("Fast Forward").setFont(Font().setBold());
frameSkipLabel.setText("Frame skip:").setToolTip({
"Set how many frames to skip while fast forwarding.\n"
"Frame skipping allows a higher maximum fast forwarding frame rate."
});
frameSkipAmount.append(ComboButtonItem().setText("None"));
frameSkipAmount.append(ComboButtonItem().setText("1 frame"));
frameSkipAmount.append(ComboButtonItem().setText("2 frames"));
frameSkipAmount.append(ComboButtonItem().setText("3 frames"));
frameSkipAmount.append(ComboButtonItem().setText("4 frames"));
frameSkipAmount.append(ComboButtonItem().setText("5 frames"));
frameSkipAmount.append(ComboButtonItem().setText("6 frames"));
frameSkipAmount.append(ComboButtonItem().setText("7 frames"));
frameSkipAmount.append(ComboButtonItem().setText("8 frames"));
frameSkipAmount.append(ComboButtonItem().setText("9 frames"));
frameSkipAmount.item(settings.fastForward.frameSkip).setSelected();
frameSkipAmount.onChange([&] {
settings.fastForward.frameSkip = frameSkipAmount.selected().offset();
});
limiterLabel.setText("Limiter:").setToolTip({
"Set the maximum speed when fast forwarding."
});
limiterAmount.append(ComboButtonItem().setText("None"));
limiterAmount.append(ComboButtonItem().setText("200%"));
limiterAmount.append(ComboButtonItem().setText("300%"));
limiterAmount.append(ComboButtonItem().setText("400%"));
limiterAmount.append(ComboButtonItem().setText("500%"));
limiterAmount.append(ComboButtonItem().setText("600%"));
limiterAmount.append(ComboButtonItem().setText("700%"));
limiterAmount.append(ComboButtonItem().setText("800%"));
if(settings.fastForward.limiter == 0) limiterAmount.item(0).setSelected();
if(settings.fastForward.limiter == 2) limiterAmount.item(1).setSelected();
if(settings.fastForward.limiter == 3) limiterAmount.item(2).setSelected();
if(settings.fastForward.limiter == 4) limiterAmount.item(3).setSelected();
if(settings.fastForward.limiter == 5) limiterAmount.item(4).setSelected();
if(settings.fastForward.limiter == 6) limiterAmount.item(5).setSelected();
if(settings.fastForward.limiter == 7) limiterAmount.item(6).setSelected();
if(settings.fastForward.limiter == 8) limiterAmount.item(7).setSelected();
limiterAmount.onChange([&] {
auto index = limiterAmount.selected().offset();
if(index == 0) settings.fastForward.limiter = 0;
if(index == 1) settings.fastForward.limiter = 2;
if(index == 2) settings.fastForward.limiter = 3;
if(index == 3) settings.fastForward.limiter = 4;
if(index == 4) settings.fastForward.limiter = 5;
if(index == 5) settings.fastForward.limiter = 6;
if(index == 6) settings.fastForward.limiter = 7;
if(index == 7) settings.fastForward.limiter = 8;
});
fastForwardMute.setText("Mute while fast forwarding").setChecked(settings.fastForward.mute).onToggle([&] {
settings.fastForward.mute = fastForwardMute.checked();
});
rewindLabel.setText("Rewind").setFont(Font().setBold());
rewindFrequencyLabel.setText("Frequency:");
rewindFrequencyOption.append(ComboButtonItem().setText("Disabled"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 10 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 20 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 30 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 40 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 50 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 60 frames"));
if(settings.rewind.frequency == 0) rewindFrequencyOption.item(0).setSelected();
if(settings.rewind.frequency == 10) rewindFrequencyOption.item(1).setSelected();
if(settings.rewind.frequency == 20) rewindFrequencyOption.item(2).setSelected();
if(settings.rewind.frequency == 30) rewindFrequencyOption.item(3).setSelected();
if(settings.rewind.frequency == 40) rewindFrequencyOption.item(4).setSelected();
if(settings.rewind.frequency == 50) rewindFrequencyOption.item(5).setSelected();
if(settings.rewind.frequency == 60) rewindFrequencyOption.item(6).setSelected();
rewindFrequencyOption.onChange([&] {
settings.rewind.frequency = rewindFrequencyOption.selected().offset() * 10;
program.rewindReset();
});
rewindLengthLabel.setText("Length:");
rewindLengthOption.append(ComboButtonItem().setText( "10 states"));
rewindLengthOption.append(ComboButtonItem().setText( "20 states"));
rewindLengthOption.append(ComboButtonItem().setText( "40 states"));
rewindLengthOption.append(ComboButtonItem().setText( "80 states"));
rewindLengthOption.append(ComboButtonItem().setText("160 states"));
rewindLengthOption.append(ComboButtonItem().setText("320 states"));
if(settings.rewind.length == 10) rewindLengthOption.item(0).setSelected();
if(settings.rewind.length == 20) rewindLengthOption.item(1).setSelected();
if(settings.rewind.length == 40) rewindLengthOption.item(2).setSelected();
if(settings.rewind.length == 80) rewindLengthOption.item(3).setSelected();
if(settings.rewind.length == 160) rewindLengthOption.item(4).setSelected();
if(settings.rewind.length == 320) rewindLengthOption.item(5).setSelected();
rewindLengthOption.onChange([&] {
settings.rewind.length = 10 << rewindLengthOption.selected().offset();
program.rewindReset();
});
rewindMute.setText("Mute while rewinding").setChecked(settings.rewind.mute).onToggle([&] {
settings.rewind.mute = rewindMute.checked();
});
}

View File

@ -61,7 +61,7 @@ auto ToolsWindow::create() -> void {
panelContainer.append(manifestViewer, Size{~0, ~0}); panelContainer.append(manifestViewer, Size{~0, ~0});
setTitle("Tools"); setTitle("Tools");
setSize({720_sx, 400_sx}); setSize({720_sx, 400_sy});
setAlignment({1.0, 1.0}); setAlignment({1.0, 1.0});
setDismissable(); setDismissable();

View File

@ -13,7 +13,7 @@ ifeq ($(ruby),)
ruby += input.sdl input.xlib input.udev ruby += input.sdl input.xlib input.udev
else ifeq ($(platform),bsd) else ifeq ($(platform),bsd)
ruby += video.glx video.glx2 video.xvideo video.xshm ruby += video.glx video.glx2 video.xvideo video.xshm
ruby += audio.oss #audio.openal ruby += audio.oss #audio.pulseaudio
ruby += input.sdl input.xlib ruby += input.sdl input.xlib
endif endif
endif endif