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.]
This commit is contained in:
Tim Allen 2018-07-02 11:55:42 +10:00
parent 40a5fbe605
commit 372e9ef42b
36 changed files with 334 additions and 122 deletions

View File

@ -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/";

View File

@ -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<bool>();
return true;
}
if(name == "Fast PPU/Hires Mode 7" && value.is<bool>()) {
settings.fastPPUHiresMode7 = value.get<bool>();
return true;
}
if(name == "Fast DSP" && value.is<bool>()) {
settings.fastDSP = value.get<bool>();
return true;

View File

@ -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;

View File

@ -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]);

View File

@ -25,6 +25,7 @@ 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);
if(!settings.fastPPUHiresMode7) {
for(int X : range(256)) {
int x = !io.mode7.hflip ? X : 255 - X;
int pixelX = originX + a * x >> 8;
@ -34,10 +35,8 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
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;
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) {
@ -59,7 +58,46 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
}
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.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);
}
}
}

View File

@ -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<uint32>(output, 512 * 480);

View File

@ -295,8 +295,8 @@ public:
array<ObjectItem[128]> items; //32 on real hardware
array<ObjectTile[128]> tiles; //34 on real hardware; 1024 max (128 * 64-width tiles)
array<Pixel[256]> above;
array<Pixel[256]> below;
array<Pixel[512]> above; //256 on real hardware
array<Pixel[512]> below; //512 for hires mode 7
array<bool[256]> windowAbove;
array<bool[256]> windowBelow;

View File

@ -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);

View File

@ -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([&] {

View File

@ -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};

View File

@ -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<uint8_t> {
}
}
}
return {};
} else {
return file::read(location);
}
}
auto Program::loadSuperFamicom(string location) -> void {
auto Program::loadSuperFamicom(string location) -> bool {
string manifest;
vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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");

View File

@ -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);
}

View File

@ -1,3 +1,4 @@
#include <nall/encode/bmp.hpp>
#include <icarus/heuristics/heuristics.hpp>
#include <icarus/heuristics/heuristics.cpp>
#include <icarus/heuristics/super-famicom.cpp>
@ -117,10 +118,11 @@ 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);
if(loadSuperFamicom(superFamicom.location)) {
return {id, dialog.option()};
}
}
}
if(id == 2 && name == "Game Boy" && type == "gb") {
if(gameQueue) {
@ -133,10 +135,11 @@ 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);
if(loadGameBoy(gameBoy.location)) {
return {id, dialog.option()};
}
}
}
if(id == 3 && name == "BS Memory" && type == "bs") {
if(gameQueue) {
@ -149,10 +152,11 @@ 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);
if(loadBSMemory(bsMemory.location)) {
return {id, dialog.option()};
}
}
}
if(id == 4 && name == "Sufami Turbo" && type == "st") {
if(gameQueue) {
@ -165,10 +169,11 @@ 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);
if(loadSufamiTurboA(sufamiTurboA.location)) {
return {id, dialog.option()};
}
}
}
if(id == 5 && name == "Sufami Turbo" && type == "st") {
if(gameQueue) {
@ -181,10 +186,11 @@ 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);
if(loadSufamiTurboB(sufamiTurboB.location)) {
return {id, dialog.option()};
}
}
}
return {};
}
@ -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;

View File

@ -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"};
}

View File

@ -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;

View File

@ -15,11 +15,11 @@ struct Program : Emulator::Platform {
//game.cpp
auto load() -> void;
auto loadFile(string location) -> vector<uint8_t>;
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;

View File

@ -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 {

View File

@ -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;

View File

@ -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 {

View File

@ -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(), "%"};

View File

@ -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();

View File

@ -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("<same as loaded game>").setForegroundColor({128, 128, 128});
}
if(auto location = settings["Path/Screenshots"].text()) {
screenshotsPath.setText(location).setForegroundColor();
} else {
screenshotsPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128});
}
}

View File

@ -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%");

View File

@ -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}};

View File

@ -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(), "%"};

View File

@ -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");
}

View File

@ -1,6 +1,7 @@
#include "../bsnes.hpp"
#include "cheat-editor.cpp"
#include "state-manager.cpp"
#include "manifest-viewer.cpp"
unique_pointer<CheatDatabase> cheatDatabase;
unique_pointer<CheatWindow> cheatWindow;
unique_pointer<StateWindow> stateWindow;

View File

@ -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> cheatDatabase;

View File

@ -1,6 +1,6 @@
#define decimal decimal_cocoa
#import <Cocoa/Cocoa.h>
#import <Carbon/Carbon.h>
#undef decimal
#include <nall/macos/guard.hpp>
#include <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#include <nall/macos/guard.hpp>
#include <nall/run.hpp>

View File

@ -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);
}

View File

@ -5,7 +5,7 @@
hiro::mWindow* window;
NSMenu* menuBar;
NSMenu* rootMenu;
NSMenuItem* disableGatekeeperAutoRearm;
NSMenuItem* disableGatekeeper;
NSTextField* statusBar;
}
-(id) initWith:(hiro::mWindow&)window;

View File

@ -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;
}

View File

@ -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);
}

15
nall/macos/guard.hpp Normal file
View File

@ -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

View File

@ -99,6 +99,7 @@ struct serializer {
template<typename T, uint Size> auto array(nall::array<T[Size]>& array) -> serializer& {
for(auto& value : array) operator()(value);
return *this;
}
template<typename T> auto operator()(T& value, typename std::enable_if<has_serialize<T>::value>::type* = 0) -> serializer& { value.serialize(*this); return *this; }

View File

@ -18,12 +18,10 @@ using namespace ruby;
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#elif defined(DISPLAY_QUARTZ)
#define Boolean CocoaBoolean
#define decimal CocoaDecimal
#include <nall/macos/guard.hpp>
#include <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#undef Boolean
#undef decimal
#include <nall/macos/guard.hpp>
#elif defined(DISPLAY_WINDOWS)
#include <windows.h>
#endif