Update to v106r42 release.

byuu says:

Changelog:

  - emulator: added `Thread::setHandle(cothread_t)`
  - icarus: added special heuristics support for the Tengai Maykou Zero
    fan translation
      - board identifier is: EXSPC7110-RAM-EPSONRTC (match on SPC7110 +
        ROM size=56mbit)
      - board ROM contents are: 8mbit program, 40mbit data, 8mbit
        expansion (sizes are fixed)
  - bsnes: show messages on game load, unload, and reset
  - bsnes: added support for BS Memory and Sufami Turbo games
  - bsnes: added support for region selection (Auto [default], NTSC,
    PAL)
  - bsnes: correct presentation window size from 223/239 to 224/240
  - bsnes: add SA-1 internal RAM on cartridges with BS Memory slot
  - bsnes: fixed recovery state to store inside .bsz archive
  - bsnes: added support for custom manifests in both game pak and game
    ROM modes
  - bsnes: added icarus game database support (manifest → database →
    heuristics)
  - bsnes: added flexible SuperFX overclocking
  - bsnes: added IPS and BPS soft-patching support to all ROM types
    (sfc,smc,gb,gbc,bs,st)
      - can load patches inside of ZIP archives (matches first “.ips” or
        “.bps” file)
  - bsnes/ppu: cache interlace/overscan/vdisp (277 → 291fps with fast
    PPU)
  - hiro/Windows: faster painting of Label widget on expose
  - hiro/Windows: immediately apply LineEdit::setBackgroundColor changes
  - hiro/Qt: inherit Window backgroundColor when one is not assigned to
    Label

Errata:

  - sfc/ppu-fast: remove `renderMode7Hires()` function (the body isn't in
    the codebase)
  - bsnes: advanced note label should probably use a lighter text color
    and/or smaller font size instead of italics

I didn't test the soft-patching at all, as I don't have any patches on
my dev box. If anyone wants to test, that'd be great. The Tengai Makyou
Zero fan translation would be a great test case.
This commit is contained in:
Tim Allen 2018-06-26 13:17:26 +10:00
parent f70a20bc42
commit 5b97fa2415
36 changed files with 673 additions and 163 deletions

View File

@ -13,7 +13,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "106.41";
static const string Version = "106.42";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";

View File

@ -15,6 +15,10 @@ struct Thread {
inline auto scalar() const { return _scalar; }
inline auto clock() const { return _clock; }
auto setHandle(cothread_t handle) -> void {
_handle = handle;
}
auto setFrequency(double frequency) -> void {
_frequency = frequency + 0.5;
_scalar = Second / _frequency;
@ -45,7 +49,7 @@ struct Thread {
s.integer(_clock);
}
//protected:
protected:
cothread_t _handle = nullptr;
uintmax _frequency = 0;
uintmax _scalar = 0;

View File

@ -334,6 +334,12 @@ auto Cartridge::loadSA1(Markup::Node node) -> void {
auto Cartridge::loadSuperFX(Markup::Node node) -> void {
has.SuperFX = true;
if(auto oscillator = game.oscillator()) {
superfx.Frequency = oscillator->frequency; //GSU-1, GSU-2
} else {
superfx.Frequency = system.cpuFrequency(); //MARIO CHIP 1
}
for(auto map : node.find("map")) {
loadMap(map, {&SuperFX::readIO, &superfx}, {&SuperFX::writeIO, &superfx});
}

View File

@ -1,6 +1,6 @@
//SPC7110 decompressor
//original implementation: neviksti
//optimized implementation: cydrak
//optimized implementation: talarubi
struct Decompressor {
SPC7110& spc7110;

View File

@ -38,7 +38,7 @@ auto SuperFX::unload() -> void {
auto SuperFX::power() -> void {
GSU::power();
create(SuperFX::Enter, system.cpuFrequency());
create(SuperFX::Enter, Frequency);
rom.writeProtect(true);
ram.writeProtect(false);

View File

@ -59,6 +59,8 @@ struct SuperFX : Processor::GSU, Thread {
//serialization.cpp
auto serialize(serializer&) -> void;
uint Frequency;
CPUROM cpurom;
CPURAM cpuram;

View File

@ -583,6 +583,8 @@ auto PPU::writeIO(uint24 address, uint8 data) -> void {
}
auto PPU::updateVideoMode() -> void {
ppubase.display.vdisp = !io.overscan ? 225 : 240;
switch(io.bgMode) {
case 0:
io.bg1.tileMode = TileMode::BPP2;

View File

@ -1,9 +1,11 @@
#include <sfc/sfc.hpp>
#define PPU PPUfast
#define ppu ppufast
namespace SuperFamicom {
PPU& ppubase = ppu;
#define PPU PPUfast
#define ppu ppufast
PPU ppu;
#include "io.cpp"
#include "line.cpp"
@ -13,6 +15,11 @@ PPU ppu;
#include "window.cpp"
#include "serialization.cpp"
auto PPU::interlace() const -> bool { return ppubase.display.interlace; }
auto PPU::overscan() const -> bool { return ppubase.display.overscan; }
auto PPU::vdisp() const -> uint { return ppubase.display.vdisp; }
auto PPU::hires() const -> bool { return latch.hires; }
PPU::PPU() {
output = new uint32[512 * 512] + 16 * 512; //overscan offset
tilecache[TileMode::BPP2] = new uint8[4096 * 8 * 8];
@ -60,8 +67,8 @@ auto PPU::main() -> void {
auto PPU::scanline() -> void {
if(vcounter() == 0) {
latch.interlace = io.interlace;
latch.overscan = io.overscan;
ppubase.display.interlace = io.interlace;
ppubase.display.overscan = io.overscan;
latch.hires = false;
io.obj.timeOver = false;
io.obj.rangeOver = false;
@ -115,6 +122,7 @@ auto PPU::power(bool reset) -> void {
latch = {};
io = {};
updateVideoMode();
Line::start = 0;
Line::count = 0;

View File

@ -9,10 +9,10 @@
#define ppu ppufast
struct PPU : Thread, PPUcounter {
alwaysinline auto interlace() const -> bool { return latch.interlace; }
alwaysinline auto overscan() const -> bool { return latch.overscan; }
alwaysinline auto hires() const -> bool { return latch.hires; }
alwaysinline auto vdisp() const -> uint { return !io.overscan ? 225 : 240; }
alwaysinline auto interlace() const -> bool;
alwaysinline auto overscan() const -> bool;
alwaysinline auto vdisp() const -> uint;
alwaysinline auto hires() const -> bool;
//ppu.cpp
PPU();
@ -276,6 +276,7 @@ public:
//mode7.cpp
auto renderMode7(PPU::IO::Background&, uint source) -> void;
auto renderMode7Hires(PPU::IO::Background&, uint source) -> void;
//object.cpp
auto renderObject(PPU::IO::Object&) -> void;

View File

@ -631,6 +631,8 @@ auto PPU::writeIO(uint24 addr, uint8 data) -> void {
}
auto PPU::updateVideoMode() -> void {
display.vdisp = !io.overscan ? 225 : 240;
switch(io.bgMode) {
case 0:
bg1.io.mode = Background::Mode::BPP2;

View File

@ -26,7 +26,7 @@ bg4(Background::ID::BG4) {
PPU::~PPU() {
if(system.fastPPU()) {
_handle = nullptr;
setHandle(nullptr);
}
output -= 16 * 512;
@ -96,8 +96,7 @@ auto PPU::load(Markup::Node node) -> bool {
auto PPU::power(bool reset) -> void {
if(system.fastPPU()) {
ppufast.power(reset);
_handle = ppufast._handle;
return;
return setHandle(ppufast.handle());
}
create(Enter, system.cpuFrequency());
@ -202,6 +201,7 @@ auto PPU::power(bool reset) -> void {
window.power();
screen.power();
updateVideoMode();
frame();
}

View File

@ -1,11 +1,9 @@
#include <sfc/ppu-fast/ppu.hpp>
struct PPU : Thread, PPUcounter {
//ppu.cpp
alwaysinline auto interlace() const -> bool { if(system.fastPPU()) return ppufast.interlace(); return display.interlace; }
alwaysinline auto overscan() const -> bool { if(system.fastPPU()) return ppufast.overscan(); return display.overscan; }
alwaysinline auto vdisp() const -> uint { if(system.fastPPU()) return ppufast.vdisp(); return !io.overscan ? 225 : 240; }
alwaysinline auto interlace() const -> bool { return display.interlace; }
alwaysinline auto overscan() const -> bool { return display.overscan; }
alwaysinline auto vdisp() const -> uint { return display.vdisp; }
//ppu.cpp
PPU();
~PPU();
@ -47,6 +45,7 @@ private:
struct {
bool interlace;
bool overscan;
uint vdisp;
} display;
auto scanline() -> void;
@ -161,6 +160,7 @@ private:
friend class PPU::Window;
friend class PPU::Screen;
friend class System;
friend class PPUfast;
};
extern PPU ppu;

View File

@ -1,4 +1,8 @@
auto PPU::serialize(serializer& s) -> void {
s.integer(display.interlace);
s.integer(display.overscan);
s.integer(display.vdisp);
if(system.fastPPU()) {
return ppufast.serialize(s);
}
@ -15,9 +19,6 @@ auto PPU::serialize(serializer& s) -> void {
s.integer(ppu2.version);
s.integer(ppu2.mdr);
s.integer(display.interlace);
s.integer(display.overscan);
s.integer(latch.vram);
s.integer(latch.oam);
s.integer(latch.cgram);

View File

@ -54,6 +54,7 @@ namespace SuperFamicom {
#include <sfc/smp/smp.hpp>
#include <sfc/dsp/dsp.hpp>
#include <sfc/ppu/ppu.hpp>
#include <sfc/ppu-fast/ppu.hpp>
#include <sfc/controller/controller.hpp>
#include <sfc/expansion/expansion.hpp>

View File

@ -1,5 +1,5 @@
database
revision: 2018-06-01
revision: 2018-06-25
//Boards (Production)
@ -565,7 +565,7 @@ board: SHVC-YJ0N-01
//Boards (Generic)
database
revision: 2018-06-01
revision: 2018-06-25
board: ARM-LOROM-RAM
memory type=ROM content=Program
@ -703,6 +703,23 @@ board: EXNEC-LOROM
map address=68-6f,e8-ef:0000-7fff mask=0x8000
oscillator
board: EXSPC7110-RAM-EPSONRTC
memory type=ROM content=Expansion
map address=40-4f:0000-ffff
processor identifier=SPC7110
map address=00-3f,80-bf:4800-483f
map address=50,58:0000-ffff
mcu
map address=00-3f,80-bf:8000-ffff mask=0x800000
map address=c0-ff:0000-ffff mask=0xc00000
memory type=ROM content=Program
memory type=ROM content=Data
memory type=RAM content=Save
map address=00-3f,80-bf:6000-7fff mask=0xe000
rtc manufacturer=Epson
map address=00-3f,80-bf:4840-4842
memory type=RTC content=Time manufacturer=Epson
board: GB-LOROM
memory type=ROM content=Program
map address=00-7d,80-ff:8000-ffff mask=0x8000

View File

@ -9,6 +9,11 @@ auto locate(string name) -> string {
string location = {Path::program(), name};
if(inode::exists(location)) return location;
if(name.beginsWith("database/")) {
location = {Path::userData(), "icarus/", name};
if(inode::exists(location)) return location;
}
directory::create({Path::userData(), "bsnes/"});
return {Path::userData(), "bsnes/", name};
}

View File

@ -40,7 +40,7 @@ auto InputManager::bindHotkeys() -> void {
}));
hotkeys.append(InputHotkey("Reset Emulation").onPress([] {
presentation->resetSystem.doActivate();
program->reset();
}));
hotkeys.append(InputHotkey("Quit Emulator").onPress([] {

View File

@ -13,10 +13,7 @@ Presentation::Presentation() {
loadRecentGame.setText("Load Recent Game");
updateRecentGames();
resetSystem.setText("Reset System").setEnabled(false).onActivate([&] {
if(emulator->loaded()) {
program->applyHacks();
emulator->reset();
}
program->reset();
});
unloadGame.setText("Unload Game").setEnabled(false).onActivate([&] {
program->unload();
@ -241,7 +238,7 @@ auto Presentation::resizeViewport() -> void {
uint windowHeight = viewportLayout.geometry().height();
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0);
uint height = (settings["View/OverscanCropping"].boolean() ? 223.0 : 239.0);
uint height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0);
uint viewportWidth, viewportHeight;
if(settings["View/IntegralScaling"].boolean()) {
@ -270,7 +267,7 @@ auto Presentation::resizeViewport() -> void {
auto Presentation::resizeWindow() -> void {
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0);
uint height = (settings["View/OverscanCropping"].boolean() ? 223.0 : 239.0);
uint height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0);
uint statusHeight = settings["UserInterface/ShowStatusBar"].boolean() ? StatusHeight : 0;
uint multiplier = 2;

View File

@ -1,7 +1,19 @@
auto Program::openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({superNintendo.location, name}, mode);
auto Program::openPakSuperFamicom(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({superFamicom.location, name}, mode);
}
auto Program::openPakGB(string name, vfs::file::mode mode) -> vfs::shared::file {
auto Program::openPakGameBoy(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({gameBoy.location, name}, mode);
}
auto Program::openPakBSMemory(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({bsMemory.location, name}, mode);
}
auto Program::openPakSufamiTurboA(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({sufamiTurboA.location, name}, mode);
}
auto Program::openPakSufamiTurboB(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({sufamiTurboB.location, name}, mode);
}

View File

@ -1,137 +1,137 @@
auto Program::openRomSFC(string name, vfs::file::mode mode) -> vfs::shared::file {
auto Program::openRomSuperFamicom(string name, vfs::file::mode mode) -> vfs::shared::file {
if(name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.program.data(), superNintendo.program.size());
return vfs::memory::file::open(superFamicom.program.data(), superFamicom.program.size());
}
if(name == "data.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.data.data(), superNintendo.data.size());
return vfs::memory::file::open(superFamicom.data.data(), superFamicom.data.size());
}
if(name == "expansion.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.expansion.data(), superNintendo.expansion.size());
return vfs::memory::file::open(superFamicom.expansion.data(), superFamicom.expansion.size());
}
if(name == "arm6.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x00000], 0x20000);
if(superFamicom.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superFamicom.firmware.data()[0x00000], 0x20000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "arm6.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x20000], 0x08000);
if(superFamicom.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superFamicom.firmware.data()[0x20000], 0x08000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xc00) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
if(superFamicom.firmware.size() == 0xc00) {
return vfs::memory::file::open(superFamicom.firmware.data(), superFamicom.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "lr35902.boot.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x100) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
if(superFamicom.firmware.size() == 0x100) {
return vfs::memory::file::open(superFamicom.firmware.data(), superFamicom.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".boot.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd7725.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0x1800);
if(superFamicom.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superFamicom.firmware.data()[0x0000], 0x1800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd7725.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x1800], 0x0800);
if(superFamicom.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superFamicom.firmware.data()[0x1800], 0x0800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd96050.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0xc000);
if(superFamicom.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superFamicom.firmware.data()[0x0000], 0xc000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd96050.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0xc000], 0x1000);
if(superFamicom.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superFamicom.firmware.data()[0xc000], 0x1000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) {
if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "save.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
return vfs::fs::file::open(path("Saves", superFamicom.location, ".srm"), mode);
}
if(name == "download.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".psr"), mode);
return vfs::fs::file::open(path("Saves", superFamicom.location, ".psr"), mode);
}
if(name == "time.rtc") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".rtc"), mode);
return vfs::fs::file::open(path("Saves", superFamicom.location, ".rtc"), mode);
}
if(name == "arm6.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
return vfs::fs::file::open(path("Saves", superFamicom.location, ".srm"), mode);
}
if(name == "hg51bs169.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
return vfs::fs::file::open(path("Saves", superFamicom.location, ".srm"), mode);
}
if(name == "upd7725.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
return vfs::fs::file::open(path("Saves", superFamicom.location, ".srm"), mode);
}
if(name == "upd96050.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
return vfs::fs::file::open(path("Saves", superFamicom.location, ".srm"), mode);
}
if(name == "msu1/data.rom") {
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), ".msu"}, mode);
return vfs::fs::file::open({Location::notsuffix(superFamicom.location), ".msu"}, mode);
}
if(name.match("msu1/track-*.pcm")) {
name.trimLeft("msu1/track-", 1L);
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), name}, mode);
return vfs::fs::file::open({Location::notsuffix(superFamicom.location), name}, mode);
}
return {};
}
auto Program::openRomGB(string name, vfs::file::mode mode) -> vfs::shared::file {
auto Program::openRomGameBoy(string name, vfs::file::mode mode) -> vfs::shared::file {
if(name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size());
}
@ -146,3 +146,40 @@ auto Program::openRomGB(string name, vfs::file::mode mode) -> vfs::shared::file
return {};
}
auto Program::openRomBSMemory(string name, vfs::file::mode mode) -> vfs::shared::file {
if(name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(bsMemory.program.data(), bsMemory.program.size());
}
if(name == "program.flash" && mode == vfs::file::mode::read) {
//write mode is not supported for ROM mode
return vfs::memory::file::open(bsMemory.program.data(), bsMemory.program.size());
}
return {};
}
auto Program::openRomSufamiTurboA(string name, vfs::file::mode mode) -> vfs::shared::file {
if(name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(sufamiTurboA.program.data(), sufamiTurboA.program.size());
}
if(name == "save.ram") {
return vfs::fs::file::open(path("Saves", sufamiTurboA.location, ".srm"), mode);
}
return {};
}
auto Program::openRomSufamiTurboB(string name, vfs::file::mode mode) -> vfs::shared::file {
if(name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(sufamiTurboB.program.data(), sufamiTurboB.program.size());
}
if(name == "save.ram") {
return vfs::fs::file::open(path("Saves", sufamiTurboB.location, ".srm"), mode);
}
return {};
}

View File

@ -10,6 +10,7 @@ auto Program::load() -> void {
connectDevices();
applyHacks();
emulator->power();
showMessage(!appliedPatch() ? "Game loaded" : "Game loaded and patch applied");
presentation->setTitle(emulator->title());
presentation->resetSystem.setEnabled(true);
presentation->unloadGame.setEnabled(true);
@ -19,8 +20,11 @@ auto Program::load() -> void {
toolsWindow->cheatEditor.loadCheats();
toolsWindow->stateManager.loadStates();
string locations = superNintendo.location;
string locations = superFamicom.location;
if(auto location = gameBoy.location) locations.append("|", location);
if(auto location = bsMemory.location) locations.append("|", location);
if(auto location = sufamiTurboA.location) locations.append("|", location);
if(auto location = sufamiTurboB.location) locations.append("|", location);
presentation->addRecentGame(locations);
}
@ -44,86 +48,206 @@ auto Program::loadFile(string location) -> vector<uint8_t> {
}
}
auto Program::loadSuperNintendo(string location) -> void {
auto Program::loadSuperFamicom(string location) -> void {
string manifest;
vector<uint8_t> rom;
//game pak
if(location.endsWith("/")) {
rom.append(file::read({location, "program.rom" }));
rom.append(file::read({location, "data.rom" }));
manifest = file::read({location, "manifest.bml"});
rom.append(file::read({location, "program.rom"}));
rom.append(file::read({location, "data.rom"}));
rom.append(file::read({location, "expansion.rom"}));
for(auto filename : directory::files(location, "*.boot.rom" )) rom.append(file::read({location, filename}));
for(auto filename : directory::files(location, "*.program.rom")) rom.append(file::read({location, filename}));
for(auto filename : directory::files(location, "*.data.rom" )) rom.append(file::read({location, filename}));
} else {
//game ROM
manifest = file::read({Location::notsuffix(location), ".bml"});
rom = loadFile(location);
}
//Heuristics::SuperFamicom() call will remove copier header from rom if present
//assume ROM and IPS agree on whether a copier header is present
superFamicom.patched = applyPatchIPS(rom, location);
if((rom.size() & 0x7fff) == 512) {
//remove copier header
memory::move(&rom[0], &rom[512], rom.size() - 512);
rom.resize(rom.size() - 512);
}
//assume BPS is made against a ROM without a copier header
if(!superFamicom.patched) superFamicom.patched = applyPatchBPS(rom, location);
auto heuristics = Heuristics::SuperFamicom(rom, location);
superNintendo.label = heuristics.label();
superNintendo.manifest = heuristics.manifest();
superNintendo.document = BML::unserialize(superNintendo.manifest);
superNintendo.location = location;
auto sha256 = Hash::SHA256(rom).digest();
if(auto document = BML::unserialize(string::read(locate("database/Super Famicom.bml")))) {
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
manifest = BML::serialize(game);
}
}
superFamicom.label = heuristics.label();
superFamicom.manifest = manifest ? manifest : heuristics.manifest();
applyHackOverclockSuperFX();
superFamicom.document = BML::unserialize(superFamicom.manifest);
superFamicom.location = location;
uint offset = 0;
if(auto size = heuristics.programRomSize()) {
superNintendo.program.resize(size);
memory::copy(&superNintendo.program[0], &rom[offset], size);
superFamicom.program.resize(size);
memory::copy(&superFamicom.program[0], &rom[offset], size);
offset += size;
}
if(auto size = heuristics.dataRomSize()) {
superNintendo.data.resize(size);
memory::copy(&superNintendo.data[0], &rom[offset], size);
superFamicom.data.resize(size);
memory::copy(&superFamicom.data[0], &rom[offset], size);
offset += size;
}
if(auto size = heuristics.expansionRomSize()) {
superNintendo.expansion.resize(size);
memory::copy(&superNintendo.expansion[0], &rom[offset], size);
superFamicom.expansion.resize(size);
memory::copy(&superFamicom.expansion[0], &rom[offset], size);
offset += size;
}
if(auto size = heuristics.firmwareRomSize()) {
superNintendo.firmware.resize(size);
memory::copy(&superNintendo.firmware[0], &rom[offset], size);
superFamicom.firmware.resize(size);
memory::copy(&superFamicom.firmware[0], &rom[offset], size);
offset += size;
}
}
auto Program::loadGameBoy(string location) -> void {
string manifest;
vector<uint8_t> rom;
//game pak
if(location.endsWith("/")) {
manifest = file::read({location, "manifest.bml"});
rom.append(file::read({location, "program.rom"}));
} else {
manifest = file::read({Location::notsuffix(location), ".bml"});
rom = loadFile(location);
}
gameBoy.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location);
auto heuristics = Heuristics::GameBoy(rom, location);
gameBoy.manifest = heuristics.manifest();
auto sha256 = Hash::SHA256(rom).digest();
if(auto document = BML::unserialize(string::read(locate("database/Game Boy.bml")))) {
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
manifest = BML::serialize(game);
}
}
if(auto document = BML::unserialize(string::read(locate("database/Game Boy Color.bml")))) {
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
manifest = BML::serialize(game);
}
}
gameBoy.manifest = manifest ? manifest : heuristics.manifest();
gameBoy.document = BML::unserialize(gameBoy.manifest);
gameBoy.location = location;
gameBoy.program = rom;
}
auto Program::loadBSMemory(string location) -> void {
string manifest;
vector<uint8_t> rom;
if(location.endsWith("/")) {
manifest = file::read({location, "manifest.bml"});
rom.append(file::read({location, "program.rom"}));
rom.append(file::read({location, "program.flash"}));
} else {
manifest = file::read({Location::notsuffix(location), ".bml"});
rom = loadFile(location);
}
bsMemory.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location);
auto heuristics = Heuristics::BSMemory(rom, location);
auto sha256 = Hash::SHA256(rom).digest();
if(auto document = BML::unserialize(string::read(locate("database/BS Memory.bml")))) {
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
manifest = BML::serialize(game);
}
}
bsMemory.manifest = manifest ? manifest : heuristics.manifest();
bsMemory.document = BML::unserialize(bsMemory.manifest);
bsMemory.location = location;
bsMemory.program = rom;
}
auto Program::loadSufamiTurboA(string location) -> void {
string manifest;
vector<uint8_t> rom;
if(location.endsWith("/")) {
manifest = file::read({location, "manifest.bml"});
rom.append(file::read({location, "program.rom"}));
} else {
manifest = file::read({Location::notsuffix(location), ".bml"});
rom = loadFile(location);
}
sufamiTurboA.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location);
auto heuristics = Heuristics::SufamiTurbo(rom, location);
auto sha256 = Hash::SHA256(rom).digest();
if(auto document = BML::unserialize(string::read(locate("database/Sufami Turbo.bml")))) {
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
manifest = BML::serialize(game);
}
}
sufamiTurboA.manifest = manifest ? manifest : heuristics.manifest();
sufamiTurboA.document = BML::unserialize(sufamiTurboA.manifest);
sufamiTurboA.location = location;
sufamiTurboA.program = rom;
}
auto Program::loadSufamiTurboB(string location) -> void {
string manifest;
vector<uint8_t> rom;
if(location.endsWith("/")) {
manifest = file::read({location, "manifest.bml"});
rom.append(file::read({location, "program.rom"}));
} else {
manifest = file::read({Location::notsuffix(location), ".bml"});
rom = loadFile(location);
}
sufamiTurboB.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location);
auto heuristics = Heuristics::SufamiTurbo(rom, location);
auto sha256 = Hash::SHA256(rom).digest();
if(auto document = BML::unserialize(string::read(locate("database/Sufami Turbo.bml")))) {
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
manifest = BML::serialize(game);
}
}
sufamiTurboB.manifest = manifest ? manifest : heuristics.manifest();
sufamiTurboB.document = BML::unserialize(sufamiTurboB.manifest);
sufamiTurboB.location = location;
sufamiTurboB.program = rom;
}
auto Program::save() -> void {
if(!emulator->loaded()) return;
emulator->save();
}
auto Program::reset() -> void {
if(!emulator->loaded()) return;
applyHacks();
emulator->reset();
showMessage("Game reset");
}
auto Program::unload() -> void {
if(!emulator->loaded()) return;
toolsWindow->cheatEditor.saveCheats();
toolsWindow->setVisible(false);
saveRecoveryState();
emulator->unload();
superNintendo = {};
showMessage("Game unloaded");
superFamicom = {};
gameBoy = {};
bsMemory = {};
sufamiTurbo[0] = {};
sufamiTurbo[1] = {};
sufamiTurboA = {};
sufamiTurboB = {};
presentation->setTitle({"bsnes v", Emulator::Version});
presentation->resetSystem.setEnabled(false);
presentation->unloadGame.setEnabled(false);

View File

@ -0,0 +1,40 @@
auto Program::applyHacks() -> void {
bool fastPPU = settingsWindow->advanced.fastPPUOption.checked();
bool fastDSP = settingsWindow->advanced.fastDSPOption.checked();
auto label = superFamicom.label;
if(label == "AIR STRIKE PATROL" || label == "DESERT FIGHTER") fastPPU = false;
if(label == "KOUSHIEN_2") fastDSP = false;
if(label == "RENDERING RANGER R2") fastDSP = false;
emulator->set("Fast PPU", fastPPU);
emulator->set("Fast DSP", fastDSP);
}
auto Program::applyHackOverclockSuperFX() -> void {
//todo: implement a better way of detecting SuperFX games
//todo: apply multiplier changes on reset, not just on game load?
double multiplier = settingsWindow->advanced.superFXValue.text().natural() / 100.0;
auto label = superFamicom.label;
if(label == "NIDAN MORITASHOGI2") return; //ST018 uses same clock speed as SuperFX
auto document = BML::unserialize(superFamicom.manifest);
//GSU-1, GSU-2 have a 21440000hz oscillator
if(auto oscillator = document["game/board/oscillator"]) {
if(oscillator["frequency"].text() == "21440000") {
oscillator["frequency"].setValue(uint(21440000 * multiplier));
superFamicom.manifest = BML::serialize(document);
}
return;
}
//MARIO CHIP 1 uses CPU oscillator; force it to use its own crystal to overclock it
bool marioChip1 = false;
if(label == "STAR FOX" || label == "STAR WING") marioChip1 = true;
if(marioChip1) {
document("game/board/oscillator/frequency").setValue(uint(21440000 * multiplier));
superFamicom.manifest = BML::serialize(document);
}
}

View File

@ -24,11 +24,11 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
if(id == 1) { //Super Famicom
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(superNintendo.manifest.data<uint8_t>(), superNintendo.manifest.size());
} else if(superNintendo.location.endsWith("/")) {
result = openPakSFC(name, mode);
result = vfs::memory::file::open(superFamicom.manifest.data<uint8_t>(), superFamicom.manifest.size());
} else if(superFamicom.location.endsWith("/")) {
result = openPakSuperFamicom(name, mode);
} else {
result = openRomSFC(name, mode);
result = openRomSuperFamicom(name, mode);
}
}
@ -36,9 +36,39 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(gameBoy.manifest.data<uint8_t>(), gameBoy.manifest.size());
} else if(gameBoy.location.endsWith("/")) {
result = openPakGB(name, mode);
result = openPakGameBoy(name, mode);
} else {
result = openRomGB(name, mode);
result = openRomGameBoy(name, mode);
}
}
if(id == 3) { //BS Memory
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(bsMemory.manifest.data<uint8_t>(), bsMemory.manifest.size());
} else if(bsMemory.location.endsWith("/")) {
result = openPakBSMemory(name, mode);
} else {
result = openRomBSMemory(name, mode);
}
}
if(id == 4) { //Sufami Turbo - Slot A
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(sufamiTurboA.manifest.data<uint8_t>(), sufamiTurboA.manifest.size());
} else if(sufamiTurboA.location.endsWith("/")) {
result = openPakSufamiTurboA(name, mode);
} else {
result = openRomSufamiTurboA(name, mode);
}
}
if(id == 5) { //Sufami Turbo - Slot B
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(sufamiTurboB.manifest.data<uint8_t>(), sufamiTurboB.manifest.size());
} else if(sufamiTurboB.location.endsWith("/")) {
result = openPakSufamiTurboB(name, mode);
} else {
result = openRomSufamiTurboB(name, mode);
}
}
@ -50,20 +80,22 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
}
auto Program::load(uint id, string name, string type, string_vector options) -> Emulator::Platform::Load {
BrowserDialog dialog;
dialog.setOptions(options);
if(id == 1 && name == "Super Famicom" && type == "sfc") {
if(gameQueue) {
superNintendo.location = gameQueue.takeLeft();
superFamicom.location = gameQueue.takeLeft();
} else {
BrowserDialog dialog;
dialog.setTitle("Load Super Nintendo");
dialog.setPath(path("Games", settings["Path/Recent/SuperNintendo"].text()));
dialog.setFilters({string{"Super Nintendo Games|*.sfc:*.smc:*.zip"}});
superNintendo.location = dialog.openObject();
dialog.setTitle("Load Super Famicom");
dialog.setPath(path("Games", settings["Path/Recent/SuperFamicom"].text()));
dialog.setFilters({string{"Super Famicom Games|*.sfc:*.smc:*.zip"}});
superFamicom.location = dialog.openObject();
}
if(inode::exists(superNintendo.location)) {
settings["Path/Recent/SuperNintendo"].setValue(Location::dir(superNintendo.location));
loadSuperNintendo(superNintendo.location);
return {id, ""};
if(inode::exists(superFamicom.location)) {
settings["Path/Recent/SuperFamicom"].setValue(Location::dir(superFamicom.location));
loadSuperFamicom(superFamicom.location);
return {id, dialog.option()};
}
}
@ -71,7 +103,6 @@ auto Program::load(uint id, string name, string type, string_vector options) ->
if(gameQueue) {
gameBoy.location = gameQueue.takeLeft();
} else {
BrowserDialog dialog;
dialog.setTitle("Load Game Boy");
dialog.setPath(path("Games", settings["Path/Recent/GameBoy"].text()));
dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}});
@ -80,7 +111,55 @@ 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, ""};
return {id, dialog.option()};
}
}
if(id == 3 && name == "BS Memory" && type == "bs") {
if(gameQueue) {
bsMemory.location = gameQueue.takeLeft();
} else {
dialog.setTitle("Load BS Memory");
dialog.setPath(path("Games", settings["Path/Recent/BSMemory"].text()));
dialog.setFilters({string{"BS Memory Games|*.bs:*.zip"}});
bsMemory.location = dialog.openObject();
}
if(inode::exists(bsMemory.location)) {
settings["Path/Recent/BSMemory"].setValue(Location::dir(bsMemory.location));
loadBSMemory(bsMemory.location);
return {id, dialog.option()};
}
}
if(id == 4 && name == "Sufami Turbo" && type == "st") {
if(gameQueue) {
sufamiTurboA.location = gameQueue.takeLeft();
} else {
dialog.setTitle("Load Sufami Turbo - Slot A");
dialog.setPath(path("Games", settings["Path/Recent/SufamiTurboA"].text()));
dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}});
sufamiTurboA.location = dialog.openObject();
}
if(inode::exists(sufamiTurboA.location)) {
settings["Path/Recent/SufamiTurboA"].setValue(Location::dir(sufamiTurboA.location));
loadSufamiTurboA(sufamiTurboA.location);
return {id, dialog.option()};
}
}
if(id == 5 && name == "Sufami Turbo" && type == "st") {
if(gameQueue) {
sufamiTurboB.location = gameQueue.takeLeft();
} else {
dialog.setTitle("Load Sufami Turbo - Slot B");
dialog.setPath(path("Games", settings["Path/Recent/SufamiTurboB"].text()));
dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}});
sufamiTurboB.location = dialog.openObject();
}
if(inode::exists(sufamiTurboB.location)) {
settings["Path/Recent/SufamiTurboB"].setValue(Location::dir(sufamiTurboB.location));
loadSufamiTurboB(sufamiTurboB.location);
return {id, dialog.option()};
}
}

View File

@ -0,0 +1,128 @@
auto Program::appliedPatch() const -> bool {
return (
superFamicom.patched
|| gameBoy.patched
|| bsMemory.patched
|| sufamiTurboA.patched
|| sufamiTurboB.patched
);
}
auto Program::applyPatchIPS(vector<uint8_t>& data, string location) -> bool {
vector<uint8_t> patch;
if(location.endsWith("/")) {
patch = file::read({location, "patch.ips"});
} else if(location.iendsWith(".zip")) {
Decode::ZIP archive;
if(archive.open(location)) {
for(auto& file : archive.file) {
if(file.name.iendsWith(".ips")) {
patch = archive.extract(file);
break;
}
}
}
if(!patch) patch = file::read(locate(path("Patches", location, ".ips")));
} else {
patch = file::read(locate(path("Patches", location, ".ips")));
}
if(!patch) return false;
//sanity checks
if(patch.size() < 8) return false;
if(patch[0] != 'P') return false;
if(patch[1] != 'A') return false;
if(patch[2] != 'T') return false;
if(patch[3] != 'C') return false;
if(patch[4] != 'H') return false;
for(uint index = 5;;) {
if(index == patch.size() - 6) {
if(patch[index + 0] == 'E' && patch[index + 1] == 'O' && patch[index + 2] == 'F') {
uint32_t truncate = 0;
truncate |= patch[index + 3] << 16;
truncate |= patch[index + 4] << 8;
truncate |= patch[index + 5] << 0;
data.resize(truncate);
return true;
}
}
if(index == patch.size() - 3) {
if(patch[index + 0] == 'E' && patch[index + 1] == 'O' && patch[index + 2] == 'F') {
return true;
}
}
if(index >= patch.size()) break;
uint32_t offset = 0;
offset |= patch(index++, 0) << 16;
offset |= patch(index++, 0) << 8;
offset |= patch(index++, 0) << 0;
uint16_t length = 0;
length |= patch(index++, 0) << 8;
length |= patch(index++, 0) << 0;
if(length == 0) {
uint16_t repeat = 0;
repeat |= patch(index++, 0) << 8;
repeat |= patch(index++, 0) << 0;
uint8_t fill = patch(index++, 0);
while(repeat--) data(offset++) = fill;
} else {
while(length--) data(offset++) = patch(index++, 0);
}
}
//"EOF" marker not found in correct place
//technically should return false, but be permissive (data was already modified)
return true;
}
#include <nall/beat/patch.hpp>
auto Program::applyPatchBPS(vector<uint8_t>& input, string location) -> bool {
vector<uint8_t> patch;
if(location.endsWith("/")) {
patch = file::read({location, "patch.bps"});
} else if(location.iendsWith(".zip")) {
Decode::ZIP archive;
if(archive.open(location)) {
for(auto& file : archive.file) {
if(file.name.iendsWith(".bps")) {
patch = archive.extract(file);
break;
}
}
}
if(!patch) patch = file::read(locate(path("Patches", location, ".bps")));
} else {
patch = file::read(locate(path("Patches", location, ".bps")));
}
if(!patch) return false;
bpspatch beat;
beat.modify(patch.data(), patch.size());
beat.source(input.data(), input.size());
vector<uint8_t> output;
output.resize(beat.size());
beat.target(output.data(), output.size());
auto result = beat.apply();
if(result == bpspatch::result::success) {
input = output;
return true;
}
MessageDialog(
"Error: patch checksum failure.\n\n"
"Please ensure you are using the correct (headerless) ROM for this patch."
).setParent(*presentation).error();
return false;
}

View File

@ -40,7 +40,7 @@ auto Program::path(string type, string location, string extension) -> string {
auto Program::gamePath() -> string {
if(!emulator->loaded()) return "";
if(gameBoy.location) return gameBoy.location;
return superNintendo.location;
return superFamicom.location;
}
auto Program::cheatPath() -> string {

View File

@ -6,6 +6,8 @@
#include "paths.cpp"
#include "states.cpp"
#include "utility.cpp"
#include "patch.cpp"
#include "hacks.cpp"
unique_pointer<Program> program;
Program::Program(string_vector arguments) {

View File

@ -15,18 +15,28 @@ struct Program : Emulator::Platform {
//game.cpp
auto load() -> void;
auto loadFile(string location) -> vector<uint8_t>;
auto loadSuperNintendo(string location) -> void;
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 save() -> void;
auto reset() -> void;
auto unload() -> void;
//game-pak.cpp
auto openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openPakGB(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openPakSuperFamicom(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openPakGameBoy(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openPakBSMemory(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openPakSufamiTurboA(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openPakSufamiTurboB(string name, vfs::file::mode mode) -> vfs::shared::file;
//game-rom.cpp
auto openRomSFC(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openRomGB(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openRomSuperFamicom(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openRomGameBoy(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openRomBSMemory(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openRomSufamiTurboA(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openRomSufamiTurboB(string name, vfs::file::mode mode) -> vfs::shared::file;
//paths.cpp
auto path(string type, string location, string extension = "") -> string;
@ -45,14 +55,22 @@ struct Program : Emulator::Platform {
auto initializeInputDriver() -> void;
auto updateVideoShader() -> void;
auto connectDevices() -> void;
auto applyHacks() -> void;
auto showMessage(string text) -> void;
auto showFrameRate(string text) -> void;
auto updateStatus() -> void;
auto focused() -> bool;
//patch.cpp
auto appliedPatch() const -> bool;
auto applyPatchIPS(vector<uint8_t>& data, string location) -> bool;
auto applyPatchBPS(vector<uint8_t>& data, string location) -> bool;
//hacks.cpp
auto applyHacks() -> void;
auto applyHackOverclockSuperFX() -> void;
public:
struct SuperNintendo {
struct SuperFamicom {
string label;
string location;
string manifest;
@ -61,20 +79,32 @@ public:
vector<uint8_t> data;
vector<uint8_t> expansion;
vector<uint8_t> firmware;
} superNintendo;
boolean patched;
} superFamicom;
struct GameBoy {
string location;
string manifest;
Markup::Node document;
vector<uint8_t> program;
boolean patched;
} gameBoy;
struct BSMemory {
string location;
string manifest;
Markup::Node document;
vector<uint8_t> program;
boolean patched;
} bsMemory;
struct SufamiTurbo {
} sufamiTurbo[2];
string location;
string manifest;
Markup::Node document;
vector<uint8_t> program;
boolean patched;
} sufamiTurboA, sufamiTurboB;
string_vector gameQueue;

View File

@ -70,11 +70,9 @@ auto Program::saveState(string filename) -> bool {
}
auto Program::saveRecoveryState() -> bool {
if(!emulator->loaded()) return false;
string location = {statePath(), "quick/recovery.bst"};
directory::create(Location::path(location));
serializer s = emulator->serialize();
if(!s.size()) return false;
if(!file::write(location, s.data(), s.size())) return false;
return true;
auto statusTime = this->statusTime;
auto statusMessage = this->statusMessage;
saveState("quick/recovery");
this->statusTime = statusTime;
this->statusMessage = statusMessage;
}

View File

@ -65,19 +65,6 @@ auto Program::connectDevices() -> void {
}
}
auto Program::applyHacks() -> void {
bool fastPPU = settingsWindow->advanced.fastPPUOption.checked();
bool fastDSP = settingsWindow->advanced.fastDSPOption.checked();
auto label = superNintendo.label;
if(label == "AIR STRIKE PATROL" || label == "DESERT FIGHTER") fastPPU = false;
if(label == "KOUSHIEN_2") fastDSP = false;
if(label == "RENDERING RANGER R2") fastDSP = false;
emulator->set("Fast PPU", fastPPU);
emulator->set("Fast DSP", fastDSP);
}
auto Program::showMessage(string text) -> void {
statusTime = chrono::timestamp();
statusMessage = text;

View File

@ -110,11 +110,18 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
}
});
emulatorLabel.setText("Emulator").setFont(Font().setBold());
fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/FastPPU"].boolean()).onToggle([&] {
settings["Emulator/FastPPU"].setValue(fastPPUOption.checked());
hacksLabel.setText("Emulator Hacks").setFont(Font().setBold());
fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/Hack/FastPPU"].boolean()).onToggle([&] {
settings["Emulator/Hack/FastPPU"].setValue(fastPPUOption.checked());
});
fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/FastDSP"].boolean()).onToggle([&] {
settings["Emulator/FastDSP"].setValue(fastDSPOption.checked());
fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/Hack/FastDSP"].boolean()).onToggle([&] {
settings["Emulator/Hack/FastDSP"].setValue(fastDSPOption.checked());
});
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.setFont(Font().setItalic()).setText("Note: hack setting changes do not take effect until after reloading games.");
}

View File

@ -45,15 +45,17 @@ Settings::Settings() {
set("Path/Saves", "");
set("Path/Cheats", "");
set("Path/States", "");
set("Path/Recent/SuperNintendo", Path::user());
set("Path/Recent/SuperFamicom", Path::user());
set("Path/Recent/GameBoy", Path::user());
set("Path/Recent/BSMemory", Path::user());
set("Path/Recent/SufamiTurbo", Path::user());
set("Path/Recent/SufamiTurboA", Path::user());
set("Path/Recent/SufamiTurboB", Path::user());
set("UserInterface/ShowStatusBar", true);
set("Emulator/FastPPU", true);
set("Emulator/FastDSP", true);
set("Emulator/Hack/FastPPU", true);
set("Emulator/Hack/FastDSP", true);
set("Emulator/Hack/FastSuperFX", "100%");
set("Emulator/AutoSaveMemory/Enable", true);
set("Emulator/AutoSaveMemory/Interval", 30);

View File

@ -115,9 +115,14 @@ public:
ComboButton audioDriverOption{&driverLayout, Size{~0, 0}};
Label inputDriverLabel{&driverLayout, Size{0, 0}};
ComboButton inputDriverOption{&driverLayout, Size{~0, 0}};
Label emulatorLabel{&layout, Size{~0, 0}, 2};
Label hacksLabel{&layout, Size{~0, 0}, 2};
CheckLabel fastPPUOption{&layout, Size{~0, 0}};
CheckLabel fastDSPOption{&layout, Size{~0, 0}};
HorizontalLayout superFXLayout{&layout, Size{~0, 0}};
Label superFXLabel{&superFXLayout, Size{0, 0}};
Label superFXValue{&superFXLayout, Size{50, 0}};
HorizontalSlider superFXClock{&superFXLayout, Size{~0, 0}};
Label hacksNote{&layout, Size{~0, 0}};
};
struct SettingsWindow : Window {

View File

@ -28,6 +28,7 @@ auto pLabel::setAlignment(Alignment alignment) -> void {
}
auto pLabel::setBackgroundColor(Color color) -> void {
if(!color) color = self().parentWindow(true)->backgroundColor();
if(color) {
QPalette palette = qtLabel->palette();
palette.setColor(QPalette::Base, QColor(color.red(), color.green(), color.blue()));

View File

@ -45,7 +45,7 @@ static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
switch(msg) {
case WM_GETDLGCODE: return DLGC_STATIC | DLGC_WANTCHARS;
case WM_ERASEBKGND: return true;
case WM_ERASEBKGND:
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
@ -96,7 +96,8 @@ static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
DeleteObject(hbmMemory);
DeleteObject(hdcMemory);
EndPaint(hwnd, &ps);
return false;
return msg == WM_ERASEBKGND;
}
}

View File

@ -28,6 +28,7 @@ auto pLineEdit::minimumSize() const -> Size {
auto pLineEdit::setBackgroundColor(Color color) -> void {
if(backgroundBrush) { DeleteObject(backgroundBrush); backgroundBrush = 0; }
backgroundBrush = CreateSolidBrush(color ? CreateRGB(color) : GetSysColor(COLOR_WINDOW));
InvalidateRect(hwnd, 0, true);
}
auto pLineEdit::setEditable(bool editable) -> void {

View File

@ -74,8 +74,17 @@ auto SuperFamicom::manifest() const -> string {
auto board = this->board().trimRight("#A", 1L).trimRight("#B", 1L).split("-");
if(auto size = romSize()) {
if(board(0) == "SPC7110") size = 0x100000;
output.append(Memory{}.type("ROM").size(size).content("Program").text());
if(board(0) == "SPC7110" && size > 0x100000) {
output.append(Memory{}.type("ROM").size(0x100000).content("Program").text());
output.append(Memory{}.type("ROM").size(size - 0x100000).content("Data").text());
} else if(board(0) == "EXSPC7110" && size == 0x700000) {
//Tengai Maykou Zero (fan translation)
output.append(Memory{}.type("ROM").size(0x100000).content("Program").text());
output.append(Memory{}.type("ROM").size(0x500000).content("Data").text());
output.append(Memory{}.type("ROM").size(0x100000).content("Expansion").text());
} else {
output.append(Memory{}.type("ROM").size(size).content("Program").text());
}
}
if(auto size = ramSize()) {
@ -115,10 +124,8 @@ auto SuperFamicom::manifest() const -> string {
output.append(Memory{}.type("ROM").size( 0x800).content("Data" ).manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).text());
output.append(Memory{}.type("RAM").size( 0x200).content("Data" ).manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).isVolatile().text());
output.append(Oscillator{}.frequency(7'600'000).text());
} else if(board(0) == "SA1") {
} else if(board(0) == "SA1" || board(1) == "SA1") { //SA1-* or BS-SA1-*
output.append(Memory{}.type("RAM").size(0x800).content("Internal").isVolatile().text());
} else if(board(0) == "SPC7110") {
output.append(Memory{}.type("ROM").size(romSize() - 0x100000).content("Data").text());
}
if(board.right() == "EPSONRTC" || board.right() == "SHARPRTC") {
@ -277,6 +284,9 @@ auto SuperFamicom::board() const -> string {
if(board.beginsWith( "LOROM-RAM")) board.append(romSize() <= 0x200000 ? "#A" : "#B");
if(board.beginsWith("NEC-LOROM-RAM")) board.append(romSize() <= 0x100000 ? "#A" : "#B");
//Tengai Makyou Zero (fan translation)
if(board.beginsWith("SPC7110-") && data.size() == 0x700000) board.prepend("EX");
return board;
}