diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index fc230d1e..f3d77c4c 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -1,8 +1,23 @@ #pragma once -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include using namespace nall; #include "types.hpp" diff --git a/higan/gb/cartridge/cartridge.cpp b/higan/gb/cartridge/cartridge.cpp index 012ff51f..1e8688c9 100644 --- a/higan/gb/cartridge/cartridge.cpp +++ b/higan/gb/cartridge/cartridge.cpp @@ -17,6 +17,19 @@ Cartridge cartridge; #include "tama/tama.cpp" #include "serialization.cpp" +auto Cartridge::Enter() -> void { + while(true) scheduler.synchronize(), cartridge.main(); +} + +auto Cartridge::main() -> void { + mapper->main(); +} + +auto Cartridge::step(uint clocks) -> void { + Thread::step(clocks); + synchronize(cpu); +} + auto Cartridge::load() -> bool { information = {}; rom = {}; @@ -97,6 +110,7 @@ auto Cartridge::load() -> bool { } information.sha256 = Hash::SHA256(rom.data, rom.size).digest(); + mapper->load(document); return true; } @@ -118,6 +132,8 @@ auto Cartridge::save() -> void { } } } + + mapper->save(document); } auto Cartridge::unload() -> void { @@ -154,6 +170,8 @@ auto Cartridge::writeIO(uint16 addr, uint8 data) -> void { } auto Cartridge::power() -> void { + create(Enter, 4 * 1024 * 1024); + for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this; for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this; bus.mmio[0xff50] = this; @@ -179,4 +197,10 @@ auto Cartridge::Memory::write(uint address, uint8 byte) -> void { data[address] = byte; } +// + +auto Cartridge::Mapper::main() -> void { + cartridge.step(cartridge.frequency()); +} + } diff --git a/higan/gb/cartridge/cartridge.hpp b/higan/gb/cartridge/cartridge.hpp index 2e25a5ad..87637638 100644 --- a/higan/gb/cartridge/cartridge.hpp +++ b/higan/gb/cartridge/cartridge.hpp @@ -1,9 +1,10 @@ -struct Cartridge : MMIO { +struct Cartridge : Thread, MMIO { auto pathID() const -> uint { return information.pathID; } auto hash() const -> string { return information.sha256; } auto manifest() const -> string { return information.manifest; } auto title() const -> string { return information.title; } + static auto Enter() -> void; auto load() -> bool; auto save() -> void; auto unload() -> void; @@ -11,6 +12,8 @@ struct Cartridge : MMIO { auto readIO(uint16 address) -> uint8; auto writeIO(uint16 address, uint8 data) -> void; + auto main() -> void; + auto step(uint clocks) -> void; auto power() -> void; auto second() -> void; @@ -35,6 +38,9 @@ struct Cartridge : MMIO { private: struct Mapper { + virtual auto load(Markup::Node document) -> void {} + virtual auto save(Markup::Node document) -> void {} + virtual auto main() -> void; virtual auto second() -> void {} virtual auto read(uint16 address) -> uint8 = 0; virtual auto write(uint16 address, uint8 data) -> void = 0; diff --git a/higan/gb/cartridge/mbc7/eeprom.cpp b/higan/gb/cartridge/mbc7/eeprom.cpp new file mode 100644 index 00000000..cd2564a8 --- /dev/null +++ b/higan/gb/cartridge/mbc7/eeprom.cpp @@ -0,0 +1,226 @@ +//Microchip 93LCx6 +// 93LC46 => 1024 cells => 128 x 8-bit or 64 x 16-bit +// 93LC56 => 2048 cells => 256 x 8-bit or 128 x 16-bit +// 93LC66 => 4096 cells => 512 x 8-bit or 256 x 16-bit + +auto Cartridge::MBC7::EEPROM::load(Markup::Node document) -> void { + for(auto& byte : data) byte = 0xff; + size = 4096; //EEPROM size is in bits + width = 16; //16-bit configuration + + if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=EEPROM,content=Save)"]}) { + if(memory.size == 128) size = 1024; //manifest size is in bytes + if(memory.size == 256) size = 2048; + if(memory.size == 512) size = 4096; + + if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read, File::Optional)) { + fp->read(data, min(fp->size(), sizeof(data))); + } + } + + command.length = 3; + if(size == 1024) address.length = width == 8 ? 6 : 7; + if(size == 2048) address.length = width == 8 ? 7 : 8; + if(size == 4096) address.length = width == 8 ? 8 : 9; + input.length = width; + output.length = 1 + width; //there is an extra zero dummy bit on reads +} + +auto Cartridge::MBC7::EEPROM::save(Markup::Node document) -> void { + if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=EEPROM,content=Save)"]}) { + if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Write)) { + fp->write(data, size >> 3); //bytes -> bits + } + } +} + +auto Cartridge::MBC7::EEPROM::main() -> void { + //step by approximately one millisecond + cartridge.step(cartridge.frequency() / 1000); + + //set during programming commands + if(busy) busy--; +} + +auto Cartridge::MBC7::EEPROM::power(bool reset) -> void { + if(!reset) { + select = 0; + writable = 0; + } + + clock = 0; + busy = 0; + + command.flush(); + address.flush(); + input.flush(); + output.flush(); +} + +auto Cartridge::MBC7::EEPROM::readIO() -> uint8 { + uint8 data = 0b00'1111'00; + data.bit(7) = select; + data.bit(6) = clock; + data.bit(1) = 1; + if(!select) { + data.bit(0) = 1; //high-z when the chip is idle (not selected) + } else if(busy) { + data.bit(0) = 0; //low when a programming command is in progress + } else if(output.count) { + data.bit(0) = output.peek(); //shift register data during read commands + } else { + data.bit(0) = 1; //high-z during all other commands + } + return data; +} + +auto Cartridge::MBC7::EEPROM::writeIO(uint8 data) -> void { + //bring chip out of idle state on rising CS edge + if(select.raise(data.bit(7))) return power(true); + + //do nothing if chip is idle + if(!select) return; + + //shift register clocks on rising edge + if(!clock.raise(data.bit(6))) return; + + //sequential read mode + if(output.count && !data.bit(1)) { + output.read(); + if(output.count == 0) { + address.value++; + read(); + } + return; + } + output.flush(); + + //wait for start bit to be set + if(command.count == 0 && !data.bit(1)) return; + + //waiting on command? + if(command.count < command.length) { + command.write(data.bit(1)); + if(command.count < command.length) return; + + return address.flush(); + } + + //waiting on address bits? + if(address.count < address.length) { + address.write(data.bit(1)); + if(address.count < address.length) return; + + uint3 opcode = command.bits(0, command.length - 1); + if(opcode == 0b100) { + uint2 mode = address.bits(address.length - 2, address.length - 1); + if(mode == 0b00) return writeDisable(); + if(mode == 0b01) return input.flush(); //writeAll + if(mode == 0b10) return eraseAll(); + if(mode == 0b11) return writeEnable(); + } + if(opcode == 0b101) return input.flush(); //write + if(opcode == 0b110) return read(); + if(opcode == 0b111) return erase(); + return; + } + + //waiting on data bits from a write or writeAll command? + if(input.count < input.length) { //block new commands and inputs until the next clock edge + input.write(data.bit(1)); + if(input.count < input.length) return; + + uint3 opcode = command.bits(0, command.length - 1); + if(opcode == 0b101) return write(); + if(opcode == 0b100) return writeAll(); + return; + } +} + +// + +auto Cartridge::MBC7::EEPROM::read() -> void { + command.flush(); + auto address = this->address.value << (width == 16) & (size >> 3) - 1; + output.value = 0; + if(width >= 8) output.value |= data[address++] << 8; + if(width >= 16) output.value |= data[address++] << 0; + output.count = output.length; +} + +auto Cartridge::MBC7::EEPROM::write() -> void { + command.flush(); + if(!writable) return; + auto address = this->address.value << (width == 16) & (size >> 3) - 1; + if(width >= 8) data[address++] = input.value >> 8; + if(width >= 16) data[address++] = input.value >> 0; + input.flush(); + busy = 4; //ms +} + +auto Cartridge::MBC7::EEPROM::erase() -> void { + command.flush(); + if(!writable) return; + auto address = this->address.value << (width == 16) & (size >> 3) - 1; + if(width >= 8) data[address++] = 0xff; + if(width >= 16) data[address++] = 0xff; + busy = 4; //ms +} + +auto Cartridge::MBC7::EEPROM::writeAll() -> void { + command.flush(); + if(!writable) return; + uint8 lo = input.byte(0); + uint8 hi = input.byte(width == 16); + for(uint address = 0; address < 512;) { + data[address++] = hi; + data[address++] = lo; + } + input.flush(); + busy = 16; //ms +} + +auto Cartridge::MBC7::EEPROM::eraseAll() -> void { + command.flush(); + if(!writable) return; + for(uint address; address < 512;) { + data[address++] = 0xff; + data[address++] = 0xff; + } + busy = 8; //ms +} + +auto Cartridge::MBC7::EEPROM::writeEnable() -> void { + command.flush(); + writable = true; +} + +auto Cartridge::MBC7::EEPROM::writeDisable() -> void { + command.flush(); + writable = false; +} + +// + +auto Cartridge::MBC7::EEPROM::ShiftRegister::flush() -> void { + value = 0; + count = 0; +} + +//read the current bit in the shift register without clocking it +auto Cartridge::MBC7::EEPROM::ShiftRegister::peek() -> bool { + return value.bit(length - 1); +} + +auto Cartridge::MBC7::EEPROM::ShiftRegister::read() -> bool { + bool bit = value.bit(length - 1); + value <<= 1; + if(count) count--; + return bit; +} + +auto Cartridge::MBC7::EEPROM::ShiftRegister::write(bool bit) -> void { + value <<= 1; + value |= bit; + count++; +} diff --git a/higan/gb/cartridge/mbc7/mbc7.cpp b/higan/gb/cartridge/mbc7/mbc7.cpp index 4494bd22..7e74ff07 100644 --- a/higan/gb/cartridge/mbc7/mbc7.cpp +++ b/higan/gb/cartridge/mbc7/mbc7.cpp @@ -1,3 +1,18 @@ +#include "eeprom.cpp" +#include "serialization.cpp" + +auto Cartridge::MBC7::load(Markup::Node document) -> void { + eeprom.load(document); +} + +auto Cartridge::MBC7::save(Markup::Node document) -> void { + eeprom.save(document); +} + +auto Cartridge::MBC7::main() -> void { + eeprom.main(); +} + auto Cartridge::MBC7::read(uint16 address) -> uint8 { if((address & 0xc000) == 0x0000) { //$0000-3fff return cartridge.rom.read(address.bits(0,13)); @@ -17,7 +32,7 @@ auto Cartridge::MBC7::read(uint16 address) -> uint8 { case 5: return io.accelerometer.y.bits(8,15); case 6: return 0x00; //z? case 7: return 0xff; //z? - case 8: return 0xff; + case 8: return eeprom.readIO(); } return 0xff; @@ -48,25 +63,24 @@ auto Cartridge::MBC7::write(uint16 address, uint8 data) -> void { if(!io.ram.enable[0] || !io.ram.enable[1]) return; switch(address.bits(4,7)) { - case 0: { if(data != 0x55) break; - io.accelerometer.x = 0x8000; - io.accelerometer.y = 0x8000; + io.accelerometer.x = Center; + io.accelerometer.y = Center; break; } case 1: { if(data != 0xaa) break; - io.accelerometer.x = 0x8000 + platform->inputPoll(ID::Port::Hardware, ID::Device::Controls, 8); - io.accelerometer.y = 0x8000 + platform->inputPoll(ID::Port::Hardware, ID::Device::Controls, 9); + io.accelerometer.x = Center + platform->inputPoll(ID::Port::Hardware, ID::Device::Controls, 8); + io.accelerometer.y = Center + platform->inputPoll(ID::Port::Hardware, ID::Device::Controls, 9); break; } case 8: { + eeprom.writeIO(data); break; } - } return; @@ -74,13 +88,6 @@ auto Cartridge::MBC7::write(uint16 address, uint8 data) -> void { } auto Cartridge::MBC7::power() -> void { + eeprom.power(); io = {}; } - -auto Cartridge::MBC7::serialize(serializer& s) -> void { - s.integer(io.rom.bank); - s.integer(io.ram.enable[0]); - s.integer(io.ram.enable[1]); - s.integer(io.accelerometer.x); - s.integer(io.accelerometer.y); -} diff --git a/higan/gb/cartridge/mbc7/mbc7.hpp b/higan/gb/cartridge/mbc7/mbc7.hpp index c32df17c..f9f3db38 100644 --- a/higan/gb/cartridge/mbc7/mbc7.hpp +++ b/higan/gb/cartridge/mbc7/mbc7.hpp @@ -1,8 +1,67 @@ struct MBC7 : Mapper { - auto read(uint16 address) -> uint8; - auto write(uint16 address, uint8 data) -> void; - auto power() -> void; - auto serialize(serializer&) -> void; + enum : uint { Center = 0x81d0 }; + + //mbc7.cpp + auto load(Markup::Node document) -> void override; + auto save(Markup::Node document) -> void override; + auto main() -> void override; + auto read(uint16 address) -> uint8 override; + auto write(uint16 address, uint8 data) -> void override; + auto power() -> void override; + + //serialization.cpp + auto serialize(serializer&) -> void override; + + struct EEPROM { + //eeprom.cpp + auto load(Markup::Node document) -> void; + auto save(Markup::Node document) -> void; + auto main() -> void; + auto power(bool reset = false) -> void; + + //Game Boy MBC7 interface + auto readIO() -> uint8; + auto writeIO(uint8 data) -> void; + + //chip commands + auto read() -> void; + auto write() -> void; + auto erase() -> void; + auto writeAll() -> void; + auto eraseAll() -> void; + auto writeEnable() -> void; + auto writeDisable() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + //it is awkward no matter if data is uint1[4096], uint8[512], or uint16[256] + uint8 data[512]; //uint8 was chosen solely for easier serialization and saving + uint size; //in bits; not bytes + uint width; //8-bit (ORG=0) or 16-bit (ORG=1) configuration + + boolean select; //CS + boolean clock; //CLK + boolean writable; //EWEN, EWDS + uint busy; //busy cycles in milliseconds remaining for programming (write) operations to complete + + struct ShiftRegister { + auto bit(uint index) { return value.bit(index); } + auto bits(uint lo, uint hi) { return value.bits(lo, hi); } + auto byte(uint index) { return value.byte(index); } + auto flush() -> void; + auto peek() -> bool; + auto read() -> bool; + auto write(bool data) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint32 value; + uint32 count; + uint32 length; + } command, address, input, output; + } eeprom; struct IO { struct ROM { @@ -12,8 +71,8 @@ struct MBC7 : Mapper { uint1 enable[2]; } ram; struct Accelerometer { - uint16 x = 0x8000; - uint16 y = 0x8000; + uint16 x = Center; + uint16 y = Center; } accelerometer; } io; } mbc7; diff --git a/higan/gb/cartridge/mbc7/serialization.cpp b/higan/gb/cartridge/mbc7/serialization.cpp new file mode 100644 index 00000000..11a03673 --- /dev/null +++ b/higan/gb/cartridge/mbc7/serialization.cpp @@ -0,0 +1,28 @@ +auto Cartridge::MBC7::serialize(serializer& s) -> void { + eeprom.serialize(s); + s.integer(io.rom.bank); + s.integer(io.ram.enable[0]); + s.integer(io.ram.enable[1]); + s.integer(io.accelerometer.x); + s.integer(io.accelerometer.y); +} + +auto Cartridge::MBC7::EEPROM::serialize(serializer& s) -> void { + s.array(data); + s.integer(size); + s.integer(width); + s.boolean(select); + s.boolean(clock); + s.boolean(writable); + s.integer(busy); + command.serialize(s); + address.serialize(s); + input.serialize(s); + output.serialize(s); +} + +auto Cartridge::MBC7::EEPROM::ShiftRegister::serialize(serializer& s) -> void { + s.integer(value); + s.integer(count); + s.integer(length); +} diff --git a/higan/gb/cartridge/serialization.cpp b/higan/gb/cartridge/serialization.cpp index a03a4d9d..d6211178 100644 --- a/higan/gb/cartridge/serialization.cpp +++ b/higan/gb/cartridge/serialization.cpp @@ -1,4 +1,5 @@ auto Cartridge::serialize(serializer& s) -> void { + Thread::serialize(s); if(ram.size) s.array(ram.data, ram.size); if(rtc.size) s.array(rtc.data, rtc.size); diff --git a/higan/gb/cpu/timing.cpp b/higan/gb/cpu/timing.cpp index c4e7e206..51dc892a 100644 --- a/higan/gb/cpu/timing.cpp +++ b/higan/gb/cpu/timing.cpp @@ -19,6 +19,7 @@ auto CPU::step(uint clocks) -> void { Thread::step(1); synchronize(ppu); synchronize(apu); + synchronize(cartridge); } if(Model::SuperGameBoy()) { diff --git a/higan/gb/system/system.cpp b/higan/gb/system/system.cpp index 5a90a522..f511e28a 100644 --- a/higan/gb/system/system.cpp +++ b/higan/gb/system/system.cpp @@ -15,6 +15,7 @@ auto System::runToSave() -> void { scheduler.synchronize(cpu); scheduler.synchronize(ppu); scheduler.synchronize(apu); + scheduler.synchronize(cartridge); } auto System::init() -> void { diff --git a/higan/target-bsnes/bsnes.cpp b/higan/target-bsnes/bsnes.cpp index bbaed88d..212e173e 100644 --- a/higan/target-bsnes/bsnes.cpp +++ b/higan/target-bsnes/bsnes.cpp @@ -24,18 +24,31 @@ auto hiro::initialize() -> void { #include auto nall::main(vector arguments) -> void { - Application::setScreenSaver(settings.general.screenSaver); - Application::setToolTips(settings.general.toolTips); + settings.location = locate("settings.bml"); - string locale; // = "日本語"; + arguments.takeLeft(); //ignore program location in argument parsing for(auto argument : arguments) { - if(argument.beginsWith("--locale=")) { - locale = argument.trimLeft("--locale=", 1L); + if(argument == "--fullscreen") { + presentation.startFullScreen = true; + } else if(argument.beginsWith("--locale=")) { + Application::locale().scan(locate("locales/")); + Application::locale().select(argument.trimLeft("--locale=", 1L)); + } else if(argument.beginsWith("--settings=")) { + settings.location = argument.trimLeft("--settings=", 1L); + } else if(inode::exists(argument)) { + //game without option + program.gameQueue.append({";", argument}); + } else if(argument.find(";")) { + //game with option + auto game = argument.split(";", 1L); + if(inode::exists(game.last())) program.gameQueue.append(argument); } } - Application::locale().scan(locate("locales/")); - Application::locale().select(locale); + + settings.load(); + Application::setScreenSaver(settings.general.screenSaver); + Application::setToolTips(settings.general.toolTips); emulator = new SuperFamicom::Interface; - program.create(arguments); + program.create(); Application::run(); } diff --git a/higan/target-bsnes/bsnes.hpp b/higan/target-bsnes/bsnes.hpp index 862c81ce..ae230649 100644 --- a/higan/target-bsnes/bsnes.hpp +++ b/higan/target-bsnes/bsnes.hpp @@ -1,18 +1,19 @@ -#include #include -#include -using namespace nall; using namespace ruby; -using namespace hiro; extern Video video; extern Audio audio; extern Input input; +#include +using namespace hiro; + #include extern unique_pointer emulator; -#include #include +#include +#include +#include #include "program/program.hpp" #include "input/input.hpp" diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index 3d81a518..a404be11 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -189,6 +189,7 @@ auto Presentation::create() -> void { setBackgroundColor({0, 0, 0}); resizeWindow(); setCentered(); + setFullScreen(startFullScreen); #if defined(PLATFORM_MACOS) Application::Cocoa::onAbout([&] { about.doActivate(); }); @@ -211,8 +212,17 @@ auto Presentation::updateStatusIcon() -> void { statusIcon.setIcon(icon); } +auto Presentation::configureViewport() -> void { + uint width = viewport.geometry().width(); + uint height = viewport.geometry().height(); + video.configure(width, height, 60, 60); +} + auto Presentation::clearViewport() -> void { - if(!emulator->loaded()) viewportLayout.setPadding(); + if(!emulator->loaded()) { + viewportLayout.setPadding(); + configureViewport(); + } if(!visible() || !video) return; uint32_t* output; @@ -277,7 +287,7 @@ auto Presentation::resizeViewport() -> void { paddingWidth - paddingWidth / 2, paddingHeight - paddingHeight / 2 }); -//clearViewport(); + configureViewport(); } auto Presentation::resizeWindow() -> void { @@ -449,7 +459,7 @@ auto Presentation::updateRecentGames() -> void { bool missing = false; if(games) { for(auto& game : games.split("|")) { - if(!inode::exists(game)) missing = true; + if(!inode::exists(game.split(";").last())) missing = true; } } if(missing) { @@ -467,12 +477,12 @@ auto Presentation::updateRecentGames() -> void { //update list for(auto index : range(RecentGames)) { - MenuItem item; + MenuItem item{&loadRecentGame}; if(auto game = settings[{"Game/Recent/", 1 + index}].text()) { string displayName; auto games = game.split("|"); - for(auto& part : games) { - displayName.append(Location::prefix(part), " + "); + for(auto& game : games) { + displayName.append(Location::prefix(game.split(";", 1L).last()), " + "); } displayName.trimRight(" + ", 1L); item.setIcon(games(0).endsWith("/") ? (image)Icon::Action::Open : (image)Icon::Emblem::File); @@ -485,7 +495,6 @@ auto Presentation::updateRecentGames() -> void { item.setText({"(", tr("empty"), ")"}); item.setEnabled(false); } - loadRecentGame.append(item); } loadRecentGame.append(MenuSeparator()); @@ -514,6 +523,8 @@ auto Presentation::addRecentGame(string location) -> void { auto Presentation::updateShaders() -> void { shaderMenu.reset(); + shaderMenu.setEnabled(video.hasShader()); + if(!video.hasShader()) return; Group shaders; @@ -533,7 +544,7 @@ auto Presentation::updateShaders() -> void { auto location = locate("shaders/"); - if(settings.video.driver == "OpenGL") { + if(settings.video.driver == "OpenGL 3.2") { for(auto shader : directory::folders(location, "*.shader")) { if(shaders.objectCount() == 2) shaderMenu.append(MenuSeparator()); MenuRadioItem item{&shaderMenu}; diff --git a/higan/target-bsnes/presentation/presentation.hpp b/higan/target-bsnes/presentation/presentation.hpp index fd5143b4..ba398c2a 100644 --- a/higan/target-bsnes/presentation/presentation.hpp +++ b/higan/target-bsnes/presentation/presentation.hpp @@ -26,6 +26,7 @@ struct Presentation : Window { enum : uint { StatusHeight = 24 }; auto updateStatusIcon() -> void; + auto configureViewport() -> void; auto clearViewport() -> void; auto resizeViewport() -> void; auto resizeWindow() -> void; @@ -40,6 +41,8 @@ struct Presentation : Window { auto addRecentGame(string location) -> void; auto updateShaders() -> void; + bool startFullScreen = false; + MenuBar menuBar{this}; Menu systemMenu{&menuBar}; MenuItem loadGame{&systemMenu}; diff --git a/higan/target-bsnes/program/game.cpp b/higan/target-bsnes/program/game.cpp index 90ee8b76..9da3a222 100644 --- a/higan/target-bsnes/program/game.cpp +++ b/higan/target-bsnes/program/game.cpp @@ -50,12 +50,13 @@ auto Program::load() -> void { stateManager.loadStates(); manifestViewer.loadManifest(); - string locations = superFamicom.location; - if(auto& location = gameBoy.location) locations.append("|", location); - if(auto& location = bsMemory.location) locations.append("|", location); - if(auto& location = sufamiTurboA.location) locations.append("|", location); - if(auto& location = sufamiTurboB.location) locations.append("|", location); - presentation.addRecentGame(locations); + string games; + if(auto& game = superFamicom) games.append(game.option, ";", game.location, "|"); + if(auto& game = gameBoy ) games.append(game.option, ";", game.location, "|"); + if(auto& game = bsMemory ) games.append(game.option, ";", game.location, "|"); + if(auto& game = sufamiTurboA) games.append(game.option, ";", game.location, "|"); + if(auto& game = sufamiTurboB) games.append(game.option, ";", game.location, "|"); + presentation.addRecentGame(games.trimRight("|", 1L)); updateVideoPalette(); updateAudioEffects(); diff --git a/higan/target-bsnes/program/patch.cpp b/higan/target-bsnes/program/patch.cpp index 58be7110..7ebb58f4 100644 --- a/higan/target-bsnes/program/patch.cpp +++ b/higan/target-bsnes/program/patch.cpp @@ -84,7 +84,7 @@ auto Program::applyPatchIPS(vector& data, string location) -> bool { return true; } -#include +#include auto Program::applyPatchBPS(vector& input, string location) -> bool { vector patch; @@ -107,22 +107,16 @@ auto Program::applyPatchBPS(vector& input, string location) -> bool { } if(!patch) return false; - bpspatch beat; - beat.modify(patch.data(), patch.size()); - beat.source(input.data(), input.size()); - vector output; - output.resize(beat.size()); - beat.target(output.data(), output.size()); - auto result = beat.apply(); - - if(result == bpspatch::result::success) { - input = output; + string manifest; + string result; + if(auto output = Beat::Single::apply(input.data(), input.size(), patch.data(), patch.size(), manifest, result)) { + input = move(*output); return true; } - MessageDialog( - "Error: patch checksum failure.\n\n" + MessageDialog({ + result, "\n\n", "Please ensure you are using the correct (headerless) ROM for this patch." - ).setParent(*presentation).error(); + }).setParent(*presentation).error(); return false; } diff --git a/higan/target-bsnes/program/platform.cpp b/higan/target-bsnes/program/platform.cpp index ab86fd2f..e7fbc5e1 100644 --- a/higan/target-bsnes/program/platform.cpp +++ b/higan/target-bsnes/program/platform.cpp @@ -106,85 +106,100 @@ auto Program::load(uint id, string name, string type, vector options) -> if(id == 1 && name == "Super Famicom" && type == "sfc") { if(gameQueue) { - superFamicom.location = gameQueue.takeLeft(); + auto game = gameQueue.takeLeft().split(";", 1L); + superFamicom.option = game(0); + superFamicom.location = game(1); } else { dialog.setTitle("Load Super Famicom"); dialog.setPath(path("Games", settings.path.recent.superFamicom)); dialog.setFilters({string{"Super Famicom Games|*.sfc:*.smc:*.zip"}}); superFamicom.location = dialog.openObject(); + superFamicom.option = dialog.option(); } if(inode::exists(superFamicom.location)) { settings.path.recent.superFamicom = Location::dir(superFamicom.location); if(loadSuperFamicom(superFamicom.location)) { - return {id, dialog.option()}; + return {id, superFamicom.option}; } } } if(id == 2 && name == "Game Boy" && type == "gb") { if(gameQueue) { - gameBoy.location = gameQueue.takeLeft(); + auto game = gameQueue.takeLeft().split(";", 1L); + gameBoy.option = game(0); + gameBoy.location = game(1); } else { dialog.setTitle("Load Game Boy"); dialog.setPath(path("Games", settings.path.recent.gameBoy)); dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}}); gameBoy.location = dialog.openObject(); + gameBoy.option = dialog.option(); } if(inode::exists(gameBoy.location)) { settings.path.recent.gameBoy = Location::dir(gameBoy.location); if(loadGameBoy(gameBoy.location)) { - return {id, dialog.option()}; + return {id, gameBoy.option}; } } } if(id == 3 && name == "BS Memory" && type == "bs") { if(gameQueue) { - bsMemory.location = gameQueue.takeLeft(); + auto game = gameQueue.takeLeft().split(";", 1L); + bsMemory.option = game(0); + bsMemory.location = game(1); } else { dialog.setTitle("Load BS Memory"); dialog.setPath(path("Games", settings.path.recent.bsMemory)); dialog.setFilters({string{"BS Memory Games|*.bs:*.zip"}}); bsMemory.location = dialog.openObject(); + bsMemory.option = dialog.option(); } if(inode::exists(bsMemory.location)) { settings.path.recent.bsMemory = Location::dir(bsMemory.location); if(loadBSMemory(bsMemory.location)) { - return {id, dialog.option()}; + return {id, bsMemory.option}; } } } if(id == 4 && name == "Sufami Turbo" && type == "st") { if(gameQueue) { - sufamiTurboA.location = gameQueue.takeLeft(); + auto game = gameQueue.takeLeft().split(";", 1L); + sufamiTurboA.option = game(0); + sufamiTurboA.location = game(1); } else { dialog.setTitle("Load Sufami Turbo - Slot A"); dialog.setPath(path("Games", settings.path.recent.sufamiTurboA)); dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}}); sufamiTurboA.location = dialog.openObject(); + sufamiTurboA.option = dialog.option(); } if(inode::exists(sufamiTurboA.location)) { settings.path.recent.sufamiTurboA = Location::dir(sufamiTurboA.location); if(loadSufamiTurboA(sufamiTurboA.location)) { - return {id, dialog.option()}; + return {id, sufamiTurboA.option}; } } } if(id == 5 && name == "Sufami Turbo" && type == "st") { if(gameQueue) { - sufamiTurboB.location = gameQueue.takeLeft(); + auto game = gameQueue.takeLeft().split(";", 1L); + sufamiTurboB.option = game(0); + sufamiTurboB.location = game(1); } else { dialog.setTitle("Load Sufami Turbo - Slot B"); dialog.setPath(path("Games", settings.path.recent.sufamiTurboB)); dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}}); sufamiTurboB.location = dialog.openObject(); + sufamiTurboB.option = dialog.option(); } if(inode::exists(sufamiTurboB.location)) { settings.path.recent.sufamiTurboB = Location::dir(sufamiTurboB.location); if(loadSufamiTurboB(sufamiTurboB.location)) { - return {id, dialog.option()}; + return {id, sufamiTurboB.option}; } } } diff --git a/higan/target-bsnes/program/program.cpp b/higan/target-bsnes/program/program.cpp index e377f7bb..2f696ae7 100644 --- a/higan/target-bsnes/program/program.cpp +++ b/higan/target-bsnes/program/program.cpp @@ -13,7 +13,7 @@ #include "hacks.cpp" Program program; -auto Program::create(vector arguments) -> void { +auto Program::create() -> void { Emulator::platform = this; presentation.create(); @@ -59,15 +59,6 @@ auto Program::create(vector arguments) -> void { driverSettings.audioDriverChanged(); driverSettings.inputDriverChanged(); - arguments.takeLeft(); //ignore program location in argument parsing - for(auto& argument : arguments) { - if(argument == "--fullscreen") { - presentation.toggleFullscreenMode(); - } else if(inode::exists(argument)) { - gameQueue.append(argument); - } - } - if(gameQueue) load(); Application::onMain({&Program::main, this}); } diff --git a/higan/target-bsnes/program/program.hpp b/higan/target-bsnes/program/program.hpp index 96330797..48c660f7 100644 --- a/higan/target-bsnes/program/program.hpp +++ b/higan/target-bsnes/program/program.hpp @@ -2,7 +2,7 @@ struct Program : Lock, Emulator::Platform { Application::Namespace tr{"Program"}; //program.cpp - auto create(vector arguments) -> void; + auto create() -> void; auto main() -> void; auto quit() -> void; @@ -108,6 +108,7 @@ public: struct Game { explicit operator bool() const { return (bool)location; } + string option; string location; string manifest; Markup::Node document; diff --git a/higan/target-bsnes/program/states.cpp b/higan/target-bsnes/program/states.cpp index 3205f6c3..e1f18fb9 100644 --- a/higan/target-bsnes/program/states.cpp +++ b/higan/target-bsnes/program/states.cpp @@ -62,7 +62,7 @@ auto Program::loadState(string filename) -> bool { if(auto memory = loadStateData(filename)) { if(filename != "Quick/Undo") saveUndoState(); if(filename == "Quick/Undo") saveRedoState(); - auto serializerRLE = Decode::RLE(memory.data() + 3 * sizeof(uint)); + auto serializerRLE = Decode::RLE<1>(memory.data() + 3 * sizeof(uint)); serializer s{serializerRLE.data(), serializerRLE.size()}; if(!emulator->unserialize(s)) return showMessage({"[", prefix, "] is in incompatible format"}), false; return showMessage({"Loaded [", prefix, "]"}), true; @@ -77,7 +77,7 @@ auto Program::saveState(string filename) -> bool { serializer s = emulator->serialize(); if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false; - auto serializerRLE = Encode::RLE(s.data(), s.size()); + auto serializerRLE = Encode::RLE<1>(s.data(), s.size()); vector previewRLE; //this can be null if a state is captured before the first frame of video output after power/reset @@ -86,7 +86,7 @@ auto Program::saveState(string filename) -> bool { preview.copy(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height); if(preview.width() != 256 || preview.height() != 240) preview.scale(256, 240, true); preview.transform(0, 15, 0x8000, 0x7c00, 0x03e0, 0x001f); - previewRLE = Encode::RLE(preview.data(), preview.size() / sizeof(uint16_t)); + previewRLE = Encode::RLE<2>(preview.data(), preview.size() / sizeof(uint16_t)); } vector saveState; diff --git a/higan/target-bsnes/program/video.cpp b/higan/target-bsnes/program/video.cpp index 6bd94a3b..989fd901 100644 --- a/higan/target-bsnes/program/video.cpp +++ b/higan/target-bsnes/program/video.cpp @@ -12,6 +12,7 @@ auto Program::updateVideoDriver(Window parent) -> void { updateVideoShader(); if(video.ready()) { + presentation.configureViewport(); presentation.clearViewport(); updateVideoShader(); } @@ -52,16 +53,7 @@ auto Program::updateVideoFormat() -> void { } auto Program::updateVideoShader() -> void { - if(settings.video.driver == "OpenGL" - && settings.video.shader != "None" - && settings.video.shader != "Blur" - ) { - video.setSmooth(false); - video.setShader(settings.video.shader); - } else { - video.setSmooth(settings.video.shader == "Blur"); - video.setShader(""); - } + video.setShader(settings.video.shader); } auto Program::updateVideoPalette() -> void { diff --git a/higan/target-bsnes/settings/settings.cpp b/higan/target-bsnes/settings/settings.cpp index e44ac0d5..a56e82a5 100644 --- a/higan/target-bsnes/settings/settings.cpp +++ b/higan/target-bsnes/settings/settings.cpp @@ -17,14 +17,14 @@ DriverSettings driverSettings; SettingsWindow settingsWindow; auto Settings::load() -> void { - Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")), " ")); + Markup::Node::operator=(BML::unserialize(string::read(location), " ")); process(true); file::write(locate("settings.bml"), BML::serialize(*this, " ")); } auto Settings::save() -> void { process(false); - file::write(locate("settings.bml"), BML::serialize(*this, " ")); + file::write(location ? location : locate("settings.bml"), BML::serialize(*this, " ")); } auto Settings::process(bool load) -> void { diff --git a/higan/target-bsnes/settings/settings.hpp b/higan/target-bsnes/settings/settings.hpp index 34fa2011..b87ef701 100644 --- a/higan/target-bsnes/settings/settings.hpp +++ b/higan/target-bsnes/settings/settings.hpp @@ -1,11 +1,10 @@ struct Settings : Markup::Node { - Settings() { load(); } - ~Settings() { save(); } - auto load() -> void; auto save() -> void; auto process(bool load) -> void; + string location; + struct Video { string driver; bool exclusive = false; diff --git a/higan/target-bsnes/tools/state-manager.cpp b/higan/target-bsnes/tools/state-manager.cpp index 84317eac..c3532301 100644 --- a/higan/target-bsnes/tools/state-manager.cpp +++ b/higan/target-bsnes/tools/state-manager.cpp @@ -158,7 +158,7 @@ auto StateManager::updateSelection() -> void { uint preview = memory::readl(saveState.data() + 2 * sizeof(uint)); if(signature == Program::State::Signature && preview) { uint offset = 3 * sizeof(uint) + serializer; - auto preview = Decode::RLE(saveState.data() + offset, max(offset, saveState.size()) - offset); + auto preview = Decode::RLE<2>(saveState.data() + offset, max(offset, saveState.size()) - offset); image icon{0, 15, 0x8000, 0x7c00, 0x03e0, 0x001f}; icon.copy(preview.data(), 256 * sizeof(uint16_t), 256, 240); icon.transform(); diff --git a/higan/target-higan/higan.hpp b/higan/target-higan/higan.hpp index 5d10bf3c..2741f067 100644 --- a/higan/target-higan/higan.hpp +++ b/higan/target-higan/higan.hpp @@ -1,13 +1,12 @@ -#include #include -#include -using namespace nall; using namespace ruby; -using namespace hiro; extern unique_pointer