Update to v106r40 release.

byuu says:

Changelog:

  - hiro: added BrowserDialog::openObject() [match file *or* folder
    by filters]
  - hiro: BrowserDialog accept button is now disabled when it would
    otherwise do nothing
      - eg openFile without a folder to enter or file to open selected
      - eg saveFile without a file name or with a file name that matches
        a folder name
  - bsnes: added support for gamepaks (game folders)
  - bsnes: store all save states inside per-game .bsz (ZIP) archives
    instead of .bst/ folders
      - this reduces the number of state files from 10+ to 1; without
        having folders sort before files
  - hiro: both gtk2 and gtk3 now use cairo to render Canvas; supports
    sx,sy [BearOso]
  - higan, bsnes: fast PPU/DSP are now run-time options instead of
    compile-time options
  - bsnes: disable fast PPU when loading Air Strike Patrol / Desert
    Fighter
  - bsnes: disable fast DSP when loading Koushien 2
  - bsnes: added options to advanced panel to disable fast PPU and/or
    fast DSP
This commit is contained in:
Tim Allen 2018-06-11 14:50:18 +10:00
parent 91bb781b73
commit 5a8c814e25
39 changed files with 643 additions and 359 deletions

View File

@ -4,11 +4,9 @@ include ../nall/GNUmakefile
binary := application
target := bsnes
profile := fast
objects := libco emulator audio video resource
flags += -I. -I..
flags += $(if $(call streq,$(profile),accurate),-DPROFILE_ACCURATE,-DPROFILE_FAST)
ifeq ($(platform),windows)
ifeq ($(binary),application)

View File

@ -13,7 +13,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "106.39";
static const string Version = "106.40";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";

View File

@ -45,7 +45,7 @@ struct Thread {
s.integer(_clock);
}
protected:
//protected:
cothread_t _handle = nullptr;
uintmax _frequency = 0;
uintmax _scalar = 0;

View File

@ -2,7 +2,7 @@ processors += wdc65816 spc700 arm7tdmi gsu hg51b upd96050
objects += sfc-interface sfc-system sfc-controller
objects += sfc-cartridge sfc-memory
objects += sfc-cpu sfc-smp sfc-dsp $(if $(call streq,$(profile),accurate),sfc-ppu,sfc-ppu-fast)
objects += sfc-cpu sfc-smp sfc-dsp sfc-ppu sfc-ppu-fast
objects += sfc-expansion sfc-satellaview sfc-21fx
objects += sfc-icd sfc-mcc sfc-dip sfc-event
objects += sfc-sa1 sfc-superfx

View File

@ -192,18 +192,18 @@ auto DSP::main() -> void {
}
auto DSP::tick() -> void {
#if defined(PROFILE_ACCURATE)
if(!system.fastDSP()) {
step(3 * 8);
synchronize(smp);
#endif
}
}
auto DSP::sample(int16 left, int16 right) -> void {
stream->sample(left / 32768.0, right / 32768.0);
#if defined(PROFILE_FAST)
if(system.fastDSP()) {
step(32 * 3 * 8);
synchronize(smp);
#endif
}
}
/* register interface for S-SMP $00f2,$00f3 */

View File

@ -233,6 +233,9 @@ auto Interface::cheatSet(const string_vector& list) -> void {
}
auto Interface::cap(const string& name) -> bool {
if(name == "Fast PPU") return true;
if(name == "Fast DSP") return true;
if(name == "Mode") return true;
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
if(name == "Scanline Emulation") return true;
@ -240,6 +243,14 @@ auto Interface::cap(const string& name) -> bool {
}
auto Interface::get(const string& name) -> any {
if(name == "Mode") return string{
system.fastPPU() && system.fastDSP() ? "[Fast PPU+DSP] "
: system.fastPPU() ? "[Fast PPU] "
: system.fastDSP() ? "[Fast DSP] "
: ""
};
if(name == "Fast PPU") return settings.fastPPU;
if(name == "Fast DSP") return settings.fastDSP;
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
if(name == "Scanline Emulation") return settings.scanlineEmulation;
@ -247,6 +258,14 @@ auto Interface::get(const string& name) -> any {
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Fast PPU" && value.is<bool>()) {
settings.fastPPU = value.get<bool>();
return true;
}
if(name == "Fast DSP" && value.is<bool>()) {
settings.fastDSP = value.get<bool>();
return true;
}
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
return true;

View File

@ -67,6 +67,9 @@ struct Interface : Emulator::Interface {
};
struct Settings {
bool fastPPU = false;
bool fastDSP = false;
bool blurEmulation = true;
bool colorEmulation = true;
bool scanlineEmulation = true;

View File

@ -1,4 +1,6 @@
#include <sfc/sfc.hpp>
#define PPU PPUfast
#define ppu ppufast
namespace SuperFamicom {
@ -10,7 +12,6 @@ PPU ppu;
#include "object.cpp"
#include "window.cpp"
#include "serialization.cpp"
#include <sfc/ppu/counter/serialization.cpp>
PPU::PPU() {
output = new uint32[512 * 512] + 16 * 512; //overscan offset

View File

@ -5,6 +5,9 @@
//* vertical mosaic coordinates are not exact
//* (hardware-mod) 128KB VRAM mode not supported
#define PPU PPUfast
#define ppu ppufast
struct PPU : Thread, PPUcounter {
alwaysinline auto interlace() const -> bool { return latch.interlace; }
alwaysinline auto overscan() const -> bool { return latch.overscan; }
@ -304,3 +307,6 @@ public:
};
extern PPU ppu;
#undef PPU
#undef ppu

View File

@ -1,3 +1,14 @@
auto PPU::latchCounters() -> void {
if(system.fastPPU()) {
return ppufast.latchCounters();
}
cpu.synchronize(ppu);
io.hcounter = hdot();
io.vcounter = vcounter();
latch.counters = 1;
}
auto PPU::addressVRAM() const -> uint16 {
uint16 address = io.vramAddress;
switch(io.vramMapping) {
@ -619,13 +630,6 @@ auto PPU::writeIO(uint24 addr, uint8 data) -> void {
}
}
auto PPU::latchCounters() -> void {
cpu.synchronize(ppu);
io.hcounter = hdot();
io.vcounter = vcounter();
latch.counters = 1;
}
auto PPU::updateVideoMode() -> void {
switch(io.bgMode) {
case 0:

View File

@ -25,6 +25,10 @@ bg4(Background::ID::BG4) {
}
PPU::~PPU() {
if(system.fastPPU()) {
_handle = nullptr;
}
output -= 16 * 512;
delete[] output;
}
@ -78,6 +82,10 @@ auto PPU::main() -> void {
}
auto PPU::load(Markup::Node node) -> bool {
if(system.fastPPU()) {
return ppufast.load(node);
}
ppu1.version = max(1, min(1, node["ppu1/version"].natural()));
ppu2.version = max(1, min(3, node["ppu2/version"].natural()));
ppu.vram.mask = node["ppu1/ram/size"].natural() / sizeof(uint16) - 1;
@ -86,6 +94,12 @@ auto PPU::load(Markup::Node node) -> bool {
}
auto PPU::power(bool reset) -> void {
if(system.fastPPU()) {
ppufast.power(reset);
_handle = ppufast._handle;
return;
}
create(Enter, system.cpuFrequency());
PPUcounter::reset();
memory::fill<uint32>(output, 512 * 480);
@ -220,6 +234,10 @@ auto PPU::frame() -> void {
}
auto PPU::refresh() -> void {
if(system.fastPPU()) {
return ppufast.refresh();
}
auto output = this->output;
if(!overscan()) output -= 12 * 512;
auto pitch = 512;

View File

@ -1,20 +1,29 @@
#include <sfc/ppu-fast/ppu.hpp>
struct PPU : Thread, PPUcounter {
alwaysinline auto interlace() const -> bool { return display.interlace; }
alwaysinline auto overscan() const -> bool { return display.overscan; }
alwaysinline auto vdisp() const -> uint { return !io.overscan ? 225 : 240; }
//ppu.cpp
alwaysinline auto interlace() const -> bool { if(system.fastPPU()) return ppufast.interlace(); return display.interlace; }
alwaysinline auto overscan() const -> bool { if(system.fastPPU()) return ppufast.overscan(); return display.overscan; }
alwaysinline auto vdisp() const -> uint { if(system.fastPPU()) return ppufast.vdisp(); return !io.overscan ? 225 : 240; }
PPU();
~PPU();
alwaysinline auto step(uint clocks) -> void;
static auto Enter() -> void;
auto main() -> void;
auto load(Markup::Node) -> bool;
auto power(bool reset) -> void;
//io.cpp
auto latchCounters() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
//ppu.cpp
alwaysinline auto step(uint clocks) -> void;
//io.cpp
alwaysinline auto addressVRAM() const -> uint16;
alwaysinline auto readVRAM() -> uint16;
@ -25,10 +34,8 @@ struct PPU : Thread, PPUcounter {
alwaysinline auto writeCGRAM(uint8 addr, uint15 data) -> void;
auto readIO(uint24 addr, uint8 data) -> uint8;
auto writeIO(uint24 addr, uint8 data) -> void;
auto latchCounters() -> void;
auto updateVideoMode() -> void;
private:
struct VRAM {
auto& operator[](uint addr) { return data[addr & mask]; }
uint16 data[64 * 1024];

View File

@ -1,4 +1,8 @@
auto PPU::serialize(serializer& s) -> void {
if(system.fastPPU()) {
return ppufast.serialize(s);
}
Thread::serialize(s);
PPUcounter::serialize(s);

View File

@ -46,21 +46,17 @@ namespace SuperFamicom {
static inline auto PAL() -> bool;
};
#include <sfc/system/system.hpp>
#include <sfc/memory/memory.hpp>
#include <sfc/ppu/counter/counter.hpp>
#include <sfc/cpu/cpu.hpp>
#include <sfc/smp/smp.hpp>
#include <sfc/dsp/dsp.hpp>
#if defined(PROFILE_ACCURATE)
#include <sfc/ppu/ppu.hpp>
#elif defined(PROFILE_FAST)
#include <sfc/ppu-fast/ppu.hpp>
#endif
#include <sfc/controller/controller.hpp>
#include <sfc/expansion/expansion.hpp>
#include <sfc/system/system.hpp>
#include <sfc/coprocessor/coprocessor.hpp>
#include <sfc/slot/slot.hpp>
#include <sfc/cartridge/cartridge.hpp>

View File

@ -13,6 +13,9 @@ auto System::serialize() -> serializer {
s.array(hash);
s.array(description);
s.boolean(hacks.fastPPU);
s.boolean(hacks.fastDSP);
serializeAll(s);
return s;
}
@ -31,6 +34,12 @@ auto System::unserialize(serializer& s) -> bool {
if(signature != 0x31545342) return false;
if(string{version} != Emulator::SerializerVersion) return false;
s.boolean(hacks.fastPPU);
s.boolean(hacks.fastDSP);
settings.fastPPU = hacks.fastPPU;
settings.fastDSP = hacks.fastDSP;
power(/* reset = */ false);
serializeAll(s);
return true;
@ -42,9 +51,9 @@ auto System::serialize(serializer& s) -> void {
}
auto System::serializeAll(serializer& s) -> void {
system.serialize(s);
random.serialize(s);
cartridge.serialize(s);
system.serialize(s);
cpu.serialize(s);
smp.serialize(s);
ppu.serialize(s);
@ -91,6 +100,9 @@ auto System::serializeInit() -> void {
s.array(hash);
s.array(description);
s.boolean(hacks.fastPPU);
s.boolean(hacks.fastDSP);
serializeAll(s);
serializeSize = s.size();
}

View File

@ -88,6 +88,9 @@ auto System::unload() -> void {
}
auto System::power(bool reset) -> void {
hacks.fastPPU = settings.fastPPU;
hacks.fastDSP = settings.fastDSP;
Emulator::video.reset();
Emulator::video.setInterface(interface);
Emulator::video.setPalette();

View File

@ -6,6 +6,9 @@ struct System {
inline auto cpuFrequency() const -> double { return information.cpuFrequency; }
inline auto apuFrequency() const -> double { return information.apuFrequency; }
inline auto fastPPU() const -> bool { return hacks.fastPPU; }
inline auto fastDSP() const -> bool { return hacks.fastDSP; }
auto run() -> void;
auto runToSave() -> void;
@ -29,6 +32,11 @@ private:
double apuFrequency = 32040.0 * 768.0;
} information;
struct Hacks {
bool fastPPU = false;
bool fastDSP = false;
} hacks;
uint serializeSize = 0;
auto serialize(serializer&) -> void;

View File

@ -10,11 +10,11 @@ auto InputManager::bindHotkeys() -> void {
}));
hotkeys.append(InputHotkey("Save State").onPress([&] {
program->saveState({"Quick/Slot ", stateSlot});
program->saveState({"quick/slot ", stateSlot});
}));
hotkeys.append(InputHotkey("Load State").onPress([&] {
program->loadState({"Quick/Slot ", stateSlot});
program->loadState({"quick/slot ", stateSlot});
}));
hotkeys.append(InputHotkey("Increment State Slot").onPress([&] {

View File

@ -13,7 +13,10 @@ Presentation::Presentation() {
loadRecentGame.setText("Load Recent Game");
updateRecentGames();
resetSystem.setText("Reset System").setEnabled(false).onActivate([&] {
if(emulator->loaded()) emulator->reset();
if(emulator->loaded()) {
program->applyHacks();
emulator->reset();
}
});
unloadGame.setText("Unload Game").setEnabled(false).onActivate([&] {
program->unload();
@ -110,18 +113,18 @@ Presentation::Presentation() {
saveState.setText("Save State");
for(uint index : range(QuickStates)) {
saveState.append(MenuItem().setText({"Slot ", 1 + index}).onActivate([=] {
program->saveState({"Quick/Slot ", 1 + index});
program->saveState({"quick/slot ", 1 + index});
}));
}
loadState.setText("Load State");
for(uint index : range(QuickStates)) {
loadState.append(MenuItem().setText({"Slot ", 1 + index}).onActivate([=] {
program->loadState({"Quick/Slot ", 1 + index});
program->loadState({"quick/slot ", 1 + index});
}));
}
loadState.append(MenuSeparator());
loadState.append(MenuItem().setText("Recovery Slot").onActivate([&] {
program->loadState("Quick/Recovery Slot");
loadState.append(MenuItem().setText("Recovery").onActivate([&] {
program->loadState("quick/recovery");
}));
pauseEmulation.setText("Pause Emulation").onToggle([&] {
if(pauseEmulation.checked()) audio->clear();
@ -134,14 +137,15 @@ Presentation::Presentation() {
aboutWindow->setCentered(*this).setVisible().setFocused();
});
viewport.setDroppable().onDrop([&](auto locations) {
viewport.setDroppable().onDrop([&](string_vector locations) {
program->gameQueue = locations;
program->load();
presentation->setFocused();
setFocused();
});
statusBar.setFont(Font().setBold());
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
program->updateMessage();
onClose([&] {
program->quit();

View File

@ -0,0 +1,7 @@
auto Program::openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({superNintendo.location, name}, mode);
}
auto Program::openPakGB(string name, vfs::file::mode mode) -> vfs::shared::file {
return vfs::fs::file::open({gameBoy.location, name}, mode);
}

View File

@ -0,0 +1,148 @@
auto Program::openRomSFC(string name, vfs::file::mode mode) -> vfs::shared::file {
if(name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.program.data(), superNintendo.program.size());
}
if(name == "data.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.data.data(), superNintendo.data.size());
}
if(name == "expansion.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.expansion.data(), superNintendo.expansion.size());
}
if(name == "arm6.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x00000], 0x20000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "arm6.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x20000], 0x08000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xc00) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "lr35902.boot.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x100) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".boot.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd7725.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0x1800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd7725.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x1800], 0x0800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd96050.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0xc000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "upd96050.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0xc000], 0x1000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(name == "save.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(name == "download.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".psr"), mode);
}
if(name == "time.rtc") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".rtc"), mode);
}
if(name == "arm6.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(name == "hg51bs169.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(name == "upd7725.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(name == "upd96050.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(name == "msu1/data.rom") {
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), ".msu"}, mode);
}
if(name.match("msu1/track-*.pcm")) {
name.trimLeft("msu1/track-", 1L);
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), name}, mode);
}
return {};
}
auto Program::openRomGB(string name, vfs::file::mode mode) -> vfs::shared::file {
if(name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size());
}
if(name == "save.ram") {
return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode);
}
if(name == "time.rtc") {
return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode);
}
return {};
}

View File

@ -8,6 +8,7 @@ auto Program::load() -> void {
if(emulator->load(media.id)) {
gameQueue = {};
connectDevices();
applyHacks();
emulator->power();
presentation->setTitle(emulator->title());
presentation->resetSystem.setEnabled(true);
@ -44,11 +45,24 @@ auto Program::loadFile(string location) -> vector<uint8_t> {
}
auto Program::loadSuperNintendo(string location) -> void {
auto rom = loadFile(location);
if(!rom) return;
vector<uint8_t> rom;
//game pak
if(location.endsWith("/")) {
rom.append(file::read({location, "program.rom" }));
rom.append(file::read({location, "data.rom" }));
rom.append(file::read({location, "expansion.rom"}));
for(auto filename : directory::files(location, "*.boot.rom" )) rom.append(file::read({location, filename}));
for(auto filename : directory::files(location, "*.program.rom")) rom.append(file::read({location, filename}));
for(auto filename : directory::files(location, "*.data.rom" )) rom.append(file::read({location, filename}));
} else {
//game ROM
rom = loadFile(location);
}
//Heuristics::SuperFamicom() call will remove copier header from rom if present
auto heuristics = Heuristics::SuperFamicom(rom, location);
superNintendo.label = heuristics.label();
superNintendo.manifest = heuristics.manifest();
superNintendo.document = BML::unserialize(superNintendo.manifest);
superNintendo.location = location;
@ -77,8 +91,14 @@ auto Program::loadSuperNintendo(string location) -> void {
}
auto Program::loadGameBoy(string location) -> void {
auto rom = loadFile(location);
if(!rom) return;
vector<uint8_t> rom;
//game pak
if(location.endsWith("/")) {
rom.append(file::read({location, "program.rom"}));
} else {
rom = loadFile(location);
}
auto heuristics = Heuristics::GameBoy(rom, location);
gameBoy.manifest = heuristics.manifest();

View File

@ -6,174 +6,47 @@
#include <icarus/heuristics/sufami-turbo.cpp>
auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file {
//System
vfs::shared::file result;
if(id == 0 && name == "manifest.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(Resource::System::Manifest.data(), Resource::System::Manifest.size());
if(id == 0) { //System
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(Resource::System::Manifest.data(), Resource::System::Manifest.size());
}
if(id == 0 && name == "boards.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(Resource::System::Boards.data(), Resource::System::Boards.size());
if(name == "boards.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(Resource::System::Boards.data(), Resource::System::Boards.size());
}
if(id == 0 && name == "ipl.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(Resource::System::IPLROM.data(), Resource::System::IPLROM.size());
}
//Super Famicom
if(id == 1 && name == "manifest.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.manifest.data<uint8_t>(), superNintendo.manifest.size());
}
if(id == 1 && name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.program.data(), superNintendo.program.size());
}
if(id == 1 && name == "data.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.data.data(), superNintendo.data.size());
}
if(id == 1 && name == "expansion.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(superNintendo.expansion.data(), superNintendo.expansion.size());
}
if(id == 1 && name == "arm6.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x00000], 0x20000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
if(name == "ipl.rom" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(Resource::System::IPLROM.data(), Resource::System::IPLROM.size());
}
}
if(id == 1 && name == "arm6.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x28000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x20000], 0x08000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
if(id == 1) { //Super Famicom
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(superNintendo.manifest.data<uint8_t>(), superNintendo.manifest.size());
} else if(superNintendo.location.endsWith("/")) {
result = openPakSFC(name, mode);
} else {
result = openRomSFC(name, mode);
}
}
if(id == 1 && name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xc00) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
if(id == 2) { //Game Boy
if(name == "manifest.bml" && mode == vfs::file::mode::read) {
result = vfs::memory::file::open(gameBoy.manifest.data<uint8_t>(), gameBoy.manifest.size());
} else if(gameBoy.location.endsWith("/")) {
result = openPakGB(name, mode);
} else {
result = openRomGB(name, mode);
}
}
if(id == 1 && name == "lr35902.boot.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x100) {
return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size());
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".boot.rom"});
return vfs::fs::file::open(location, mode);
}
if(!result && required) {
MessageDialog({"Error: missing required data: ", name}).setParent(*presentation).error();
}
if(id == 1 && name == "upd7725.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0x1800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "upd7725.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0x2000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x1800], 0x0800);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "upd96050.program.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0xc000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "upd96050.data.rom" && mode == vfs::file::mode::read) {
if(superNintendo.firmware.size() == 0xd000) {
return vfs::memory::file::open(&superNintendo.firmware.data()[0xc000], 0x1000);
}
if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) {
string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"});
return vfs::fs::file::open(location, mode);
}
}
if(id == 1 && name == "save.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "download.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".psr"), mode);
}
if(id == 1 && name == "time.rtc") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".rtc"), mode);
}
if(id == 1 && name == "arm6.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "hg51bs169.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "upd7725.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "upd96050.data.ram") {
return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode);
}
if(id == 1 && name == "msu1/data.rom") {
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), ".msu"}, mode);
}
if(id == 1 && name.match("msu1/track-*.pcm")) {
name.trimLeft("msu1/track-", 1L);
return vfs::fs::file::open({Location::notsuffix(superNintendo.location), name}, mode);
}
//Game Boy
if(id == 2 && name == "manifest.bml" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(gameBoy.manifest.data<uint8_t>(), gameBoy.manifest.size());
}
if(id == 2 && name == "program.rom" && mode == vfs::file::mode::read) {
return vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size());
}
if(id == 2 && name == "save.ram") {
return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode);
}
if(id == 2 && name == "time.rtc") {
return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode);
}
return {};
return result;
}
auto Program::load(uint id, string name, string type, string_vector options) -> Emulator::Platform::Load {
@ -185,10 +58,10 @@ auto Program::load(uint id, string name, string type, string_vector options) ->
dialog.setTitle("Load Super Nintendo");
dialog.setPath(path("Games", settings["Path/Recent/SuperNintendo"].text()));
dialog.setFilters({string{"Super Nintendo Games|*.sfc:*.smc:*.zip"}});
superNintendo.location = dialog.openFile();
superNintendo.location = dialog.openObject();
}
if(file::exists(superNintendo.location)) {
settings["Path/Recent/SuperNintendo"].setValue(Location::path(superNintendo.location));
if(inode::exists(superNintendo.location)) {
settings["Path/Recent/SuperNintendo"].setValue(Location::dir(superNintendo.location));
loadSuperNintendo(superNintendo.location);
return {id, ""};
}
@ -202,10 +75,10 @@ auto Program::load(uint id, string name, string type, string_vector options) ->
dialog.setTitle("Load Game Boy");
dialog.setPath(path("Games", settings["Path/Recent/GameBoy"].text()));
dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}});
gameBoy.location = dialog.openFile();
gameBoy.location = dialog.openObject();
}
if(file::exists(gameBoy.location)) {
settings["Path/Recent/GameBoy"].setValue(Location::path(gameBoy.location));
if(inode::exists(gameBoy.location)) {
settings["Path/Recent/GameBoy"].setValue(Location::dir(gameBoy.location));
loadGameBoy(gameBoy.location);
return {id, ""};
}
@ -242,7 +115,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
current = chrono::timestamp();
if(current != previous) {
previous = current;
statusText = {"FPS: ", frameCounter};
statusText = {emulator->get("Mode").get<string>(), "FPS: ", frameCounter};
frameCounter = 0;
}
}

View File

@ -22,17 +22,43 @@ auto Program::path(string type, string location, string extension) -> string {
}
}
if(type == "States") {
if(auto path = settings["Path/States"].text()) {
pathname = path;
}
}
if(type == "Cheats") {
if(auto path = settings["Path/Cheats"].text()) {
pathname = path;
}
}
if(type == "States") {
if(auto path = settings["Path/States"].text()) {
pathname = path;
}
}
return {pathname, prefix, suffix};
}
auto Program::gamePath() -> string {
if(!emulator->loaded()) return "";
if(gameBoy.location) return gameBoy.location;
return superNintendo.location;
}
auto Program::cheatPath() -> string {
if(!emulator->loaded()) return "";
auto location = gamePath();
if(location.endsWith("/")) {
return {location, "cheats.bml"};
} else {
return path("Cheats", location, ".cht");
}
}
auto Program::statePath() -> string {
if(!emulator->loaded()) return "";
auto location = gamePath();
if(location.endsWith("/")) {
return {location, "bsnes/states/"};
} else {
return path("States", location, ".bsz");
}
}

View File

@ -1,8 +1,10 @@
#include "../bsnes.hpp"
#include "interface.cpp"
#include "game.cpp"
#include "game-pak.cpp"
#include "game-rom.cpp"
#include "paths.cpp"
#include "state.cpp"
#include "states.cpp"
#include "utility.cpp"
unique_pointer<Program> program;
@ -66,7 +68,7 @@ Program::Program(string_vector arguments) {
for(auto& argument : arguments) {
if(argument == "--fullscreen") {
presentation->toggleFullscreenMode();
} else if(file::exists(argument)) {
} else if(inode::exists(argument)) {
gameQueue.append(argument);
}
}

View File

@ -20,11 +20,21 @@ struct Program : Emulator::Platform {
auto save() -> void;
auto unload() -> void;
//game-pak.cpp
auto openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openPakGB(string name, vfs::file::mode mode) -> vfs::shared::file;
//game-rom.cpp
auto openRomSFC(string name, vfs::file::mode mode) -> vfs::shared::file;
auto openRomGB(string name, vfs::file::mode mode) -> vfs::shared::file;
//paths.cpp
auto path(string type, string location, string extension = "") -> string;
//state.cpp
auto gamePath() -> string;
auto cheatPath() -> string;
auto statePath() -> string;
//states.cpp
auto loadState(string filename) -> bool;
auto saveState(string filename) -> bool;
auto saveRecoveryState() -> bool;
@ -35,12 +45,14 @@ struct Program : Emulator::Platform {
auto initializeInputDriver() -> void;
auto updateVideoShader() -> void;
auto connectDevices() -> void;
auto applyHacks() -> void;
auto showMessage(string text) -> void;
auto updateMessage() -> void;
auto focused() -> bool;
public:
struct SuperNintendo {
string label;
string location;
string manifest;
Markup::Node document;

View File

@ -1,36 +0,0 @@
auto Program::statePath() -> string {
if(!emulator->loaded()) return "";
return path("States", superNintendo.location, ".bst/");
}
auto Program::loadState(string filename) -> bool {
if(!emulator->loaded()) return false;
string location = {statePath(), filename, ".bst"};
string prefix = Location::prefix(location);
if(!file::exists(location)) return showMessage({"[", prefix, "] not found"}), false;
if(filename != "Quick/Recovery Slot") saveRecoveryState();
auto memory = file::read(location);
serializer s{memory.data(), memory.size()};
if(!emulator->unserialize(s)) return showMessage({"[", prefix, "] is in incompatible format"}), false;
return showMessage({"Loaded [", prefix, "]"}), true;
}
auto Program::saveState(string filename) -> bool {
if(!emulator->loaded()) return false;
directory::create({statePath(), "Quick/"});
string location = {statePath(), filename, ".bst"};
string prefix = Location::prefix(location);
serializer s = emulator->serialize();
if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false;
if(!file::write(location, s.data(), s.size())) return showMessage({"Unable to write [", prefix, "] to disk"}), false;
return showMessage({"Saved [", prefix, "]"}), true;
}
auto Program::saveRecoveryState() -> bool {
if(!emulator->loaded()) return false;
directory::create({statePath(), "Quick/"});
serializer s = emulator->serialize();
if(!s.size()) return false;
if(!file::write({statePath(), "Quick/Recovery Slot.bst"}, s.data(), s.size())) return false;
return true;
}

View File

@ -0,0 +1,80 @@
auto Program::loadState(string filename) -> bool {
if(!emulator->loaded()) return false;
string prefix = Location::file(filename);
vector<uint8_t> memory;
if(gamePath().endsWith("/")) {
string location = {statePath(), filename, ".bst"};
if(!file::exists(location)) return showMessage({"[", prefix, "] not found"}), false;
if(filename != "quick/recovery") saveRecoveryState();
memory = file::read(location);
} else {
string location = {filename, ".bst"};
Decode::ZIP input;
if(input.open(statePath())) {
for(auto& file : input.file) {
if(file.name != location) continue;
memory = input.extract(file);
break;
}
}
}
if(memory) {
serializer s{memory.data(), memory.size()};
if(!emulator->unserialize(s)) return showMessage({"[", prefix, "] is in incompatible format"}), false;
return showMessage({"Loaded [", prefix, "]"}), true;
} else {
return showMessage({"[", prefix, "] not found"}), false;
}
}
auto Program::saveState(string filename) -> bool {
if(!emulator->loaded()) return false;
string prefix = Location::file(filename);
serializer s = emulator->serialize();
if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false;
if(gamePath().endsWith("/")) {
string location = {statePath(), filename, ".bst"};
directory::create(Location::path(location));
if(!file::write(location, s.data(), s.size())) return showMessage({"Unable to write [", prefix, "] to disk"}), false;
} else {
string location = {filename, ".bst"};
struct State {
string name;
vector<uint8_t> memory;
};
vector<State> states;
Decode::ZIP input;
if(input.open(statePath())) {
for(auto& file : input.file) {
if(!file.name.endsWith(".bst")) continue;
if(file.name == location) continue;
states.append({file.name, input.extract(file)});
}
}
Encode::ZIP output{statePath()};
for(auto& state : states) {
output.append(state.name, state.memory.data(), state.memory.size());
}
output.append(location, s.data(), s.size());
}
return showMessage({"Saved [", prefix, "]"}), true;
}
auto Program::saveRecoveryState() -> bool {
if(!emulator->loaded()) return false;
string location = {statePath(), "quick/recovery.bst"};
directory::create(Location::path(location));
serializer s = emulator->serialize();
if(!s.size()) return false;
if(!file::write(location, s.data(), s.size())) return false;
return true;
}

View File

@ -65,6 +65,18 @@ auto Program::connectDevices() -> void {
}
}
auto Program::applyHacks() -> void {
bool fastPPU = settingsWindow->advanced.fastPPUOption.checked();
bool fastDSP = settingsWindow->advanced.fastDSPOption.checked();
auto label = superNintendo.label;
if(label == "AIR STRIKE PATROL" || label == "DESERT FIGHTER") fastPPU = false;
if(label == "KOUSHIEN_2") fastDSP = false;
emulator->set("Fast PPU", fastPPU);
emulator->set("Fast DSP", fastDSP);
}
auto Program::showMessage(string text) -> void {
statusTime = chrono::timestamp();
statusMessage = text;

View File

@ -109,4 +109,12 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
settings.save();
}
});
emulatorLabel.setText("Emulator").setFont(Font().setBold());
fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/FastPPU"].boolean()).onToggle([&] {
settings["Emulator/FastPPU"].setValue(fastPPUOption.checked());
});
fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/FastDSP"].boolean()).onToggle([&] {
settings["Emulator/FastDSP"].setValue(fastDSPOption.checked());
});
}

View File

@ -39,18 +39,6 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) {
settings["Path/Saves"].setValue("");
refreshPaths();
});
statesLabel.setText("States:");
statesPath.setEditable(false);
statesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/States"].setValue(location);
refreshPaths();
}
});
statesReset.setText("Reset").onActivate([&] {
settings["Path/States"].setValue("");
refreshPaths();
});
cheatsLabel.setText("Cheats:");
cheatsPath.setEditable(false);
cheatsAssign.setText("Assign ...").onActivate([&] {
@ -63,6 +51,18 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) {
settings["Path/Cheats"].setValue("");
refreshPaths();
});
statesLabel.setText("States:");
statesPath.setEditable(false);
statesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/States"].setValue(location);
refreshPaths();
}
});
statesReset.setText("Reset").onActivate([&] {
settings["Path/States"].setValue("");
refreshPaths();
});
refreshPaths();
}
@ -83,14 +83,14 @@ auto PathSettings::refreshPaths() -> void {
} else {
savesPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128});
}
if(auto location = settings["Path/States"].text()) {
statesPath.setText(location).setForegroundColor();
} else {
statesPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128});
}
if(auto location = settings["Path/Cheats"].text()) {
cheatsPath.setText(location).setForegroundColor();
} else {
cheatsPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128});
}
if(auto location = settings["Path/States"].text()) {
statesPath.setText(location).setForegroundColor();
} else {
statesPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128});
}
}

View File

@ -43,8 +43,8 @@ Settings::Settings() {
set("Path/Games", "");
set("Path/Patches", "");
set("Path/Saves", "");
set("Path/States", "");
set("Path/Cheats", "");
set("Path/States", "");
set("Path/Recent/SuperNintendo", Path::user());
set("Path/Recent/GameBoy", Path::user());
set("Path/Recent/BSMemory", Path::user());
@ -52,6 +52,8 @@ Settings::Settings() {
set("UserInterface/ShowStatusBar", true);
set("Emulator/FastPPU", true);
set("Emulator/FastDSP", true);
set("Emulator/AutoSaveMemory/Enable", true);
set("Emulator/AutoSaveMemory/Interval", 30);

View File

@ -90,16 +90,16 @@ public:
LineEdit savesPath{&savesLayout, Size{~0, 0}};
Button savesAssign{&savesLayout, Size{80, 0}};
Button savesReset{&savesLayout, Size{80, 0}};
HorizontalLayout statesLayout{&layout, Size{~0, 0}};
Label statesLabel{&statesLayout, Size{55, 0}};
LineEdit statesPath{&statesLayout, Size{~0, 0}};
Button statesAssign{&statesLayout, Size{80, 0}};
Button statesReset{&statesLayout, Size{80, 0}};
HorizontalLayout cheatsLayout{&layout, Size{~0, 0}};
Label cheatsLabel{&cheatsLayout, Size{55, 0}};
LineEdit cheatsPath{&cheatsLayout, Size{~0, 0}};
Button cheatsAssign{&cheatsLayout, Size{80, 0}};
Button cheatsReset{&cheatsLayout, Size{80, 0}};
HorizontalLayout statesLayout{&layout, Size{~0, 0}};
Label statesLabel{&statesLayout, Size{55, 0}};
LineEdit statesPath{&statesLayout, Size{~0, 0}};
Button statesAssign{&statesLayout, Size{80, 0}};
Button statesReset{&statesLayout, Size{80, 0}};
};
struct AdvancedSettings : TabFrameItem {
@ -115,6 +115,9 @@ public:
ComboButton audioDriverOption{&driverLayout, Size{~0, 0}};
Label inputDriverLabel{&driverLayout, Size{0, 0}};
ComboButton inputDriverOption{&driverLayout, Size{~0, 0}};
Label emulatorLabel{&layout, Size{~0, 0}, 2};
CheckLabel fastPPUOption{&layout, Size{~0, 0}};
CheckLabel fastDSPOption{&layout, Size{~0, 0}};
};
struct SettingsWindow : Window {

View File

@ -191,7 +191,7 @@ auto CheatEditor::removeCheats() -> void {
auto CheatEditor::loadCheats() -> void {
cheats.reset();
auto location = program->path("Cheats", program->superNintendo.location, ".cht");
auto location = program->cheatPath();
auto document = BML::unserialize(string::read(location));
for(auto cheat : document.find("cheat")) {
cheats.append({cheat["name"].text(), cheat["code"].text(), (bool)cheat["enable"]});
@ -211,7 +211,7 @@ auto CheatEditor::saveCheats() -> void {
document.append(" enable\n");
document.append("\n");
}
auto location = program->path("Cheats", program->superNintendo.location, ".cht");
auto location = program->cheatPath();
if(document) {
file::write(location, document);
} else {

View File

@ -48,7 +48,7 @@ auto StateWindow::doChange() -> void {
|| c == '|') valid = false;
}
if(auto input = nameValue.property("input")) {
if(name != input && file::exists({program->statePath(), name, ".bst"})) valid = false;
if(name != input && file::exists({program->statePath(), "managed/", name, ".bst"})) valid = false;
}
nameValue.setBackgroundColor(valid ? Color{} : Color{255, 224, 224});
acceptButton.setEnabled(valid);
@ -81,12 +81,12 @@ StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) {
});
loadButton.setText("Load").onActivate([&] {
if(auto item = stateList.selected()) {
program->loadState(item.cell(0).text());
program->loadState({"managed/", item.cell(0).text()});
}
});
saveButton.setText("Save").onActivate([&] {
if(auto item = stateList.selected()) {
program->saveState(item.cell(0).text());
program->saveState({"managed/", item.cell(0).text()});
}
});
addButton.setText("Add").onActivate([&] {
@ -107,16 +107,33 @@ auto StateManager::loadStates() -> void {
stateList.append(TableViewHeader().setVisible(false)
.append(TableViewColumn().setExpandable())
);
for(auto filename : directory::ifiles(program->statePath(), "*.bst")) {
if(program->gamePath().endsWith("/")) {
for(auto filename : directory::ifiles({program->statePath(), "managed/"}, "*.bst")) {
stateList.append(TableViewItem()
.append(TableViewCell().setText(filename.trimRight(".bst", 1L)))
);
}
} else {
Decode::ZIP input;
if(input.open(program->statePath())) {
string_vector states;
for(auto& file : input.file) {
if(!file.name.match("managed/*.bst")) continue;
states.append(Location::prefix(file.name));
}
states.isort();
for(auto& state : states) {
stateList.append(TableViewItem()
.append(TableViewCell().setText(state))
);
}
}
}
stateList.resizeColumns().doChange();
}
auto StateManager::createState(string name) -> void {
program->saveState(name);
program->saveState({"managed/", name});
loadStates();
for(auto item : stateList.items()) {
if(item.cell(0).text() == name) item.setSelected();
@ -126,8 +143,8 @@ auto StateManager::createState(string name) -> void {
auto StateManager::modifyState(string name) -> void {
if(auto item = stateList.selected()) {
string from = {program->statePath(), item.cell(0).text(), ".bst"};
string to = {program->statePath(), name, ".bst"};
string from = {program->statePath(), "managed/", item.cell(0).text(), ".bst"};
string to = {program->statePath(), "managed/", name, ".bst"};
if(from != to) {
file::rename(from, to);
loadStates();
@ -144,7 +161,7 @@ auto StateManager::removeStates() -> void {
if(MessageDialog("Are you sure you want to permanently remove the selected state(s)?")
.setParent(*toolsWindow).question() == "Yes") {
for(auto item : batched) {
string location = {program->statePath(), item.cell(0).text(), ".bst"};
string location = {program->statePath(), "managed/", item.cell(0).text(), ".bst"};
file::remove(location);
}
loadStates();

View File

@ -36,28 +36,37 @@ private:
auto BrowserDialogWindow::accept() -> void {
auto batched = view.batched();
if(state.action == "openFile" && batched) {
string name = batched.left()->cell(0)->text();
if(state.action == "openFile" && batched.size() == 1) {
string name = batched[0].text();
if(isFolder(name)) return setPath({state.path, name});
response.selected.append(string{state.path, name});
}
if(state.action == "openFiles") {
if(state.action == "openFiles" && batched) {
for(auto item : batched) {
string name = item->cell(0)->text();
response.selected.append(string{state.path, name, isFolder(name) ? "/" : ""});
string name = item.text();
if(isFolder(name)) {
response.selected.reset();
return;
}
response.selected.append(string{state.path, name});
}
}
if(state.action == "openFolder" && batched) {
string name = batched.left()->cell(0)->text();
if(state.action == "openFolder" && batched.size() == 1) {
string name = batched[0].text();
if(!isMatch(name)) return setPath({state.path, name});
response.selected.append(string{state.path, name, "/"});
}
if(state.action == "openObject" && batched.size() == 1) {
string name = batched[0].text();
if(!isMatch(name) && isFolder(name)) return setPath({state.path, name});
response.selected.append(string{state.path, name, isFolder(name) ? "/" : ""});
}
if(state.action == "saveFile") {
string name = fileName.text();
if(!name && batched) name = batched.left()->cell(0)->text();
if(!name || isFolder(name)) return;
if(file::exists({state.path, name})) {
if(MessageDialog("File already exists. Overwrite it?").question() != "Yes") return;
@ -66,11 +75,11 @@ auto BrowserDialogWindow::accept() -> void {
}
if(state.action == "selectFolder") {
if(batched) {
string name = batched.left()->cell(0)->text();
if(isFolder(name)) response.selected.append(string{state.path, name, "/"});
} else {
if(!batched) {
response.selected.append(state.path);
} else if(batched.size() == 1) {
string name = batched[0].text();
if(isFolder(name)) response.selected.append(string{state.path, name, "/"});
}
}
@ -79,16 +88,16 @@ auto BrowserDialogWindow::accept() -> void {
//table view item double-clicked, or enter pressed on selected table view item
auto BrowserDialogWindow::activate() -> void {
auto selectedItem = view.selected();
auto batched = view.batched();
if(state.action == "saveFile" && selectedItem) {
string name = selectedItem->cell(0)->text();
if(state.action == "saveFile" && batched.size() == 1) {
string name = batched[0].text();
if(isFolder(name)) return setPath({state.path, name});
fileName.setText(name);
}
if(state.action == "selectFolder" && selectedItem) {
string name = selectedItem->cell(0)->text();
if(state.action == "selectFolder" && batched.size() == 1) {
string name = batched[0].text();
if(isFolder(name)) return setPath({state.path, name});
}
@ -97,12 +106,30 @@ auto BrowserDialogWindow::activate() -> void {
//table view item changed
auto BrowserDialogWindow::change() -> void {
fileName.setText("");
if(state.action == "saveFile") {
if(auto selectedItem = view.selected()) {
string name = selectedItem->cell(0)->text();
if(!isFolder(name)) fileName.setText(name);
auto batched = view.batched();
if(state.action == "openFile") {
acceptButton.setEnabled(batched.size() == 1);
}
if(state.action == "openFiles") {
bool enabled = true;
for(auto item : batched) enabled &= !isFolder(item.text());
if(batched.size() == 1 && isFolder(batched[0].text())) enabled = true;
acceptButton.setEnabled(enabled);
}
if(state.action == "openFolder") {
acceptButton.setEnabled(batched.size() == 1);
}
if(state.action == "openObject") {
acceptButton.setEnabled(batched.size() == 1);
}
if(state.action == "saveFile") {
if(batched.size() == 1) {
string name = batched[0].text();
if(!isFolder(name)) fileName.setText(name).doChange();
}
}
if(state.action == "selectFolder") {
acceptButton.setEnabled(!batched || (batched.size() == 1 && isFolder(batched[0].text())));
}
}
@ -138,11 +165,15 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
optionList.append(ComboButtonItem().setText(option));
}
optionList.doChange(); //updates response.option to point to the default (first) option
fileName.setVisible(state.action == "saveFile").onActivate([&] { accept(); });
fileName.setVisible(state.action == "saveFile").onActivate([&] { accept(); }).onChange([&] {
auto name = fileName.text();
acceptButton.setEnabled(name && !isFolder(name));
fileName.setBackgroundColor(acceptButton.enabled() ? Color{} : Color{255, 224, 224});
});
acceptButton.onActivate([&] { accept(); });
if(state.action == "openFile" || state.action == "openFiles" || state.action == "openFolder") acceptButton.setText("Open");
if(state.action == "saveFile") acceptButton.setText("Save");
if(state.action == "selectFolder") acceptButton.setText("Select");
if(state.action.beginsWith("open")) acceptButton.setText("Open");
if(state.action.beginsWith("save")) acceptButton.setText("Save");
if(state.action.beginsWith("select")) acceptButton.setText("Select");
cancelButton.setText("Cancel").onActivate([&] { window.setModal(false); });
if(!state.filters) state.filters.append("All|*");
@ -170,26 +201,32 @@ auto BrowserDialogWindow::setPath(string path) -> void {
path.transform("\\", "/");
if((path || Path::root() == "/") && !path.endsWith("/")) path.append("/");
pathName.setText(state.path = path);
view.reset();
auto contents = directory::icontents(path);
bool folderMode = state.action == "openFolder";
for(auto content : contents) {
if(!content.endsWith("/")) continue;
content.trimRight("/");
if(folderMode && isMatch(content)) continue;
bool isFolder = content.endsWith("/");
if(isFolder) {
content.trimRight("/", 1L);
if(state.action == "openFolder" || state.action == "openObject") {
if(isMatch(content)) continue;
}
} else {
continue;
}
view.append(ListViewItem().setText(content).setIcon(Icon::Emblem::Folder));
}
for(auto content : contents) {
if(content.endsWith("/") != folderMode) continue; //file mode shows files; folder mode shows folders
content.trimRight("/");
bool isFolder = content.endsWith("/");
if(isFolder) {
content.trimRight("/", 1L);
if(state.action != "openFolder" && state.action != "openObject") continue;
} else {
if(state.action == "openFolder") continue;
}
if(!isMatch(content)) continue;
view.append(ListViewItem().setText(content).setIcon(folderMode ? Icon::Action::Open : Icon::Emblem::File));
view.append(ListViewItem().setText(content).setIcon(isFolder ? Icon::Action::Open : Icon::Emblem::File));
}
Application::processEvents();
@ -223,6 +260,13 @@ auto BrowserDialog::openFolder() -> string {
return {};
}
auto BrowserDialog::openObject() -> string {
state.action = "openObject";
if(!state.title) state.title = "Open Object";
if(auto result = _run()) return result.left();
return {};
}
auto BrowserDialog::option() -> string {
return response.option;
}

View File

@ -7,8 +7,9 @@ struct BrowserDialog {
BrowserDialog();
auto openFile() -> string; //one existing file
auto openFiles() -> string_vector; //any existing files or folders
auto openFiles() -> string_vector; //any existing files
auto openFolder() -> string; //one existing folder
auto openObject() -> string; //one existing file or folder
auto option() -> string;
auto saveFile() -> string; //one file
auto selected() -> string_vector;

View File

@ -10,11 +10,13 @@ GtkSelectionData* data, unsigned type, unsigned timestamp, pCanvas* p) -> void {
p->self().doDrop(paths);
}
//GTK3
static auto Canvas_draw(GtkWidget* widget, cairo_t* context, pCanvas* p) -> signed {
p->_onDraw(context);
return true;
}
//GTK2
static auto Canvas_expose(GtkWidget* widget, GdkEventExpose* event, pCanvas* p) -> signed {
p->_onExpose(event);
return true;
@ -116,7 +118,8 @@ auto pCanvas::update() -> void {
}
auto pCanvas::_onDraw(cairo_t* context) -> void {
#if HIRO_GTK==3
if(!surface) return;
int sx = 0, sy = 0, dx = 0, dy = 0;
int width = surfaceWidth, height = surfaceHeight;
auto geometry = pSizable::state().geometry;
@ -137,41 +140,19 @@ auto pCanvas::_onDraw(cairo_t* context) -> void {
dy = 0;
}
//TODO: support non-zero sx,sy
gdk_cairo_set_source_pixbuf(context, surface, dx, dy);
cairo_set_source_rgba(context, 0.0, 0.0, 0.0, 0.0);
cairo_paint(context);
gdk_cairo_set_source_pixbuf(context, surface, dx - sx, dy - sy);
cairo_rectangle(context, dx, dy, width, height);
cairo_paint(context);
#endif
}
auto pCanvas::_onExpose(GdkEventExpose* expose) -> void {
#if HIRO_GTK==2
if(surface == nullptr) return;
if(!surface) return;
int sx = 0, sy = 0, dx = 0, dy = 0;
int width = surfaceWidth;
int height = surfaceHeight;
auto geometry = pSizable::state().geometry;
if(width <= geometry.width()) {
sx = 0;
dx = (geometry.width() - width) / 2;
} else {
sx = (width - geometry.width()) / 2;
dx = 0;
width = geometry.width();
}
if(height <= geometry.height()) {
sy = 0;
dy = (geometry.height() - height) / 2;
} else {
sy = (height - geometry.height()) / 2;
dy = 0;
height = geometry.height();
}
gdk_draw_pixbuf(gtk_widget_get_window(gtkWidget), nullptr, surface, sx, sy, dx, dy, width, height, GDK_RGB_DITHER_NONE, 0, 0);
#endif
cairo_t* context = gdk_cairo_create(gtk_widget_get_window(gtkWidget));
_onDraw(context);
cairo_destroy(context);
}
auto pCanvas::_rasterize() -> void {

View File

@ -72,6 +72,7 @@
#include <nall/encode/base64.hpp>
#include <nall/encode/html.hpp>
#include <nall/encode/url.hpp>
#include <nall/encode/zip.hpp>
#include <nall/hash/crc16.hpp>
#include <nall/hash/crc32.hpp>
#include <nall/hash/crc64.hpp>