Update to 20180726 release.

byuu says:

Once again, I wasn't able to complete a full WIP revision.

This WIP-WIP adds very sophisticated emulation of the Sega Genesis
Lock-On and Game Genie cartridges ... essentially, through recursion and
a linked list, higan supports an infinite nesting of cartridges.

Of course, on real hardware, after you stack more than three or four
cartridges, the power draw gets too high and things start glitching out
more and more as you keep stacking. I've heard that someone chained up
to ten Sonic & Knuckles cartridges before it finally became completely
unplayable.

And so of course, higan emulates this limitation as well ^-^. On the
fourth cartridge and beyond, it will become more and more likely that
address and/or data lines "glitch" out randomly, causing various
glitches. It's a completely silly easter egg that requires no speed
impact whatsoever beyond the impact of the new linked list cartridge
system.

I also designed the successor to Emulator::Interface::cap,get,set. Those
were holdovers from the older, since-removed ruby-style accessors.

In its place is the new Emulator::Interface::configuration,configure
API. There's the usual per-property access, and there's also access to
read and write all configurable options at once. In essence, this
enables introspection into core-specific features.

So far, you can control processor version#s, PPU VRAM size, video
settings, and hacks. As such, the .sys/manifest.bml files are no longer
necessary. Instead, it all goes into .sys/configuration.bml, which is
generated by the emulator if it's missing.

higan is going to take this even further and allow each option under
"Systems" to have its own editable configuration file. So if you wanted,
you could have a 1/1/1 SNES menu option, and a 2/1/3 SNES menu option.
Or a Model 1 Genesis option, and a Model 2 Genesis option. Or the
various Game Boy model revisions. Or an "SNES-Fast" and "SNES-Accurate"
option.

I've not fully settled on the syntax of the new configuration API. I
feel it might be useful to provide type information, but I really quite
passionately hate any<T> container objects. For now it's all
string-based, because strings can hold anything in nall.

I might also change the access rules. Right now it's like:
emulator→configure("video/blurEmulation", true); but it might be nicer
as "Video::Blur Emulation", or "Video.BlurEmulation", or something like
that.
This commit is contained in:
Tim Allen 2018-07-26 20:36:43 +10:00
parent 22bd4b9277
commit 876b4be1d2
38 changed files with 461 additions and 354 deletions

View File

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

View File

@ -87,6 +87,12 @@ struct Interface {
//cheat functions
virtual auto cheats(const vector<string>& = {}) -> void {}
//configuration
virtual auto configuration() -> string { return {}; }
virtual auto configuration(string name) -> string { return {}; }
virtual auto configure(string configuration = "") -> bool { return false; }
virtual auto configure(string name, string value) -> bool { return false; }
//settings
virtual auto cap(const string& name) -> bool { return false; }
virtual auto get(const string& name) -> any { return {}; }

View File

@ -5,144 +5,145 @@ namespace MegaDrive {
Cartridge cartridge;
#include "serialization.cpp"
auto Cartridge::region() const -> string {
return game.region;
}
auto Cartridge::hashes() const -> vector<string> {
vector<string> hashes;
hashes.append(game.hash);
if(lockOn.hash) hashes.append(lockOn.hash);
hashes.append(information.hash);
if(slot) for(auto& hash : slot->hashes()) hashes.append(hash);
return hashes;
}
auto Cartridge::manifests() const -> vector<string> {
vector<string> manifests;
manifests.append(game.manifest);
if(lockOn.manifest) manifests.append(lockOn.manifest);
manifests.append(information.manifest);
if(slot) for(auto& manifest : slot->manifests()) manifests.append(manifest);
return manifests;
}
auto Cartridge::titles() const -> vector<string> {
vector<string> titles;
titles.append(game.title);
if(lockOn.title) titles.append(lockOn.title);
titles.append(information.title);
if(slot) for(auto& title : slot->titles()) titles.append(title);
return titles;
}
auto Cartridge::load() -> bool {
game = {};
lockOn = {};
read.reset();
write.reset();
unload();
if(!loadGame()) {
game = {};
return false;
if(auto loaded = platform->load(ID::MegaDrive, "Mega Drive", "md", {"Auto", "NTSC-J", "NTSC-U", "PAL"})) {
information.pathID = loaded.pathID;
information.region = loaded.option;
} else return false;
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
information.document = BML::unserialize(information.manifest);
information.hash = information.document["game/sha256"].text();
information.title = information.document["game/label"].text();
if(!loadROM(rom, information.document["game/board/memory(type=ROM,content=Program)"])) {
return unload(), false;
}
read = {&Cartridge::readGame, this};
write = {&Cartridge::writeGame, this};
if(!loadROM(patch, information.document["game/board/memory(type=ROM,content=Patch)"])) {
patch.reset();
}
if(game.patch.size) {
if(!loadRAM(ram, information.document["game/board/memory(type=RAM,content=Save)"])) {
ram.reset();
}
if(information.region == "Auto") {
if(auto region = information.document["game/region"].text()) {
information.region = region.upcase();
} else {
information.region = "NTSC-J";
}
}
read = {&Cartridge::readLinear, this};
write = {&Cartridge::writeLinear, this};
if(rom.size > 0x200000) {
read = {&Cartridge::readBanked, this};
write = {&Cartridge::writeBanked, this};
}
if(patch) {
slot = new Cartridge{depth + 1};
if(!slot->load()) slot.reset();
read = {&Cartridge::readLockOn, this};
write = {&Cartridge::writeLockOn, this};
if(!loadLockOn()) lockOn = {};
}
if(rom.data[0x120>>1]==0x4761 || rom.data[0x120>>1]==0x6147) {
slot = new Cartridge{depth + 1};
if(!slot->load()) slot.reset();
read = {&Cartridge::readGameGenie, this};
write = {&Cartridge::writeGameGenie, this};
}
//easter egg: power draw increases with each successively stacked cartridge
//simulate increasing address/data line errors as stacking increases
if(depth >= 3) {
auto reader = read;
auto writer = write;
auto scramble = [=](uint32 value) -> uint32 {
uint chance = max(1, (1 << 19) >> depth) - 1;
if((random() & chance) == 0) value ^= 1 << (random() & 31);
return value;
};
read = [=](uint22 address) -> uint16 {
return scramble(reader(scramble(address)));
};
write = [=](uint22 address, uint16 data) -> void {
writer(scramble(address), scramble(data));
};
}
return true;
}
auto Cartridge::loadGame() -> bool {
if(auto loaded = platform->load(ID::MegaDrive, "Mega Drive", "md", {"Auto", "NTSC-J", "NTSC-U", "PAL"})) {
game.pathID = loaded.pathID;
game.region = loaded.option;
} else return false;
if(auto fp = platform->open(game.pathID, "manifest.bml", File::Read, File::Required)) {
game.manifest = fp->reads();
} else return false;
game.document = BML::unserialize(game.manifest);
game.hash = game.document["game/sha256"].text();
game.title = game.document["game/label"].text();
if(!loadROM(game.rom, game.pathID, game.document["game/board/memory(type=ROM,content=Program)"])) {
game.rom.reset();
return false;
}
if(!loadROM(game.patch, game.pathID, game.document["game/board/memory(type=ROM,content=Patch)"])) {
game.patch.reset();
}
if(!loadRAM(game.ram, game.pathID, game.document["game/board/memory(type=RAM,content=Save)"])) {
game.ram.reset();
}
if(game.region == "Auto") {
if(auto region = game.document["game/region"].text()) {
game.region = region.upcase();
} else {
game.region = "NTSC-J";
}
}
return true;
auto Cartridge::save() -> void {
saveRAM(ram, information.document["game/board/memory(type=RAM,content=Save)"]);
if(slot) slot->save();
}
auto Cartridge::loadLockOn() -> bool {
if(auto loaded = platform->load(ID::MegaDrive, "Mega Drive", "md")) {
lockOn.pathID = loaded.pathID;
} else return false;
if(auto fp = platform->open(lockOn.pathID, "manifest.bml", File::Read, File::Required)) {
lockOn.manifest = fp->reads();
} else return false;
lockOn.document = BML::unserialize(lockOn.manifest);
lockOn.hash = lockOn.document["game/sha256"].text();
lockOn.title = lockOn.document["game/label"].text();
if(!loadROM(lockOn.rom, lockOn.pathID, lockOn.document["game/board/memory(type=ROM,content=Program)"])) {
lockOn.rom.reset();
return false;
}
if(!loadRAM(lockOn.ram, lockOn.pathID, lockOn.document["game/board/memory(type=RAM,content=Save)"])) {
lockOn.ram.reset();
}
if(lockOn.rom.size >= 0x200) {
string name;
name.resize(48);
for(uint n : range(24)) {
name.get()[n * 2 + 0] = lockOn.rom.data[0x120 / 2 + n].byte(1);
name.get()[n * 2 + 1] = lockOn.rom.data[0x120 / 2 + n].byte(0);
}
name.strip();
while(name.find(" ")) name.replace(" ", " ");
lockOn.patch = name == "SONIC THE HEDGEHOG 2";
}
return true;
auto Cartridge::unload() -> void {
rom.reset();
patch.reset();
ram.reset();
read.reset();
write.reset();
if(slot) slot->unload();
slot.reset();
}
auto Cartridge::loadROM(Memory& rom, uint pathID, Markup::Node memory) -> bool {
auto Cartridge::power() -> void {
ramEnable = 1;
ramWritable = 1;
for(uint n : range(8)) romBank[n] = n;
gameGenie = {};
}
//
auto Cartridge::loadROM(Memory& rom, Markup::Node memory) -> bool {
if(!memory) return false;
auto name = string{memory["content"].text(), ".", memory["type"].text()}.downcase();
rom.size = memory["size"].natural() >> 1;
rom.mask = bit::round(rom.size) - 1;
rom.data = new uint16[rom.mask + 1]();
if(auto fp = platform->open(pathID, name, File::Read, File::Required)) {
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
for(uint n : range(rom.size)) rom.data[n] = fp->readm(2);
} else return false;
return true;
}
auto Cartridge::loadRAM(Memory& ram, uint pathID, Markup::Node memory) -> bool {
auto Cartridge::loadRAM(Memory& ram, Markup::Node memory) -> bool {
if(!memory) return false;
auto name = string{memory["content"].text(), ".", memory["type"].text()}.downcase();
@ -155,7 +156,7 @@ auto Cartridge::loadRAM(Memory& ram, uint pathID, Markup::Node memory) -> bool {
ram.mask = bit::round(ram.size) - 1;
ram.data = new uint16[ram.mask + 1]();
if(!(bool)memory["volatile"]) {
if(auto fp = platform->open(pathID, name, File::Read)) {
if(auto fp = platform->open(pathID(), name, File::Read)) {
for(uint n : range(ram.size)) {
if(ram.bits != 0xffff) ram.data[n] = fp->readm(1) * 0x0101;
if(ram.bits == 0xffff) ram.data[n] = fp->readm(2);
@ -166,17 +167,12 @@ auto Cartridge::loadRAM(Memory& ram, uint pathID, Markup::Node memory) -> bool {
return true;
}
auto Cartridge::save() -> void {
saveRAM(game.ram, game.pathID, game.document["game/board/memory(type=RAM,content=Save)"]);
saveRAM(lockOn.ram, lockOn.pathID, lockOn.document["game/board/memory(type=RAM,content=Save)"]);
}
auto Cartridge::saveRAM(Memory& ram, uint pathID, Markup::Node memory) -> bool {
auto Cartridge::saveRAM(Memory& ram, Markup::Node memory) -> bool {
if(!memory) return false;
if((bool)memory["volatile"]) return true;
auto name = string{memory["content"].text(), ".", memory["type"].text()}.downcase();
if(auto fp = platform->open(pathID, name, File::Write)) {
if(auto fp = platform->open(pathID(), name, File::Write)) {
for(uint n : range(ram.size)) {
if(ram.bits != 0xffff) fp->writem(ram.data[n], 1);
if(ram.bits == 0xffff) fp->writem(ram.data[n], 2);
@ -186,92 +182,99 @@ auto Cartridge::saveRAM(Memory& ram, uint pathID, Markup::Node memory) -> bool {
return true;
}
auto Cartridge::unload() -> void {
game.rom.reset();
game.patch.reset();
game.ram.reset();
game = {};
lockOn.rom.reset();
lockOn.ram.reset();
lockOn = {};
}
auto Cartridge::power() -> void {
ramEnable = 1;
ramWritable = 1;
for(auto n : range(8)) bank[n] = n;
}
//
auto Cartridge::readIO(uint24 address) -> uint16 {
if(slot) slot->readIO(address);
return 0x0000;
}
auto Cartridge::writeIO(uint24 address, uint16 data) -> void {
if(address == 0xa130f1) ramEnable = data.bit(0), ramWritable = data.bit(1);
if(address == 0xa130f3) bank[1] = data;
if(address == 0xa130f5) bank[2] = data;
if(address == 0xa130f7) bank[3] = data;
if(address == 0xa130f9) bank[4] = data;
if(address == 0xa130fb) bank[5] = data;
if(address == 0xa130fd) bank[6] = data;
if(address == 0xa130ff) bank[7] = data;
if(address == 0xa130f3) romBank[1] = data;
if(address == 0xa130f5) romBank[2] = data;
if(address == 0xa130f7) romBank[3] = data;
if(address == 0xa130f9) romBank[4] = data;
if(address == 0xa130fb) romBank[5] = data;
if(address == 0xa130fd) romBank[6] = data;
if(address == 0xa130ff) romBank[7] = data;
if(slot) slot->writeIO(address, data);
}
//
auto Cartridge::readGame(uint24 address) -> uint16 {
if(address >= 0x200000 && game.ram.size && ramEnable) {
return game.ram.data[address >> 1 & game.ram.mask];
} else {
address = bank[address.bits(19,21)] << 19 | address.bits(0,18);
return game.rom.data[address >> 1 & game.rom.mask];
}
auto Cartridge::readLinear(uint22 address) -> uint16 {
if(ramEnable && ram && address >= 0x200000) return ram.read(address);
return rom.read(address);
}
auto Cartridge::writeGame(uint24 address, uint16 data) -> void {
//emulating RAM write protect bit breaks some commercial software
if(address >= 0x200000 && game.ram.size && ramEnable /* && ramWritable */) {
if(game.ram.bits == 0x00ff) data = data.byte(0) * 0x0101;
if(game.ram.bits == 0xff00) data = data.byte(1) * 0x0101;
game.ram.data[address >> 1 & game.ram.mask] = data;
auto Cartridge::writeLinear(uint22 address, uint16 data) -> void {
//emulating ramWritable will break commercial software:
//it does not appear that many (any?) games actually connect $a130f1.d1 to /WE;
//hence RAM ends up always being writable, and many games fail to set d1=1
if(ramEnable && ram && address >= 0x200000) return ram.write(address, data);
}
//
auto Cartridge::readBanked(uint22 address) -> uint16 {
address = romBank[address.bits(19,21)] << 19 | address.bits(0,18);
return rom.read(address);
}
auto Cartridge::writeBanked(uint22 address, uint16 data) -> void {
}
//
auto Cartridge::readLockOn(uint22 address) -> uint16 {
if(address < 0x200000) return rom.read(address);
if(ramEnable && address >= 0x300000) return patch.read(address);
if(slot) return slot->read(address);
return 0x0000;
}
auto Cartridge::writeLockOn(uint22 address, uint16 data) -> void {
if(slot) return slot->write(address, data);
}
//
auto Cartridge::readGameGenie(uint22 address) -> uint16 {
if(gameGenie.enable) {
for(auto& code : gameGenie.codes) {
if(code.enable && code.address == address) return code.data;
}
if(slot) return slot->read(address);
}
return rom.read(address);
}
auto Cartridge::writeGameGenie(uint22 address, uint16 data) -> void {
if(gameGenie.enable) {
if(slot) return slot->write(address, data);
}
if(address == 0x02 && data == 0x0001) {
gameGenie.enable = true;
}
if(address >= 0x04 && address <= 0x20 && !address.bit(0)) {
address = address - 0x04 >> 1;
auto& code = gameGenie.codes[address / 3];
if(address % 3 == 0) code.address.bits(16,23) = data.byte(0);
if(address % 3 == 1) code.address.bits( 0,15) = data;
if(address % 3 == 2) code.data = data, code.enable = true;
}
}
//
auto Cartridge::readLockOn(uint24 address) -> uint16 {
if(address >= 0x200000 && lockOn.ram.size && ramEnable) {
return lockOn.ram.data[address >> 1 & lockOn.ram.mask];
}
if(address >= 0x300000 && lockOn.patch) {
return game.patch.data[address >> 1 & game.patch.mask];
}
if(address >= 0x200000 && lockOn.rom.data) {
return lockOn.rom.data[address >> 1 & lockOn.rom.mask];
}
if(address >= 0x200000) {
return 0x00;
}
return game.rom.data[address >> 1 & game.rom.mask];
Cartridge::Memory::operator bool() const {
return size;
}
auto Cartridge::writeLockOn(uint24 address, uint16 data) -> void {
if(address >= 0x200000 && lockOn.ram.size && ramEnable) {
if(lockOn.ram.bits == 0x00ff) data = data.byte(0) * 0x0101;
if(lockOn.ram.bits == 0xff00) data = data.byte(1) * 0x0101;
lockOn.ram.data[address >> 1 & lockOn.ram.mask] = data;
}
}
//
auto Cartridge::Memory::reset() -> void {
delete[] data;
data = nullptr;
@ -280,4 +283,16 @@ auto Cartridge::Memory::reset() -> void {
bits = 0;
}
auto Cartridge::Memory::read(uint24 address) -> uint16 {
if(!size) return 0x0000;
return data[address >> 1 & mask];
}
auto Cartridge::Memory::write(uint24 address, uint16 word) -> void {
if(!size) return;
if(bits == 0x00ff) word.byte(1) = word.byte(0);
if(bits == 0xff00) word.byte(0) = word.byte(1);
data[address >> 1 & mask] = word;
}
}

View File

@ -1,73 +1,87 @@
struct Cartridge {
auto region() const -> string;
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
Cartridge() = default;
Cartridge(uint depth) : depth(depth) {}
auto hashes() const -> vector<string>;
auto manifests() const -> vector<string>;
auto titles() const -> vector<string>;
struct Memory;
auto load() -> bool;
auto loadGame() -> bool;
auto loadLockOn() -> bool;
auto loadROM(Memory& rom, uint pathID, Markup::Node memory) -> bool;
auto loadRAM(Memory& ram, uint pathID, Markup::Node memory) -> bool;
auto save() -> void;
auto saveRAM(Memory& ram, uint pathID, Markup::Node memory) -> bool;
auto unload() -> void;
auto power() -> void;
function<uint16 (uint24 address)> read;
function<void (uint24 address, uint16 data)> write;
auto loadROM(Memory& rom, Markup::Node memory) -> bool;
auto loadRAM(Memory& ram, Markup::Node memory) -> bool;
auto saveRAM(Memory& ram, Markup::Node memory) -> bool;
auto readIO(uint24 address) -> uint16;
auto writeIO(uint24 address, uint16 data) -> void;
auto readGame(uint24 address) -> uint16;
auto writeGame(uint24 address, uint16 data) -> void;
auto readLinear(uint22 address) -> uint16;
auto writeLinear(uint22 address, uint16 data) -> void;
auto readLockOn(uint24 address) -> uint16;
auto writeLockOn(uint24 address, uint16 data) -> void;
auto readBanked(uint22 address) -> uint16;
auto writeBanked(uint22 address, uint16 data) -> void;
auto readLockOn(uint22 address) -> uint16;
auto writeLockOn(uint22 address, uint16 data) -> void;
auto readGameGenie(uint22 address) -> uint16;
auto writeGameGenie(uint22 address, uint16 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Memory {
auto reset() -> void;
uint16* data = nullptr;
uint size = 0;
uint mask = 0;
uint bits = 0;
};
struct Game {
struct Information {
uint pathID = 0;
string region;
string hash;
string manifest;
string title;
Markup::Node document;
Memory rom;
Memory patch;
Memory ram;
} game;
};
struct LockOn {
uint pathID = 0;
string hash;
string manifest;
string title;
struct Memory {
explicit operator bool() const;
auto reset() -> void;
auto read(uint24 address) -> uint16;
auto write(uint24 address, uint16 word) -> void;
Markup::Node document;
Memory rom;
Memory ram;
uint16* data = nullptr;
uint size = 0; //16-bit word size
uint mask = 0;
uint bits = 0;
};
bool patch = false;
} lockOn;
Information information;
Memory rom;
Memory patch;
Memory ram;
uint1 ramEnable;
uint1 ramWritable;
uint6 bank[8];
uint6 romBank[8];
struct GameGenie {
boolean enable;
struct Code {
boolean enable;
uint24 address;
uint16 data;
} codes[5];
} gameGenie;
function<uint16 (uint22 address)> read;
function<void (uint22 address, uint16 data)> write;
unique_pointer<Cartridge> slot;
const uint depth = 0;
};
extern Cartridge cartridge;

View File

@ -1,4 +1,7 @@
auto Cartridge::serialize(serializer& s) -> void {
if(game.ram.size) s.array(game.ram.data, game.ram.size);
if(lockOn.ram.size) s.array(lockOn.ram.data, lockOn.ram.size);
if(ram.size) s.array(ram.data, ram.size);
s.integer(ramEnable);
s.integer(ramWritable);
s.array(romBank);
if(slot) slot->serialize(s);
}

View File

@ -6,6 +6,7 @@
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/random.hpp>
#include <emulator/cheat.hpp>
#include <processor/m68k/m68k.hpp>
@ -15,8 +16,10 @@ namespace MegaDrive {
#define platform Emulator::platform
namespace File = Emulator::File;
using Scheduler = Emulator::Scheduler;
using Random = Emulator::Random;
using Cheat = Emulator::Cheat;
extern Scheduler scheduler;
extern Random random;
extern Cheat cheat;
struct Wait {

View File

@ -4,6 +4,7 @@ namespace MegaDrive {
System system;
Scheduler scheduler;
Random random;
Cheat cheat;
#include "serialization.cpp"
@ -67,6 +68,8 @@ auto System::power(bool reset) -> void {
Emulator::audio.reset(interface);
random.entropy(Random::Entropy::High);
scheduler.reset();
cartridge.power();
cpu.power(reset);

View File

@ -44,8 +44,8 @@ auto CPU::main() -> void {
instruction();
}
auto CPU::load(Markup::Node node) -> bool {
version = node["cpu/version"].natural();
auto CPU::load() -> bool {
version = configuration.system.cpu.version;
if(version < 1) version = 1;
if(version > 2) version = 2;
return true;

View File

@ -6,7 +6,7 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
//cpu.cpp
static auto Enter() -> void;
auto main() -> void;
auto load(Markup::Node) -> bool;
auto load() -> bool;
auto power(bool reset) -> void;
//dma.cpp

View File

@ -234,7 +234,7 @@ auto DSP::write(uint8 addr, uint8 data) -> void {
/* initialization */
auto DSP::load(Markup::Node node) -> bool {
auto DSP::load() -> bool {
return true;
}

View File

@ -13,7 +13,7 @@ struct DSP : Thread {
auto write(uint8 addr, uint8 data) -> void;
auto main() -> void;
auto load(Markup::Node) -> bool;
auto load() -> bool;
auto power(bool reset) -> void;
//serialization.cpp

View File

@ -0,0 +1,92 @@
Configuration configuration;
auto Configuration::read() -> string {
return {
"system\n"
" cpu version=", system.cpu.version, "\n"
" ppu1 version=", system.ppu1.version, "\n"
" vram size=0x", hex(system.ppu1.vram.size), "\n"
" ppu2 version=", system.ppu2.version, "\n"
"\n"
"video\n"
" blurEmulation: ", video.blurEmulation, "\n"
" colorEmulation: ", video.colorEmulation, "\n"
"\n"
"hacks\n"
" ppuFast\n"
" enable: ", hacks.ppuFast.enable, "\n"
" noSpriteLimit: ", hacks.ppuFast.noSpriteLimit, "\n"
" hiresMode7: ", hacks.ppuFast.hiresMode7, "\n"
" dspFast\n"
" enable: ", hacks.dspFast.enable, "\n"
};
}
auto Configuration::read(string name) -> string {
#define bind(id) { \
string key = {string{#id}.transform(".", "/")}; \
if(name == key) return name; \
}
bind(system.cpu.version);
bind(system.ppu1.version);
bind(system.ppu1.vram.size);
bind(system.ppu2.version);
bind(video.blurEmulation);
bind(video.colorEmulation);
bind(hacks.ppuFast.enable);
bind(hacks.ppuFast.noSpriteLimit);
bind(hacks.ppuFast.hiresMode7);
bind(hacks.dspFast.enable);
#undef bind
return {};
}
auto Configuration::write(string configuration) -> bool {
*this = {};
auto document = BML::unserialize(configuration);
if(!document) return false;
#define bind(type, id) { \
string key = {string{#id}.transform(".", "/")}; \
if(auto node = document[key]) id = node.type(); \
}
bind(natural, system.cpu.version);
bind(natural, system.ppu1.version);
bind(natural, system.ppu1.vram.size);
bind(natural, system.ppu2.version);
bind(boolean, video.blurEmulation);
bind(boolean, video.colorEmulation);
bind(boolean, hacks.ppuFast.enable);
bind(boolean, hacks.ppuFast.noSpriteLimit);
bind(boolean, hacks.ppuFast.hiresMode7);
bind(boolean, hacks.dspFast.enable);
#undef bind
return true;
}
auto Configuration::write(string name, string value) -> bool {
#define bind(type, id) { \
string key = {string{#id}.transform(".", "/")}; \
if(name == key) return id = Markup::Node().setValue(value).type(), true; \
}
bind(boolean, video.blurEmulation);
bind(boolean, video.colorEmulation);
bind(boolean, hacks.ppuFast.enable);
bind(boolean, hacks.ppuFast.noSpriteLimit);
bind(boolean, hacks.ppuFast.hiresMode7);
bind(boolean, hacks.dspFast.enable);
if(SuperFamicom::system.loaded()) return false;
bind(natural, system.cpu.version);
bind(natural, system.ppu1.version);
bind(natural, system.ppu1.vram.size);
bind(natural, system.ppu2.version);
#undef bind
return false;
}

View File

@ -0,0 +1,39 @@
struct Configuration {
auto read() -> string;
auto read(string) -> string;
auto write(string) -> bool;
auto write(string, string) -> bool;
struct System {
struct CPU {
uint version = 2;
} cpu;
struct PPU1 {
uint version = 1;
struct VRAM {
uint size = 0x10000;
} vram;
} ppu1;
struct PPU2 {
uint version = 3;
} ppu2;
} system;
struct Video {
bool blurEmulation = true;
bool colorEmulation = true;
} video;
struct Hacks {
struct PPUFast {
bool enable = false;
bool noSpriteLimit = false;
bool hiresMode7 = false;
} ppuFast;
struct DSPFast {
bool enable = false;
} dspFast;
} hacks;
};
extern Configuration configuration;

View File

@ -3,6 +3,7 @@
namespace SuperFamicom {
Settings settings;
#include "configuration.cpp"
auto Interface::information() -> Information {
Information information;
@ -40,7 +41,7 @@ auto Interface::color(uint32 color) -> uint64 {
uint64 G = L * image::normalize(g, 5, 16);
uint64 B = L * image::normalize(b, 5, 16);
if(settings.colorEmulation) {
if(SuperFamicom::configuration.video.colorEmulation) {
static const uint8 gammaRamp[32] = {
0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78,
@ -253,60 +254,20 @@ auto Interface::cheats(const vector<string>& list) -> void {
cheat.assign(list);
}
auto Interface::cap(const string& name) -> bool {
if(name == "Fast PPU") return true;
if(name == "Fast PPU/No Sprite Limit") return true;
if(name == "Fast PPU/Hires Mode 7") return true;
if(name == "Fast DSP") return true;
if(name == "Mode") return true;
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
if(name == "Scanline Emulation") return true;
return false;
auto Interface::configuration() -> string {
return SuperFamicom::configuration.read();
}
auto Interface::get(const string& name) -> any {
if(name == "Fast PPU") return settings.fastPPU;
if(name == "Fast PPU/No Sprite Limit") return settings.fastPPUNoSpriteLimit;
if(name == "Fast PPU/Hires Mode 7") return settings.fastPPUHiresMode7;
if(name == "Fast DSP") return settings.fastDSP;
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
if(name == "Scanline Emulation") return settings.scanlineEmulation;
return {};
auto Interface::configuration(string name) -> string {
return SuperFamicom::configuration.read(name);
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Fast PPU" && value.is<bool>()) {
settings.fastPPU = value.get<bool>();
return true;
}
if(name == "Fast PPU/No Sprite Limit" && value.is<bool>()) {
settings.fastPPUNoSpriteLimit = value.get<bool>();
return true;
}
if(name == "Fast PPU/Hires Mode 7" && value.is<bool>()) {
settings.fastPPUHiresMode7 = value.get<bool>();
return true;
}
if(name == "Fast DSP" && value.is<bool>()) {
settings.fastDSP = value.get<bool>();
return true;
}
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
Emulator::video.setPalette();
return true;
}
if(name == "Scanline Emulation" && value.is<bool>()) {
settings.scanlineEmulation = value.get<bool>();
return true;
}
return false;
auto Interface::configure(string configuration) -> bool {
return SuperFamicom::configuration.write(configuration);
}
auto Interface::configure(string name, string value) -> bool {
return SuperFamicom::configuration.write(name, value);
}
}

View File

@ -64,21 +64,15 @@ struct Interface : Emulator::Interface {
auto cheats(const vector<string>&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
auto configuration() -> string override;
auto configuration(string name) -> string override;
auto configure(string configuration) -> bool override;
auto configure(string name, string value) -> bool override;
};
#include "configuration.hpp"
struct Settings {
bool fastPPU = false;
bool fastPPUNoSpriteLimit = false;
bool fastPPUHiresMode7 = false;
bool fastDSP = false;
bool blurEmulation = true;
bool colorEmulation = true;
bool scanlineEmulation = true;
uint controllerPort1 = ID::Device::Gamepad;
uint controllerPort2 = ID::Device::Gamepad;
uint expansionPort = ID::Device::None;

View File

@ -23,7 +23,7 @@ auto PPU::Line::render() -> void {
}
bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
bool hiresMode7 = io.bgMode == 7 && settings.fastPPUHiresMode7;
bool hiresMode7 = io.bgMode == 7 && configuration.hacks.ppuFast.hiresMode7;
auto aboveColor = cgram[0];
auto belowColor = hires ? cgram[0] : io.col.fixedColor;
for(uint x : range(256 << hiresMode7)) {

View File

@ -25,7 +25,7 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
renderWindow(self.window, self.window.aboveEnable, windowAbove);
renderWindow(self.window, self.window.belowEnable, windowBelow);
if(!settings.fastPPUHiresMode7) {
if(!configuration.hacks.ppuFast.hiresMode7) {
for(int X : range(256)) {
int x = !io.mode7.hflip ? X : 255 - X;
int pixelX = originX + a * x >> 8;

View File

@ -76,7 +76,7 @@ auto PPU::scanline() -> void {
if(vcounter() > 0 && vcounter() < vdisp()) {
latch.hires |= io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
latch.hires |= io.bgMode == 7 && settings.fastPPUHiresMode7;
latch.hires |= io.bgMode == 7 && configuration.hacks.ppuFast.hiresMode7;
}
if(vcounter() == vdisp() && !io.displayDisable) {
@ -95,16 +95,15 @@ auto PPU::refresh() -> void {
auto pitch = 512 << !interlace();
auto width = 256 << hires();
auto height = 240 << interlace();
Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation && hires());
Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, configuration.video.blurEmulation && hires());
Emulator::video.refresh(output, pitch * sizeof(uint32), width, height);
}
auto PPU::load(Markup::Node node) -> bool {
auto PPU::load() -> bool {
return true;
}
auto PPU::power(bool reset) -> void {
//settings.fastPPUHiresMode7=false;
create(Enter, system.cpuFrequency());
PPUcounter::reset();
memory::fill<uint32>(output, 512 * 480);
@ -126,8 +125,8 @@ auto PPU::power(bool reset) -> void {
io = {};
updateVideoMode();
ItemLimit = !settings.fastPPUNoSpriteLimit ? 32 : 128;
TileLimit = !settings.fastPPUNoSpriteLimit ? 34 : 128;
ItemLimit = !configuration.hacks.ppuFast.noSpriteLimit ? 32 : 128;
TileLimit = !configuration.hacks.ppuFast.noSpriteLimit ? 34 : 128;
Line::start = 0;
Line::count = 0;

View File

@ -23,7 +23,7 @@ struct PPU : Thread, PPUcounter {
auto main() -> void;
auto scanline() -> void;
auto refresh() -> void;
auto load(Markup::Node) -> bool;
auto load() -> bool;
auto power(bool reset) -> void;
//serialization.cpp

View File

@ -81,15 +81,15 @@ auto PPU::main() -> void {
step(lineclocks() - hcounter());
}
auto PPU::load(Markup::Node node) -> bool {
auto PPU::load() -> bool {
if(system.fastPPU()) {
return ppufast.load(node);
return ppufast.load();
}
ppu1.version = max(1, min(1, node["ppu1/version"].natural()));
ppu2.version = max(1, min(3, node["ppu2/version"].natural()));
ppu.vram.mask = node["ppu1/ram/size"].natural() / sizeof(uint16) - 1;
if(ppu.vram.mask != 0xffff) ppu.vram.mask = 0x7fff;
ppu1.version = max(1, min(1, configuration.system.ppu1.version));
ppu2.version = max(1, min(3, configuration.system.ppu2.version));
vram.mask = configuration.system.ppu1.vram.size / sizeof(uint16) - 1;
if(vram.mask != 0xffff) vram.mask = 0x7fff;
return true;
}
@ -243,7 +243,7 @@ auto PPU::refresh() -> void {
auto pitch = 512;
auto width = 512;
auto height = 480;
Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation);
Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, configuration.video.blurEmulation);
Emulator::video.refresh(output, pitch * sizeof(uint32), width, height);
}

View File

@ -9,7 +9,7 @@ struct PPU : Thread, PPUcounter {
static auto Enter() -> void;
auto main() -> void;
auto load(Markup::Node) -> bool;
auto load() -> bool;
auto power(bool reset) -> void;
//io.cpp

View File

@ -18,12 +18,10 @@ auto SMP::main() -> void {
instruction();
}
auto SMP::load(Markup::Node node) -> bool {
if(auto name = node["smp/rom/name"].text()) {
if(auto fp = platform->open(ID::System, name, File::Read, File::Required)) {
fp->read(iplrom, 64);
return true;
}
auto SMP::load() -> bool {
if(auto fp = platform->open(ID::System, "ipl.rom", File::Read, File::Required)) {
fp->read(iplrom, 64);
return true;
}
return false;
}

View File

@ -10,7 +10,7 @@ struct SMP : Processor::SPC700, Thread {
//smp.cpp
static auto Enter() -> void;
auto main() -> void;
auto load(Markup::Node) -> bool;
auto load() -> bool;
auto power(bool reset) -> void;
//serialization.cpp

View File

@ -32,11 +32,9 @@ auto System::unserialize(serializer& s) -> bool {
s.boolean(hacks.fastPPU);
s.boolean(hacks.fastDSP);
settings.fastPPU = hacks.fastPPU;
settings.fastDSP = hacks.fastDSP;
power(/* reset = */ false);
serializeAll(s);
serializeInit(); //hacks.fastPPU setting changes serializeSize
return true;
}

View File

@ -23,19 +23,14 @@ auto System::runToSave() -> void {
auto System::load(Emulator::Interface* interface) -> bool {
information = {};
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
auto system = document["system"];
hacks.fastPPU = configuration.hacks.ppuFast.enable;
hacks.fastDSP = configuration.hacks.dspFast.enable;
bus.reset();
if(!cpu.load(system)) return false;
if(!smp.load(system)) return false;
if(!ppu.load(system)) return false;
if(!dsp.load(system)) return false;
if(!cpu.load()) return false;
if(!smp.load()) return false;
if(!ppu.load()) return false;
if(!dsp.load()) return false;
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC") {
@ -88,9 +83,6 @@ auto System::unload() -> void {
}
auto System::power(bool reset) -> void {
hacks.fastPPU = settings.fastPPU;
hacks.fastDSP = settings.fastDSP;
Emulator::video.reset(interface);
Emulator::video.setPalette();

View File

@ -25,7 +25,6 @@ private:
Emulator::Interface* interface = nullptr;
struct Information {
string manifest;
bool loaded = false;
Region region = Region::NTSC;
double cpuFrequency = Emulator::Constants::Colorburst::NTSC * 6.0;

View File

@ -1,12 +0,0 @@
system name:Super Famicom
cpu version=2
ram name=work.ram size=0x20000 volatile
smp
rom name=ipl.rom size=64
ppu1 version=1
ram name=video.ram size=0x10000 volatile
ram name=object.ram size=544 volatile
ppu2 version=3
ram name=palette.ram size=512 volatile
dsp
ram name=apu.ram size=0x10000 volatile

View File

@ -53,7 +53,7 @@ Presentation::Presentation() {
});
blurEmulation.setText("Blur Emulation").setChecked(settings["View/BlurEmulation"].boolean()).onToggle([&] {
settings["View/BlurEmulation"].setValue(blurEmulation.checked());
emulator->set("Blur Emulation", blurEmulation.checked());
emulator->configure("video/blurEmulation", blurEmulation.checked());
}).doToggle();
shaderMenu.setIcon(Icon::Emblem::Image).setText("Shader");
synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Blocking"].boolean()).onToggle([&] {

View File

@ -1,5 +1,10 @@
auto Program::load() -> void {
unload();
if(auto configuration = string::read(locate("configuration.bml"))) {
emulator->configure(configuration);
settingsWindow->advanced.updateConfiguration();
}
if(!emulator->load()) return;
gameQueue = {};
@ -282,6 +287,9 @@ auto Program::unload() -> void {
if(settingsWindow->advanced.autoSaveStateOnUnload.checked()) {
saveUndoState();
}
if(auto configuration = emulator->configuration()) {
file::write(locate("configuration.bml"), configuration);
}
emulator->unload();
showMessage("Game unloaded");
superFamicom = {};

View File

@ -11,10 +11,6 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
vfs::shared::file result;
if(id == 0) { //System
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(Resource::System::Manifest.data(), Resource::System::Manifest.size());
}
if(name == "boards.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(Resource::System::Boards.data(), Resource::System::Boards.size());
}

View File

@ -45,7 +45,7 @@ auto Program::updateVideoShader() -> void {
}
auto Program::updateVideoPalette() -> void {
emulator->set("Color Emulation", false);
emulator->configure("video/colorEmulation", false);
double luminance = settings["Video/Luminance"].natural() / 100.0;
double saturation = settings["Video/Saturation"].natural() / 100.0;
double gamma = settings["Video/Gamma"].natural() / 100.0;

View File

@ -2,6 +2,5 @@ namespace name=Resource
binary name=Icon file=icon.png
binary name=Logo file=logo.png
namespace name=System
binary name=Manifest file="../../systems/Super Famicom.sys/manifest.bml"
binary name=Boards file="../../systems/Super Famicom.sys/boards.bml"
binary name=IPLROM file="../../systems/Super Famicom.sys/ipl.rom"

View File

@ -850,19 +850,6 @@ const nall::vector<uint8_t> Logo = { //size: 23467
0,0,0,73,69,78,68,174,66,96,130,
};
namespace System {
const nall::vector<uint8_t> Manifest = { //size: 334
115,121,115,116,101,109,32,110,97,109,101,58,83,117,112,101,114,32,70,97,109,105,99,111,109,10,32,32,99,112,117,32,
118,101,114,115,105,111,110,61,50,10,32,32,32,32,114,97,109,32,110,97,109,101,61,119,111,114,107,46,114,97,109,32,
115,105,122,101,61,48,120,50,48,48,48,48,32,118,111,108,97,116,105,108,101,10,32,32,115,109,112,10,32,32,32,32,
114,111,109,32,110,97,109,101,61,105,112,108,46,114,111,109,32,115,105,122,101,61,54,52,10,32,32,112,112,117,49,32,
118,101,114,115,105,111,110,61,49,10,32,32,32,32,114,97,109,32,110,97,109,101,61,118,105,100,101,111,46,114,97,109,
32,115,105,122,101,61,48,120,49,48,48,48,48,32,118,111,108,97,116,105,108,101,10,32,32,32,32,114,97,109,32,110,
97,109,101,61,111,98,106,101,99,116,46,114,97,109,32,115,105,122,101,61,53,52,52,32,118,111,108,97,116,105,108,101,
10,32,32,112,112,117,50,32,118,101,114,115,105,111,110,61,51,10,32,32,32,32,114,97,109,32,110,97,109,101,61,112,
97,108,101,116,116,101,46,114,97,109,32,115,105,122,101,61,53,49,50,32,118,111,108,97,116,105,108,101,10,32,32,100,
115,112,10,32,32,32,32,114,97,109,32,110,97,109,101,61,97,112,117,46,114,97,109,32,115,105,122,101,61,48,120,49,
48,48,48,48,32,118,111,108,97,116,105,108,101,10,
};
const nall::vector<uint8_t> Boards = { //size: 30182
100,97,116,97,98,97,115,101,10,32,32,114,101,118,105,115,105,111,110,58,32,50,48,49,56,45,48,55,45,50,53,10,
10,47,47,66,111,97,114,100,115,32,40,80,114,111,100,117,99,116,105,111,110,41,10,10,100,97,116,97,98,97,115,101,

View File

@ -2,7 +2,6 @@ namespace Resource {
extern const nall::vector<uint8_t> Icon;
extern const nall::vector<uint8_t> Logo;
namespace System {
extern const nall::vector<uint8_t> Manifest;
extern const nall::vector<uint8_t> Boards;
extern const nall::vector<uint8_t> IPLROM;
}

View File

@ -97,7 +97,6 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
});
hiresMode7.setText("Hires mode 7").setChecked(settings["Emulator/Hack/FastPPU/HiresMode7"].boolean()).onToggle([&] {
settings["Emulator/Hack/FastPPU/HiresMode7"].setValue(hiresMode7.checked());
emulator->set("Fast PPU/Hires Mode 7", hiresMode7.checked());
});
fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/Hack/FastDSP"].boolean()).onToggle([&] {
settings["Emulator/Hack/FastDSP"].setValue(fastDSPOption.checked());
@ -140,3 +139,10 @@ auto AdvancedSettings::updateInputDriver() -> void {
if(input && input->driver() == driver) item.setSelected();
}
}
auto AdvancedSettings::updateConfiguration() -> void {
emulator->configure("hacks/ppuFast/enable", fastPPUOption.checked());
emulator->configure("hacks/ppuFast/noSpriteLimit", noSpriteLimit.checked());
emulator->configure("hacks/ppuFast/hiresMode7", hiresMode7.checked());
emulator->configure("hacks/dspFast/enable", fastDSPOption.checked());
}

View File

@ -34,7 +34,6 @@ Settings::Settings() {
set("Audio/Skew", "0");
set("Audio/Volume", "100%");
set("Audio/Balance", "50%");
set("Audio/Reverb", false);
set("Input/Driver", Input::safestDriver());
set("Input/Frequency", 5);

View File

@ -157,6 +157,7 @@ struct AdvancedSettings : TabFrameItem {
auto updateVideoDriver() -> void;
auto updateAudioDriver() -> void;
auto updateInputDriver() -> void;
auto updateConfiguration() -> void;
public:
VerticalLayout layout{this};

View File

@ -19,6 +19,14 @@ auto Program::load(Emulator::Interface& interface) -> void {
gamePaths.append(locate({"systems/", information.name, ".sys/"}));
inputManager->bind(emulator = &interface);
if(auto configuration = string::read({gamePaths[0], "configuration.bml"})) {
emulator->configure(configuration);
}
if(auto configuration = emulator->configuration()) {
file::write({gamePaths[0], "configuration.bml"}, configuration);
}
presentation->updateEmulatorMenu();
if(!emulator->load()) {
emulator = nullptr;