mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
f70a20bc42
commit
5b97fa2415
|
@ -13,7 +13,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
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 Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "https://byuu.org/";
|
static const string Website = "https://byuu.org/";
|
||||||
|
|
|
@ -15,6 +15,10 @@ struct Thread {
|
||||||
inline auto scalar() const { return _scalar; }
|
inline auto scalar() const { return _scalar; }
|
||||||
inline auto clock() const { return _clock; }
|
inline auto clock() const { return _clock; }
|
||||||
|
|
||||||
|
auto setHandle(cothread_t handle) -> void {
|
||||||
|
_handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
auto setFrequency(double frequency) -> void {
|
auto setFrequency(double frequency) -> void {
|
||||||
_frequency = frequency + 0.5;
|
_frequency = frequency + 0.5;
|
||||||
_scalar = Second / _frequency;
|
_scalar = Second / _frequency;
|
||||||
|
@ -45,7 +49,7 @@ struct Thread {
|
||||||
s.integer(_clock);
|
s.integer(_clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
//protected:
|
protected:
|
||||||
cothread_t _handle = nullptr;
|
cothread_t _handle = nullptr;
|
||||||
uintmax _frequency = 0;
|
uintmax _frequency = 0;
|
||||||
uintmax _scalar = 0;
|
uintmax _scalar = 0;
|
||||||
|
|
|
@ -334,6 +334,12 @@ auto Cartridge::loadSA1(Markup::Node node) -> void {
|
||||||
auto Cartridge::loadSuperFX(Markup::Node node) -> void {
|
auto Cartridge::loadSuperFX(Markup::Node node) -> void {
|
||||||
has.SuperFX = true;
|
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")) {
|
for(auto map : node.find("map")) {
|
||||||
loadMap(map, {&SuperFX::readIO, &superfx}, {&SuperFX::writeIO, &superfx});
|
loadMap(map, {&SuperFX::readIO, &superfx}, {&SuperFX::writeIO, &superfx});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//SPC7110 decompressor
|
//SPC7110 decompressor
|
||||||
//original implementation: neviksti
|
//original implementation: neviksti
|
||||||
//optimized implementation: cydrak
|
//optimized implementation: talarubi
|
||||||
|
|
||||||
struct Decompressor {
|
struct Decompressor {
|
||||||
SPC7110& spc7110;
|
SPC7110& spc7110;
|
||||||
|
|
|
@ -38,7 +38,7 @@ auto SuperFX::unload() -> void {
|
||||||
|
|
||||||
auto SuperFX::power() -> void {
|
auto SuperFX::power() -> void {
|
||||||
GSU::power();
|
GSU::power();
|
||||||
create(SuperFX::Enter, system.cpuFrequency());
|
create(SuperFX::Enter, Frequency);
|
||||||
|
|
||||||
rom.writeProtect(true);
|
rom.writeProtect(true);
|
||||||
ram.writeProtect(false);
|
ram.writeProtect(false);
|
||||||
|
|
|
@ -59,6 +59,8 @@ struct SuperFX : Processor::GSU, Thread {
|
||||||
//serialization.cpp
|
//serialization.cpp
|
||||||
auto serialize(serializer&) -> void;
|
auto serialize(serializer&) -> void;
|
||||||
|
|
||||||
|
uint Frequency;
|
||||||
|
|
||||||
CPUROM cpurom;
|
CPUROM cpurom;
|
||||||
CPURAM cpuram;
|
CPURAM cpuram;
|
||||||
|
|
||||||
|
|
|
@ -583,6 +583,8 @@ auto PPU::writeIO(uint24 address, uint8 data) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::updateVideoMode() -> void {
|
auto PPU::updateVideoMode() -> void {
|
||||||
|
ppubase.display.vdisp = !io.overscan ? 225 : 240;
|
||||||
|
|
||||||
switch(io.bgMode) {
|
switch(io.bgMode) {
|
||||||
case 0:
|
case 0:
|
||||||
io.bg1.tileMode = TileMode::BPP2;
|
io.bg1.tileMode = TileMode::BPP2;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#include <sfc/sfc.hpp>
|
#include <sfc/sfc.hpp>
|
||||||
#define PPU PPUfast
|
|
||||||
#define ppu ppufast
|
|
||||||
|
|
||||||
namespace SuperFamicom {
|
namespace SuperFamicom {
|
||||||
|
|
||||||
|
PPU& ppubase = ppu;
|
||||||
|
#define PPU PPUfast
|
||||||
|
#define ppu ppufast
|
||||||
|
|
||||||
PPU ppu;
|
PPU ppu;
|
||||||
#include "io.cpp"
|
#include "io.cpp"
|
||||||
#include "line.cpp"
|
#include "line.cpp"
|
||||||
|
@ -13,6 +15,11 @@ PPU ppu;
|
||||||
#include "window.cpp"
|
#include "window.cpp"
|
||||||
#include "serialization.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() {
|
PPU::PPU() {
|
||||||
output = new uint32[512 * 512] + 16 * 512; //overscan offset
|
output = new uint32[512 * 512] + 16 * 512; //overscan offset
|
||||||
tilecache[TileMode::BPP2] = new uint8[4096 * 8 * 8];
|
tilecache[TileMode::BPP2] = new uint8[4096 * 8 * 8];
|
||||||
|
@ -60,8 +67,8 @@ auto PPU::main() -> void {
|
||||||
|
|
||||||
auto PPU::scanline() -> void {
|
auto PPU::scanline() -> void {
|
||||||
if(vcounter() == 0) {
|
if(vcounter() == 0) {
|
||||||
latch.interlace = io.interlace;
|
ppubase.display.interlace = io.interlace;
|
||||||
latch.overscan = io.overscan;
|
ppubase.display.overscan = io.overscan;
|
||||||
latch.hires = false;
|
latch.hires = false;
|
||||||
io.obj.timeOver = false;
|
io.obj.timeOver = false;
|
||||||
io.obj.rangeOver = false;
|
io.obj.rangeOver = false;
|
||||||
|
@ -115,6 +122,7 @@ auto PPU::power(bool reset) -> void {
|
||||||
|
|
||||||
latch = {};
|
latch = {};
|
||||||
io = {};
|
io = {};
|
||||||
|
updateVideoMode();
|
||||||
|
|
||||||
Line::start = 0;
|
Line::start = 0;
|
||||||
Line::count = 0;
|
Line::count = 0;
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
#define ppu ppufast
|
#define ppu ppufast
|
||||||
|
|
||||||
struct PPU : Thread, PPUcounter {
|
struct PPU : Thread, PPUcounter {
|
||||||
alwaysinline auto interlace() const -> bool { return latch.interlace; }
|
alwaysinline auto interlace() const -> bool;
|
||||||
alwaysinline auto overscan() const -> bool { return latch.overscan; }
|
alwaysinline auto overscan() const -> bool;
|
||||||
alwaysinline auto hires() const -> bool { return latch.hires; }
|
alwaysinline auto vdisp() const -> uint;
|
||||||
alwaysinline auto vdisp() const -> uint { return !io.overscan ? 225 : 240; }
|
alwaysinline auto hires() const -> bool;
|
||||||
|
|
||||||
//ppu.cpp
|
//ppu.cpp
|
||||||
PPU();
|
PPU();
|
||||||
|
@ -276,6 +276,7 @@ public:
|
||||||
|
|
||||||
//mode7.cpp
|
//mode7.cpp
|
||||||
auto renderMode7(PPU::IO::Background&, uint source) -> void;
|
auto renderMode7(PPU::IO::Background&, uint source) -> void;
|
||||||
|
auto renderMode7Hires(PPU::IO::Background&, uint source) -> void;
|
||||||
|
|
||||||
//object.cpp
|
//object.cpp
|
||||||
auto renderObject(PPU::IO::Object&) -> void;
|
auto renderObject(PPU::IO::Object&) -> void;
|
||||||
|
|
|
@ -631,6 +631,8 @@ auto PPU::writeIO(uint24 addr, uint8 data) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::updateVideoMode() -> void {
|
auto PPU::updateVideoMode() -> void {
|
||||||
|
display.vdisp = !io.overscan ? 225 : 240;
|
||||||
|
|
||||||
switch(io.bgMode) {
|
switch(io.bgMode) {
|
||||||
case 0:
|
case 0:
|
||||||
bg1.io.mode = Background::Mode::BPP2;
|
bg1.io.mode = Background::Mode::BPP2;
|
||||||
|
|
|
@ -26,7 +26,7 @@ bg4(Background::ID::BG4) {
|
||||||
|
|
||||||
PPU::~PPU() {
|
PPU::~PPU() {
|
||||||
if(system.fastPPU()) {
|
if(system.fastPPU()) {
|
||||||
_handle = nullptr;
|
setHandle(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
output -= 16 * 512;
|
output -= 16 * 512;
|
||||||
|
@ -96,8 +96,7 @@ auto PPU::load(Markup::Node node) -> bool {
|
||||||
auto PPU::power(bool reset) -> void {
|
auto PPU::power(bool reset) -> void {
|
||||||
if(system.fastPPU()) {
|
if(system.fastPPU()) {
|
||||||
ppufast.power(reset);
|
ppufast.power(reset);
|
||||||
_handle = ppufast._handle;
|
return setHandle(ppufast.handle());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create(Enter, system.cpuFrequency());
|
create(Enter, system.cpuFrequency());
|
||||||
|
@ -202,6 +201,7 @@ auto PPU::power(bool reset) -> void {
|
||||||
window.power();
|
window.power();
|
||||||
screen.power();
|
screen.power();
|
||||||
|
|
||||||
|
updateVideoMode();
|
||||||
frame();
|
frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <sfc/ppu-fast/ppu.hpp>
|
|
||||||
|
|
||||||
struct PPU : Thread, PPUcounter {
|
struct PPU : Thread, PPUcounter {
|
||||||
//ppu.cpp
|
alwaysinline auto interlace() const -> bool { return display.interlace; }
|
||||||
alwaysinline auto interlace() const -> bool { if(system.fastPPU()) return ppufast.interlace(); return display.interlace; }
|
alwaysinline auto overscan() const -> bool { return display.overscan; }
|
||||||
alwaysinline auto overscan() const -> bool { if(system.fastPPU()) return ppufast.overscan(); return display.overscan; }
|
alwaysinline auto vdisp() const -> uint { return display.vdisp; }
|
||||||
alwaysinline auto vdisp() const -> uint { if(system.fastPPU()) return ppufast.vdisp(); return !io.overscan ? 225 : 240; }
|
|
||||||
|
|
||||||
|
//ppu.cpp
|
||||||
PPU();
|
PPU();
|
||||||
~PPU();
|
~PPU();
|
||||||
|
|
||||||
|
@ -47,6 +45,7 @@ private:
|
||||||
struct {
|
struct {
|
||||||
bool interlace;
|
bool interlace;
|
||||||
bool overscan;
|
bool overscan;
|
||||||
|
uint vdisp;
|
||||||
} display;
|
} display;
|
||||||
|
|
||||||
auto scanline() -> void;
|
auto scanline() -> void;
|
||||||
|
@ -161,6 +160,7 @@ private:
|
||||||
friend class PPU::Window;
|
friend class PPU::Window;
|
||||||
friend class PPU::Screen;
|
friend class PPU::Screen;
|
||||||
friend class System;
|
friend class System;
|
||||||
|
friend class PPUfast;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern PPU ppu;
|
extern PPU ppu;
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
auto PPU::serialize(serializer& s) -> void {
|
auto PPU::serialize(serializer& s) -> void {
|
||||||
|
s.integer(display.interlace);
|
||||||
|
s.integer(display.overscan);
|
||||||
|
s.integer(display.vdisp);
|
||||||
|
|
||||||
if(system.fastPPU()) {
|
if(system.fastPPU()) {
|
||||||
return ppufast.serialize(s);
|
return ppufast.serialize(s);
|
||||||
}
|
}
|
||||||
|
@ -15,9 +19,6 @@ auto PPU::serialize(serializer& s) -> void {
|
||||||
s.integer(ppu2.version);
|
s.integer(ppu2.version);
|
||||||
s.integer(ppu2.mdr);
|
s.integer(ppu2.mdr);
|
||||||
|
|
||||||
s.integer(display.interlace);
|
|
||||||
s.integer(display.overscan);
|
|
||||||
|
|
||||||
s.integer(latch.vram);
|
s.integer(latch.vram);
|
||||||
s.integer(latch.oam);
|
s.integer(latch.oam);
|
||||||
s.integer(latch.cgram);
|
s.integer(latch.cgram);
|
||||||
|
|
|
@ -54,6 +54,7 @@ namespace SuperFamicom {
|
||||||
#include <sfc/smp/smp.hpp>
|
#include <sfc/smp/smp.hpp>
|
||||||
#include <sfc/dsp/dsp.hpp>
|
#include <sfc/dsp/dsp.hpp>
|
||||||
#include <sfc/ppu/ppu.hpp>
|
#include <sfc/ppu/ppu.hpp>
|
||||||
|
#include <sfc/ppu-fast/ppu.hpp>
|
||||||
|
|
||||||
#include <sfc/controller/controller.hpp>
|
#include <sfc/controller/controller.hpp>
|
||||||
#include <sfc/expansion/expansion.hpp>
|
#include <sfc/expansion/expansion.hpp>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
database
|
database
|
||||||
revision: 2018-06-01
|
revision: 2018-06-25
|
||||||
|
|
||||||
//Boards (Production)
|
//Boards (Production)
|
||||||
|
|
||||||
|
@ -565,7 +565,7 @@ board: SHVC-YJ0N-01
|
||||||
//Boards (Generic)
|
//Boards (Generic)
|
||||||
|
|
||||||
database
|
database
|
||||||
revision: 2018-06-01
|
revision: 2018-06-25
|
||||||
|
|
||||||
board: ARM-LOROM-RAM
|
board: ARM-LOROM-RAM
|
||||||
memory type=ROM content=Program
|
memory type=ROM content=Program
|
||||||
|
@ -703,6 +703,23 @@ board: EXNEC-LOROM
|
||||||
map address=68-6f,e8-ef:0000-7fff mask=0x8000
|
map address=68-6f,e8-ef:0000-7fff mask=0x8000
|
||||||
oscillator
|
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
|
board: GB-LOROM
|
||||||
memory type=ROM content=Program
|
memory type=ROM content=Program
|
||||||
map address=00-7d,80-ff:8000-ffff mask=0x8000
|
map address=00-7d,80-ff:8000-ffff mask=0x8000
|
||||||
|
|
|
@ -9,6 +9,11 @@ auto locate(string name) -> string {
|
||||||
string location = {Path::program(), name};
|
string location = {Path::program(), name};
|
||||||
if(inode::exists(location)) return location;
|
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/"});
|
directory::create({Path::userData(), "bsnes/"});
|
||||||
return {Path::userData(), "bsnes/", name};
|
return {Path::userData(), "bsnes/", name};
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ auto InputManager::bindHotkeys() -> void {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
hotkeys.append(InputHotkey("Reset Emulation").onPress([] {
|
hotkeys.append(InputHotkey("Reset Emulation").onPress([] {
|
||||||
presentation->resetSystem.doActivate();
|
program->reset();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
hotkeys.append(InputHotkey("Quit Emulator").onPress([] {
|
hotkeys.append(InputHotkey("Quit Emulator").onPress([] {
|
||||||
|
|
|
@ -13,10 +13,7 @@ Presentation::Presentation() {
|
||||||
loadRecentGame.setText("Load Recent Game");
|
loadRecentGame.setText("Load Recent Game");
|
||||||
updateRecentGames();
|
updateRecentGames();
|
||||||
resetSystem.setText("Reset System").setEnabled(false).onActivate([&] {
|
resetSystem.setText("Reset System").setEnabled(false).onActivate([&] {
|
||||||
if(emulator->loaded()) {
|
program->reset();
|
||||||
program->applyHacks();
|
|
||||||
emulator->reset();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
unloadGame.setText("Unload Game").setEnabled(false).onActivate([&] {
|
unloadGame.setText("Unload Game").setEnabled(false).onActivate([&] {
|
||||||
program->unload();
|
program->unload();
|
||||||
|
@ -241,7 +238,7 @@ auto Presentation::resizeViewport() -> void {
|
||||||
uint windowHeight = viewportLayout.geometry().height();
|
uint windowHeight = viewportLayout.geometry().height();
|
||||||
|
|
||||||
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0);
|
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0);
|
||||||
uint height = (settings["View/OverscanCropping"].boolean() ? 223.0 : 239.0);
|
uint height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0);
|
||||||
uint viewportWidth, viewportHeight;
|
uint viewportWidth, viewportHeight;
|
||||||
|
|
||||||
if(settings["View/IntegralScaling"].boolean()) {
|
if(settings["View/IntegralScaling"].boolean()) {
|
||||||
|
@ -270,7 +267,7 @@ auto Presentation::resizeViewport() -> void {
|
||||||
|
|
||||||
auto Presentation::resizeWindow() -> void {
|
auto Presentation::resizeWindow() -> void {
|
||||||
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0);
|
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0);
|
||||||
uint height = (settings["View/OverscanCropping"].boolean() ? 223.0 : 239.0);
|
uint height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0);
|
||||||
uint statusHeight = settings["UserInterface/ShowStatusBar"].boolean() ? StatusHeight : 0;
|
uint statusHeight = settings["UserInterface/ShowStatusBar"].boolean() ? StatusHeight : 0;
|
||||||
|
|
||||||
uint multiplier = 2;
|
uint multiplier = 2;
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
auto Program::openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file {
|
auto Program::openPakSuperFamicom(string name, vfs::file::mode mode) -> vfs::shared::file {
|
||||||
return vfs::fs::file::open({superNintendo.location, name}, mode);
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
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) {
|
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) {
|
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(name == "arm6.program.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0x28000) {
|
if(superFamicom.firmware.size() == 0x28000) {
|
||||||
return vfs::memory::file::open(&superNintendo.firmware.data()[0x00000], 0x20000);
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "arm6.data.rom" && mode == vfs::file::mode::read) {
|
if(name == "arm6.data.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0x28000) {
|
if(superFamicom.firmware.size() == 0x28000) {
|
||||||
return vfs::memory::file::open(&superNintendo.firmware.data()[0x20000], 0x08000);
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) {
|
if(name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0xc00) {
|
if(superFamicom.firmware.size() == 0xc00) {
|
||||||
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "lr35902.boot.rom" && mode == vfs::file::mode::read) {
|
if(name == "lr35902.boot.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0x100) {
|
if(superFamicom.firmware.size() == 0x100) {
|
||||||
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".boot.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "upd7725.program.rom" && mode == vfs::file::mode::read) {
|
if(name == "upd7725.program.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0x2000) {
|
if(superFamicom.firmware.size() == 0x2000) {
|
||||||
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0x1800);
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "upd7725.data.rom" && mode == vfs::file::mode::read) {
|
if(name == "upd7725.data.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0x2000) {
|
if(superFamicom.firmware.size() == 0x2000) {
|
||||||
return vfs::memory::file::open(&superNintendo.firmware.data()[0x1800], 0x0800);
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "upd96050.program.rom" && mode == vfs::file::mode::read) {
|
if(name == "upd96050.program.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0xd000) {
|
if(superFamicom.firmware.size() == 0xd000) {
|
||||||
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0xc000);
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "upd96050.data.rom" && mode == vfs::file::mode::read) {
|
if(name == "upd96050.data.rom" && mode == vfs::file::mode::read) {
|
||||||
if(superNintendo.firmware.size() == 0xd000) {
|
if(superFamicom.firmware.size() == 0xd000) {
|
||||||
return vfs::memory::file::open(&superNintendo.firmware.data()[0xc000], 0x1000);
|
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"});
|
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
|
||||||
return vfs::fs::file::open(location, mode);
|
return vfs::fs::file::open(location, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == "save.ram") {
|
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") {
|
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") {
|
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") {
|
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") {
|
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") {
|
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") {
|
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") {
|
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")) {
|
if(name.match("msu1/track-*.pcm")) {
|
||||||
name.trimLeft("msu1/track-", 1L);
|
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 {};
|
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) {
|
if(name == "program.rom" && mode == vfs::file::mode::read) {
|
||||||
return vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size());
|
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 {};
|
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 {};
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ auto Program::load() -> void {
|
||||||
connectDevices();
|
connectDevices();
|
||||||
applyHacks();
|
applyHacks();
|
||||||
emulator->power();
|
emulator->power();
|
||||||
|
showMessage(!appliedPatch() ? "Game loaded" : "Game loaded and patch applied");
|
||||||
presentation->setTitle(emulator->title());
|
presentation->setTitle(emulator->title());
|
||||||
presentation->resetSystem.setEnabled(true);
|
presentation->resetSystem.setEnabled(true);
|
||||||
presentation->unloadGame.setEnabled(true);
|
presentation->unloadGame.setEnabled(true);
|
||||||
|
@ -19,8 +20,11 @@ auto Program::load() -> void {
|
||||||
toolsWindow->cheatEditor.loadCheats();
|
toolsWindow->cheatEditor.loadCheats();
|
||||||
toolsWindow->stateManager.loadStates();
|
toolsWindow->stateManager.loadStates();
|
||||||
|
|
||||||
string locations = superNintendo.location;
|
string locations = superFamicom.location;
|
||||||
if(auto location = gameBoy.location) locations.append("|", 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);
|
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;
|
vector<uint8_t> rom;
|
||||||
|
|
||||||
//game pak
|
|
||||||
if(location.endsWith("/")) {
|
if(location.endsWith("/")) {
|
||||||
rom.append(file::read({location, "program.rom" }));
|
manifest = file::read({location, "manifest.bml"});
|
||||||
rom.append(file::read({location, "data.rom" }));
|
rom.append(file::read({location, "program.rom"}));
|
||||||
|
rom.append(file::read({location, "data.rom"}));
|
||||||
rom.append(file::read({location, "expansion.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, "*.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, "*.program.rom")) rom.append(file::read({location, filename}));
|
||||||
for(auto filename : directory::files(location, "*.data.rom" )) rom.append(file::read({location, filename}));
|
for(auto filename : directory::files(location, "*.data.rom" )) rom.append(file::read({location, filename}));
|
||||||
} else {
|
} else {
|
||||||
//game ROM
|
manifest = file::read({Location::notsuffix(location), ".bml"});
|
||||||
rom = loadFile(location);
|
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);
|
auto heuristics = Heuristics::SuperFamicom(rom, location);
|
||||||
superNintendo.label = heuristics.label();
|
auto sha256 = Hash::SHA256(rom).digest();
|
||||||
superNintendo.manifest = heuristics.manifest();
|
if(auto document = BML::unserialize(string::read(locate("database/Super Famicom.bml")))) {
|
||||||
superNintendo.document = BML::unserialize(superNintendo.manifest);
|
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
|
||||||
superNintendo.location = location;
|
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;
|
uint offset = 0;
|
||||||
if(auto size = heuristics.programRomSize()) {
|
if(auto size = heuristics.programRomSize()) {
|
||||||
superNintendo.program.resize(size);
|
superFamicom.program.resize(size);
|
||||||
memory::copy(&superNintendo.program[0], &rom[offset], size);
|
memory::copy(&superFamicom.program[0], &rom[offset], size);
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
if(auto size = heuristics.dataRomSize()) {
|
if(auto size = heuristics.dataRomSize()) {
|
||||||
superNintendo.data.resize(size);
|
superFamicom.data.resize(size);
|
||||||
memory::copy(&superNintendo.data[0], &rom[offset], size);
|
memory::copy(&superFamicom.data[0], &rom[offset], size);
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
if(auto size = heuristics.expansionRomSize()) {
|
if(auto size = heuristics.expansionRomSize()) {
|
||||||
superNintendo.expansion.resize(size);
|
superFamicom.expansion.resize(size);
|
||||||
memory::copy(&superNintendo.expansion[0], &rom[offset], size);
|
memory::copy(&superFamicom.expansion[0], &rom[offset], size);
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
if(auto size = heuristics.firmwareRomSize()) {
|
if(auto size = heuristics.firmwareRomSize()) {
|
||||||
superNintendo.firmware.resize(size);
|
superFamicom.firmware.resize(size);
|
||||||
memory::copy(&superNintendo.firmware[0], &rom[offset], size);
|
memory::copy(&superFamicom.firmware[0], &rom[offset], size);
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::loadGameBoy(string location) -> void {
|
auto Program::loadGameBoy(string location) -> void {
|
||||||
|
string manifest;
|
||||||
vector<uint8_t> rom;
|
vector<uint8_t> rom;
|
||||||
|
|
||||||
//game pak
|
|
||||||
if(location.endsWith("/")) {
|
if(location.endsWith("/")) {
|
||||||
|
manifest = file::read({location, "manifest.bml"});
|
||||||
rom.append(file::read({location, "program.rom"}));
|
rom.append(file::read({location, "program.rom"}));
|
||||||
} else {
|
} else {
|
||||||
|
manifest = file::read({Location::notsuffix(location), ".bml"});
|
||||||
rom = loadFile(location);
|
rom = loadFile(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gameBoy.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location);
|
||||||
auto heuristics = Heuristics::GameBoy(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.document = BML::unserialize(gameBoy.manifest);
|
||||||
gameBoy.location = location;
|
gameBoy.location = location;
|
||||||
|
|
||||||
gameBoy.program = rom;
|
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 {
|
auto Program::save() -> void {
|
||||||
if(!emulator->loaded()) return;
|
if(!emulator->loaded()) return;
|
||||||
emulator->save();
|
emulator->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Program::reset() -> void {
|
||||||
|
if(!emulator->loaded()) return;
|
||||||
|
applyHacks();
|
||||||
|
emulator->reset();
|
||||||
|
showMessage("Game reset");
|
||||||
|
}
|
||||||
|
|
||||||
auto Program::unload() -> void {
|
auto Program::unload() -> void {
|
||||||
if(!emulator->loaded()) return;
|
if(!emulator->loaded()) return;
|
||||||
toolsWindow->cheatEditor.saveCheats();
|
toolsWindow->cheatEditor.saveCheats();
|
||||||
toolsWindow->setVisible(false);
|
toolsWindow->setVisible(false);
|
||||||
saveRecoveryState();
|
saveRecoveryState();
|
||||||
emulator->unload();
|
emulator->unload();
|
||||||
superNintendo = {};
|
showMessage("Game unloaded");
|
||||||
|
superFamicom = {};
|
||||||
gameBoy = {};
|
gameBoy = {};
|
||||||
bsMemory = {};
|
bsMemory = {};
|
||||||
sufamiTurbo[0] = {};
|
sufamiTurboA = {};
|
||||||
sufamiTurbo[1] = {};
|
sufamiTurboB = {};
|
||||||
presentation->setTitle({"bsnes v", Emulator::Version});
|
presentation->setTitle({"bsnes v", Emulator::Version});
|
||||||
presentation->resetSystem.setEnabled(false);
|
presentation->resetSystem.setEnabled(false);
|
||||||
presentation->unloadGame.setEnabled(false);
|
presentation->unloadGame.setEnabled(false);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,11 +24,11 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
|
||||||
|
|
||||||
if(id == 1) { //Super Famicom
|
if(id == 1) { //Super Famicom
|
||||||
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
|
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
|
||||||
result = vfs::memory::file::open(superNintendo.manifest.data<uint8_t>(), superNintendo.manifest.size());
|
result = vfs::memory::file::open(superFamicom.manifest.data<uint8_t>(), superFamicom.manifest.size());
|
||||||
} else if(superNintendo.location.endsWith("/")) {
|
} else if(superFamicom.location.endsWith("/")) {
|
||||||
result = openPakSFC(name, mode);
|
result = openPakSuperFamicom(name, mode);
|
||||||
} else {
|
} 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) {
|
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
|
||||||
result = vfs::memory::file::open(gameBoy.manifest.data<uint8_t>(), gameBoy.manifest.size());
|
result = vfs::memory::file::open(gameBoy.manifest.data<uint8_t>(), gameBoy.manifest.size());
|
||||||
} else if(gameBoy.location.endsWith("/")) {
|
} else if(gameBoy.location.endsWith("/")) {
|
||||||
result = openPakGB(name, mode);
|
result = openPakGameBoy(name, mode);
|
||||||
} else {
|
} 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 {
|
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(id == 1 && name == "Super Famicom" && type == "sfc") {
|
||||||
if(gameQueue) {
|
if(gameQueue) {
|
||||||
superNintendo.location = gameQueue.takeLeft();
|
superFamicom.location = gameQueue.takeLeft();
|
||||||
} else {
|
} else {
|
||||||
BrowserDialog dialog;
|
dialog.setTitle("Load Super Famicom");
|
||||||
dialog.setTitle("Load Super Nintendo");
|
dialog.setPath(path("Games", settings["Path/Recent/SuperFamicom"].text()));
|
||||||
dialog.setPath(path("Games", settings["Path/Recent/SuperNintendo"].text()));
|
dialog.setFilters({string{"Super Famicom Games|*.sfc:*.smc:*.zip"}});
|
||||||
dialog.setFilters({string{"Super Nintendo Games|*.sfc:*.smc:*.zip"}});
|
superFamicom.location = dialog.openObject();
|
||||||
superNintendo.location = dialog.openObject();
|
|
||||||
}
|
}
|
||||||
if(inode::exists(superNintendo.location)) {
|
if(inode::exists(superFamicom.location)) {
|
||||||
settings["Path/Recent/SuperNintendo"].setValue(Location::dir(superNintendo.location));
|
settings["Path/Recent/SuperFamicom"].setValue(Location::dir(superFamicom.location));
|
||||||
loadSuperNintendo(superNintendo.location);
|
loadSuperFamicom(superFamicom.location);
|
||||||
return {id, ""};
|
return {id, dialog.option()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +103,6 @@ auto Program::load(uint id, string name, string type, string_vector options) ->
|
||||||
if(gameQueue) {
|
if(gameQueue) {
|
||||||
gameBoy.location = gameQueue.takeLeft();
|
gameBoy.location = gameQueue.takeLeft();
|
||||||
} else {
|
} else {
|
||||||
BrowserDialog dialog;
|
|
||||||
dialog.setTitle("Load Game Boy");
|
dialog.setTitle("Load Game Boy");
|
||||||
dialog.setPath(path("Games", settings["Path/Recent/GameBoy"].text()));
|
dialog.setPath(path("Games", settings["Path/Recent/GameBoy"].text()));
|
||||||
dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}});
|
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)) {
|
if(inode::exists(gameBoy.location)) {
|
||||||
settings["Path/Recent/GameBoy"].setValue(Location::dir(gameBoy.location));
|
settings["Path/Recent/GameBoy"].setValue(Location::dir(gameBoy.location));
|
||||||
loadGameBoy(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()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ auto Program::path(string type, string location, string extension) -> string {
|
||||||
auto Program::gamePath() -> string {
|
auto Program::gamePath() -> string {
|
||||||
if(!emulator->loaded()) return "";
|
if(!emulator->loaded()) return "";
|
||||||
if(gameBoy.location) return gameBoy.location;
|
if(gameBoy.location) return gameBoy.location;
|
||||||
return superNintendo.location;
|
return superFamicom.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::cheatPath() -> string {
|
auto Program::cheatPath() -> string {
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "paths.cpp"
|
#include "paths.cpp"
|
||||||
#include "states.cpp"
|
#include "states.cpp"
|
||||||
#include "utility.cpp"
|
#include "utility.cpp"
|
||||||
|
#include "patch.cpp"
|
||||||
|
#include "hacks.cpp"
|
||||||
unique_pointer<Program> program;
|
unique_pointer<Program> program;
|
||||||
|
|
||||||
Program::Program(string_vector arguments) {
|
Program::Program(string_vector arguments) {
|
||||||
|
|
|
@ -15,18 +15,28 @@ struct Program : Emulator::Platform {
|
||||||
//game.cpp
|
//game.cpp
|
||||||
auto load() -> void;
|
auto load() -> void;
|
||||||
auto loadFile(string location) -> vector<uint8_t>;
|
auto loadFile(string location) -> vector<uint8_t>;
|
||||||
auto loadSuperNintendo(string location) -> void;
|
auto loadSuperFamicom(string location) -> void;
|
||||||
auto loadGameBoy(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 save() -> void;
|
||||||
|
auto reset() -> void;
|
||||||
auto unload() -> void;
|
auto unload() -> void;
|
||||||
|
|
||||||
//game-pak.cpp
|
//game-pak.cpp
|
||||||
auto openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file;
|
auto openPakSuperFamicom(string name, vfs::file::mode mode) -> vfs::shared::file;
|
||||||
auto openPakGB(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
|
//game-rom.cpp
|
||||||
auto openRomSFC(string name, vfs::file::mode mode) -> vfs::shared::file;
|
auto openRomSuperFamicom(string name, vfs::file::mode mode) -> vfs::shared::file;
|
||||||
auto openRomGB(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
|
//paths.cpp
|
||||||
auto path(string type, string location, string extension = "") -> string;
|
auto path(string type, string location, string extension = "") -> string;
|
||||||
|
@ -45,14 +55,22 @@ struct Program : Emulator::Platform {
|
||||||
auto initializeInputDriver() -> void;
|
auto initializeInputDriver() -> void;
|
||||||
auto updateVideoShader() -> void;
|
auto updateVideoShader() -> void;
|
||||||
auto connectDevices() -> void;
|
auto connectDevices() -> void;
|
||||||
auto applyHacks() -> void;
|
|
||||||
auto showMessage(string text) -> void;
|
auto showMessage(string text) -> void;
|
||||||
auto showFrameRate(string text) -> void;
|
auto showFrameRate(string text) -> void;
|
||||||
auto updateStatus() -> void;
|
auto updateStatus() -> void;
|
||||||
auto focused() -> bool;
|
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:
|
public:
|
||||||
struct SuperNintendo {
|
struct SuperFamicom {
|
||||||
string label;
|
string label;
|
||||||
string location;
|
string location;
|
||||||
string manifest;
|
string manifest;
|
||||||
|
@ -61,20 +79,32 @@ public:
|
||||||
vector<uint8_t> data;
|
vector<uint8_t> data;
|
||||||
vector<uint8_t> expansion;
|
vector<uint8_t> expansion;
|
||||||
vector<uint8_t> firmware;
|
vector<uint8_t> firmware;
|
||||||
} superNintendo;
|
boolean patched;
|
||||||
|
} superFamicom;
|
||||||
|
|
||||||
struct GameBoy {
|
struct GameBoy {
|
||||||
string location;
|
string location;
|
||||||
string manifest;
|
string manifest;
|
||||||
Markup::Node document;
|
Markup::Node document;
|
||||||
vector<uint8_t> program;
|
vector<uint8_t> program;
|
||||||
|
boolean patched;
|
||||||
} gameBoy;
|
} gameBoy;
|
||||||
|
|
||||||
struct BSMemory {
|
struct BSMemory {
|
||||||
|
string location;
|
||||||
|
string manifest;
|
||||||
|
Markup::Node document;
|
||||||
|
vector<uint8_t> program;
|
||||||
|
boolean patched;
|
||||||
} bsMemory;
|
} bsMemory;
|
||||||
|
|
||||||
struct SufamiTurbo {
|
struct SufamiTurbo {
|
||||||
} sufamiTurbo[2];
|
string location;
|
||||||
|
string manifest;
|
||||||
|
Markup::Node document;
|
||||||
|
vector<uint8_t> program;
|
||||||
|
boolean patched;
|
||||||
|
} sufamiTurboA, sufamiTurboB;
|
||||||
|
|
||||||
string_vector gameQueue;
|
string_vector gameQueue;
|
||||||
|
|
||||||
|
|
|
@ -70,11 +70,9 @@ auto Program::saveState(string filename) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::saveRecoveryState() -> bool {
|
auto Program::saveRecoveryState() -> bool {
|
||||||
if(!emulator->loaded()) return false;
|
auto statusTime = this->statusTime;
|
||||||
string location = {statePath(), "quick/recovery.bst"};
|
auto statusMessage = this->statusMessage;
|
||||||
directory::create(Location::path(location));
|
saveState("quick/recovery");
|
||||||
serializer s = emulator->serialize();
|
this->statusTime = statusTime;
|
||||||
if(!s.size()) return false;
|
this->statusMessage = statusMessage;
|
||||||
if(!file::write(location, s.data(), s.size())) return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
auto Program::showMessage(string text) -> void {
|
||||||
statusTime = chrono::timestamp();
|
statusTime = chrono::timestamp();
|
||||||
statusMessage = text;
|
statusMessage = text;
|
||||||
|
|
|
@ -110,11 +110,18 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
emulatorLabel.setText("Emulator").setFont(Font().setBold());
|
hacksLabel.setText("Emulator Hacks").setFont(Font().setBold());
|
||||||
fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/FastPPU"].boolean()).onToggle([&] {
|
fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/Hack/FastPPU"].boolean()).onToggle([&] {
|
||||||
settings["Emulator/FastPPU"].setValue(fastPPUOption.checked());
|
settings["Emulator/Hack/FastPPU"].setValue(fastPPUOption.checked());
|
||||||
});
|
});
|
||||||
fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/FastDSP"].boolean()).onToggle([&] {
|
fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/Hack/FastDSP"].boolean()).onToggle([&] {
|
||||||
settings["Emulator/FastDSP"].setValue(fastDSPOption.checked());
|
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.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,15 +45,17 @@ Settings::Settings() {
|
||||||
set("Path/Saves", "");
|
set("Path/Saves", "");
|
||||||
set("Path/Cheats", "");
|
set("Path/Cheats", "");
|
||||||
set("Path/States", "");
|
set("Path/States", "");
|
||||||
set("Path/Recent/SuperNintendo", Path::user());
|
set("Path/Recent/SuperFamicom", Path::user());
|
||||||
set("Path/Recent/GameBoy", Path::user());
|
set("Path/Recent/GameBoy", Path::user());
|
||||||
set("Path/Recent/BSMemory", 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("UserInterface/ShowStatusBar", true);
|
||||||
|
|
||||||
set("Emulator/FastPPU", true);
|
set("Emulator/Hack/FastPPU", true);
|
||||||
set("Emulator/FastDSP", true);
|
set("Emulator/Hack/FastDSP", true);
|
||||||
|
set("Emulator/Hack/FastSuperFX", "100%");
|
||||||
set("Emulator/AutoSaveMemory/Enable", true);
|
set("Emulator/AutoSaveMemory/Enable", true);
|
||||||
set("Emulator/AutoSaveMemory/Interval", 30);
|
set("Emulator/AutoSaveMemory/Interval", 30);
|
||||||
|
|
||||||
|
|
|
@ -115,9 +115,14 @@ public:
|
||||||
ComboButton audioDriverOption{&driverLayout, Size{~0, 0}};
|
ComboButton audioDriverOption{&driverLayout, Size{~0, 0}};
|
||||||
Label inputDriverLabel{&driverLayout, Size{0, 0}};
|
Label inputDriverLabel{&driverLayout, Size{0, 0}};
|
||||||
ComboButton inputDriverOption{&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 fastPPUOption{&layout, Size{~0, 0}};
|
||||||
CheckLabel fastDSPOption{&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 {
|
struct SettingsWindow : Window {
|
||||||
|
|
|
@ -28,6 +28,7 @@ auto pLabel::setAlignment(Alignment alignment) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pLabel::setBackgroundColor(Color color) -> void {
|
auto pLabel::setBackgroundColor(Color color) -> void {
|
||||||
|
if(!color) color = self().parentWindow(true)->backgroundColor();
|
||||||
if(color) {
|
if(color) {
|
||||||
QPalette palette = qtLabel->palette();
|
QPalette palette = qtLabel->palette();
|
||||||
palette.setColor(QPalette::Base, QColor(color.red(), color.green(), color.blue()));
|
palette.setColor(QPalette::Base, QColor(color.red(), color.green(), color.blue()));
|
||||||
|
|
|
@ -45,7 +45,7 @@ static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
|
||||||
|
|
||||||
switch(msg) {
|
switch(msg) {
|
||||||
case WM_GETDLGCODE: return DLGC_STATIC | DLGC_WANTCHARS;
|
case WM_GETDLGCODE: return DLGC_STATIC | DLGC_WANTCHARS;
|
||||||
case WM_ERASEBKGND: return true;
|
case WM_ERASEBKGND:
|
||||||
case WM_PAINT: {
|
case WM_PAINT: {
|
||||||
PAINTSTRUCT ps;
|
PAINTSTRUCT ps;
|
||||||
BeginPaint(hwnd, &ps);
|
BeginPaint(hwnd, &ps);
|
||||||
|
@ -96,7 +96,8 @@ static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
|
||||||
DeleteObject(hbmMemory);
|
DeleteObject(hbmMemory);
|
||||||
DeleteObject(hdcMemory);
|
DeleteObject(hdcMemory);
|
||||||
EndPaint(hwnd, &ps);
|
EndPaint(hwnd, &ps);
|
||||||
return false;
|
|
||||||
|
return msg == WM_ERASEBKGND;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ auto pLineEdit::minimumSize() const -> Size {
|
||||||
auto pLineEdit::setBackgroundColor(Color color) -> void {
|
auto pLineEdit::setBackgroundColor(Color color) -> void {
|
||||||
if(backgroundBrush) { DeleteObject(backgroundBrush); backgroundBrush = 0; }
|
if(backgroundBrush) { DeleteObject(backgroundBrush); backgroundBrush = 0; }
|
||||||
backgroundBrush = CreateSolidBrush(color ? CreateRGB(color) : GetSysColor(COLOR_WINDOW));
|
backgroundBrush = CreateSolidBrush(color ? CreateRGB(color) : GetSysColor(COLOR_WINDOW));
|
||||||
|
InvalidateRect(hwnd, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pLineEdit::setEditable(bool editable) -> void {
|
auto pLineEdit::setEditable(bool editable) -> void {
|
||||||
|
|
|
@ -74,8 +74,17 @@ auto SuperFamicom::manifest() const -> string {
|
||||||
auto board = this->board().trimRight("#A", 1L).trimRight("#B", 1L).split("-");
|
auto board = this->board().trimRight("#A", 1L).trimRight("#B", 1L).split("-");
|
||||||
|
|
||||||
if(auto size = romSize()) {
|
if(auto size = romSize()) {
|
||||||
if(board(0) == "SPC7110") size = 0x100000;
|
if(board(0) == "SPC7110" && size > 0x100000) {
|
||||||
output.append(Memory{}.type("ROM").size(size).content("Program").text());
|
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()) {
|
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("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(Memory{}.type("RAM").size( 0x200).content("Data" ).manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).isVolatile().text());
|
||||||
output.append(Oscillator{}.frequency(7'600'000).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());
|
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") {
|
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( "LOROM-RAM")) board.append(romSize() <= 0x200000 ? "#A" : "#B");
|
||||||
if(board.beginsWith("NEC-LOROM-RAM")) board.append(romSize() <= 0x100000 ? "#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;
|
return board;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue