From f9adb4d2c61a3c9a82e452010c82ac5b804b639e Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 21 Aug 2018 13:17:12 +1000 Subject: [PATCH] Update to v106r58 release. byuu says: The main thing I worked on today was emulating the MBC7 EEPROM. And... I have many things to say about that, but not here, and not now... The missing EEPROM support is why the accelerometer was broken. Although it's not evidently clear that I'm emulating the actual values incorrectly. I'll think about it and get it fixed, though. bsnes went from ~308fps to ~328fps, and I don't even know why. Probably something somewhere in the 140KB of changes to other things made in this WIP. --- higan/emulator/emulator.hpp | 17 +- higan/gb/cartridge/cartridge.cpp | 24 + higan/gb/cartridge/cartridge.hpp | 8 +- higan/gb/cartridge/mbc7/eeprom.cpp | 226 +++ higan/gb/cartridge/mbc7/mbc7.cpp | 37 +- higan/gb/cartridge/mbc7/mbc7.hpp | 71 +- higan/gb/cartridge/mbc7/serialization.cpp | 28 + higan/gb/cartridge/serialization.cpp | 1 + higan/gb/cpu/timing.cpp | 1 + higan/gb/system/system.cpp | 1 + higan/target-bsnes/bsnes.cpp | 29 +- higan/target-bsnes/bsnes.hpp | 11 +- .../presentation/presentation.cpp | 27 +- .../presentation/presentation.hpp | 3 + higan/target-bsnes/program/game.cpp | 13 +- higan/target-bsnes/program/patch.cpp | 22 +- higan/target-bsnes/program/platform.cpp | 35 +- higan/target-bsnes/program/program.cpp | 11 +- higan/target-bsnes/program/program.hpp | 3 +- higan/target-bsnes/program/states.cpp | 6 +- higan/target-bsnes/program/video.cpp | 12 +- higan/target-bsnes/settings/settings.cpp | 4 +- higan/target-bsnes/settings/settings.hpp | 5 +- higan/target-bsnes/tools/state-manager.cpp | 2 +- higan/target-higan/higan.hpp | 7 +- .../presentation/presentation.cpp | 15 +- .../presentation/presentation.hpp | 1 + higan/target-higan/program/utility.cpp | 13 +- hiro/gtk/header.hpp | 6 - hiro/windows/header.hpp | 8 +- icarus/GNUmakefile | 2 + icarus/heuristics/game-boy.cpp | 46 +- nall/adaptive-array.hpp | 1 + nall/array.hpp | 76 +- nall/beat/archive.hpp | 131 -- nall/beat/delta.hpp | 211 --- nall/beat/file.hpp | 23 - nall/beat/linear.hpp | 148 -- nall/beat/metadata.hpp | 117 -- nall/beat/multi.hpp | 239 --- nall/beat/patch.hpp | 214 --- nall/beat/single/apply.hpp | 92 ++ nall/beat/single/create.hpp | 123 ++ nall/counting-sort.hpp | 19 + nall/decode/bwt.hpp | 56 + nall/decode/huffman.hpp | 42 + nall/decode/lzsa.hpp | 75 + nall/decode/lzss.hpp | 44 + nall/decode/mtf.hpp | 31 + nall/decode/rle.hpp | 33 +- nall/directory.hpp | 8 +- nall/div-suf-sort.hpp | 1440 +++++++++++++++++ nall/dl.hpp | 1 - nall/encode/bwt.hpp | 49 + nall/encode/dictionary.hpp | 73 + nall/encode/huffman.hpp | 90 ++ nall/encode/lzsa.hpp | 98 ++ nall/encode/lzss.hpp | 76 + nall/encode/mtf.hpp | 36 + nall/encode/rle.hpp | 57 +- nall/file.hpp | 19 +- nall/filemap.hpp | 5 +- nall/literals.hpp | 25 + nall/locale.hpp | 4 +- nall/main.hpp | 2 +- nall/matrix.hpp | 19 +- nall/maybe.hpp | 1 + nall/memory.hpp | 13 +- nall/{sort.hpp => merge-sort.hpp} | 40 +- nall/nall.hpp | 2 +- nall/platform.hpp | 24 +- nall/smtp.hpp | 3 +- nall/string.hpp | 11 +- nall/string/allocator/copy-on-write.hpp | 14 +- .../allocator/small-string-optimization.hpp | 14 +- nall/string/allocator/vector.hpp | 12 +- nall/string/utility.hpp | 4 + nall/suffix-array.hpp | 202 +++ nall/vector.hpp | 14 +- nall/vector/core.hpp | 24 +- nall/vector/utility.hpp | 10 + nall/windows/guard.hpp | 18 + nall/windows/utf8.hpp | 23 +- ruby/audio/xaudio2.cpp | 1 - ruby/ruby.cpp | 2 +- ruby/video/cgl.cpp | 11 +- ruby/video/direct3d.cpp | 6 +- ruby/video/directdraw.cpp | 1 + ruby/video/gdi.cpp | 1 + ruby/video/glx.cpp | 13 +- ruby/video/glx2.cpp | 31 +- ruby/video/opengl/main.hpp | 10 +- ruby/video/opengl/opengl.hpp | 2 +- ruby/video/video.cpp | 15 +- ruby/video/video.hpp | 15 +- ruby/video/wgl.cpp | 11 +- ruby/video/xshm.cpp | 40 +- ruby/video/xvideo.cpp | 1 + 98 files changed, 3422 insertions(+), 1539 deletions(-) create mode 100644 higan/gb/cartridge/mbc7/eeprom.cpp create mode 100644 higan/gb/cartridge/mbc7/serialization.cpp delete mode 100644 nall/beat/archive.hpp delete mode 100644 nall/beat/delta.hpp delete mode 100644 nall/beat/file.hpp delete mode 100644 nall/beat/linear.hpp delete mode 100644 nall/beat/metadata.hpp delete mode 100644 nall/beat/multi.hpp delete mode 100644 nall/beat/patch.hpp create mode 100644 nall/beat/single/apply.hpp create mode 100644 nall/beat/single/create.hpp create mode 100644 nall/counting-sort.hpp create mode 100644 nall/decode/bwt.hpp create mode 100644 nall/decode/huffman.hpp create mode 100644 nall/decode/lzsa.hpp create mode 100644 nall/decode/lzss.hpp create mode 100644 nall/decode/mtf.hpp create mode 100644 nall/div-suf-sort.hpp create mode 100644 nall/encode/bwt.hpp create mode 100644 nall/encode/dictionary.hpp create mode 100644 nall/encode/huffman.hpp create mode 100644 nall/encode/lzsa.hpp create mode 100644 nall/encode/lzss.hpp create mode 100644 nall/encode/mtf.hpp create mode 100644 nall/literals.hpp rename nall/{sort.hpp => merge-sort.hpp} (54%) create mode 100644 nall/suffix-array.hpp 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