From 77ac5f9e88fb06ac2f171f51e5e7b40076fadd6b Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Sun, 3 Jun 2018 23:14:42 +1000 Subject: [PATCH] Update to v106r35 release. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit byuu says: Changelog: - sfc/ppu-fast: fixed overscan crash - sfc/ppu-fast: fixed direct color mode - sfc: reconnected MSU1 support - higan: game.sfc/msu1/data.rom, game.sfc/msu1/track-#.pcm - bsnes: game.msu, game-#.pcm - bsnes: added cheat code editor - bsnes: added cheat code database support - sfc/ppu-fast: clear overscan lines when overscan disabled - sfc: output 223/239 lines instead of 224/240 lines - bsnes: fix aspect correction calculation - bsnes: crop line 224 when overscan masking is enabled - bsnes: exposed Expansion Port menu; but hid “21fx” from the list of devices - bsnes: tools menu is hidden until a game is loaded - ruby/input/keyboard/quartz: fixed compilation error So only bsnes the automated overscan cropping option. In higan, you can crop however many lines you like from the top or bottom of the image. But for bsnes, it automatically eats sixteen lines. My view right now is that if bsnes is meant to be the casual gaming emulator, that it should eat line 224 in this mode. Most games show content here, but because of the way the SNES PPU works, the very last line ends up on its very own tile row (line 0 isn't rendered), if the scroll registers don't account for it. There's a small number of games that will draw junk data to the very last scanline of the frame as a result of this. So I chose, at least for now, to hide it. Users can obviously disable overscan cropping to see this scanline. I'm open to being convinced not to do this, if someone has a compelling reason. We're pretty much screwed one way or the other with no overscan masking. If we output 239 lines, then most games will render 7 blank lines + 224 drawn lines + 8 blank lines, and the black top and bottom aren't centered. But if we output 240 lines to get 8 + 224 + 8, then games that do use overscan will have a blank line at the very bottom of the window. I'm also trying out a modified cheat code file format. It's been forever since I bothered to look at it, and the “cartridge” parent node doesn't match what I'm doing with trying to rename “cartridge” to “game” in manifests. And indeed, the idea of requiring a root node is rather superfluous for a cheat code file. Current format looks like this: cheat description: foo code: 7e2000=20+7e2001=30?40 enabled cheat description: bar code: 7e4000=80 Open to discussing this, and I'd like to sync up with Snes9X before they push out a new release, and I'll agree to finalize and never change this format again. I chose to use .cht for the extension when using game files (eg gamename.cht) --- higan/emulator/emulator.hpp | 2 +- higan/sfc/cartridge/cartridge.hpp | 2 +- higan/sfc/cartridge/load.cpp | 11 +- higan/sfc/coprocessor/msu1/msu1.cpp | 16 +- higan/sfc/coprocessor/msu1/msu1.hpp | 10 +- higan/sfc/coprocessor/msu1/serialization.cpp | 10 +- higan/sfc/interface/interface.cpp | 7 +- higan/sfc/ppu-fast/background.cpp | 1 - higan/sfc/ppu-fast/line.cpp | 47 +++--- higan/sfc/ppu-fast/mode7.cpp | 2 +- higan/sfc/ppu-fast/object.cpp | 1 - higan/sfc/ppu-fast/ppu.cpp | 19 ++- higan/sfc/ppu-fast/ppu.hpp | 2 +- higan/sfc/ppu/ppu.cpp | 9 +- higan/sfc/system/system.cpp | 4 +- higan/sfc/system/system.hpp | 4 - higan/sfc/system/video.cpp | 7 - higan/target-bsnes/GNUmakefile | 4 +- higan/target-bsnes/bsnes.hpp | 1 + .../presentation/presentation.cpp | 24 +-- .../presentation/presentation.hpp | 3 + higan/target-bsnes/program/game.cpp | 10 +- higan/target-bsnes/program/interface.cpp | 13 +- higan/target-bsnes/program/program.cpp | 2 + higan/target-bsnes/tools/cheat-database.cpp | 57 +++++++ higan/target-bsnes/tools/cheat-editor.cpp | 146 ++++++++++++++++++ higan/target-bsnes/tools/tools.cpp | 31 ++++ higan/target-bsnes/tools/tools.hpp | 65 ++++++++ higan/target-higan/tools/cheat-editor.cpp | 22 +-- higan/target-higan/tools/state-manager.cpp | 4 +- ruby/input/keyboard/quartz.cpp | 2 +- 31 files changed, 414 insertions(+), 124 deletions(-) delete mode 100644 higan/sfc/system/video.cpp create mode 100644 higan/target-bsnes/tools/cheat-database.cpp create mode 100644 higan/target-bsnes/tools/cheat-editor.cpp create mode 100644 higan/target-bsnes/tools/tools.cpp create mode 100644 higan/target-bsnes/tools/tools.hpp 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); }