Update to v102r28 release.

byuu says:

Changelog:

  - higan: `Emulator::<Platform::load>()` now returns a struct containing
    both a path ID and a string option
  - higan: `Emulator::<Platform::load>()` now takes an optional final
    argument of string options
  - fc: added PAL emulation (finally, only took six years)
  - md: added PAL emulation
  - md: fixed address parameter to `VDP::Sprite::write()`; fixes missing
    sprites in Super Street Fighter II
  - md: emulated HIRQ counter; fixes many games
      - Super Street Fighter II - status bar
      - Altered Beast - status bar
      - Sonic the Hedgehog - Labyrinth Zone - water effect
      - etc.
  - ms: added PAL emulation
  - sfc: added the ability to override the default region auto-detection
  - sfc: removed "system.region" override setting from `Super Famicom.sys`
  - tomoko: added options list to game folder load dialog window
  - tomoko: added the ability to specify game folder load options on the
    command-line

So, basically ... Sega forced a change with the way region detection
works. You end up with games that can run on multiple regions, and the
content changes accordingly. Bare Knuckle in NTSC-J mode will become
Streets of Rage in NTSC-U mode. Some games can even run in both NTSC and
PAL mode.

In my view, there should be a separate ROM for each region a game was
released in, even if the ROM content were identical. But unfortunately
that's not how things were done by anyone else.

So to support this, the higan load dialog now has a drop-down at the
bottom-right, where you can choose the region to load games from. On the
SNES, it defaults to "Auto", which will pull the region setting from the
manifest, or fall back on NTSC. On the Mega Drive ... unfortunately, I
can't auto-detect the region from the ROM header. $1f0 is supposed to
contain a string like "JUE", but instead you get games like Maui Mallard
that put an "A" there, and other such nonsense. Sega was far more lax
than Nintendo with the ROM header validity. So for now at least, you
have to manually select your region every time you play a Mega Drive
game, thus you have "NTSC-J", "NTSC-U", and "PAL". The same goes for the
Master System for the same reason, but there's only "NTSC" and "PAL"
here. I'm not sure if games have a way to detect domestic vs
international consoles.

And for now ... the Famicom is the same as well, with no auto-detection.
I'd sincerely hope iNES has a header bit for the region, but I didn't
bother with updating icarus to support that yet.

The way to pass these parameters on the command-line is to prefix the
game path with "option:", so for example:

    higan "PAL:/path/to/Sonic the Hedgehog (USA, Europe).md"

If you don't provide a prefix, it uses the default (NTSC-J, NTSC, or
Auto.) Obviously, it's not possible to pass parameters with
drag-and-drop, so you will always get the default option in said case.
This commit is contained in:
Tim Allen 2017-06-20 22:34:50 +10:00
parent e7806dd6e8
commit 8476f35153
41 changed files with 226 additions and 116 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "102.27";
static const string Version = "102.28";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -3,9 +3,22 @@
namespace Emulator {
struct Platform {
struct Load {
Load() : _pathID(nothing) {}
Load(uint pathID, string option = "") : _pathID(pathID), _option(option) {}
explicit operator bool() const { return (bool)_pathID; }
auto pathID() const -> uint { return _pathID(); }
auto option() const -> string { return _option; }
private:
maybe<uint> _pathID;
string _option;
};
virtual auto path(uint id) -> string { return ""; }
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
virtual auto load(uint id, string name, string type) -> maybe<uint> { return nothing; }
virtual auto load(uint id, string name, string type, string_vector options = {}) -> Load { return {}; }
virtual auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {}
virtual auto audioSample(const double* samples, uint channels) -> void {}
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }

View File

@ -15,8 +15,9 @@ auto Cartridge::main() -> void {
}
auto Cartridge::load() -> bool {
if(auto pathID = platform->load(ID::Famicom, "Famicom", "fc")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC", "PAL"})) {
information.pathID = loaded.pathID();
information.region = loaded.option();
} else return false;
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {

View File

@ -6,6 +6,7 @@ struct Cartridge : Thread {
auto main() -> void;
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
@ -20,6 +21,7 @@ struct Cartridge : Thread {
struct Information {
uint pathID = 0;
string region;
string sha256;
string manifest;
string title;

View File

@ -29,6 +29,11 @@ namespace Famicom {
}
};
struct Region {
static inline auto NTSC() -> bool;
static inline auto PAL() -> bool;
};
#include <fc/controller/controller.hpp>
#include <fc/system/system.hpp>
#include <fc/memory/memory.hpp>

View File

@ -16,16 +16,18 @@ auto PPU::main() -> void {
}
auto PPU::step(uint clocks) -> void {
uint L = vlines();
while(clocks--) {
if(io.ly == 240 && io.lx == 340) io.nmiHold = 1;
if(io.ly == 241 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == 241 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
if(io.ly == 260 && io.lx == 340) io.spriteZeroHit = 0, io.spriteOverflow = 0;
if(io.ly == L-2 && io.lx == 340) io.spriteZeroHit = 0, io.spriteOverflow = 0;
if(io.ly == 260 && io.lx == 340) io.nmiHold = 0;
if(io.ly == 261 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == 261 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
if(io.ly == L-2 && io.lx == 340) io.nmiHold = 0;
if(io.ly == L-1 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == L-1 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
Thread::step(4);
synchronize(cpu);
@ -36,7 +38,7 @@ auto PPU::step(uint clocks) -> void {
auto PPU::scanline() -> void {
io.lx = 0;
if(++io.ly == 262) {
if(++io.ly == vlines()) {
io.ly = 0;
frame();
}

View File

@ -1,4 +1,7 @@
struct PPU : Thread {
inline auto vlines() const -> uint { return Region::NTSC() ? 262 : 312; }
//ppu.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;

View File

@ -60,7 +60,7 @@ auto PPU::renderSprite() -> void {
if(!enable()) return;
uint n = latch.oamIterator++;
int ly = io.ly == 261 ? -1 : io.ly;
int ly = io.ly == vlines() - 1 ? -1 : io.ly;
uint y = ly - oam[n * 4 + 0];
if(y >= io.spriteHeight) return;
@ -79,7 +79,7 @@ auto PPU::renderSprite() -> void {
auto PPU::renderScanline() -> void {
//Vblank
if(io.ly >= 240 && io.ly <= 260) return step(341), scanline();
if(io.ly >= 240 && io.ly <= vlines() - 2) return step(341), scanline();
latch.oamIterator = 0;
latch.oamCounter = 0;
@ -162,7 +162,7 @@ auto PPU::renderScanline() -> void {
latch.oam[sprite].tiledataHi = loadCHR(tileaddr + 8);
step(2);
if(enable() && sprite == 6 && io.ly == 261) {
if(enable() && sprite == 6 && io.ly == vlines() - 1) {
//305
io.v.address = io.t.address;
}
@ -197,7 +197,7 @@ auto PPU::renderScanline() -> void {
//337-338
loadCHR(0x2000 | (uint12)io.v.address);
step(1);
bool skip = enable() && io.field == 1 && io.ly == 261;
bool skip = enable() && io.field == 1 && io.ly == vlines() - 1;
step(1);
//339

View File

@ -22,7 +22,8 @@ auto System::runToSave() -> void {
}
auto System::load(Emulator::Interface* interface) -> bool {
information = Information();
information = {};
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else {
@ -31,8 +32,16 @@ auto System::load(Emulator::Interface* interface) -> bool {
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC") {
information.region = Region::NTSC;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
}
if(cartridge.region() == "PAL") {
information.region = Region::PAL;
information.colorburst = Emulator::Constants::Colorburst::PAL * 4.0 / 5.0;
}
this->interface = interface;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
serializeInit();
return information.loaded = true;
}

View File

@ -1,5 +1,8 @@
struct System {
enum class Region : uint { NTSC, PAL };
auto loaded() const -> bool { return information.loaded; }
auto region() const -> Region { return information.region; }
auto colorburst() const -> double { return information.colorburst; }
auto run() -> void;
@ -30,7 +33,8 @@ private:
struct Information {
bool loaded = false;
double colorburst = 0.0;
Region region = Region::NTSC;
double colorburst = Emulator::Constants::Colorburst::NTSC;
string manifest;
} information;
@ -48,3 +52,6 @@ struct Peripherals {
extern System system;
extern Peripherals peripherals;
auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }

View File

@ -18,20 +18,20 @@ auto Cartridge::load() -> bool {
information = {};
if(Model::GameBoy()) {
if(auto pathID = platform->load(ID::GameBoy, "Game Boy", "gb")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::GameBoy, "Game Boy", "gb")) {
information.pathID = loaded.pathID();
} else return false;
}
if(Model::GameBoyColor()) {
if(auto pathID = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
information.pathID = loaded.pathID();
} else return false;
}
if(Model::SuperGameBoy()) {
if(auto pathID = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
information.pathID = loaded.pathID();
} else return false;
}

View File

@ -26,8 +26,8 @@ Cartridge::~Cartridge() {
auto Cartridge::load() -> bool {
information = Information();
if(auto pathID = platform->load(ID::GameBoyAdvance, "Game Boy Advance", "gba")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::GameBoyAdvance, "Game Boy Advance", "gba")) {
information.pathID = loaded.pathID();
} else return false;
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {

View File

@ -8,8 +8,9 @@ Cartridge cartridge;
auto Cartridge::load() -> bool {
information = {};
if(auto pathID = platform->load(ID::MegaDrive, "Mega Drive", "md")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::MegaDrive, "Mega Drive", "md", {"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)) {

View File

@ -1,5 +1,6 @@
struct Cartridge {
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
@ -20,6 +21,7 @@ struct Cartridge {
struct Information {
uint pathID = 0;
string region;
string sha256;
string manifest;
string title;

View File

@ -30,9 +30,20 @@ auto System::load(Emulator::Interface* interface, maybe<Region> region) -> bool
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC-J") {
information.region = Region::NTSCJ;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
}
if(cartridge.region() == "NTSC-U") {
information.region = Region::NTSCU;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
}
if(cartridge.region() == "PAL") {
information.region = Region::PAL;
information.colorburst = Emulator::Constants::Colorburst::PAL * 4.0 / 5.0;
}
serializeInit();
information.region = Region::NTSCU;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
this->interface = interface;
return information.loaded = true;
}

View File

@ -28,10 +28,10 @@ private:
Emulator::Interface* interface = nullptr;
struct Information {
string manifest;
bool loaded = false;
Region region = Region::NTSCJ;
string manifest;
double colorburst = 0.0;
double colorburst = Emulator::Constants::Colorburst::NTSC;
uint serializeSize = 0;
} information;
};

View File

@ -13,7 +13,7 @@ auto VDP::read(uint24 addr) -> uint16 {
//counter
case 0xc00008: case 0xc0000a: case 0xc0000c: case 0xc0000e: {
return state.y << 8 | (state.x >> 1) << 0;
return state.vcounter << 8 | (state.hdot >> 1) << 0;
}
}
@ -115,8 +115,8 @@ auto VDP::readControlPort() -> uint16 {
uint16 result = 0b0011'0100'0000'0000;
result |= 1 << 9; //FIFO empty
result |= (state.y >= screenHeight()) << 3; //vertical blank
result |= (state.y >= screenHeight() || state.x >= 320) << 2; //horizontal blank
result |= (state.vcounter >= screenHeight()) << 3; //vertical blank
result |= (state.vcounter >= screenHeight() || state.hcounter >= 1280) << 2; //horizontal blank
result |= io.command.bit(5) << 1; //DMA active
return result;
}

View File

@ -6,7 +6,7 @@ auto VDP::VRAM::write(uint15 address, uint16 data) -> void {
memory[address] = data;
if(address < vdp.sprite.io.attributeAddress) return;
if(address > vdp.sprite.io.attributeAddress + 319) return;
vdp.sprite.write(address, data);
vdp.sprite.write(address - vdp.sprite.io.attributeAddress, data);
}
auto VDP::VRAM::readByte(uint16 address) const -> uint8 {

View File

@ -3,33 +3,33 @@ auto VDP::frame() -> void {
}
auto VDP::scanline() -> void {
if(++state.y >= 262) state.y = 0;
if(state.y == 0) frame();
state.x = 0;
state.hdot = 0;
state.hcounter = 0;
if(++state.vcounter >= frameHeight()) state.vcounter = 0;
if(state.vcounter == 0) frame();
latch.displayWidth = io.displayWidth;
if(state.y < screenHeight()) {
planeA.scanline(state.y);
window.scanline(state.y);
planeB.scanline(state.y);
sprite.scanline(state.y);
if(state.vcounter < screenHeight()) {
planeA.scanline(state.vcounter);
window.scanline(state.vcounter);
planeB.scanline(state.vcounter);
sprite.scanline(state.vcounter);
}
if(state.y == 240) scheduler.exit(Scheduler::Event::Frame);
if(state.vcounter == 240) scheduler.exit(Scheduler::Event::Frame);
state.output = buffer + (state.y * 2 + 0) * 1280;
state.output = buffer + (state.vcounter * 2 + 0) * 1280;
}
auto VDP::run() -> void {
if(!io.displayEnable) return outputPixel(0);
if(state.y >= screenHeight()) return outputPixel(0);
if(state.vcounter >= screenHeight()) return outputPixel(0);
auto& planeA = window.isWindowed(state.x, state.y) ? window : this->planeA;
planeA.run(state.x, state.y);
planeB.run(state.x, state.y);
sprite.run(state.x, state.y);
auto& planeA = window.isWindowed(state.hdot, state.vcounter) ? window : this->planeA;
planeA.run(state.hdot, state.vcounter);
planeB.run(state.hdot, state.vcounter);
sprite.run(state.hdot, state.vcounter);
auto output = io.backgroundColor;
if(auto color = planeB.output.color) output = color;
@ -40,7 +40,7 @@ auto VDP::run() -> void {
if(sprite.output.priority) if(auto color = sprite.output.color) output = color;
outputPixel(cram.read(output));
state.x++;
state.hdot++;
}
auto VDP::outputPixel(uint9 color) -> void {

View File

@ -37,11 +37,12 @@ auto VDP::serialize(serializer& s) -> void {
s.integer(io.dataIncrement);
s.integer(latch.overscan);
s.integer(latch.horizontalInterruptCounter);
s.integer(latch.displayWidth);
s.integer(state.hdot);
s.integer(state.hcounter);
s.integer(state.x);
s.integer(state.y);
s.integer(state.vcounter);
}
auto VDP::DMA::serialize(serializer& s) -> void {

View File

@ -17,28 +17,38 @@ auto VDP::Enter() -> void {
auto VDP::main() -> void {
scanline();
if(state.y < screenHeight()) {
if(state.y == 0) {
cpu.lower(CPU::Interrupt::VerticalBlank);
cpu.lower(CPU::Interrupt::HorizontalBlank);
apu.setINT(false);
if(state.vcounter == 0) {
latch.horizontalInterruptCounter = io.horizontalInterruptCounter;
cpu.lower(CPU::Interrupt::VerticalBlank);
}
if(state.vcounter == screenHeight()) {
if(io.verticalBlankInterruptEnable) {
cpu.raise(CPU::Interrupt::VerticalBlank);
}
cpu.lower(CPU::Interrupt::HorizontalBlank);
apu.setINT(true);
}
if(state.vcounter < screenHeight()) {
while(state.hcounter < 1280) {
run();
step(pixelWidth());
}
if(io.horizontalBlankInterruptEnable) {
cpu.raise(CPU::Interrupt::HorizontalBlank);
if(latch.horizontalInterruptCounter-- == 0) {
latch.horizontalInterruptCounter = io.horizontalInterruptCounter;
if(io.horizontalBlankInterruptEnable) {
cpu.raise(CPU::Interrupt::HorizontalBlank);
}
}
step(430);
} else {
if(state.y == screenHeight()) {
if(io.verticalBlankInterruptEnable) {
cpu.raise(CPU::Interrupt::VerticalBlank);
}
apu.setINT(true);
}
step(1710);
apu.setINT(false);
}
}

View File

@ -144,6 +144,7 @@ private:
auto pixelWidth() const -> uint { return latch.displayWidth ? 4 : 5; }
auto screenWidth() const -> uint { return latch.displayWidth ? 320 : 256; }
auto screenHeight() const -> uint { return latch.overscan ? 240 : 224; }
auto frameHeight() const -> uint { return Region::PAL() ? 312 : 262; }
//video RAM
struct VRAM {
@ -234,6 +235,7 @@ private:
struct Latch {
//per-frame
uint1 overscan;
uint8 horizontalInterruptCounter;
//per-scanline
uint2 displayWidth;
@ -241,9 +243,9 @@ private:
struct State {
uint32* output = nullptr;
uint hdot;
uint hcounter;
uint x;
uint y;
uint vcounter;
} state;
uint32 buffer[1280 * 480];

View File

@ -10,14 +10,15 @@ auto Cartridge::load() -> bool {
information = {};
if(Model::MasterSystem()) {
if(auto pathID = platform->load(ID::MasterSystem, "Master System", "ms")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::MasterSystem, "Master System", "ms", {"NTSC", "PAL"})) {
information.pathID = loaded.pathID();
information.region = loaded.option();
} else return false;
}
if(Model::GameGear()) {
if(auto pathID = platform->load(ID::GameGear, "Game Gear", "gg")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::GameGear, "Game Gear", "gg")) {
information.pathID = loaded.pathID();
} else return false;
}

View File

@ -1,5 +1,6 @@
struct Cartridge {
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
@ -20,6 +21,7 @@ struct Cartridge {
private:
struct Information {
uint pathID = 0;
string region;
string sha256;
string manifest;
string title;

View File

@ -34,6 +34,11 @@ namespace MasterSystem {
inline static auto GameGear() -> bool;
};
struct Region {
inline static auto NTSC() -> bool;
inline static auto PAL() -> bool;
};
#include <ms/controller/controller.hpp>
#include <ms/cpu/cpu.hpp>

View File

@ -32,9 +32,17 @@ auto System::load(Emulator::Interface* interface, Model model) -> bool {
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC") {
information.region = Region::NTSC;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
}
if(cartridge.region() == "PAL") {
information.region = Region::PAL;
information.colorburst = Emulator::Constants::Colorburst::PAL * 4.0 / 5.0;
}
serializeInit();
this->interface = interface;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
return information.loaded = true;
}

View File

@ -1,8 +1,10 @@
struct System {
enum class Model : uint { MasterSystem, GameGear };
enum class Region : uint { NTSC, PAL };
auto loaded() const -> bool { return information.loaded; }
auto model() const -> Model { return information.model; }
auto region() const -> Region { return information.region; }
auto colorburst() const -> double { return information.colorburst; }
auto run() -> void;
@ -27,8 +29,9 @@ private:
struct Information {
bool loaded = false;
Model model = Model::MasterSystem;
Region region = Region::NTSC;
double colorburst = Emulator::Constants::Colorburst::NTSC;
string manifest;
double colorburst = 0.0;
uint serializeSize = 0;
} information;
};
@ -47,3 +50,6 @@ extern Peripherals peripherals;
auto Model::MasterSystem() -> bool { return system.model() == System::Model::MasterSystem; }
auto Model::GameGear() -> bool { return system.model() == System::Model::GameGear; }
auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }

View File

@ -61,7 +61,7 @@ auto VDP::step(uint clocks) -> void {
while(clocks--) {
if(++io.hcounter == 684) {
io.hcounter = 0;
if(++io.vcounter == 262) {
if(++io.vcounter == (Region::NTSC() ? 262 : 312)) {
io.vcounter = 0;
}
}

View File

@ -8,14 +8,14 @@ auto Cartridge::load() -> bool {
information = {};
if(Model::PCEngine()) {
if(auto pathID = platform->load(ID::PCEngine, "PC Engine", "pce")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::PCEngine, "PC Engine", "pce")) {
information.pathID = loaded.pathID();
} else return false;
}
if(Model::SuperGrafx()) {
if(auto pathID = platform->load(ID::SuperGrafx, "SuperGrafx", "sg")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::SuperGrafx, "SuperGrafx", "sg")) {
information.pathID = loaded.pathID();
} else return false;
}

View File

@ -29,8 +29,9 @@ auto Cartridge::load() -> bool {
information = {};
has = {};
if(auto pathID = platform->load(ID::SuperFamicom, "Super Famicom", "sfc")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::SuperFamicom, "Super Famicom", "sfc", {"Auto", "NTSC", "PAL"})) {
information.pathID = loaded.pathID();
information.region = loaded.option();
} else return false;
if(auto fp = platform->open(ID::SuperFamicom, "manifest.bml", File::Read, File::Required)) {

View File

@ -1,9 +1,7 @@
struct Cartridge {
enum class Region : uint { NTSC, PAL };
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
auto sha256() const -> string { return information.sha256; }
auto region() const -> Region { return information.region; }
auto manifest() const -> string;
auto title() const -> string;
@ -18,8 +16,8 @@ struct Cartridge {
struct Information {
uint pathID = 0;
string region;
string sha256;
Region region = Region::NTSC;
struct Manifest {
string cartridge;

View File

@ -1,17 +1,20 @@
auto Cartridge::loadCartridge(Markup::Node node) -> void {
information.title.cartridge = node["information/title"].text();
auto board = node["board"];
information.region = board["region"].text() == "pal" ? Region::PAL : Region::NTSC;
if(!region() || region() == "Auto") {
if(board["region"].text() == "ntsc") information.region = "NTSC";
if(board["region"].text() == "pal") information.region = "PAL";
}
if(board["mcc"] || board["bsmemory"]) {
if(auto pathID = platform->load(ID::BSMemory, "BS Memory", "bs")) {
bsmemory.pathID = pathID();
if(auto loaded = platform->load(ID::BSMemory, "BS Memory", "bs")) {
bsmemory.pathID = loaded.pathID();
loadBSMemory();
}
}
if(board["sufamiturbo"]) {
if(auto pathID = platform->load(ID::SufamiTurboA, "Sufami Turbo", "st")) {
sufamiturboA.pathID = pathID();
if(auto loaded = platform->load(ID::SufamiTurboA, "Sufami Turbo", "st")) {
sufamiturboA.pathID = loaded.pathID();
loadSufamiTurboA();
}
}
@ -55,8 +58,8 @@ auto Cartridge::loadSufamiTurboA(Markup::Node node) -> void {
loadMemory(sufamiturboA.ram, node["board/ram"], File::Optional, sufamiturboA.pathID);
if(node["board/linkable"]) {
if(auto pathID = platform->load(ID::SufamiTurboB, "Sufami Turbo", "st")) {
sufamiturboB.pathID = pathID();
if(auto loaded = platform->load(ID::SufamiTurboB, "Sufami Turbo", "st")) {
sufamiturboB.pathID = loaded.pathID();
loadSufamiTurboB();
}
}

View File

@ -38,6 +38,11 @@ namespace SuperFamicom {
}
};
struct Region {
static inline auto NTSC() -> bool;
static inline auto PAL() -> bool;
};
#include <sfc/memory/memory.hpp>
#include <sfc/ppu/counter/counter.hpp>

View File

@ -39,7 +39,6 @@ auto System::unserialize(serializer& s) -> bool {
//internal
auto System::serialize(serializer& s) -> void {
s.integer((uint&)information.region);
}
auto System::serializeAll(serializer& s) -> void {

View File

@ -47,7 +47,7 @@ auto System::term() -> void {
}
auto System::load(Emulator::Interface* interface) -> bool {
information = Information();
information = {};
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
@ -63,13 +63,14 @@ auto System::load(Emulator::Interface* interface) -> bool {
if(!dsp.load(system)) return false;
if(!cartridge.load()) return false;
information.region = cartridge.region() == Cartridge::Region::NTSC ? Region::NTSC : Region::PAL;
if(system["region"].text() == "NTSC") information.region = Region::NTSC;
if(system["region"].text() == "PAL" ) information.region = Region::PAL;
information.colorburst = region() == Region::NTSC
? Emulator::Constants::Colorburst::NTSC
: Emulator::Constants::Colorburst::PAL * 4.0 / 5.0;
if(cartridge.region() == "NTSC") {
information.region = Region::NTSC;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
}
if(cartridge.region() == "PAL") {
information.region = Region::PAL;
information.colorburst = Emulator::Constants::Colorburst::PAL * 4.0 / 5.0;
}
if(cartridge.has.ICD2) icd2.load();
if(cartridge.has.MCC) mcc.load();

View File

@ -1,5 +1,5 @@
struct System {
enum class Region : bool { NTSC = 0, PAL = 1 };
enum class Region : uint { NTSC, PAL };
inline auto loaded() const -> bool { return information.loaded; }
inline auto region() const -> Region { return information.region; }
@ -30,7 +30,7 @@ private:
string manifest;
bool loaded = false;
Region region = Region::NTSC;
double colorburst = 0.0;
double colorburst = Emulator::Constants::Colorburst::NTSC;
} information;
uint serializeSize = 0;
@ -64,3 +64,6 @@ private:
extern System system;
extern Peripherals peripherals;
extern Random random;
auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }

View File

@ -1,4 +1,4 @@
system region=auto name:Super Famicom
system name:Super Famicom
cpu version=2
ram name=work.ram size=0x20000 volatile
smp

View File

@ -23,22 +23,30 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
return {};
}
auto Program::load(uint id, string name, string type) -> maybe<uint> {
string location;
auto Program::load(uint id, string name, string type, string_vector options) -> Emulator::Platform::Load {
string location, option;
if(mediumQueue) {
location = mediumQueue.takeLeft();
auto entry = mediumQueue.takeLeft().split(":", 1L);
location = entry.right();
if(entry.size() == 2) option = entry.left();
} else {
location = BrowserDialog()
BrowserDialog dialog;
location = dialog
.setTitle({"Load ", name})
.setPath({settings["Library/Location"].text(), name})
.setFilters({string{name, "|*.", type}, "All|*.*"})
.setOptions(options)
.openFolder();
option = dialog.option();
}
if(!directory::exists(location)) {
mediumQueue.reset();
return {};
}
if(!directory::exists(location)) return mediumQueue.reset(), nothing;
uint pathID = mediumPaths.size();
mediumPaths.append(location);
return pathID;
return {pathID, option};
}
auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {

View File

@ -69,7 +69,8 @@ Program::Program(string_vector args) {
for(auto& argument : args) {
if(argument == "--fullscreen") {
presentation->toggleFullScreen();
} else if(directory::exists(argument)) {
} else if(directory::exists(argument.split(":", 1L).right())) {
if(!argument.transform("\\", "/").endsWith("/")) argument.append("/");
mediumQueue.append(argument);
} else if(file::exists(argument)) {
if(auto result = execute("icarus", "--import", argument)) {

View File

@ -7,7 +7,7 @@ struct Program : Emulator::Platform {
//interface.cpp
auto path(uint id) -> string override;
auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override;
auto load(uint id, string name, string type) -> maybe<uint> override;
auto load(uint id, string name, string type, string_vector options = {}) -> Emulator::Platform::Load override;
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override;

View File

@ -42,18 +42,18 @@ auto Cartridge::power() -> void {
}
auto Cartridge::load() -> bool {
information = Information();
information = {};
switch(system.model()) {
case Model::WonderSwan:
if(auto pathID = platform->load(ID::WonderSwan, "WonderSwan", "ws")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::WonderSwan, "WonderSwan", "ws")) {
information.pathID = loaded.pathID();
} else return false;
break;
case Model::WonderSwanColor:
case Model::SwanCrystal:
if(auto pathID = platform->load(ID::WonderSwanColor, "WonderSwan Color", "wsc")) {
information.pathID = pathID();
if(auto loaded = platform->load(ID::WonderSwanColor, "WonderSwan Color", "wsc")) {
information.pathID = loaded.pathID();
} else return false;
break;
}