diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index fa440e01..0dcec0a9 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 = "106.34"; + static const string Version = "106.35"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/sfc/cartridge/cartridge.hpp b/higan/sfc/cartridge/cartridge.hpp index ce8d1003..e440d708 100644 --- a/higan/sfc/cartridge/cartridge.hpp +++ b/higan/sfc/cartridge/cartridge.hpp @@ -89,7 +89,7 @@ private: auto loadSPC7110(Markup::Node) -> void; auto loadSDD1(Markup::Node) -> void; auto loadOBC1(Markup::Node) -> void; - auto loadMSU1(Markup::Node) -> void; + auto loadMSU1() -> void; //save.cpp auto saveCartridge(Markup::Node) -> void; diff --git a/higan/sfc/cartridge/load.cpp b/higan/sfc/cartridge/load.cpp index 3f3bd025..20ae6dda 100644 --- a/higan/sfc/cartridge/load.cpp +++ b/higan/sfc/cartridge/load.cpp @@ -67,7 +67,8 @@ auto Cartridge::loadCartridge(Markup::Node node) -> void { if(auto node = board["processor(identifier=SPC7110)"]) loadSPC7110(node); if(auto node = board["processor(identifier=SDD1)"]) loadSDD1(node); if(auto node = board["processor(identifier=OBC1)"]) loadOBC1(node); - if(auto node = board["processor(identifier=MSU1)"]) loadMSU1(node); + + if(auto fp = platform->open(pathID(), "msu1/data.rom", File::Read)) loadMSU1(); } auto Cartridge::loadCartridgeGameBoy(Markup::Node node) -> void { @@ -642,11 +643,9 @@ auto Cartridge::loadOBC1(Markup::Node node) -> void { } } -//processor(identifier=MSU1) -auto Cartridge::loadMSU1(Markup::Node node) -> void { +//file::exists("msu1/data.rom") +auto Cartridge::loadMSU1() -> void { has.MSU1 = true; - for(auto map : node.find("map")) { - loadMap(map, {&MSU1::readIO, &msu1}, {&MSU1::writeIO, &msu1}); - } + bus.map({&MSU1::readIO, &msu1}, {&MSU1::writeIO, &msu1}, "00-3f,80-bf:2000-2007"); } diff --git a/higan/sfc/coprocessor/msu1/msu1.cpp b/higan/sfc/coprocessor/msu1/msu1.cpp index 7fa447fd..e5060dfa 100644 --- a/higan/sfc/coprocessor/msu1/msu1.cpp +++ b/higan/sfc/coprocessor/msu1/msu1.cpp @@ -11,7 +11,7 @@ auto MSU1::Enter() -> void { } auto MSU1::main() -> void { - double left = 0.0; + double left = 0.0; double right = 0.0; if(io.audioPlay) { @@ -72,9 +72,7 @@ auto MSU1::power() -> void { auto MSU1::dataOpen() -> void { dataFile.reset(); - auto document = Markup::Node(); //todo: fix this - string name = document["board/msu1/rom/name"].text(); - if(!name) name = "msu1.rom"; + string name = {"msu1/data.rom"}; if(dataFile = platform->open(ID::SuperFamicom, name, File::Read)) { dataFile->seek(io.dataReadOffset); } @@ -82,13 +80,7 @@ auto MSU1::dataOpen() -> void { auto MSU1::audioOpen() -> void { audioFile.reset(); - auto document = Markup::Node(); //todo: fix this - string name = {"track-", io.audioTrack, ".pcm"}; - for(auto track : document.find("board/msu1/track")) { - if(track["number"].natural() != io.audioTrack) continue; - name = track["name"].text(); - break; - } + string name = {"msu1/track-", io.audioTrack, ".pcm"}; if(audioFile = platform->open(ID::SuperFamicom, name, File::Read)) { if(audioFile->size() >= 8) { uint32 header = audioFile->readm(4); @@ -164,7 +156,7 @@ auto MSU1::writeIO(uint24 addr, uint8 data) -> void { if(io.audioError) break; io.audioPlay = data.bit(0); io.audioRepeat = data.bit(1); - bool audioResume = data.bit(2); + boolean audioResume = data.bit(2); if(!io.audioPlay && audioResume) { io.audioResumeTrack = io.audioTrack; io.audioResumeOffset = io.audioPlayOffset; diff --git a/higan/sfc/coprocessor/msu1/msu1.hpp b/higan/sfc/coprocessor/msu1/msu1.hpp index a4fa6bb4..7e50d9e7 100644 --- a/higan/sfc/coprocessor/msu1/msu1.hpp +++ b/higan/sfc/coprocessor/msu1/msu1.hpp @@ -40,11 +40,11 @@ private: uint32 audioResumeTrack; uint32 audioResumeOffset; - bool audioError; - bool audioPlay; - bool audioRepeat; - bool audioBusy; - bool dataBusy; + boolean audioError; + boolean audioPlay; + boolean audioRepeat; + boolean audioBusy; + boolean dataBusy; } io; }; diff --git a/higan/sfc/coprocessor/msu1/serialization.cpp b/higan/sfc/coprocessor/msu1/serialization.cpp index 798cf6c0..d6658dff 100644 --- a/higan/sfc/coprocessor/msu1/serialization.cpp +++ b/higan/sfc/coprocessor/msu1/serialization.cpp @@ -13,11 +13,11 @@ auto MSU1::serialize(serializer& s) -> void { s.integer(io.audioResumeTrack); s.integer(io.audioResumeOffset); - s.integer(io.audioError); - s.integer(io.audioPlay); - s.integer(io.audioRepeat); - s.integer(io.audioBusy); - s.integer(io.dataBusy); + s.boolean(io.audioError); + s.boolean(io.audioPlay); + s.boolean(io.audioRepeat); + s.boolean(io.audioBusy); + s.boolean(io.dataBusy); dataOpen(); audioOpen(); diff --git a/higan/sfc/interface/interface.cpp b/higan/sfc/interface/interface.cpp index 842d1dfb..8bcbcb67 100644 --- a/higan/sfc/interface/interface.cpp +++ b/higan/sfc/interface/interface.cpp @@ -120,9 +120,9 @@ auto Interface::title() -> string { auto Interface::videoInformation() -> VideoInformation { VideoInformation vi; vi.width = 256; - vi.height = 240; + vi.height = 239; vi.internalWidth = 512; - vi.internalHeight = 480; + vi.internalHeight = 478; vi.aspectCorrection = 8.0 / 7.0; if(Region::NTSC()) vi.refreshRate = system.cpuFrequency() / (262.0 * 1364.0); if(Region::PAL()) vi.refreshRate = system.cpuFrequency() / (312.0 * 1364.0); @@ -249,12 +249,11 @@ auto Interface::get(const string& name) -> any { auto Interface::set(const string& name, const any& value) -> bool { if(name == "Blur Emulation" && value.is()) { settings.blurEmulation = value.get(); - system.configureVideoEffects(); return true; } if(name == "Color Emulation" && value.is()) { settings.colorEmulation = value.get(); - system.configureVideoPalette(); + Emulator::video.setPalette(); return true; } if(name == "Scanline Emulation" && value.is()) return settings.scanlineEmulation = value.get(), true; diff --git a/higan/sfc/ppu-fast/background.cpp b/higan/sfc/ppu-fast/background.cpp index 3ecab3fd..74f4214d 100644 --- a/higan/sfc/ppu-fast/background.cpp +++ b/higan/sfc/ppu-fast/background.cpp @@ -1,5 +1,4 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void { - if(io.displayDisable) return; if(!self.aboveEnable && !self.belowEnable) return; if(self.tileMode == TileMode::Mode7) return renderMode7(self, source); if(self.tileMode == TileMode::Inactive) return; diff --git a/higan/sfc/ppu-fast/line.cpp b/higan/sfc/ppu-fast/line.cpp index d92d3489..7c1e5b97 100644 --- a/higan/sfc/ppu-fast/line.cpp +++ b/higan/sfc/ppu-fast/line.cpp @@ -3,7 +3,7 @@ uint PPU::Line::count = 0; auto PPU::Line::flush() -> void { if(Line::count) { - #pragma omp parallel for + #pragma omp parallel for if(Line::count >= 8) for(uint y = 0; y < Line::count; y++) { ppu.lines[Line::start + y].render(); } @@ -13,15 +13,21 @@ auto PPU::Line::flush() -> void { } auto PPU::Line::render() -> void { - bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; + auto output = ppu.output + y * 1024; + if(ppu.interlace() && ppu.field()) output += 512; + auto width = !ppu.hires() ? 256 : 512; - if(!io.displayDisable) { - auto aboveColor = cgram[0]; - auto belowColor = hires ? cgram[0] : io.col.fixedColor; - for(uint x : range(256)) { - above[x] = {Source::COL, 0, aboveColor}; - below[x] = {Source::COL, 0, belowColor}; - } + if(io.displayDisable) { + memory::fill(output, width); + return; + } + + bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; + auto aboveColor = cgram[0]; + auto belowColor = hires ? cgram[0] : io.col.fixedColor; + for(uint x : range(256)) { + above[x] = {Source::COL, 0, aboveColor}; + below[x] = {Source::COL, 0, belowColor}; } renderBackground(io.bg1, Source::BG1); @@ -29,20 +35,10 @@ auto PPU::Line::render() -> void { renderBackground(io.bg3, Source::BG3); renderBackground(io.bg4, Source::BG4); renderObject(io.obj); - - auto output = ppu.output + y * 1024; - if(ppu.interlace() && ppu.field()) output += 512; - auto width = !ppu.hires() ? 256 : 512; - auto luma = io.displayBrightness << 15; - - if(io.displayDisable) { - for(uint x : range(width)) output[x] = 0; - return; - } - renderWindow(io.col.window, io.col.window.aboveMask, windowAbove); renderWindow(io.col.window, io.col.window.belowMask, windowBelow); + auto luma = io.displayBrightness << 15; if(width == 256) for(uint x : range(width)) { *output++ = luma | pixel(x, above[x], below[x]); } else if(!hires) for(uint x : range(256)) { @@ -83,10 +79,13 @@ auto PPU::Line::blend(uint x, uint y, bool halve) const -> uint15 { } } -auto PPU::Line::directColor(uint palette, uint tile) const -> uint15 { - return (palette << 7 & 0x6000) + (tile >> 0 & 0x1000) - + (palette << 4 & 0x0380) + (tile >> 5 & 0x0040) - + (palette << 2 & 0x001c) + (tile >> 9 & 0x0002); +auto PPU::Line::directColor(uint paletteIndex, uint paletteColor) const -> uint15 { + //paletteIndex = bgr + //paletteColor = BBGGGRRR + //output = 0 BBb00 GGGg0 RRRr0 + return (paletteColor << 2 & 0x001c) + (paletteIndex << 1 & 0x0002) //R + + (paletteColor << 4 & 0x0380) + (paletteIndex << 5 & 0x0040) //G + + (paletteColor << 7 & 0x6000) + (paletteIndex << 10 & 0x1000); //B } auto PPU::Line::plotAbove(uint x, uint source, uint priority, uint color) -> void { diff --git a/higan/sfc/ppu-fast/mode7.cpp b/higan/sfc/ppu-fast/mode7.cpp index 6543818f..3678e057 100644 --- a/higan/sfc/ppu-fast/mode7.cpp +++ b/higan/sfc/ppu-fast/mode7.cpp @@ -51,7 +51,7 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void { mosaicCounter = 1 + io.mosaicSize; mosaicPalette = palette; mosaicPriority = priority; - if(io.col.directColor) { + if(io.col.directColor && source == Source::BG1) { mosaicColor = directColor(0, palette); } else { mosaicColor = cgram[palette]; diff --git a/higan/sfc/ppu-fast/object.cpp b/higan/sfc/ppu-fast/object.cpp index 87ee5d04..dcd4f917 100644 --- a/higan/sfc/ppu-fast/object.cpp +++ b/higan/sfc/ppu-fast/object.cpp @@ -1,5 +1,4 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void { - if(io.displayDisable) return; if(!self.aboveEnable && !self.belowEnable) return; bool windowAbove[256]; diff --git a/higan/sfc/ppu-fast/ppu.cpp b/higan/sfc/ppu-fast/ppu.cpp index 209c9ef9..07c93065 100644 --- a/higan/sfc/ppu-fast/ppu.cpp +++ b/higan/sfc/ppu-fast/ppu.cpp @@ -44,9 +44,13 @@ auto PPU::main() -> void { scanline(); uint y = vcounter(); step(512); - if(y >= 1 && y <= vdisp()) { - memcpy(&lines[y].io, &io, sizeof(io)); - memcpy(&lines[y].cgram, &cgram, sizeof(cgram)); + if(y >= 1 && y <= 239) { + if(io.displayDisable || y >= vdisp()) { + lines[y].io.displayDisable = true; + } else { + memcpy(&lines[y].io, &io, sizeof(io)); + memcpy(&lines[y].cgram, &cgram, sizeof(cgram)); + } if(!Line::count) Line::start = y; Line::count++; } @@ -78,13 +82,12 @@ auto PPU::scanline() -> void { auto PPU::refresh() -> void { auto output = this->output; - if(!overscan()) output -= 14 * 512; + if(!overscan()) output -= 12 * 512; auto pitch = 512 << !interlace(); auto width = 256 << hires(); - auto height = 240 << interlace(); - if(!hires()) Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, false); + auto height = 239 << interlace(); + Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation && hires()); Emulator::video.refresh(output, pitch * sizeof(uint32), width, height); - if(!hires()) Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation); } auto PPU::load(Markup::Node node) -> bool { @@ -94,7 +97,7 @@ auto PPU::load(Markup::Node node) -> bool { auto PPU::power(bool reset) -> void { create(Enter, system.cpuFrequency()); PPUcounter::reset(); - memory::fill(output, 512 * 480); + memory::fill(output, 512 * 478); function uint8> reader{&PPU::readIO, this}; function void> writer{&PPU::writeIO, this}; diff --git a/higan/sfc/ppu-fast/ppu.hpp b/higan/sfc/ppu-fast/ppu.hpp index ea21277d..41b9e185 100644 --- a/higan/sfc/ppu-fast/ppu.hpp +++ b/higan/sfc/ppu-fast/ppu.hpp @@ -263,7 +263,7 @@ public: auto render() -> void; auto pixel(uint x, Pixel above, Pixel below) const -> uint15; auto blend(uint x, uint y, bool halve) const -> uint15; - alwaysinline auto directColor(uint palette, uint tile) const -> uint15; + alwaysinline auto directColor(uint paletteIndex, uint paletteColor) const -> uint15; alwaysinline auto plotAbove(uint x, uint source, uint priority, uint color) -> void; alwaysinline auto plotBelow(uint x, uint source, uint priority, uint color) -> void; diff --git a/higan/sfc/ppu/ppu.cpp b/higan/sfc/ppu/ppu.cpp index 75bc241d..12e500f6 100644 --- a/higan/sfc/ppu/ppu.cpp +++ b/higan/sfc/ppu/ppu.cpp @@ -72,11 +72,9 @@ auto PPU::main() -> void { step(14); obj.tilefetch(); - } else { - step(1052 + 14 + 136); } - step(lineclocks() - 28 - 1052 - 14 - 136); + step(lineclocks() - hcounter()); } auto PPU::load(Markup::Node node) -> bool { @@ -223,10 +221,11 @@ auto PPU::frame() -> void { auto PPU::refresh() -> void { auto output = this->output; - if(!overscan()) output -= 14 * 512; + if(!overscan()) output -= 12 * 512; auto pitch = 512; auto width = 512; - auto height = 480; + auto height = 478; + Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation); Emulator::video.refresh(output, pitch * sizeof(uint32), width, height); } diff --git a/higan/sfc/system/system.cpp b/higan/sfc/system/system.cpp index 5286bef1..8eb586d2 100644 --- a/higan/sfc/system/system.cpp +++ b/higan/sfc/system/system.cpp @@ -6,7 +6,6 @@ System system; Scheduler scheduler; Random random; Cheat cheat; -#include "video.cpp" #include "serialization.cpp" auto System::run() -> void { @@ -91,8 +90,7 @@ auto System::unload() -> void { auto System::power(bool reset) -> void { Emulator::video.reset(); Emulator::video.setInterface(interface); - configureVideoPalette(); - configureVideoEffects(); + Emulator::video.setPalette(); Emulator::audio.reset(); Emulator::audio.setInterface(interface); diff --git a/higan/sfc/system/system.hpp b/higan/sfc/system/system.hpp index 15acbf66..e07bf3b4 100644 --- a/higan/sfc/system/system.hpp +++ b/higan/sfc/system/system.hpp @@ -14,10 +14,6 @@ struct System { auto unload() -> void; auto power(bool reset) -> void; - //video.cpp - auto configureVideoPalette() -> void; - auto configureVideoEffects() -> void; - //serialization.cpp auto serialize() -> serializer; auto unserialize(serializer&) -> bool; diff --git a/higan/sfc/system/video.cpp b/higan/sfc/system/video.cpp deleted file mode 100644 index f537903b..00000000 --- a/higan/sfc/system/video.cpp +++ /dev/null @@ -1,7 +0,0 @@ -auto System::configureVideoPalette() -> void { - Emulator::video.setPalette(); -} - -auto System::configureVideoEffects() -> void { - Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation); -} diff --git a/higan/target-bsnes/GNUmakefile b/higan/target-bsnes/GNUmakefile index 8dff9969..964bbcbc 100644 --- a/higan/target-bsnes/GNUmakefile +++ b/higan/target-bsnes/GNUmakefile @@ -5,7 +5,8 @@ include sfc/GNUmakefile include gb/GNUmakefile include processor/GNUmakefile -ui_objects := ui-bsnes ui-program ui-input ui-presentation ui-settings ui-resource +ui_objects := ui-bsnes ui-program ui-input ui-presentation +ui_objects += ui-settings ui-tools ui-resource ui_objects += ruby hiro ui_objects += $(if $(call streq,$(platform),windows),ui-windows) @@ -51,6 +52,7 @@ obj/ui-program.o: $(ui)/program/program.cpp obj/ui-input.o: $(ui)/input/input.cpp obj/ui-presentation.o: $(ui)/presentation/presentation.cpp obj/ui-settings.o: $(ui)/settings/settings.cpp +obj/ui-tools.o: $(ui)/tools/tools.cpp obj/ui-resource.o: $(ui)/resource/resource.cpp obj/ui-windows.o: diff --git a/higan/target-bsnes/bsnes.hpp b/higan/target-bsnes/bsnes.hpp index ea64f4cc..68f769db 100644 --- a/higan/target-bsnes/bsnes.hpp +++ b/higan/target-bsnes/bsnes.hpp @@ -15,6 +15,7 @@ extern unique_pointer emulator; #include "input/input.hpp" #include "presentation/presentation.hpp" #include "settings/settings.hpp" +#include "tools/tools.hpp" #include "resource/resource.hpp" auto locate(string name) -> string; diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index 253d48f9..a8770d40 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -20,15 +20,18 @@ Presentation::Presentation() { }); controllerPort1.setText("Controller Port 1"); controllerPort2.setText("Controller Port 2"); + expansionPort.setText("Expansion Port"); for(auto& port : emulator->ports) { Menu* menu = nullptr; if(port.name == "Controller Port 1") menu = &controllerPort1; if(port.name == "Controller Port 2") menu = &controllerPort2; + if(port.name == "Expansion Port") menu = &expansionPort; if(!menu) continue; Group devices; for(auto& device : port.devices) { - if(device.name == "None") continue; + if(port.name != "Expansion Port" && device.name == "None") continue; + if(port.name == "Expansion Port" && device.name == "21fx") continue; MenuRadioItem item{menu}; item.setText(device.name).onActivate([=] { auto path = string{"Emulator/", port.name}.replace(" ", ""); @@ -101,14 +104,14 @@ Presentation::Presentation() { pathSettings.setText("Paths ...").onActivate([&] { settingsWindow->show(2); }); advancedSettings.setText("Advanced ...").onActivate([&] { settingsWindow->show(3); }); - toolsMenu.setText("Tools"); - saveState.setText("Save State").setEnabled(false); + toolsMenu.setText("Tools").setVisible(false); + saveState.setText("Save State"); saveState1.setText("Slot 1").onActivate([&] { program->saveState(1); }); saveState2.setText("Slot 2").onActivate([&] { program->saveState(2); }); saveState3.setText("Slot 3").onActivate([&] { program->saveState(3); }); saveState4.setText("Slot 4").onActivate([&] { program->saveState(4); }); saveState5.setText("Slot 5").onActivate([&] { program->saveState(5); }); - loadState.setText("Load State").setEnabled(false); + loadState.setText("Load State"); loadState1.setText("Slot 1").onActivate([&] { program->loadState(1); }); loadState2.setText("Slot 2").onActivate([&] { program->loadState(2); }); loadState3.setText("Slot 3").onActivate([&] { program->loadState(3); }); @@ -117,6 +120,7 @@ Presentation::Presentation() { pauseEmulation.setText("Pause Emulation").onToggle([&] { if(pauseEmulation.checked()) audio->clear(); }); + cheatEditor.setText("Cheat Editor ...").onActivate([&] { toolsWindow->show(0); }); helpMenu.setText("Help"); about.setText("About ...").onActivate([&] { @@ -212,8 +216,8 @@ auto Presentation::resizeViewport() -> void { return clearViewport(); } - double width = 224 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0); - double height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0); + uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0); + uint height = (settings["View/OverscanCropping"].boolean() ? 223.0 : 239.0); if(settings["View/IntegralScaling"].boolean()) { uint widthMultiplier = windowWidth / width; @@ -226,8 +230,8 @@ auto Presentation::resizeViewport() -> void { viewportWidth, viewportHeight }); } else { - double widthMultiplier = windowWidth / width; - double heightMultiplier = windowHeight / height; + double widthMultiplier = (double)windowWidth / width; + double heightMultiplier = (double)windowHeight / height; double multiplier = min(widthMultiplier, heightMultiplier); uint viewportWidth = width * multiplier; uint viewportHeight = height * multiplier; @@ -241,8 +245,8 @@ auto Presentation::resizeViewport() -> void { } auto Presentation::resizeWindow() -> void { - double width = 224 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0); - double height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0); + uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0); + uint height = (settings["View/OverscanCropping"].boolean() ? 223.0 : 239.0); uint multiplier = 2; if(settings["View/Size"].text() == "Small" ) multiplier = 2; diff --git a/higan/target-bsnes/presentation/presentation.hpp b/higan/target-bsnes/presentation/presentation.hpp index 8c3c1349..7fe2d9eb 100644 --- a/higan/target-bsnes/presentation/presentation.hpp +++ b/higan/target-bsnes/presentation/presentation.hpp @@ -29,6 +29,7 @@ struct Presentation : Window { MenuSeparator portSeparator{&systemMenu}; Menu controllerPort1{&systemMenu}; Menu controllerPort2{&systemMenu}; + Menu expansionPort{&systemMenu}; MenuSeparator quitSeparator{&systemMenu}; MenuItem quit{&systemMenu}; Menu settingsMenu{&menuBar}; @@ -64,6 +65,8 @@ struct Presentation : Window { MenuItem loadState4{&loadState}; MenuItem loadState5{&loadState}; MenuCheckItem pauseEmulation{&toolsMenu}; + MenuSeparator toolsSeparator{&toolsMenu}; + MenuItem cheatEditor{&toolsMenu}; Menu helpMenu{&menuBar}; MenuItem about{&helpMenu}; diff --git a/higan/target-bsnes/program/game.cpp b/higan/target-bsnes/program/game.cpp index d40cc3d8..6238c57c 100644 --- a/higan/target-bsnes/program/game.cpp +++ b/higan/target-bsnes/program/game.cpp @@ -12,9 +12,10 @@ auto Program::load() -> void { presentation->setTitle(emulator->title()); presentation->resetSystem.setEnabled(true); presentation->unloadGame.setEnabled(true); - presentation->saveState.setEnabled(true); - presentation->loadState.setEnabled(true); + presentation->toolsMenu.setVisible(true); + presentation->pauseEmulation.setChecked(false); presentation->resizeViewport(); + toolsWindow->cheatEditor.loadCheats(); string locations = superNintendo.location; if(auto location = gameBoy.location) locations.append("|", location); @@ -93,6 +94,8 @@ auto Program::save() -> void { auto Program::unload() -> void { if(!emulator->loaded()) return; + toolsWindow->cheatEditor.saveCheats(); + toolsWindow->setVisible(false); emulator->unload(); superNintendo = {}; gameBoy = {}; @@ -102,7 +105,6 @@ auto Program::unload() -> void { presentation->setTitle({"bsnes v", Emulator::Version}); presentation->resetSystem.setEnabled(false); presentation->unloadGame.setEnabled(false); - presentation->saveState.setEnabled(false); - presentation->loadState.setEnabled(false); + presentation->toolsMenu.setVisible(false); presentation->clearViewport(); } diff --git a/higan/target-bsnes/program/interface.cpp b/higan/target-bsnes/program/interface.cpp index b1525329..11472cf1 100644 --- a/higan/target-bsnes/program/interface.cpp +++ b/higan/target-bsnes/program/interface.cpp @@ -146,6 +146,15 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); } + if(id == 1 && name == "msu1/data.rom") { + return vfs::fs::file::open({Location::notsuffix(superNintendo.location), ".msu"}, mode); + } + + if(id == 1 && name.match("msu1/track-*.pcm")) { + name.trimLeft("msu1/track-", 1L); + return vfs::fs::file::open({Location::notsuffix(superNintendo.location), name}, mode); + } + //Game Boy if(id == 2 && name == "manifest.bml" && mode == vfs::file::mode::read) { @@ -211,8 +220,8 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig pitch >>= 2; if(presentation->overscanCropping.checked()) { - if(height == 240) data += 8 * pitch, height -= 16; - if(height == 480) data += 16 * pitch, height -= 32; + if(height == 239) data += 8 * pitch, height -= 16; + if(height == 478) data += 16 * pitch, height -= 32; } if(video->lock(output, length, width, height)) { diff --git a/higan/target-bsnes/program/program.cpp b/higan/target-bsnes/program/program.cpp index d3d66953..51a83f3b 100644 --- a/higan/target-bsnes/program/program.cpp +++ b/higan/target-bsnes/program/program.cpp @@ -56,6 +56,8 @@ Program::Program(string_vector arguments) { new InputManager; new SettingsWindow; + new CheatDatabase; + new ToolsWindow; new AboutWindow; arguments.takeLeft(); //ignore program location in argument parsing diff --git a/higan/target-bsnes/tools/cheat-database.cpp b/higan/target-bsnes/tools/cheat-database.cpp new file mode 100644 index 00000000..7cd745fd --- /dev/null +++ b/higan/target-bsnes/tools/cheat-database.cpp @@ -0,0 +1,57 @@ +CheatDatabase::CheatDatabase() { + cheatDatabase = this; + + layout.setMargin(5); + selectAllButton.setText("Select All").onActivate([&] { + for(auto item : cheatList.items()) item.setChecked(true); + }); + unselectAllButton.setText("Unselect All").onActivate([&] { + for(auto item : cheatList.items()) item.setChecked(false); + }); + addCheatsButton.setText("Add Cheats").onActivate([&] { + addCheats(); + }); + + setSize({800, 400}); + setAlignment({0.5, 1.0}); + setDismissable(); +} + +auto CheatDatabase::findCheats() -> void { + auto sha256 = emulator->sha256(); + + auto document = BML::unserialize(string::read(locate("cheats.bml"))); + for(auto game : document.find("cartridge")) { + if(game["sha256"].text() != sha256) continue; + + cheatList.reset(); + for(auto cheat : game.find("cheat")) { + cheatList.append(ListViewItem() + .setCheckable() + .setText(cheat["description"].text()) + .setProperty("code", cheat["code"].text()) + ); + } + + setTitle(game["name"].text()); + setVisible(); + return; + } + + MessageDialog().setParent(*toolsWindow).setText("Sorry, no cheats were found for this game.").information(); +} + +auto CheatDatabase::addCheats() -> void { + for(auto item : cheatList.items()) { + if(!item.checked()) continue; + + string code = item.property("code").replace("/", "=", 1L).replace("/", "?", 1L); + string description = item.text(); + if(!toolsWindow->cheatEditor.addCode(false, code, description)) { + MessageDialog().setParent(*this).setText("Free slots exhausted. Not all cheats could be added.").warning(); + break; + } + } + setVisible(false); + toolsWindow->cheatEditor.doRefresh(); +} diff --git a/higan/target-bsnes/tools/cheat-editor.cpp b/higan/target-bsnes/tools/cheat-editor.cpp new file mode 100644 index 00000000..d35ca085 --- /dev/null +++ b/higan/target-bsnes/tools/cheat-editor.cpp @@ -0,0 +1,146 @@ +CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Edit::Replace); + setText("Cheat Editor"); + + layout.setMargin(5); + cheatList.append(TableViewHeader().setVisible() + .append(TableViewColumn()) + .append(TableViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setAlignment(1.0)) + .append(TableViewColumn().setText("Code(s)")) + .append(TableViewColumn().setText("Description").setExpandable()) + ); + for(uint slot : range(Slots)) { + cheatList.append(TableViewItem() + .append(TableViewCell().setCheckable()) + .append(TableViewCell().setText(1 + slot)) + .append(TableViewCell()) + .append(TableViewCell()) + ); + } + cheatList.onChange([&] { doChangeSelected(); }); + cheatList.onToggle([&](auto cell) { + cheats[cell.parent().offset()].enabled = cell.checked(); + this->synchronizeCodes(); + }); + codeLabel.setText("Code(s):"); + codeValue.setEnabled(false).onChange([&] { doModify(); }); + descriptionLabel.setText("Description:"); + descriptionValue.setEnabled(false).onChange([&] { doModify(); }); + findCodesButton.setText("Find Codes ...").onActivate([&] { cheatDatabase->findCheats(); }); + 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 { + if(auto item = cheatList.selected()) { + auto& cheat = cheats[item.offset()]; + codeValue.setEnabled(true).setText(cheat.code); + descriptionValue.setEnabled(true).setText(cheat.description); + eraseButton.setEnabled(true); + } else { + codeValue.setEnabled(false).setText(""); + descriptionValue.setEnabled(false).setText(""); + eraseButton.setEnabled(false); + } +} + +auto CheatEditor::doModify() -> void { + if(auto item = cheatList.selected()) { + auto& cheat = cheats[item.offset()]; + cheat.code = codeValue.text(); + cheat.description = descriptionValue.text(); + doRefresh(); + synchronizeCodes(); + } +} + +auto CheatEditor::doRefresh() -> void { + for(uint slot : range(Slots)) { + auto& cheat = cheats[slot]; + if(cheat.code || cheat.description) { + auto codes = cheat.code.split("+"); + if(codes.size() > 1) codes[0].append("+..."); + cheatList.item(slot).cell(0).setChecked(cheat.enabled); + cheatList.item(slot).cell(2).setText(codes[0]); + cheatList.item(slot).cell(3).setText(cheat.description).setForegroundColor({0, 0, 0}); + } else { + cheatList.item(slot).cell(0).setChecked(false); + cheatList.item(slot).cell(2).setText(""); + cheatList.item(slot).cell(3).setText("").setForegroundColor({128, 128, 128}); + } + } + cheatList.resizeColumns(); +} + +auto CheatEditor::doReset(bool force) -> void { + if(force || MessageDialog().setParent(*toolsWindow).setText("Permamently erase all cheats?").question() == "Yes") { + for(auto& cheat : cheats) cheat = {}; + for(auto& item : cheatList.items()) item.cell(0).setChecked(false); + doChangeSelected(); + doRefresh(); + synchronizeCodes(); + } +} + +auto CheatEditor::doErase() -> void { + if(auto item = cheatList.selected()) { + cheats[item.offset()] = {}; + codeValue.setText(""); + descriptionValue.setText(""); + doRefresh(); + synchronizeCodes(); + } +} + +auto CheatEditor::synchronizeCodes() -> void { + string_vector codes; + for(auto& cheat : cheats) { + if(!cheat.enabled || !cheat.code) continue; + codes.append(cheat.code); + } + emulator->cheatSet(codes); +} + +auto CheatEditor::addCode(bool enabled, string code, string description) -> bool { + for(auto& cheat : cheats) { + if(cheat.code || cheat.description) continue; + cheat.enabled = enabled; + cheat.code = code; + cheat.description = description; + return true; + } + return false; +} + +auto CheatEditor::loadCheats() -> void { + doReset(true); + auto location = program->path("Cheats", program->superNintendo.location, ".cht"); + auto document = BML::unserialize(string::read(location)); + for(auto cheat : document.find("cheat")) { + if(!addCode((bool)cheat["enabled"], cheat["code"].text(), cheat["description"].text())) break; + } + doRefresh(); + synchronizeCodes(); +} + +auto CheatEditor::saveCheats() -> void { + string document; + for(auto& cheat : cheats) { + if(!cheat.code && !cheat.description) continue; + document.append("cheat\n"); + document.append(" description: ", cheat.description, "\n"); + document.append(" code: ", cheat.code, "\n"); + if(cheat.enabled) + document.append(" enabled\n"); + document.append("\n"); + } + auto location = program->path("Cheats", program->superNintendo.location, ".cht"); + if(document) { + file::write(location, document); + } else { + file::remove(location); + } +} diff --git a/higan/target-bsnes/tools/tools.cpp b/higan/target-bsnes/tools/tools.cpp new file mode 100644 index 00000000..70fc7ab1 --- /dev/null +++ b/higan/target-bsnes/tools/tools.cpp @@ -0,0 +1,31 @@ +#include "../bsnes.hpp" +#include "cheat-database.cpp" +#include "cheat-editor.cpp" +unique_pointer cheatDatabase; +unique_pointer toolsWindow; + +ToolsWindow::ToolsWindow() { + toolsWindow = this; + + layout.setMargin(5); + + setTitle("Tools"); + setSize({600, 400}); + setAlignment({1.0, 1.0}); + setDismissable(); + + onSize([&] { + cheatEditor.cheatList.resizeColumns(); + }); +} + +auto ToolsWindow::setVisible(bool visible) -> ToolsWindow& { + return Window::setVisible(visible), *this; +} + +auto ToolsWindow::show(uint index) -> void { + panel.item(index)->setSelected(); + setVisible(); + setFocused(); + doSize(); +} diff --git a/higan/target-bsnes/tools/tools.hpp b/higan/target-bsnes/tools/tools.hpp new file mode 100644 index 00000000..f252b71d --- /dev/null +++ b/higan/target-bsnes/tools/tools.hpp @@ -0,0 +1,65 @@ +struct CheatDatabase : Window { + CheatDatabase(); + auto findCheats() -> void; + auto addCheats() -> void; + +public: + VerticalLayout layout{this}; + ListView cheatList{&layout, Size{~0, ~0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Button selectAllButton{&controlLayout, Size{100, 0}}; + Button unselectAllButton{&controlLayout, Size{100, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button addCheatsButton{&controlLayout, Size{100, 0}}; +}; + +struct CheatEditor : TabFrameItem { + enum : uint { Slots = 128 }; + + CheatEditor(TabFrame*); + auto doChangeSelected() -> void; + auto doModify() -> void; + auto doRefresh() -> void; + auto doReset(bool force = false) -> void; + auto doErase() -> void; + auto synchronizeCodes() -> void; + auto addCode(bool enabled, string code, string description) -> bool; + auto loadCheats() -> void; + auto saveCheats() -> void; + +public: + struct Cheat { + bool enabled = false; + string code; + string description; + }; + Cheat cheats[Slots]; + + VerticalLayout layout{this}; + TableView cheatList{&layout, Size{~0, ~0}}; + HorizontalLayout codeLayout{&layout, Size{~0, 0}}; + Label codeLabel{&codeLayout, Size{70, 0}}; + LineEdit codeValue{&codeLayout, Size{~0, 0}}; + HorizontalLayout descriptionLayout{&layout, Size{~0, 0}}; + Label descriptionLabel{&descriptionLayout, Size{70, 0}}; + LineEdit descriptionValue{&descriptionLayout, Size{~0, 0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Button findCodesButton{&controlLayout, Size{120, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button resetButton{&controlLayout, Size{80, 0}}; + Button eraseButton{&controlLayout, Size{80, 0}}; +}; + +struct ToolsWindow : Window { + ToolsWindow(); + auto setVisible(bool visible = true) -> ToolsWindow&; + auto show(uint index) -> void; + +public: + VerticalLayout layout{this}; + TabFrame panel{&layout, Size{~0, ~0}}; + CheatEditor cheatEditor{&panel}; +}; + +extern unique_pointer cheatDatabase; +extern unique_pointer toolsWindow; diff --git a/higan/target-higan/tools/cheat-editor.cpp b/higan/target-higan/tools/cheat-editor.cpp index 89fdad55..c10a4de2 100644 --- a/higan/target-higan/tools/cheat-editor.cpp +++ b/higan/target-higan/tools/cheat-editor.cpp @@ -18,9 +18,9 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { ); } cheatList.onChange([&] { doChangeSelected(); }); - cheatList.onToggle([&](TableViewCell cell) { + cheatList.onToggle([&](auto cell) { cheats[cell.parent().offset()].enabled = cell.checked(); - synchronizeCodes(); + this->synchronizeCodes(); }); codeLabel.setText("Code(s):"); codeValue.onChange([&] { doModify(); }); @@ -69,7 +69,7 @@ auto CheatEditor::doRefresh() -> void { } else { cheatList.item(slot).cell(0).setChecked(false); cheatList.item(slot).cell(2).setText(""); - cheatList.item(slot).cell(3).setText("(empty)").setForegroundColor({128, 128, 128}); + cheatList.item(slot).cell(3).setText("").setForegroundColor({128, 128, 128}); } } @@ -77,15 +77,9 @@ auto CheatEditor::doRefresh() -> void { } auto CheatEditor::doReset(bool force) -> void { - if(force || MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == "Yes") { - for(auto& cheat : cheats) { - cheat.enabled = false; - cheat.code = ""; - cheat.description = ""; - } - for(auto& item : cheatList.items()) { - item.cell(0).setChecked(false); - } + if(force || MessageDialog().setParent(*toolsManager).setText("Permanently erase all cheats?").question() == "Yes") { + for(auto& cheat : cheats) cheat = {}; + for(auto& item : cheatList.items()) item.cell(0).setChecked(false); doChangeSelected(); doRefresh(); synchronizeCodes(); @@ -95,9 +89,7 @@ auto CheatEditor::doReset(bool force) -> void { auto CheatEditor::doErase() -> void { if(auto item = cheatList.selected()) { auto& cheat = cheats[item.offset()]; - cheat.enabled = false; - cheat.code = ""; - cheat.description = ""; + cheats[item.offset()] = {}; codeValue.setText(""); descriptionValue.setText(""); doRefresh(); diff --git a/higan/target-higan/tools/state-manager.cpp b/higan/target-higan/tools/state-manager.cpp index 4e7c86d8..97a940f6 100644 --- a/higan/target-higan/tools/state-manager.cpp +++ b/higan/target-higan/tools/state-manager.cpp @@ -70,7 +70,7 @@ auto StateManager::doRefresh() -> void { description.resize(description.length()); stateList.item(slot).cell(1).setText(description).setForegroundColor({0, 0, 0}); } else { - stateList.item(slot).cell(1).setText("(empty)").setForegroundColor({128, 128, 128}); + stateList.item(slot).cell(1).setText("").setForegroundColor({128, 128, 128}); } } } @@ -105,7 +105,7 @@ auto StateManager::doSave() -> void { } auto StateManager::doReset() -> void { - if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == "Yes") { + if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all states?").question() == "Yes") { for(auto slot : range(Slots)) file::remove(program->stateName(1 + slot, true)); doRefresh(); doUpdateControls(); diff --git a/ruby/input/keyboard/quartz.cpp b/ruby/input/keyboard/quartz.cpp index 1454fffe..3fa44579 100644 --- a/ruby/input/keyboard/quartz.cpp +++ b/ruby/input/keyboard/quartz.cpp @@ -144,7 +144,7 @@ struct InputKeyboardQuartz { hid->setVendorID(HID::Keyboard::GenericVendorID); hid->setProductID(HID::Keyboard::GenericProductID); - hid->setPath(0); + hid->setPathID(0); for(auto& key : keys) { hid->buttons().append(key.name); }