From 372e9ef42be32c2644366d6d10bec3c2c1922b9c Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Mon, 2 Jul 2018 11:55:42 +1000 Subject: [PATCH] Update to v106r45 release. byuu says: Changelog: - sfc/ppu-fast: added hires mode 7 option (doubles the sampling rate of mode 7 pixels to reduce aliasing) - sfc/ppu-fast: fixed mode 7 horizontal screen flip [hex_usr] - bsnes: added capture screenshot function and path selection - for now, it saves as BMP. I need a deflate implementation that won't add an external dependency for PNG - the output resolution is from the emulator: (256 or 512)x(240 or 480 minus overscan cropping if enabled) - it captures the NEXT output frame, not the current one ... but it may be wise to change this behavior - it'd be a problem if the core were to exit and an image was captured halfway through frame rendering - bsnes: recovery state renamed to undo state - bsnes: added manifest viewer tool - bsnes: mention if game has been verified or not on the status bar message at load time - bsnes, nall: fixed a few missing function return values [SuperMikeMan] - bsnes: guard more strongly against failure to load games to avoid crashes - hiro, ruby: various fixes for macOS [Sintendo] - hiro/Windows: paint on `WM_ERASEBKGND` to prevent status bar flickering at startup - icarus: SPC7110 heuristics fixes [hex_usr] Errata: - sfc/ppu-fast: remove debug hires mode7 force disable comment from PPU::power() [The `WM_ERASEBKGND` fix was already present in the 106r44 public beta -Ed.] --- higan/emulator/emulator.hpp | 2 +- higan/sfc/interface/interface.cpp | 12 +- higan/sfc/interface/interface.hpp | 1 + higan/sfc/ppu-fast/line.cpp | 9 +- higan/sfc/ppu-fast/mode7.cpp | 104 ++++++++++++------ higan/sfc/ppu-fast/ppu.cpp | 2 + higan/sfc/ppu-fast/ppu.hpp | 4 +- higan/target-bsnes/input/hotkeys.cpp | 4 + .../presentation/presentation.cpp | 7 +- .../presentation/presentation.hpp | 2 + higan/target-bsnes/program/game.cpp | 33 ++++-- higan/target-bsnes/program/hacks.cpp | 2 + higan/target-bsnes/program/interface.cpp | 35 ++++-- higan/target-bsnes/program/paths.cpp | 22 ++++ higan/target-bsnes/program/program.cpp | 5 +- higan/target-bsnes/program/program.hpp | 15 ++- higan/target-bsnes/program/states.cpp | 7 +- higan/target-bsnes/program/utility.cpp | 7 ++ higan/target-bsnes/settings/advanced.cpp | 16 ++- higan/target-bsnes/settings/audio.cpp | 6 +- higan/target-bsnes/settings/input.cpp | 8 +- higan/target-bsnes/settings/paths.cpp | 33 +++++- higan/target-bsnes/settings/settings.cpp | 2 + higan/target-bsnes/settings/settings.hpp | 16 ++- higan/target-bsnes/settings/video.cpp | 6 +- higan/target-bsnes/tools/manifest-viewer.cpp | 20 ++++ higan/target-bsnes/tools/tools.cpp | 1 + higan/target-bsnes/tools/tools.hpp | 13 +++ hiro/cocoa/header.hpp | 8 +- hiro/cocoa/window.cpp | 4 +- hiro/cocoa/window.hpp | 2 +- icarus/heuristics/super-famicom.cpp | 9 +- nall/encode/bmp.hpp | 17 +-- nall/macos/guard.hpp | 15 +++ nall/serializer.hpp | 1 + ruby/ruby.cpp | 6 +- 36 files changed, 334 insertions(+), 122 deletions(-) create mode 100644 higan/target-bsnes/tools/manifest-viewer.cpp create mode 100644 nall/macos/guard.hpp diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 849ffffc..7b8e1680 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -13,7 +13,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.44"; + static const string Version = "106.45"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/sfc/interface/interface.cpp b/higan/sfc/interface/interface.cpp index 7be42763..98b5236a 100644 --- a/higan/sfc/interface/interface.cpp +++ b/higan/sfc/interface/interface.cpp @@ -235,6 +235,7 @@ auto Interface::cheatSet(const string_vector& list) -> void { auto Interface::cap(const string& name) -> bool { if(name == "Fast PPU") return true; if(name == "Fast PPU/No Sprite Limit") return true; + if(name == "Fast PPU/Hires Mode 7") return true; if(name == "Fast DSP") return true; if(name == "Mode") return true; if(name == "Blur Emulation") return true; @@ -244,14 +245,9 @@ auto Interface::cap(const string& name) -> bool { } auto Interface::get(const string& name) -> any { - if(name == "Mode") return string{ - system.fastPPU() && system.fastDSP() ? "[Fast PPU+DSP] " - : system.fastPPU() ? "[Fast PPU] " - : system.fastDSP() ? "[Fast DSP] " - : "" - }; if(name == "Fast PPU") return settings.fastPPU; if(name == "Fast PPU/No Sprite Limit") return settings.fastPPUNoSpriteLimit; + if(name == "Fast PPU/Hires Mode 7") return settings.fastPPUHiresMode7; if(name == "Fast DSP") return settings.fastDSP; if(name == "Blur Emulation") return settings.blurEmulation; if(name == "Color Emulation") return settings.colorEmulation; @@ -268,6 +264,10 @@ auto Interface::set(const string& name, const any& value) -> bool { settings.fastPPUNoSpriteLimit = value.get(); return true; } + if(name == "Fast PPU/Hires Mode 7" && value.is()) { + settings.fastPPUHiresMode7 = value.get(); + return true; + } if(name == "Fast DSP" && value.is()) { settings.fastDSP = value.get(); return true; diff --git a/higan/sfc/interface/interface.hpp b/higan/sfc/interface/interface.hpp index bcc44abc..6a56921e 100644 --- a/higan/sfc/interface/interface.hpp +++ b/higan/sfc/interface/interface.hpp @@ -69,6 +69,7 @@ struct Interface : Emulator::Interface { struct Settings { bool fastPPU = false; bool fastPPUNoSpriteLimit = false; + bool fastPPUHiresMode7 = false; bool fastDSP = false; bool blurEmulation = true; diff --git a/higan/sfc/ppu-fast/line.cpp b/higan/sfc/ppu-fast/line.cpp index 7c1e5b97..ad5488d1 100644 --- a/higan/sfc/ppu-fast/line.cpp +++ b/higan/sfc/ppu-fast/line.cpp @@ -23,9 +23,10 @@ auto PPU::Line::render() -> void { } bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; + bool hiresMode7 = io.bgMode == 7 && settings.fastPPUHiresMode7; auto aboveColor = cgram[0]; auto belowColor = hires ? cgram[0] : io.col.fixedColor; - for(uint x : range(256)) { + for(uint x : range(256 << hiresMode7)) { above[x] = {Source::COL, 0, aboveColor}; below[x] = {Source::COL, 0, belowColor}; } @@ -39,7 +40,11 @@ auto PPU::Line::render() -> void { renderWindow(io.col.window, io.col.window.belowMask, windowBelow); auto luma = io.displayBrightness << 15; - if(width == 256) for(uint x : range(width)) { + if(hiresMode7) for(uint x : range(512)) { + auto Above = above[x >> 1].source >= Source::OBJ1 ? above[x >> 1] : above[x >> 1 | (x & 1 ? 256 : 0)]; + auto Below = below[x >> 1].source >= Source::OBJ1 ? below[x >> 1] : below[x >> 1 | (x & 1 ? 256 : 0)]; + *output++ = luma | pixel(x >> 1, Above, Below); + } else if(width == 256) for(uint x : range(256)) { *output++ = luma | pixel(x, above[x], below[x]); } else if(!hires) for(uint x : range(256)) { auto color = luma | pixel(x, above[x], below[x]); diff --git a/higan/sfc/ppu-fast/mode7.cpp b/higan/sfc/ppu-fast/mode7.cpp index 48749338..80f35794 100644 --- a/higan/sfc/ppu-fast/mode7.cpp +++ b/higan/sfc/ppu-fast/mode7.cpp @@ -25,41 +25,79 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void { renderWindow(self.window, self.window.aboveEnable, windowAbove); renderWindow(self.window, self.window.belowEnable, windowBelow); - for(int X : range(256)) { - int x = !io.mode7.hflip ? X : 255 - X; - int pixelX = originX + a * x >> 8; - int pixelY = originY + c * x >> 8; - int tileX = pixelX >> 3 & 127; - int tileY = pixelY >> 3 & 127; - bool outOfBounds = (pixelX | pixelY) & ~1023; - uint15 tileAddress = tileY * 128 + tileX; - uint15 paletteAddress = ((pixelY & 7) << 3) + (pixelX & 7); - uint8 tile = ppu.vram[tileAddress].byte(0); - if(io.mode7.repeat == 3 && outOfBounds) tile = 0; - uint8 palette = ppu.vram[paletteAddress + (tile << 6)].byte(1); - if(io.mode7.repeat == 2 && outOfBounds) palette = 0; + if(!settings.fastPPUHiresMode7) { + for(int X : range(256)) { + int x = !io.mode7.hflip ? X : 255 - X; + int pixelX = originX + a * x >> 8; + int pixelY = originY + c * x >> 8; + int tileX = pixelX >> 3 & 127; + int tileY = pixelY >> 3 & 127; + bool outOfBounds = (pixelX | pixelY) & ~1023; + uint15 tileAddress = tileY * 128 + tileX; + uint15 paletteAddress = ((pixelY & 7) << 3) + (pixelX & 7); + uint8 tile = io.mode7.repeat == 3 && outOfBounds ? 0 : ppu.vram[tileAddress].byte(0); + uint8 palette = io.mode7.repeat == 2 && outOfBounds ? 0 : ppu.vram[paletteAddress + (tile << 6)].byte(1); - uint priority; - if(source == Source::BG1) { - priority = self.priority[0]; - } else if(source == Source::BG2) { - priority = self.priority[palette >> 7]; - palette &= 0x7f; - } - - if(!self.mosaicEnable || --mosaicCounter == 0) { - mosaicCounter = 1 + io.mosaicSize; - mosaicPalette = palette; - mosaicPriority = priority; - if(io.col.directColor && source == Source::BG1) { - mosaicColor = directColor(0, palette); - } else { - mosaicColor = cgram[palette]; + uint priority; + if(source == Source::BG1) { + priority = self.priority[0]; + } else if(source == Source::BG2) { + priority = self.priority[palette >> 7]; + palette &= 0x7f; } - } - if(!mosaicPalette) continue; - if(self.aboveEnable && !windowAbove[x]) plotAbove(x, source, mosaicPriority, mosaicColor); - if(self.belowEnable && !windowBelow[x]) plotBelow(x, source, mosaicPriority, mosaicColor); + if(!self.mosaicEnable || --mosaicCounter == 0) { + mosaicCounter = 1 + io.mosaicSize; + mosaicPalette = palette; + mosaicPriority = priority; + if(io.col.directColor && source == Source::BG1) { + mosaicColor = directColor(0, palette); + } else { + mosaicColor = cgram[palette]; + } + } + if(!mosaicPalette) continue; + + if(self.aboveEnable && !windowAbove[X]) plotAbove(X, source, mosaicPriority, mosaicColor); + if(self.belowEnable && !windowBelow[X]) plotBelow(X, source, mosaicPriority, mosaicColor); + } + } else { + //emulator enhancement option: render 512 pixels instead of 256 pixels of horizontal resolution + //note: this mode is impossible on real hardware due to needing 512 above+below pixels + for(int X : range(512)) { + int x = !io.mode7.hflip ? X : 511 - X; + int pixelX = 2 * originX + a * x >> 9; + int pixelY = 2 * originY + c * x >> 9; + int tileX = pixelX >> 3 & 127; + int tileY = pixelY >> 3 & 127; + bool outOfBounds = (pixelX | pixelY) & ~1023; + uint15 tileAddress = tileY * 128 + tileX; + uint15 paletteAddress = ((pixelY & 7) << 3) + (pixelX & 7); + uint8 tile = io.mode7.repeat == 3 && outOfBounds ? 0 : ppu.vram[tileAddress].byte(0); + uint8 palette = io.mode7.repeat == 2 && outOfBounds ? 0 : ppu.vram[paletteAddress + (tile << 6)].byte(1); + + uint priority; + if(source == Source::BG1) { + priority = self.priority[0]; + } else if(source == Source::BG2) { + priority = self.priority[palette >> 7]; + palette &= 0x7f; + } + + if(!self.mosaicEnable || !io.mosaicSize || --mosaicCounter == 0) { + mosaicCounter = (1 + io.mosaicSize) << 1; + mosaicPalette = palette; + mosaicPriority = priority; + if(io.col.directColor && source == Source::BG1) { + mosaicColor = directColor(0, palette); + } else { + mosaicColor = cgram[palette]; + } + } + if(!mosaicPalette) continue; + + if(self.aboveEnable && !windowAbove[X >> 1]) plotAbove(X >> 1 | (X & 1 ? 256 : 0), source, mosaicPriority, mosaicColor); + if(self.belowEnable && !windowBelow[X >> 1]) plotBelow(X >> 1 | (X & 1 ? 256 : 0), source, mosaicPriority, mosaicColor); + } } } diff --git a/higan/sfc/ppu-fast/ppu.cpp b/higan/sfc/ppu-fast/ppu.cpp index 8c190649..41f23a7f 100644 --- a/higan/sfc/ppu-fast/ppu.cpp +++ b/higan/sfc/ppu-fast/ppu.cpp @@ -76,6 +76,7 @@ auto PPU::scanline() -> void { if(vcounter() > 0 && vcounter() < vdisp()) { latch.hires |= io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; + latch.hires |= io.bgMode == 7 && settings.fastPPUHiresMode7; } if(vcounter() == vdisp() && !io.displayDisable) { @@ -103,6 +104,7 @@ auto PPU::load(Markup::Node node) -> bool { } auto PPU::power(bool reset) -> void { +//settings.fastPPUHiresMode7=false; create(Enter, system.cpuFrequency()); PPUcounter::reset(); memory::fill(output, 512 * 480); diff --git a/higan/sfc/ppu-fast/ppu.hpp b/higan/sfc/ppu-fast/ppu.hpp index be89f9d8..b1e8da56 100644 --- a/higan/sfc/ppu-fast/ppu.hpp +++ b/higan/sfc/ppu-fast/ppu.hpp @@ -295,8 +295,8 @@ public: array items; //32 on real hardware array tiles; //34 on real hardware; 1024 max (128 * 64-width tiles) - array above; - array below; + array above; //256 on real hardware + array below; //512 for hires mode 7 array windowAbove; array windowBelow; diff --git a/higan/target-bsnes/input/hotkeys.cpp b/higan/target-bsnes/input/hotkeys.cpp index ac7db281..eb2009c3 100644 --- a/higan/target-bsnes/input/hotkeys.cpp +++ b/higan/target-bsnes/input/hotkeys.cpp @@ -27,6 +27,10 @@ auto InputManager::bindHotkeys() -> void { program->showMessage({"Selected state slot ", stateSlot}); })); + hotkeys.append(InputHotkey("Capture Screenshot").onPress([] { + presentation->captureScreenshot.doActivate(); + })); + hotkeys.append(InputHotkey("Fast Forward").onPress([] { video->setBlocking(false); audio->setBlocking(false); diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index d8a1888e..85806ec1 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -139,7 +139,7 @@ Presentation::Presentation() { } loadState.append(MenuSeparator()); loadState.append(MenuItem().setIcon(Icon::Edit::Undo).setText("Undo Last Save").onActivate([&] { - program->loadState("quick/recovery"); + program->loadState("quick/undo"); })); speedMenu.setIcon(Icon::Device::Clock).setText("Speed"); speedSlowest.setText("Slowest (50%)").setProperty("multiplier", "2.0").onActivate([&] { program->updateAudioFrequency(); }); @@ -150,8 +150,13 @@ Presentation::Presentation() { pauseEmulation.setText("Pause Emulation").onToggle([&] { if(pauseEmulation.checked()) audio->clear(); }); + captureScreenshot.setIcon(Icon::Emblem::Image).setText("Capture Screenshot").onActivate([&] { + if(program->paused()) program->showMessage("The next video frame will be captured"); + program->captureScreenshot = true; + }); cheatEditor.setIcon(Icon::Edit::Replace).setText("Cheat Editor ...").onActivate([&] { toolsWindow->show(0); }); stateManager.setIcon(Icon::Application::FileManager).setText("State Manager ...").onActivate([&] { toolsWindow->show(1); }); + manifestViewer.setIcon(Icon::Emblem::Text).setText("Manifest Viewer ...").onActivate([&] { toolsWindow->show(2); }); helpMenu.setText("Help"); documentation.setIcon(Icon::Application::Browser).setText("Documentation ...").onActivate([&] { diff --git a/higan/target-bsnes/presentation/presentation.hpp b/higan/target-bsnes/presentation/presentation.hpp index 2fa979da..60ae2de6 100644 --- a/higan/target-bsnes/presentation/presentation.hpp +++ b/higan/target-bsnes/presentation/presentation.hpp @@ -74,9 +74,11 @@ struct Presentation : Window { MenuRadioItem speedFastest{&speedMenu}; Group speedGroup{&speedSlowest, &speedSlow, &speedNormal, &speedFast, &speedFastest}; MenuCheckItem pauseEmulation{&toolsMenu}; + MenuItem captureScreenshot{&toolsMenu}; MenuSeparator toolsSeparatorB{&toolsMenu}; MenuItem cheatEditor{&toolsMenu}; MenuItem stateManager{&toolsMenu}; + MenuItem manifestViewer{&toolsMenu}; Menu helpMenu{&menuBar}; MenuItem documentation{&helpMenu}; MenuSeparator helpSeparator{&helpMenu}; diff --git a/higan/target-bsnes/program/game.cpp b/higan/target-bsnes/program/game.cpp index b0aaf43e..973ef529 100644 --- a/higan/target-bsnes/program/game.cpp +++ b/higan/target-bsnes/program/game.cpp @@ -7,6 +7,7 @@ auto Program::load() -> void { Emulator::audio.reset(2, audio->frequency()); if(emulator->load(media.id)) { gameQueue = {}; + captureScreenshot = false; if(!verified() && settingsWindow->advanced.warnOnUnverifiedGames.checked()) { //todo: MessageDialog crashes with GTK+; unsure the reason why this happens //once MessageDialog functions, add an "Always" option @@ -22,9 +23,12 @@ auto Program::load() -> void { hackCompatibility(); emulator->power(); if(settingsWindow->advanced.autoLoadStateOnLoad.checked()) { - program->loadState("quick/recovery"); + program->loadState("quick/undo"); } - showMessage(!appliedPatch() ? "Game loaded" : "Game loaded and patch applied"); + showMessage({ + verified() ? "Verified" : "Unverified", " game loaded", + appliedPatch() ? " and patch applied" : "" + }); presentation->setTitle(emulator->title()); presentation->resetSystem.setEnabled(true); presentation->unloadGame.setEnabled(true); @@ -35,6 +39,7 @@ auto Program::load() -> void { presentation->resizeViewport(); toolsWindow->cheatEditor.loadCheats(); toolsWindow->stateManager.loadStates(); + toolsWindow->manifestViewer.loadManifest(); string locations = superFamicom.location; if(auto location = gameBoy.location) locations.append("|", location); @@ -63,12 +68,13 @@ auto Program::loadFile(string location) -> vector { } } } + return {}; } else { return file::read(location); } } -auto Program::loadSuperFamicom(string location) -> void { +auto Program::loadSuperFamicom(string location) -> bool { string manifest; vector rom; @@ -84,6 +90,7 @@ auto Program::loadSuperFamicom(string location) -> void { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } + if(rom.size() < 0x8000) return false; //assume ROM and IPS agree on whether a copier header is present superFamicom.patched = applyPatchIPS(rom, location); @@ -130,9 +137,11 @@ auto Program::loadSuperFamicom(string location) -> void { memory::copy(&superFamicom.firmware[0], &rom[offset], size); offset += size; } + + return true; } -auto Program::loadGameBoy(string location) -> void { +auto Program::loadGameBoy(string location) -> bool { string manifest; vector rom; @@ -143,6 +152,7 @@ auto Program::loadGameBoy(string location) -> void { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } + if(rom.size() < 0x4000) return false; gameBoy.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::GameBoy(rom, location); @@ -164,9 +174,10 @@ auto Program::loadGameBoy(string location) -> void { gameBoy.location = location; gameBoy.program = rom; + return true; } -auto Program::loadBSMemory(string location) -> void { +auto Program::loadBSMemory(string location) -> bool { string manifest; vector rom; @@ -178,6 +189,7 @@ auto Program::loadBSMemory(string location) -> void { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } + if(rom.size() < 0x8000) return false; bsMemory.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::BSMemory(rom, location); @@ -193,9 +205,10 @@ auto Program::loadBSMemory(string location) -> void { bsMemory.location = location; bsMemory.program = rom; + return true; } -auto Program::loadSufamiTurboA(string location) -> void { +auto Program::loadSufamiTurboA(string location) -> bool { string manifest; vector rom; @@ -206,6 +219,7 @@ auto Program::loadSufamiTurboA(string location) -> void { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } + if(rom.size() < 0x20000) return false; sufamiTurboA.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::SufamiTurbo(rom, location); @@ -221,9 +235,10 @@ auto Program::loadSufamiTurboA(string location) -> void { sufamiTurboA.location = location; sufamiTurboA.program = rom; + return true; } -auto Program::loadSufamiTurboB(string location) -> void { +auto Program::loadSufamiTurboB(string location) -> bool { string manifest; vector rom; @@ -234,6 +249,7 @@ auto Program::loadSufamiTurboB(string location) -> void { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } + if(rom.size() < 0x20000) return false; sufamiTurboB.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::SufamiTurbo(rom, location); @@ -249,6 +265,7 @@ auto Program::loadSufamiTurboB(string location) -> void { sufamiTurboB.location = location; sufamiTurboB.program = rom; + return true; } auto Program::save() -> void { @@ -268,7 +285,7 @@ auto Program::unload() -> void { toolsWindow->cheatEditor.saveCheats(); toolsWindow->setVisible(false); if(settingsWindow->advanced.autoSaveStateOnUnload.checked()) { - saveRecoveryState(); + saveUndoState(); } emulator->unload(); showMessage("Game unloaded"); diff --git a/higan/target-bsnes/program/hacks.cpp b/higan/target-bsnes/program/hacks.cpp index 77f7a577..8a24b9d5 100644 --- a/higan/target-bsnes/program/hacks.cpp +++ b/higan/target-bsnes/program/hacks.cpp @@ -1,6 +1,7 @@ auto Program::hackCompatibility() -> void { bool fastPPU = settingsWindow->advanced.fastPPUOption.checked(); bool fastPPUNoSpriteLimit = settingsWindow->advanced.noSpriteLimit.checked(); + bool fastPPUHiresMode7 = settingsWindow->advanced.hiresMode7.checked(); bool fastDSP = settingsWindow->advanced.fastDSPOption.checked(); auto label = superFamicom.label; @@ -10,6 +11,7 @@ auto Program::hackCompatibility() -> void { emulator->set("Fast PPU", fastPPU); emulator->set("Fast PPU/No Sprite Limit", fastPPUNoSpriteLimit); + emulator->set("Fast PPU/Hires Mode 7", fastPPUHiresMode7); emulator->set("Fast DSP", fastDSP); } diff --git a/higan/target-bsnes/program/interface.cpp b/higan/target-bsnes/program/interface.cpp index 50cc4a42..392d2389 100644 --- a/higan/target-bsnes/program/interface.cpp +++ b/higan/target-bsnes/program/interface.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -117,8 +118,9 @@ auto Program::load(uint id, string name, string type, string_vector options) -> } if(inode::exists(superFamicom.location)) { settings["Path/Recent/SuperFamicom"].setValue(Location::dir(superFamicom.location)); - loadSuperFamicom(superFamicom.location); - return {id, dialog.option()}; + if(loadSuperFamicom(superFamicom.location)) { + return {id, dialog.option()}; + } } } @@ -133,8 +135,9 @@ auto Program::load(uint id, string name, string type, string_vector options) -> } if(inode::exists(gameBoy.location)) { settings["Path/Recent/GameBoy"].setValue(Location::dir(gameBoy.location)); - loadGameBoy(gameBoy.location); - return {id, dialog.option()}; + if(loadGameBoy(gameBoy.location)) { + return {id, dialog.option()}; + } } } @@ -149,8 +152,9 @@ auto Program::load(uint id, string name, string type, string_vector options) -> } if(inode::exists(bsMemory.location)) { settings["Path/Recent/BSMemory"].setValue(Location::dir(bsMemory.location)); - loadBSMemory(bsMemory.location); - return {id, dialog.option()}; + if(loadBSMemory(bsMemory.location)) { + return {id, dialog.option()}; + } } } @@ -165,8 +169,9 @@ auto Program::load(uint id, string name, string type, string_vector options) -> } if(inode::exists(sufamiTurboA.location)) { settings["Path/Recent/SufamiTurboA"].setValue(Location::dir(sufamiTurboA.location)); - loadSufamiTurboA(sufamiTurboA.location); - return {id, dialog.option()}; + if(loadSufamiTurboA(sufamiTurboA.location)) { + return {id, dialog.option()}; + } } } @@ -181,8 +186,9 @@ auto Program::load(uint id, string name, string type, string_vector options) -> } if(inode::exists(sufamiTurboB.location)) { settings["Path/Recent/SufamiTurboB"].setValue(Location::dir(sufamiTurboB.location)); - loadSufamiTurboB(sufamiTurboB.location); - return {id, dialog.option()}; + if(loadSufamiTurboB(sufamiTurboB.location)) { + return {id, dialog.option()}; + } } } @@ -199,6 +205,15 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig if(height == 480) data += 16 * pitch, height -= 32; } + if(captureScreenshot) { + captureScreenshot = false; + if(auto filename = screenshotPath()) { + if(Encode::BMP::create(filename, (const uint32_t*)data, pitch << 2, width, height, false)) { + showMessage({"Captured screenshot [", Location::file(filename), "]"}); + } + } + } + if(video->lock(output, length, width, height)) { length >>= 2; diff --git a/higan/target-bsnes/program/paths.cpp b/higan/target-bsnes/program/paths.cpp index 1b66b4d7..3efd2a7e 100644 --- a/higan/target-bsnes/program/paths.cpp +++ b/higan/target-bsnes/program/paths.cpp @@ -34,6 +34,12 @@ auto Program::path(string type, string location, string extension) -> string { } } + if(type == "Screenshots") { + if(auto path = settings["Path/Screenshots"].text()) { + pathname = path; + } + } + return {pathname, prefix, suffix}; } @@ -62,3 +68,19 @@ auto Program::statePath() -> string { return path("States", location, ".bsz"); } } + +auto Program::screenshotPath() -> string { + if(!emulator->loaded()) return ""; + auto location = gamePath(); + if(location.endsWith("/")) { + directory::create({location, "bsnes/screenshots/"}); + location = {location, "bsnes/screenshots/capture"}; + } else { + location = path("Screenshots", location); + } + for(uint n : range(1, 999)) { + string filename = {location, ".", pad(n, 3, '0'), ".bmp"}; + if(!file::exists(filename)) return filename; + } + return {location, ".000.bmp"}; +} diff --git a/higan/target-bsnes/program/program.cpp b/higan/target-bsnes/program/program.cpp index 7250a977..f39acaaf 100644 --- a/higan/target-bsnes/program/program.cpp +++ b/higan/target-bsnes/program/program.cpp @@ -67,10 +67,7 @@ auto Program::main() -> void { inputManager->poll(); inputManager->pollHotkeys(); - if(!emulator->loaded() - || presentation->pauseEmulation.checked() - || (!focused() && settingsWindow->input.pauseEmulation.checked()) - ) { + if(paused()) { audio->clear(); usleep(20 * 1000); return; diff --git a/higan/target-bsnes/program/program.hpp b/higan/target-bsnes/program/program.hpp index d20ff1ab..8a53efbb 100644 --- a/higan/target-bsnes/program/program.hpp +++ b/higan/target-bsnes/program/program.hpp @@ -15,11 +15,11 @@ struct Program : Emulator::Platform { //game.cpp auto load() -> void; auto loadFile(string location) -> vector; - auto loadSuperFamicom(string location) -> void; - auto loadGameBoy(string location) -> void; - auto loadBSMemory(string location) -> void; - auto loadSufamiTurboA(string location) -> void; - auto loadSufamiTurboB(string location) -> void; + auto loadSuperFamicom(string location) -> bool; + auto loadGameBoy(string location) -> bool; + auto loadBSMemory(string location) -> bool; + auto loadSufamiTurboA(string location) -> bool; + auto loadSufamiTurboB(string location) -> bool; auto save() -> void; auto reset() -> void; auto unload() -> void; @@ -44,12 +44,13 @@ struct Program : Emulator::Platform { auto gamePath() -> string; auto cheatPath() -> string; auto statePath() -> string; + auto screenshotPath() -> string; //states.cpp auto managedStates() -> string_vector; auto loadState(string filename) -> bool; auto saveState(string filename) -> bool; - auto saveRecoveryState() -> bool; + auto saveUndoState() -> bool; auto removeState(string filename) -> bool; auto renameState(string from, string to) -> bool; @@ -76,6 +77,7 @@ struct Program : Emulator::Platform { auto showMessage(string text) -> void; auto showFrameRate(string text) -> void; auto updateStatus() -> void; + auto paused() -> bool; auto focused() -> bool; //patch.cpp @@ -120,6 +122,7 @@ public: } sufamiTurboA, sufamiTurboB; string_vector gameQueue; + boolean captureScreenshot; uint64 autoSaveTime; diff --git a/higan/target-bsnes/program/states.cpp b/higan/target-bsnes/program/states.cpp index caa7377e..138e0af5 100644 --- a/higan/target-bsnes/program/states.cpp +++ b/higan/target-bsnes/program/states.cpp @@ -27,7 +27,7 @@ auto Program::loadState(string filename) -> bool { if(gamePath().endsWith("/")) { string location = {statePath(), filename, ".bst"}; if(!file::exists(location)) return showMessage({"[", prefix, "] not found"}), false; - if(filename != "quick/recovery") saveRecoveryState(); + if(filename != "quick/undo") saveUndoState(); memory = file::read(location); } else { string location = {filename, ".bst"}; @@ -87,12 +87,13 @@ auto Program::saveState(string filename) -> bool { return showMessage({"Saved [", prefix, "]"}), true; } -auto Program::saveRecoveryState() -> bool { +auto Program::saveUndoState() -> bool { auto statusTime = this->statusTime; auto statusMessage = this->statusMessage; - saveState("quick/recovery"); + auto result = saveState("quick/undo"); this->statusTime = statusTime; this->statusMessage = statusMessage; + return result; } auto Program::removeState(string filename) -> bool { diff --git a/higan/target-bsnes/program/utility.cpp b/higan/target-bsnes/program/utility.cpp index 117cdfd9..74ff538a 100644 --- a/higan/target-bsnes/program/utility.cpp +++ b/higan/target-bsnes/program/utility.cpp @@ -31,6 +31,13 @@ auto Program::updateStatus() -> void { } } +auto Program::paused() -> bool { + if(!emulator->loaded()) return true; + if(presentation->pauseEmulation.checked()) return true; + if(!focused() && settingsWindow->input.pauseEmulation.checked()) return true; + return false; +} + auto Program::focused() -> bool { //exclusive mode creates its own top-level window: presentation window will not have focus if(video && video->exclusive()) return true; diff --git a/higan/target-bsnes/settings/advanced.cpp b/higan/target-bsnes/settings/advanced.cpp index fe09f1c4..ecf01bf4 100644 --- a/higan/target-bsnes/settings/advanced.cpp +++ b/higan/target-bsnes/settings/advanced.cpp @@ -15,7 +15,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { "Do you wish to proceed with the video driver change now anyway?" ).setParent(*settingsWindow).question() == "Yes") { program->save(); - program->saveRecoveryState(); + program->saveUndoState(); settings["Crashed"].setValue(true); settings.save(); program->updateVideoDriver(); @@ -34,7 +34,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { "Do you wish to proceed with the audio driver change now anyway?" ).setParent(*settingsWindow).question() == "Yes") { program->save(); - program->saveRecoveryState(); + program->saveUndoState(); settings["Crashed"].setValue(true); settings.save(); program->updateAudioDriver(); @@ -53,7 +53,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { "Do you wish to proceed with the input driver change now anyway?" ).setParent(*settingsWindow).question() == "Yes") { program->save(); - program->saveRecoveryState(); + program->saveUndoState(); settings["Crashed"].setValue(true); settings.save(); program->updateInputDriver(); @@ -86,23 +86,29 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { settings["Emulator/Hack/FastPPU"].setValue(fastPPUOption.checked()); if(!fastPPUOption.checked()) { noSpriteLimit.setEnabled(false).setChecked(false).doToggle(); + hiresMode7.setEnabled(false).setChecked(false).doToggle(); } else { noSpriteLimit.setEnabled(true); + hiresMode7.setEnabled(true); } }).doToggle(); noSpriteLimit.setText("No sprite limit").setChecked(settings["Emulator/Hack/FastPPU/NoSpriteLimit"].boolean()).onToggle([&] { settings["Emulator/Hack/FastPPU/NoSpriteLimit"].setValue(noSpriteLimit.checked()); }); + hiresMode7.setText("Hires mode 7").setChecked(settings["Emulator/Hack/FastPPU/HiresMode7"].boolean()).onToggle([&] { + settings["Emulator/Hack/FastPPU/HiresMode7"].setValue(hiresMode7.checked()); + emulator->set("Fast PPU/Hires Mode 7", hiresMode7.checked()); + }); fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/Hack/FastDSP"].boolean()).onToggle([&] { settings["Emulator/Hack/FastDSP"].setValue(fastDSPOption.checked()); }); - superFXLabel.setText("SuperFX Clock Speed:"); + superFXLabel.setText("SuperFX clock speed:"); superFXValue.setAlignment(0.5); superFXClock.setLength(71).setPosition((settings["Emulator/Hack/FastSuperFX"].natural() - 100) / 10).onChange([&] { settings["Emulator/Hack/FastSuperFX"].setValue({superFXClock.position() * 10 + 100, "%"}); superFXValue.setText(settings["Emulator/Hack/FastSuperFX"].text()); }).doChange(); - hacksNote.setForegroundColor({224, 0, 0}).setText("Note: hack setting changes do not take effect until after reloading games."); + hacksNote.setForegroundColor({224, 0, 0}).setText("Note: some hack setting changes do not take effect until after reloading games."); } auto AdvancedSettings::updateVideoDriver() -> void { diff --git a/higan/target-bsnes/settings/audio.cpp b/higan/target-bsnes/settings/audio.cpp index 89914cf1..b3fc45fd 100644 --- a/higan/target-bsnes/settings/audio.cpp +++ b/higan/target-bsnes/settings/audio.cpp @@ -26,7 +26,7 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { }); effectsLabel.setFont(Font().setBold()).setText("Effects"); - skewLabel.setText("Skew:"); + skewLabel.setAlignment(1.0).setText("Skew:"); skewValue.setAlignment(0.5); skewSlider.setLength(10001).setPosition(settings["Audio/Skew"].integer() + 5000).onChange([&] { string value = {skewSlider.position() > 5000 ? "+" : "", (int)skewSlider.position() - 5000}; @@ -34,7 +34,7 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { skewValue.setText(value); program->updateAudioFrequency(); }).doChange(); - volumeLabel.setText("Volume:"); + volumeLabel.setAlignment(1.0).setText("Volume:"); volumeValue.setAlignment(0.5); volumeSlider.setLength(201).setPosition(settings["Audio/Volume"].natural()).onChange([&] { string value = {volumeSlider.position(), "%"}; @@ -42,7 +42,7 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { volumeValue.setText(value); program->updateAudioEffects(); }).doChange(); - balanceLabel.setText("Balance:"); + balanceLabel.setAlignment(1.0).setText("Balance:"); balanceValue.setAlignment(0.5); balanceSlider.setLength(101).setPosition(settings["Audio/Balance"].natural()).onChange([&] { string value = {balanceSlider.position(), "%"}; diff --git a/higan/target-bsnes/settings/input.cpp b/higan/target-bsnes/settings/input.cpp index fa833ee9..869389ef 100644 --- a/higan/target-bsnes/settings/input.cpp +++ b/higan/target-bsnes/settings/input.cpp @@ -3,14 +3,14 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) { setText("Input"); layout.setMargin(5); - defocusLabel.setText("When Focus is Lost:"); - pauseEmulation.setText("Pause Emulation").onActivate([&] { + defocusLabel.setText("When focus is lost:"); + pauseEmulation.setText("Pause emulation").onActivate([&] { settings["Input/Defocus"].setValue("Pause"); }); - blockInput.setText("Block Input").onActivate([&] { + blockInput.setText("Block input").onActivate([&] { settings["Input/Defocus"].setValue("Block"); }); - allowInput.setText("Allow Input").onActivate([&] { + allowInput.setText("Allow input").onActivate([&] { settings["Input/Defocus"].setValue("Allow"); }); if(settings["Input/Defocus"].text() == "Pause") pauseEmulation.setChecked(); diff --git a/higan/target-bsnes/settings/paths.cpp b/higan/target-bsnes/settings/paths.cpp index 1699bbac..7c15f7cd 100644 --- a/higan/target-bsnes/settings/paths.cpp +++ b/higan/target-bsnes/settings/paths.cpp @@ -3,7 +3,8 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { setText("Paths"); layout.setMargin(5); - gamesLabel.setText("Games:"); + + gamesLabel.setAlignment(1.0).setText("Games:"); gamesPath.setEditable(false); gamesAssign.setText("Assign ...").onActivate([&] { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { @@ -15,7 +16,8 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { settings["Path/Games"].setValue(""); refreshPaths(); }); - patchesLabel.setText("Patches:"); + + patchesLabel.setAlignment(1.0).setText("Patches:"); patchesPath.setEditable(false); patchesAssign.setText("Assign ...").onActivate([&] { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { @@ -27,7 +29,8 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { settings["Path/Patches"].setValue(""); refreshPaths(); }); - savesLabel.setText("Saves:"); + + savesLabel.setAlignment(1.0).setText("Saves:"); savesPath.setEditable(false); savesAssign.setText("Assign ...").onActivate([&] { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { @@ -39,7 +42,8 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { settings["Path/Saves"].setValue(""); refreshPaths(); }); - cheatsLabel.setText("Cheats:"); + + cheatsLabel.setAlignment(1.0).setText("Cheats:"); cheatsPath.setEditable(false); cheatsAssign.setText("Assign ...").onActivate([&] { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { @@ -51,7 +55,8 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { settings["Path/Cheats"].setValue(""); refreshPaths(); }); - statesLabel.setText("States:"); + + statesLabel.setAlignment(1.0).setText("States:"); statesPath.setEditable(false); statesAssign.setText("Assign ...").onActivate([&] { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { @@ -64,6 +69,19 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { refreshPaths(); }); + screenshotsLabel.setAlignment(1.0).setText("Screenshots:"); + screenshotsPath.setEditable(false); + screenshotsAssign.setText("Assign ...").onActivate([&] { + if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { + settings["Path/Screenshots"].setValue(location); + refreshPaths(); + } + }); + screenshotsReset.setText("Reset").onActivate([&] { + settings["Path/Screenshots"].setValue(""); + refreshPaths(); + }); + refreshPaths(); } @@ -93,4 +111,9 @@ auto PathSettings::refreshPaths() -> void { } else { statesPath.setText("").setForegroundColor({128, 128, 128}); } + if(auto location = settings["Path/Screenshots"].text()) { + screenshotsPath.setText(location).setForegroundColor(); + } else { + screenshotsPath.setText("").setForegroundColor({128, 128, 128}); + } } diff --git a/higan/target-bsnes/settings/settings.cpp b/higan/target-bsnes/settings/settings.cpp index fa5dc296..af751ae1 100644 --- a/higan/target-bsnes/settings/settings.cpp +++ b/higan/target-bsnes/settings/settings.cpp @@ -51,6 +51,7 @@ Settings::Settings() { set("Path/Saves", ""); set("Path/Cheats", ""); set("Path/States", ""); + set("Path/Screenshots", ""); set("Path/Recent/SuperFamicom", Path::user()); set("Path/Recent/GameBoy", Path::user()); set("Path/Recent/BSMemory", Path::user()); @@ -66,6 +67,7 @@ Settings::Settings() { set("Emulator/AutoLoadStateOnLoad", false); set("Emulator/Hack/FastPPU", true); set("Emulator/Hack/FastPPU/NoSpriteLimit", false); + set("Emulator/Hack/FastPPU/HiresMode7", false); set("Emulator/Hack/FastDSP", true); set("Emulator/Hack/FastSuperFX", "100%"); diff --git a/higan/target-bsnes/settings/settings.hpp b/higan/target-bsnes/settings/settings.hpp index 3c2fd5bc..645b12e2 100644 --- a/higan/target-bsnes/settings/settings.hpp +++ b/higan/target-bsnes/settings/settings.hpp @@ -123,30 +123,35 @@ struct PathSettings : TabFrameItem { public: VerticalLayout layout{this}; HorizontalLayout gamesLayout{&layout, Size{~0, 0}}; - Label gamesLabel{&gamesLayout, Size{55, 0}}; + Label gamesLabel{&gamesLayout, Size{80, 0}}; LineEdit gamesPath{&gamesLayout, Size{~0, 0}}; Button gamesAssign{&gamesLayout, Size{80, 0}}; Button gamesReset{&gamesLayout, Size{80, 0}}; HorizontalLayout patchesLayout{&layout, Size{~0, 0}}; - Label patchesLabel{&patchesLayout, Size{55, 0}}; + Label patchesLabel{&patchesLayout, Size{80, 0}}; LineEdit patchesPath{&patchesLayout, Size{~0, 0}}; Button patchesAssign{&patchesLayout, Size{80, 0}}; Button patchesReset{&patchesLayout, Size{80, 0}}; HorizontalLayout savesLayout{&layout, Size{~0, 0}}; - Label savesLabel{&savesLayout, Size{55, 0}}; + Label savesLabel{&savesLayout, Size{80, 0}}; LineEdit savesPath{&savesLayout, Size{~0, 0}}; Button savesAssign{&savesLayout, Size{80, 0}}; Button savesReset{&savesLayout, Size{80, 0}}; HorizontalLayout cheatsLayout{&layout, Size{~0, 0}}; - Label cheatsLabel{&cheatsLayout, Size{55, 0}}; + Label cheatsLabel{&cheatsLayout, Size{80, 0}}; LineEdit cheatsPath{&cheatsLayout, Size{~0, 0}}; Button cheatsAssign{&cheatsLayout, Size{80, 0}}; Button cheatsReset{&cheatsLayout, Size{80, 0}}; HorizontalLayout statesLayout{&layout, Size{~0, 0}}; - Label statesLabel{&statesLayout, Size{55, 0}}; + Label statesLabel{&statesLayout, Size{80, 0}}; LineEdit statesPath{&statesLayout, Size{~0, 0}}; Button statesAssign{&statesLayout, Size{80, 0}}; Button statesReset{&statesLayout, Size{80, 0}}; + HorizontalLayout screenshotsLayout{&layout, Size{~0, 0}}; + Label screenshotsLabel{&screenshotsLayout, Size{80, 0}}; + LineEdit screenshotsPath{&screenshotsLayout, Size{~0, 0}}; + Button screenshotsAssign{&screenshotsLayout, Size{80, 0}}; + Button screenshotsReset{&screenshotsLayout, Size{80, 0}}; }; struct AdvancedSettings : TabFrameItem { @@ -175,6 +180,7 @@ public: HorizontalLayout fastPPULayout{&layout, Size{~0, 0}}; CheckLabel fastPPUOption{&fastPPULayout, Size{0, 0}}; CheckLabel noSpriteLimit{&fastPPULayout, Size{0, 0}}; + CheckLabel hiresMode7{&fastPPULayout, Size{0, 0}}; CheckLabel fastDSPOption{&layout, Size{~0, 0}}; HorizontalLayout superFXLayout{&layout, Size{~0, 0}}; Label superFXLabel{&superFXLayout, Size{0, 0}}; diff --git a/higan/target-bsnes/settings/video.cpp b/higan/target-bsnes/settings/video.cpp index 94dec706..e6b86eb7 100644 --- a/higan/target-bsnes/settings/video.cpp +++ b/higan/target-bsnes/settings/video.cpp @@ -5,7 +5,7 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { layout.setMargin(5); colorAdjustmentLabel.setFont(Font().setBold()).setText("Color Adjustment"); - luminanceLabel.setText("Luminance:"); + luminanceLabel.setAlignment(1.0).setText("Luminance:"); luminanceValue.setAlignment(0.5); luminanceSlider.setLength(101).setPosition(settings["Video/Luminance"].natural()).onChange([&] { string value = {luminanceSlider.position(), "%"}; @@ -13,7 +13,7 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { luminanceValue.setText(value); program->updateVideoPalette(); }).doChange(); - saturationLabel.setText("Saturation:"); + saturationLabel.setAlignment(1.0).setText("Saturation:"); saturationValue.setAlignment(0.5); saturationSlider.setLength(201).setPosition(settings["Video/Saturation"].natural()).onChange([&] { string value = {saturationSlider.position(), "%"}; @@ -21,7 +21,7 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { saturationValue.setText(value); program->updateVideoPalette(); }).doChange(); - gammaLabel.setText("Gamma:"); + gammaLabel.setAlignment(1.0).setText("Gamma:"); gammaValue.setAlignment(0.5); gammaSlider.setLength(101).setPosition(settings["Video/Gamma"].natural() - 100).onChange([&] { string value = {100 + gammaSlider.position(), "%"}; diff --git a/higan/target-bsnes/tools/manifest-viewer.cpp b/higan/target-bsnes/tools/manifest-viewer.cpp new file mode 100644 index 00000000..eb788b4c --- /dev/null +++ b/higan/target-bsnes/tools/manifest-viewer.cpp @@ -0,0 +1,20 @@ +ManifestViewer::ManifestViewer(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Emblem::Text); + setText("Manifest Viewer"); + + layout.setMargin(5); + manifestView.setEditable(false).setWordWrap(false).setFont(Font().setFamily(Font::Mono)); +} + +auto ManifestViewer::loadManifest() -> void { + if(!emulator->loaded()) { + manifestView.setText(""); + verifiedIcon.setIcon({}); + verifiedLabel.setText(""); + return; + } + + manifestView.setText(emulator->manifest()); + verifiedIcon.setIcon(program->verified() ? Icon::Emblem::Program : Icon::Emblem::Binary); + verifiedLabel.setText(program->verified() ? "Verified" : "Unverified"); +} diff --git a/higan/target-bsnes/tools/tools.cpp b/higan/target-bsnes/tools/tools.cpp index b4c14427..7dee36bb 100644 --- a/higan/target-bsnes/tools/tools.cpp +++ b/higan/target-bsnes/tools/tools.cpp @@ -1,6 +1,7 @@ #include "../bsnes.hpp" #include "cheat-editor.cpp" #include "state-manager.cpp" +#include "manifest-viewer.cpp" unique_pointer cheatDatabase; unique_pointer cheatWindow; unique_pointer stateWindow; diff --git a/higan/target-bsnes/tools/tools.hpp b/higan/target-bsnes/tools/tools.hpp index 1c99ee59..70497b94 100644 --- a/higan/target-bsnes/tools/tools.hpp +++ b/higan/target-bsnes/tools/tools.hpp @@ -107,6 +107,18 @@ public: Button removeButton{&controlLayout, Size{80, 0}}; }; +struct ManifestViewer : TabFrameItem { + ManifestViewer(TabFrame*); + auto loadManifest() -> void; + +public: + VerticalLayout layout{this}; + TextEdit manifestView{&layout, Size{~0, ~0}}; + HorizontalLayout verifiedLayout{&layout, Size{~0, 0}}; + Canvas verifiedIcon{&verifiedLayout, Size{16, 16}}; + Label verifiedLabel{&verifiedLayout, Size{~0, 0}}; +}; + struct ToolsWindow : Window { ToolsWindow(); auto setVisible(bool visible = true) -> ToolsWindow&; @@ -117,6 +129,7 @@ public: TabFrame panel{&layout, Size{~0, ~0}}; CheatEditor cheatEditor{&panel}; StateManager stateManager{&panel}; + ManifestViewer manifestViewer{&panel}; }; extern unique_pointer cheatDatabase; diff --git a/hiro/cocoa/header.hpp b/hiro/cocoa/header.hpp index c46361ab..01dcd16d 100644 --- a/hiro/cocoa/header.hpp +++ b/hiro/cocoa/header.hpp @@ -1,6 +1,6 @@ -#define decimal decimal_cocoa -#import -#import -#undef decimal +#include +#include +#include +#include #include diff --git a/hiro/cocoa/window.cpp b/hiro/cocoa/window.cpp index 7ff104cc..3cec6838 100644 --- a/hiro/cocoa/window.cpp +++ b/hiro/cocoa/window.cpp @@ -156,12 +156,12 @@ | kAuthorizationFlagExtendRights, nullptr); if(status == errAuthorizationSuccess) { { char program[] = "/usr/sbin/spctl"; - char arguments[] = {"--master-disable", nullptr}; + char* arguments[] = {"--master-disable", nullptr}; FILE* pipe = nullptr; AuthorizationExecuteWithPrivileges(authorization, program, kAuthorizationFlagDefaults, arguments, &pipe); } { char program[] = "/usr/bin/defaults"; - char arguments[] = {"write /Library/Preferences/com.apple.security GKAutoRearm -bool NO"}; + char* arguments[] = {"write /Library/Preferences/com.apple.security GKAutoRearm -bool NO"}; FILE* pipe = nullptr; AuthorizationExecuteWithPrivileges(authorization, program, kAuthorizationFlagDefaults, arguments, &pipe); } diff --git a/hiro/cocoa/window.hpp b/hiro/cocoa/window.hpp index 49dc140d..2e7d87f7 100644 --- a/hiro/cocoa/window.hpp +++ b/hiro/cocoa/window.hpp @@ -5,7 +5,7 @@ hiro::mWindow* window; NSMenu* menuBar; NSMenu* rootMenu; - NSMenuItem* disableGatekeeperAutoRearm; + NSMenuItem* disableGatekeeper; NSTextField* statusBar; } -(id) initWith:(hiro::mWindow&)window; diff --git a/icarus/heuristics/super-famicom.cpp b/icarus/heuristics/super-famicom.cpp index 2475728e..100797e0 100644 --- a/icarus/heuristics/super-famicom.cpp +++ b/icarus/heuristics/super-famicom.cpp @@ -128,8 +128,10 @@ auto SuperFamicom::manifest() const -> string { output.append(Memory{}.type("RAM").size(0x800).content("Internal").isVolatile().text()); } - if(board.right() == "EPSONRTC" || board.right() == "SHARPRTC") { - output.append(Memory{}.type("RTC").size(0x10).content("Time").text()); + if(board.right() == "EPSONRTC") { + output.append(Memory{}.type("RTC").size(0x10).content("Time").manufacturer("Epson").text()); + } else if(board.right() == "SHARPRTC") { + output.append(Memory{}.type("RTC").size(0x10).content("Time").manufacturer("Sharp").text()); } return output; @@ -418,15 +420,18 @@ auto SuperFamicom::romSize() const -> uint { auto SuperFamicom::programRomSize() const -> uint { if(board().beginsWith("SPC7110-")) return 0x100000; + if(board().beginsWith("EXSPC7110-")) return 0x100000; return romSize(); } auto SuperFamicom::dataRomSize() const -> uint { if(board().beginsWith("SPC7110-")) return romSize() - 0x100000; + if(board().beginsWith("EXSPC7110-")) return 0x500000; return 0; } auto SuperFamicom::expansionRomSize() const -> uint { + if(board().beginsWith("EXSPC7110-")) return 0x100000; return 0; } diff --git a/nall/encode/bmp.hpp b/nall/encode/bmp.hpp index 2aa037ff..03062212 100644 --- a/nall/encode/bmp.hpp +++ b/nall/encode/bmp.hpp @@ -3,16 +3,16 @@ namespace nall { namespace Encode { struct BMP { - static auto create(const string& filename, const uint32_t* data, unsigned width, unsigned height, bool alpha) -> bool { + static auto create(const string& filename, const uint32_t* data, uint pitch, uint width, uint height, bool alpha) -> bool { file fp{filename, file::mode::write}; if(!fp) return false; - unsigned bitsPerPixel = alpha ? 32 : 24; - unsigned bytesPerPixel = bitsPerPixel / 8; - unsigned alignedWidth = width * bytesPerPixel; - unsigned paddingLength = 0; - unsigned imageSize = alignedWidth * height; - unsigned fileSize = 0x36 + imageSize; + uint bitsPerPixel = alpha ? 32 : 24; + uint bytesPerPixel = bitsPerPixel / 8; + uint alignedWidth = width * bytesPerPixel; + uint paddingLength = 0; + uint imageSize = alignedWidth * height; + uint fileSize = 0x36 + imageSize; while(alignedWidth % 4) alignedWidth++, paddingLength++; fp.writel(0x4d42, 2); //signature @@ -33,8 +33,9 @@ struct BMP { fp.writel(0, 4); //palette size fp.writel(0, 4); //important color count + pitch >>= 2; for(auto y : range(height)) { - const uint32_t* p = data + y * width; + const uint32_t* p = data + y * pitch; for(auto x : range(width)) fp.writel(*p++, bytesPerPixel); if(paddingLength) fp.writel(0, paddingLength); } diff --git a/nall/macos/guard.hpp b/nall/macos/guard.hpp new file mode 100644 index 00000000..96cd4d69 --- /dev/null +++ b/nall/macos/guard.hpp @@ -0,0 +1,15 @@ +#ifndef NALL_MACOS_GUARD_HPP +#define NALL_MACOS_GUARD_HPP + +#define Boolean CocoaBoolean +#define decimal CocoaDecimal +#define DEBUG CocoaDebug + +#else +#undef NALL_MACOS_GUARD_HPP + +#undef Boolean +#undef decimal +#undef DEBUG + +#endif diff --git a/nall/serializer.hpp b/nall/serializer.hpp index 7f8f2239..72aed5d9 100644 --- a/nall/serializer.hpp +++ b/nall/serializer.hpp @@ -99,6 +99,7 @@ struct serializer { template auto array(nall::array& array) -> serializer& { for(auto& value : array) operator()(value); + return *this; } template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { value.serialize(*this); return *this; } diff --git a/ruby/ruby.cpp b/ruby/ruby.cpp index 670856a1..6c397053 100644 --- a/ruby/ruby.cpp +++ b/ruby/ruby.cpp @@ -18,12 +18,10 @@ using namespace ruby; #include #include #elif defined(DISPLAY_QUARTZ) - #define Boolean CocoaBoolean - #define decimal CocoaDecimal + #include #include #include - #undef Boolean - #undef decimal + #include #elif defined(DISPLAY_WINDOWS) #include #endif