From 8476f35153a2ae28ef77e11765bdf99908078388 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 20 Jun 2017 22:34:50 +1000 Subject: [PATCH] Update to v102r28 release. byuu says: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changelog: - higan: `Emulator::()` now returns a struct containing both a path ID and a string option - higan: `Emulator::()` 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. --- higan/emulator/emulator.hpp | 2 +- higan/emulator/platform.hpp | 15 +++++++- higan/fc/cartridge/cartridge.cpp | 5 +-- higan/fc/cartridge/cartridge.hpp | 2 ++ higan/fc/fc.hpp | 5 +++ higan/fc/ppu/ppu.cpp | 12 ++++--- higan/fc/ppu/ppu.hpp | 3 ++ higan/fc/ppu/render.cpp | 8 ++--- higan/fc/system/system.cpp | 13 +++++-- higan/fc/system/system.hpp | 9 ++++- higan/gb/cartridge/cartridge.cpp | 12 +++---- higan/gba/cartridge/cartridge.cpp | 4 +-- higan/md/cartridge/cartridge.cpp | 5 +-- higan/md/cartridge/cartridge.hpp | 2 ++ higan/md/system/system.cpp | 15 ++++++-- higan/md/system/system.hpp | 4 +-- higan/md/vdp/io.cpp | 6 ++-- higan/md/vdp/memory.cpp | 2 +- higan/md/vdp/render.cpp | 32 ++++++++--------- higan/md/vdp/serialization.cpp | 5 +-- higan/md/vdp/vdp.cpp | 36 +++++++++++++------- higan/md/vdp/vdp.hpp | 6 ++-- higan/ms/cartridge/cartridge.cpp | 9 ++--- higan/ms/cartridge/cartridge.hpp | 2 ++ higan/ms/ms.hpp | 5 +++ higan/ms/system/system.cpp | 10 +++++- higan/ms/system/system.hpp | 8 ++++- higan/ms/vdp/vdp.cpp | 2 +- higan/pce/cartridge/cartridge.cpp | 8 ++--- higan/sfc/cartridge/cartridge.cpp | 5 +-- higan/sfc/cartridge/cartridge.hpp | 6 ++-- higan/sfc/cartridge/load.cpp | 17 +++++---- higan/sfc/sfc.hpp | 5 +++ higan/sfc/system/serialization.cpp | 1 - higan/sfc/system/system.cpp | 17 ++++----- higan/sfc/system/system.hpp | 7 ++-- higan/systems/Super Famicom.sys/manifest.bml | 2 +- higan/target-tomoko/program/interface.cpp | 20 +++++++---- higan/target-tomoko/program/program.cpp | 3 +- higan/target-tomoko/program/program.hpp | 2 +- higan/ws/cartridge/cartridge.cpp | 10 +++--- 41 files changed, 226 insertions(+), 116 deletions(-) diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 36d10282..b1c65d58 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -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/"; diff --git a/higan/emulator/platform.hpp b/higan/emulator/platform.hpp index 9e0a7c96..cd341fad 100644 --- a/higan/emulator/platform.hpp +++ b/higan/emulator/platform.hpp @@ -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 _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 { 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; } diff --git a/higan/fc/cartridge/cartridge.cpp b/higan/fc/cartridge/cartridge.cpp index 9bc8b803..480e8d32 100644 --- a/higan/fc/cartridge/cartridge.cpp +++ b/higan/fc/cartridge/cartridge.cpp @@ -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)) { diff --git a/higan/fc/cartridge/cartridge.hpp b/higan/fc/cartridge/cartridge.hpp index 165f2c5b..09d85c59 100644 --- a/higan/fc/cartridge/cartridge.hpp +++ b/higan/fc/cartridge/cartridge.hpp @@ -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; diff --git a/higan/fc/fc.hpp b/higan/fc/fc.hpp index 30015fef..598da789 100644 --- a/higan/fc/fc.hpp +++ b/higan/fc/fc.hpp @@ -29,6 +29,11 @@ namespace Famicom { } }; + struct Region { + static inline auto NTSC() -> bool; + static inline auto PAL() -> bool; + }; + #include #include #include diff --git a/higan/fc/ppu/ppu.cpp b/higan/fc/ppu/ppu.cpp index 69faf451..67b44ecc 100644 --- a/higan/fc/ppu/ppu.cpp +++ b/higan/fc/ppu/ppu.cpp @@ -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(); } diff --git a/higan/fc/ppu/ppu.hpp b/higan/fc/ppu/ppu.hpp index 1e3e7040..a23dc539 100644 --- a/higan/fc/ppu/ppu.hpp +++ b/higan/fc/ppu/ppu.hpp @@ -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; diff --git a/higan/fc/ppu/render.cpp b/higan/fc/ppu/render.cpp index ec5863b5..f7735fd7 100644 --- a/higan/fc/ppu/render.cpp +++ b/higan/fc/ppu/render.cpp @@ -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 diff --git a/higan/fc/system/system.cpp b/higan/fc/system/system.cpp index e0e8e676..d0ec443b 100644 --- a/higan/fc/system/system.cpp +++ b/higan/fc/system/system.cpp @@ -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; } diff --git a/higan/fc/system/system.hpp b/higan/fc/system/system.hpp index 1635ca06..95de6ac2 100644 --- a/higan/fc/system/system.hpp +++ b/higan/fc/system/system.hpp @@ -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; } diff --git a/higan/gb/cartridge/cartridge.cpp b/higan/gb/cartridge/cartridge.cpp index 53f5e9af..25ab6997 100644 --- a/higan/gb/cartridge/cartridge.cpp +++ b/higan/gb/cartridge/cartridge.cpp @@ -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; } diff --git a/higan/gba/cartridge/cartridge.cpp b/higan/gba/cartridge/cartridge.cpp index 83cfe212..372bb58b 100644 --- a/higan/gba/cartridge/cartridge.cpp +++ b/higan/gba/cartridge/cartridge.cpp @@ -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)) { diff --git a/higan/md/cartridge/cartridge.cpp b/higan/md/cartridge/cartridge.cpp index 480bb489..c51ab4d1 100644 --- a/higan/md/cartridge/cartridge.cpp +++ b/higan/md/cartridge/cartridge.cpp @@ -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)) { diff --git a/higan/md/cartridge/cartridge.hpp b/higan/md/cartridge/cartridge.hpp index 23b8cbfe..d3705c95 100644 --- a/higan/md/cartridge/cartridge.hpp +++ b/higan/md/cartridge/cartridge.hpp @@ -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; diff --git a/higan/md/system/system.cpp b/higan/md/system/system.cpp index ea2fa323..7ddf9459 100644 --- a/higan/md/system/system.cpp +++ b/higan/md/system/system.cpp @@ -30,9 +30,20 @@ auto System::load(Emulator::Interface* interface, maybe 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; } diff --git a/higan/md/system/system.hpp b/higan/md/system/system.hpp index 579057d9..64fc4c35 100644 --- a/higan/md/system/system.hpp +++ b/higan/md/system/system.hpp @@ -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; }; diff --git a/higan/md/vdp/io.cpp b/higan/md/vdp/io.cpp index 3e174269..8c723998 100644 --- a/higan/md/vdp/io.cpp +++ b/higan/md/vdp/io.cpp @@ -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; } diff --git a/higan/md/vdp/memory.cpp b/higan/md/vdp/memory.cpp index aad8c0b2..7d4f3eca 100644 --- a/higan/md/vdp/memory.cpp +++ b/higan/md/vdp/memory.cpp @@ -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 { diff --git a/higan/md/vdp/render.cpp b/higan/md/vdp/render.cpp index accb14cc..14bd15d9 100644 --- a/higan/md/vdp/render.cpp +++ b/higan/md/vdp/render.cpp @@ -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 { diff --git a/higan/md/vdp/serialization.cpp b/higan/md/vdp/serialization.cpp index d9e736f0..3d152899 100644 --- a/higan/md/vdp/serialization.cpp +++ b/higan/md/vdp/serialization.cpp @@ -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 { diff --git a/higan/md/vdp/vdp.cpp b/higan/md/vdp/vdp.cpp index 03065215..8c349263 100644 --- a/higan/md/vdp/vdp.cpp +++ b/higan/md/vdp/vdp.cpp @@ -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); } } diff --git a/higan/md/vdp/vdp.hpp b/higan/md/vdp/vdp.hpp index a0b9af95..77607e83 100644 --- a/higan/md/vdp/vdp.hpp +++ b/higan/md/vdp/vdp.hpp @@ -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]; diff --git a/higan/ms/cartridge/cartridge.cpp b/higan/ms/cartridge/cartridge.cpp index a330e8df..75027961 100644 --- a/higan/ms/cartridge/cartridge.cpp +++ b/higan/ms/cartridge/cartridge.cpp @@ -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; } diff --git a/higan/ms/cartridge/cartridge.hpp b/higan/ms/cartridge/cartridge.hpp index 348ab248..855e89db 100644 --- a/higan/ms/cartridge/cartridge.hpp +++ b/higan/ms/cartridge/cartridge.hpp @@ -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; diff --git a/higan/ms/ms.hpp b/higan/ms/ms.hpp index 76540292..a3398e1e 100644 --- a/higan/ms/ms.hpp +++ b/higan/ms/ms.hpp @@ -34,6 +34,11 @@ namespace MasterSystem { inline static auto GameGear() -> bool; }; + struct Region { + inline static auto NTSC() -> bool; + inline static auto PAL() -> bool; + }; + #include #include diff --git a/higan/ms/system/system.cpp b/higan/ms/system/system.cpp index 6a7d370e..13b320a1 100644 --- a/higan/ms/system/system.cpp +++ b/higan/ms/system/system.cpp @@ -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; } diff --git a/higan/ms/system/system.hpp b/higan/ms/system/system.hpp index 2f676f51..e786b07a 100644 --- a/higan/ms/system/system.hpp +++ b/higan/ms/system/system.hpp @@ -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; } diff --git a/higan/ms/vdp/vdp.cpp b/higan/ms/vdp/vdp.cpp index 5e7d43b2..051e2b31 100644 --- a/higan/ms/vdp/vdp.cpp +++ b/higan/ms/vdp/vdp.cpp @@ -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; } } diff --git a/higan/pce/cartridge/cartridge.cpp b/higan/pce/cartridge/cartridge.cpp index a8183aac..d74730a6 100644 --- a/higan/pce/cartridge/cartridge.cpp +++ b/higan/pce/cartridge/cartridge.cpp @@ -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; } diff --git a/higan/sfc/cartridge/cartridge.cpp b/higan/sfc/cartridge/cartridge.cpp index 0e4c6826..89781b1c 100644 --- a/higan/sfc/cartridge/cartridge.cpp +++ b/higan/sfc/cartridge/cartridge.cpp @@ -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)) { diff --git a/higan/sfc/cartridge/cartridge.hpp b/higan/sfc/cartridge/cartridge.hpp index 22eda2fe..5cada2d4 100644 --- a/higan/sfc/cartridge/cartridge.hpp +++ b/higan/sfc/cartridge/cartridge.hpp @@ -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; diff --git a/higan/sfc/cartridge/load.cpp b/higan/sfc/cartridge/load.cpp index 2ffd3663..8fd2acf7 100644 --- a/higan/sfc/cartridge/load.cpp +++ b/higan/sfc/cartridge/load.cpp @@ -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(); } } diff --git a/higan/sfc/sfc.hpp b/higan/sfc/sfc.hpp index 86dd61b2..688aa12e 100644 --- a/higan/sfc/sfc.hpp +++ b/higan/sfc/sfc.hpp @@ -38,6 +38,11 @@ namespace SuperFamicom { } }; + struct Region { + static inline auto NTSC() -> bool; + static inline auto PAL() -> bool; + }; + #include #include diff --git a/higan/sfc/system/serialization.cpp b/higan/sfc/system/serialization.cpp index 7f883df8..cc5202bf 100644 --- a/higan/sfc/system/serialization.cpp +++ b/higan/sfc/system/serialization.cpp @@ -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 { diff --git a/higan/sfc/system/system.cpp b/higan/sfc/system/system.cpp index f90a6d4a..8a7b2a4f 100644 --- a/higan/sfc/system/system.cpp +++ b/higan/sfc/system/system.cpp @@ -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(); diff --git a/higan/sfc/system/system.hpp b/higan/sfc/system/system.hpp index bd324df5..e8beb422 100644 --- a/higan/sfc/system/system.hpp +++ b/higan/sfc/system/system.hpp @@ -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; } diff --git a/higan/systems/Super Famicom.sys/manifest.bml b/higan/systems/Super Famicom.sys/manifest.bml index 1d180bac..3190f010 100644 --- a/higan/systems/Super Famicom.sys/manifest.bml +++ b/higan/systems/Super Famicom.sys/manifest.bml @@ -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 diff --git a/higan/target-tomoko/program/interface.cpp b/higan/target-tomoko/program/interface.cpp index 415dac60..f87ecb9f 100644 --- a/higan/target-tomoko/program/interface.cpp +++ b/higan/target-tomoko/program/interface.cpp @@ -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 { - 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 { diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index 3d295154..74170868 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -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)) { diff --git a/higan/target-tomoko/program/program.hpp b/higan/target-tomoko/program/program.hpp index 7c1eae70..51a988a3 100644 --- a/higan/target-tomoko/program/program.hpp +++ b/higan/target-tomoko/program/program.hpp @@ -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 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; diff --git a/higan/ws/cartridge/cartridge.cpp b/higan/ws/cartridge/cartridge.cpp index e63e2714..73c38758 100644 --- a/higan/ws/cartridge/cartridge.cpp +++ b/higan/ws/cartridge/cartridge.cpp @@ -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; }