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 {
|
||||
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/";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//SPC7110 decompressor
|
||||
//original implementation: neviksti
|
||||
//optimized implementation: cydrak
|
||||
//optimized implementation: talarubi
|
||||
|
||||
struct Decompressor {
|
||||
SPC7110& spc7110;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -59,6 +59,8 @@ struct SuperFX : Processor::GSU, Thread {
|
|||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint Frequency;
|
||||
|
||||
CPUROM cpurom;
|
||||
CPURAM cpuram;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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([] {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(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()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
if(!emulator->loaded()) return "";
|
||||
if(gameBoy.location) return gameBoy.location;
|
||||
return superNintendo.location;
|
||||
return superFamicom.location;
|
||||
}
|
||||
|
||||
auto Program::cheatPath() -> string {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue