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; }