diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index fa27d877..d18b11da 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "103.09"; + static const string Version = "103.10"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/sfc/smp/timing.cpp b/higan/sfc/smp/timing.cpp index 56ba817b..a2f6ac37 100644 --- a/higan/sfc/smp/timing.cpp +++ b/higan/sfc/smp/timing.cpp @@ -1,7 +1,7 @@ //DSP clock (~24576khz) / 12 (~2048khz) is fed into the SMP //from here, the wait states value is really a clock divider of {2, 4, 8, 16} -//because dividers of 8 and 16 are not evenly divislbe into 12, the SMP glitches -//in these two cases, the SMP ends up consuming 10 and 20 cycles instead +//due to an unknown hardware issue, clock dividers of 8 and 16 are glitchy +//the SMP ends up consuming 10 and 20 clocks per opcode cycle instead //this causes unpredictable behavior on real hardware //sometimes the SMP will run far slower than expected //other times (and more likely), the SMP will deadlock until the system is reset diff --git a/higan/target-tomoko/configuration/configuration.cpp b/higan/target-tomoko/configuration/configuration.cpp index 9c4e2c06..7576991b 100644 --- a/higan/target-tomoko/configuration/configuration.cpp +++ b/higan/target-tomoko/configuration/configuration.cpp @@ -30,14 +30,15 @@ Settings::Settings() { set("Video/Overscan/Vertical", 8); set("Video/Windowed/AspectCorrection", true); - set("Video/Windowed/Adaptive", false); - set("Video/Windowed/Multiplier", "Small"); - set("Video/Windowed/Multiplier/Small", 2); - set("Video/Windowed/Multiplier/Medium", 3); - set("Video/Windowed/Multiplier/Large", 4); + set("Video/Windowed/IntegralScaling", true); + set("Video/Windowed/AdaptiveSizing", false); + set("Video/Windowed/Scale", "Small"); + set("Video/Windowed/Scale/Small", "640x480"); + set("Video/Windowed/Scale/Medium", "960x720"); + set("Video/Windowed/Scale/Large", "1280x960"); set("Video/Fullscreen/AspectCorrection", true); - set("Video/Fullscreen/Adaptive", false); + set("Video/Fullscreen/IntegralScaling", true); set("Audio/Driver", ruby::Audio::optimalDriver()); set("Audio/Device", ""); diff --git a/higan/target-tomoko/input/hotkeys.cpp b/higan/target-tomoko/input/hotkeys.cpp index 643e6a06..42d2095c 100644 --- a/higan/target-tomoko/input/hotkeys.cpp +++ b/higan/target-tomoko/input/hotkeys.cpp @@ -37,7 +37,7 @@ auto InputManager::appendHotkeys() -> void { hotkey->name = "Decrement Quick State"; hotkey->press = [&] { if(--quickStateSlot < 1) quickStateSlot = 5; - program->showMessage({"Selected quick slot ", quickStateSlot}); + program->showMessage({"Selected quick state slot ", quickStateSlot}); }; hotkeys.append(hotkey); } @@ -46,7 +46,7 @@ auto InputManager::appendHotkeys() -> void { hotkey->name = "Increment Quick State"; hotkey->press = [&] { if(++quickStateSlot > 5) quickStateSlot = 1; - program->showMessage({"Selected quick slot ", quickStateSlot}); + program->showMessage({"Selected quick state slot ", quickStateSlot}); }; hotkeys.append(hotkey); } diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index 5b97dafa..cd72e97d 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -47,19 +47,16 @@ Presentation::Presentation() { settingsMenu.setText("Settings"); videoScaleMenu.setText("Video Scale"); - if(settings["Video/Windowed/Multiplier"].text() == "Small") videoScaleSmall.setChecked(); - if(settings["Video/Windowed/Multiplier"].text() == "Medium") videoScaleMedium.setChecked(); - if(settings["Video/Windowed/Multiplier"].text() == "Large") videoScaleLarge.setChecked(); videoScaleSmall.setText("Small").onActivate([&] { - settings["Video/Windowed/Multiplier"].setValue("Small"); + settings["Video/Windowed/Scale"].setValue("Small"); resizeViewport(); }); videoScaleMedium.setText("Medium").onActivate([&] { - settings["Video/Windowed/Multiplier"].setValue("Medium"); + settings["Video/Windowed/Scale"].setValue("Medium"); resizeViewport(); }); videoScaleLarge.setText("Large").onActivate([&] { - settings["Video/Windowed/Multiplier"].setValue("Large"); + settings["Video/Windowed/Scale"].setValue("Large"); resizeViewport(); }); videoEmulationMenu.setText("Video Emulation"); @@ -122,13 +119,13 @@ Presentation::Presentation() { }); toolsMenu.setText("Tools").setVisible(false); - saveStateMenu.setText("Save Quickstate"); + saveQuickStateMenu.setText("Save Quick State"); saveSlot1.setText("Slot 1").onActivate([&] { program->saveState(1); }); saveSlot2.setText("Slot 2").onActivate([&] { program->saveState(2); }); saveSlot3.setText("Slot 3").onActivate([&] { program->saveState(3); }); saveSlot4.setText("Slot 4").onActivate([&] { program->saveState(4); }); saveSlot5.setText("Slot 5").onActivate([&] { program->saveState(5); }); - loadStateMenu.setText("Load Quickstate"); + loadQuickStateMenu.setText("Load Quick State"); loadSlot1.setText("Slot 1").onActivate([&] { program->loadState(1); }); loadSlot2.setText("Slot 2").onActivate([&] { program->loadState(2); }); loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); }); @@ -155,10 +152,15 @@ Presentation::Presentation() { program->loadMedium(); }); - onClose([&] { program->quit(); }); + onSize([&] { + resizeViewport(true); + }); + + onClose([&] { + program->quit(); + }); setTitle({"higan v", Emulator::Version}); - setResizable(false); setBackgroundColor({0, 0, 0}); resizeViewport(); setCentered(); @@ -235,10 +237,16 @@ auto Presentation::clearViewport() -> void { } } -auto Presentation::resizeViewport() -> void { +//onSize is true only for events generated from window resizing +//it will suppress automatic viewport scaling, and disable adaptive scaling +//it does this so that the main window can always be resizable +auto Presentation::resizeViewport(bool onSize) -> void { //clear video area before resizing to avoid seeing distorted video momentarily clearViewport(); + uint viewportWidth = geometry().width(); + uint viewportHeight = geometry().height(); + double emulatorWidth = 320; double emulatorHeight = 240; double aspectCorrection = 1.0; @@ -257,30 +265,37 @@ auto Presentation::resizeViewport() -> void { if(!fullScreen()) { if(settings["Video/Windowed/AspectCorrection"].boolean()) emulatorWidth *= aspectCorrection; - uint viewportMultiplier = 2; - if(settings["Video/Windowed/Multiplier"].text() == "Small") viewportMultiplier = settings["Video/Windowed/Multiplier/Small"].natural(); - if(settings["Video/Windowed/Multiplier"].text() == "Medium") viewportMultiplier = settings["Video/Windowed/Multiplier/Medium"].natural(); - if(settings["Video/Windowed/Multiplier"].text() == "Large") viewportMultiplier = settings["Video/Windowed/Multiplier/Large"].natural(); - uint viewportWidth = 320 * viewportMultiplier; - uint viewportHeight = 240 * viewportMultiplier; - uint multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight); - if(!settings["Video/Windowed/Adaptive"].boolean()) { + + if(!onSize) { + string viewportScale = "640x480"; + if(settings["Video/Windowed/Scale"].text() == "Small") viewportScale = settings["Video/Windowed/Scale/Small"].text(); + if(settings["Video/Windowed/Scale"].text() == "Medium") viewportScale = settings["Video/Windowed/Scale/Medium"].text(); + if(settings["Video/Windowed/Scale"].text() == "Large") viewportScale = settings["Video/Windowed/Scale/Large"].text(); + auto resolution = viewportScale.isplit("x", 1L); + viewportWidth = resolution(0).natural(); + viewportHeight = resolution(1).natural(); + } + + if(settings["Video/Windowed/AdaptiveSizing"].boolean() && !onSize) { + uint multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight); emulatorWidth *= multiplier; emulatorHeight *= multiplier; - setSize({viewportWidth, viewportHeight}); - viewport.setGeometry({ - (viewportWidth - emulatorWidth) / 2, (viewportHeight - emulatorHeight) / 2, - emulatorWidth, emulatorHeight - }); + setSize({viewportWidth = emulatorWidth, viewportHeight = emulatorHeight}); + } else if(settings["Video/Windowed/IntegralScaling"].boolean()) { + uint multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight); + emulatorWidth *= multiplier; + emulatorHeight *= multiplier; + if(!onSize) setSize({viewportWidth, viewportHeight}); } else { - setSize({emulatorWidth * multiplier, emulatorHeight * multiplier}); - viewport.setGeometry({0, 0, emulatorWidth * multiplier, emulatorHeight * multiplier}); + double multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight); + emulatorWidth *= multiplier; + emulatorHeight *= multiplier; + if(!onSize) setSize({viewportWidth, viewportHeight}); } } else { if(settings["Video/Fullscreen/AspectCorrection"].boolean()) emulatorWidth *= aspectCorrection; - uint viewportWidth = geometry().width(); - uint viewportHeight = geometry().height(); - if(!settings["Video/Fullscreen/Adaptive"].boolean()) { + + if(settings["Video/Fullscreen/IntegralScaling"].boolean()) { uint multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight); emulatorWidth *= multiplier; emulatorHeight *= multiplier; @@ -289,12 +304,13 @@ auto Presentation::resizeViewport() -> void { emulatorWidth *= multiplier; emulatorHeight *= multiplier; } - viewport.setGeometry({ - (viewportWidth - emulatorWidth) / 2, (viewportHeight - emulatorHeight) / 2, - emulatorWidth, emulatorHeight - }); } + viewport.setGeometry({ + (viewportWidth - emulatorWidth) / 2, (viewportHeight - emulatorHeight) / 2, + emulatorWidth, emulatorHeight + }); + //clear video area again to ensure entire viewport area has been painted in clearViewport(); } diff --git a/higan/target-tomoko/presentation/presentation.hpp b/higan/target-tomoko/presentation/presentation.hpp index c80fd9b9..c56d3886 100644 --- a/higan/target-tomoko/presentation/presentation.hpp +++ b/higan/target-tomoko/presentation/presentation.hpp @@ -12,7 +12,7 @@ struct Presentation : Window { Presentation(); auto updateEmulator() -> void; auto clearViewport() -> void; - auto resizeViewport() -> void; + auto resizeViewport(bool onSize = false) -> void; auto toggleFullScreen() -> void; auto loadShaders() -> void; @@ -27,10 +27,10 @@ struct Presentation : Window { MenuItem unloadSystem{&systemMenu}; Menu settingsMenu{&menuBar}; Menu videoScaleMenu{&settingsMenu}; - MenuRadioItem videoScaleSmall{&videoScaleMenu}; - MenuRadioItem videoScaleMedium{&videoScaleMenu}; - MenuRadioItem videoScaleLarge{&videoScaleMenu}; - Group videoScales{&videoScaleSmall, &videoScaleMedium, &videoScaleLarge}; + MenuItem videoScaleSmall{&videoScaleMenu}; + MenuItem videoScaleMedium{&videoScaleMenu}; + MenuItem videoScaleLarge{&videoScaleMenu}; + //Group videoScales{&videoScaleSmall, &videoScaleMedium, &videoScaleLarge}; Menu videoEmulationMenu{&settingsMenu}; MenuCheckItem blurEmulation{&videoEmulationMenu}; MenuCheckItem colorEmulation{&videoEmulationMenu}; @@ -48,18 +48,18 @@ struct Presentation : Window { MenuSeparator showConfigurationSeparator{&settingsMenu}; MenuItem showConfiguration{&settingsMenu}; Menu toolsMenu{&menuBar}; - Menu saveStateMenu{&toolsMenu}; - MenuItem saveSlot1{&saveStateMenu}; - MenuItem saveSlot2{&saveStateMenu}; - MenuItem saveSlot3{&saveStateMenu}; - MenuItem saveSlot4{&saveStateMenu}; - MenuItem saveSlot5{&saveStateMenu}; - Menu loadStateMenu{&toolsMenu}; - MenuItem loadSlot1{&loadStateMenu}; - MenuItem loadSlot2{&loadStateMenu}; - MenuItem loadSlot3{&loadStateMenu}; - MenuItem loadSlot4{&loadStateMenu}; - MenuItem loadSlot5{&loadStateMenu}; + Menu saveQuickStateMenu{&toolsMenu}; + MenuItem saveSlot1{&saveQuickStateMenu}; + MenuItem saveSlot2{&saveQuickStateMenu}; + MenuItem saveSlot3{&saveQuickStateMenu}; + MenuItem saveSlot4{&saveQuickStateMenu}; + MenuItem saveSlot5{&saveQuickStateMenu}; + Menu loadQuickStateMenu{&toolsMenu}; + MenuItem loadSlot1{&loadQuickStateMenu}; + MenuItem loadSlot2{&loadQuickStateMenu}; + MenuItem loadSlot3{&loadQuickStateMenu}; + MenuItem loadSlot4{&loadQuickStateMenu}; + MenuItem loadSlot5{&loadQuickStateMenu}; MenuSeparator toolsMenuSeparator{&toolsMenu}; MenuItem cheatEditor{&toolsMenu}; MenuItem stateManager{&toolsMenu}; diff --git a/higan/target-tomoko/program/state.cpp b/higan/target-tomoko/program/state.cpp index 5fa9af0b..17542b86 100644 --- a/higan/target-tomoko/program/state.cpp +++ b/higan/target-tomoko/program/state.cpp @@ -14,7 +14,7 @@ auto Program::loadState(uint slot, bool managed) -> bool { if(memory.size() == 0) return showMessage({"Slot ", slot, " ", type, " state does not exist"}), false; serializer s(memory.data(), memory.size()); if(emulator->unserialize(s) == false) return showMessage({"Slot ", slot, " ", type, " state incompatible"}), false; - return showMessage({"Loaded from ", type, " slot ", slot}), true; + return showMessage({"Loaded ", type, " state from slot ", slot}), true; } auto Program::saveState(uint slot, bool managed) -> bool { @@ -22,10 +22,10 @@ auto Program::saveState(uint slot, bool managed) -> bool { string type = managed ? "managed" : "quick"; auto location = stateName(slot, managed); serializer s = emulator->serialize(); - if(s.size() == 0) return showMessage({"Failed to save state to slot ", slot}), false; + if(s.size() == 0) return showMessage({"Failed to save ", type, " state to slot ", slot}), false; directory::create(Location::path(location)); if(file::write(location, s.data(), s.size()) == false) { - return showMessage({"Unable to write to ", type, " slot ", slot}), false; + return showMessage({"Unable to write ", type, " state to slot ", slot}), false; } - return showMessage({"Saved to ", type, " slot ", slot}), true; + return showMessage({"Saved ", type, " state to slot ", slot}), true; } diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index 673358cd..68dd33c1 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -27,11 +27,12 @@ struct VideoSettings : TabFrameItem { Label windowedModeLabel{&layout, Size{~0, 0}, 2}; HorizontalLayout windowedModeLayout{&layout, Size{~0, 0}}; CheckLabel windowedModeAspectCorrection{&windowedModeLayout, Size{0, 0}}; - CheckLabel windowedModeAdaptive{&windowedModeLayout, Size{0, 0}}; + CheckLabel windowedModeIntegralScaling{&windowedModeLayout, Size{0, 0}}; + CheckLabel windowedModeAdaptiveSizing{&windowedModeLayout, Size{0, 0}}; Label fullscreenModeLabel{&layout, Size{~0, 0}, 2}; HorizontalLayout fullscreenModeLayout{&layout, Size{~0, 0}}; CheckLabel fullscreenModeAspectCorrection{&fullscreenModeLayout, Size{0, 0}}; - CheckLabel fullscreenModeAdaptive{&fullscreenModeLayout, Size{0, 0}}; + CheckLabel fullscreenModeIntegralScaling{&fullscreenModeLayout, Size{0, 0}}; auto updateColor() -> void; auto updateOverscan() -> void; diff --git a/higan/target-tomoko/settings/video.cpp b/higan/target-tomoko/settings/video.cpp index 772d7f33..ba7346ad 100644 --- a/higan/target-tomoko/settings/video.cpp +++ b/higan/target-tomoko/settings/video.cpp @@ -24,12 +24,13 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { verticalMaskSlider.setLength(25).setPosition(settings["Video/Overscan/Vertical"].natural()).onChange([&] { updateOverscan(); }); windowedModeLabel.setFont(Font().setBold()).setText("Windowed Mode"); - windowedModeAspectCorrection.setText("Correct aspect ratio").setChecked(settings["Video/Windowed/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); }); - windowedModeAdaptive.setText("Resize window to viewport").setChecked(settings["Video/Windowed/Adaptive"].boolean()).onToggle([&] { updateViewport(); }); + windowedModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Windowed/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); }); + windowedModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Windowed/IntegralScaling"].boolean()).onToggle([&] { updateViewport(); }); + windowedModeAdaptiveSizing.setText("Adaptive sizing").setChecked(settings["Video/Windowed/AdaptiveSizing"].boolean()).onToggle([&] { updateViewport(); }); fullscreenModeLabel.setFont(Font().setBold()).setText("Fullscreen Mode"); - fullscreenModeAspectCorrection.setText("Correct aspect ratio").setChecked(settings["Video/Fullscreen/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); }); - fullscreenModeAdaptive.setText("Resize viewport to window").setChecked(settings["Video/Fullscreen/Adaptive"].boolean()).onToggle([&] { updateViewport(); }); + fullscreenModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Fullscreen/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); }); + fullscreenModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Fullscreen/IntegralScaling"].boolean()).onToggle([&] { updateViewport(); }); updateColor(); updateOverscan(); @@ -56,8 +57,9 @@ auto VideoSettings::updateOverscan() -> void { auto VideoSettings::updateViewport() -> void { settings["Video/Windowed/AspectCorrection"].setValue(windowedModeAspectCorrection.checked()); - settings["Video/Windowed/Adaptive"].setValue(windowedModeAdaptive.checked()); + settings["Video/Windowed/IntegralScaling"].setValue(windowedModeIntegralScaling.checked()); + settings["Video/Windowed/AdaptiveSizing"].setValue(windowedModeAdaptiveSizing.checked()); settings["Video/Fullscreen/AspectCorrection"].setValue(fullscreenModeAspectCorrection.checked()); - settings["Video/Fullscreen/Adaptive"].setValue(fullscreenModeAdaptive.checked()); + settings["Video/Fullscreen/IntegralScaling"].setValue(fullscreenModeIntegralScaling.checked()); presentation->resizeViewport(); } diff --git a/higan/target-tomoko/tools/cheat-editor.cpp b/higan/target-tomoko/tools/cheat-editor.cpp index 4337b0b1..f81c5ba3 100644 --- a/higan/target-tomoko/tools/cheat-editor.cpp +++ b/higan/target-tomoko/tools/cheat-editor.cpp @@ -27,6 +27,9 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { findCodesButton.setText("Find Codes ...").onActivate([&] { cheatDatabase->findCodes(); }); resetButton.setText("Reset").onActivate([&] { doReset(); }); eraseButton.setText("Erase").onActivate([&] { doErase(); }); + + //do not display "Find Codes" button if there is no cheat database to look up codes in + if(!file::exists(locate("cheats.bml"))) findCodesButton.setVisible(false); } auto CheatEditor::doChangeSelected() -> void { @@ -128,7 +131,7 @@ auto CheatEditor::addCode(const string& code, const string& description, bool en auto CheatEditor::loadCheats() -> void { doReset(true); - auto contents = string::read({program->mediumPaths(1), "cheats.bml"}); + auto contents = string::read({program->mediumPaths(1), "higan/cheats.bml"}); auto document = BML::unserialize(contents); for(auto cheat : document["cartridge"].find("cheat")) { if(!addCode(cheat["code"].text(), cheat["description"].text(), (bool)cheat["enabled"])) break; @@ -149,9 +152,10 @@ auto CheatEditor::saveCheats() -> void { count++; } if(count) { - file::write({program->mediumPaths(1), "cheats.bml"}, document); + directory::create({program->mediumPaths(1), "higan/"}); + file::write({program->mediumPaths(1), "higan/cheats.bml"}, document); } else { - file::remove({program->mediumPaths(1), "cheats.bml"}); + file::remove({program->mediumPaths(1), "higan/cheats.bml"}); } doReset(true); }