mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
e7806dd6e8
commit
8476f35153
|
@ -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/";
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue