Update to v094r39 release.

byuu says:

Changelog:
- SNES mid-scanline BGMODE fixes finally merged (can run
  atx2.zip{mode7.smc}+mtest(2).sfc properly now)
- Makefile now discards all built-in rules and variables
- switch on bool warning disabled for GCC now as well (was already
  disabled for Clang)
- when loading a game, if any required files are missing, display
  a warning message box (manifest.bml, program.rom, bios.rom, etc)
- when loading a game (or a game slot), if manifest.bml is missing, it
  will invoke icarus to try and generate it
  - if that fails (icarus is missing or the folder is bad), you will get
    a warning telling you that the manifest can't be loaded

The warning prompt on missing files work for both games and the .sys
folders and their files. For some reason, failing to load the DMG/CGB
BIOS is causing a crash before I can display the modal dialog. I have no
idea why, and the stack frame backtrace is junk.

I also can't seem to abort the failed loading process. If I call
Program::unloadMedia(), I get a nasty segfault. Again with a really
nasty stack trace. So for now, it'll just end up sitting there emulating
an empty ROM (solid black screen.) In time, I'd like to fix that too.

Lastly, I need a better method than popen for Windows. popen is kind of
ugly and flashes a console window for a brief second even if the
application launched is linked with -mwindows. Not sure if there even is
one (I need to read the stdout result, so CreateProcess may not work
unless I do something nasty like "> %tmp%/temp") I'm also using the
regular popen instead of _wpopen, so for this WIP, it won't work if your
game folder has non-English letters in the path.
This commit is contained in:
Tim Allen 2015-08-04 19:00:55 +10:00
parent 1b0b54a690
commit 0271d6a12b
59 changed files with 561 additions and 459 deletions

View File

@ -8,7 +8,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "094.38";
static const string Version = "094.39";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -47,8 +47,8 @@ struct Interface {
vector<Port> port;
struct Bind {
virtual auto loadRequest(unsigned, string, string) -> void {}
virtual auto loadRequest(unsigned, string) -> void {}
virtual auto loadRequest(unsigned, string, string, bool) -> void {}
virtual auto loadRequest(unsigned, string, bool) -> void {}
virtual auto saveRequest(unsigned, string) -> void {}
virtual auto videoColor(unsigned, uint16_t, uint16_t, uint16_t, uint16_t) -> uint32_t { return 0u; }
virtual auto videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) -> void {}
@ -62,8 +62,8 @@ struct Interface {
Bind* bind = nullptr;
//callback bindings (provided by user interface)
auto loadRequest(unsigned id, string name, string type) -> void { return bind->loadRequest(id, name, type); }
auto loadRequest(unsigned id, string path) -> void { return bind->loadRequest(id, path); }
auto loadRequest(unsigned id, string name, string type, bool required) -> void { return bind->loadRequest(id, name, type, required); }
auto loadRequest(unsigned id, string path, bool required) -> void { return bind->loadRequest(id, path, required); }
auto saveRequest(unsigned id, string path) -> void { return bind->saveRequest(id, path); }
auto videoColor(unsigned source, uint16_t alpha, uint16_t red, uint16_t green, uint16_t blue) -> uint32_t { return bind->videoColor(source, alpha, red, green, blue); }
auto videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) -> void { return bind->videoRefresh(palette, data, pitch, width, height); }

View File

@ -104,10 +104,10 @@ Board::Board(Markup::Node& document) {
if(chrrom.size) chrrom.data = new uint8[chrrom.size]();
if(chrram.size) chrram.data = new uint8[chrram.size]();
if(auto name = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name);
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name);
if(auto name = crom["name"].text()) interface->loadRequest(ID::CharacterROM, name);
if(auto name = cram["name"].text()) interface->loadRequest(ID::CharacterRAM, name);
if(auto name = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name, true);
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
if(auto name = crom["name"].text()) interface->loadRequest(ID::CharacterROM, name, true);
if(auto name = cram["name"].text()) interface->loadRequest(ID::CharacterRAM, name, false);
if(auto name = pram["name"].text()) Famicom::cartridge.memory.append({ID::ProgramRAM, name});
if(auto name = cram["name"].text()) Famicom::cartridge.memory.append({ID::CharacterRAM, name});

View File

@ -19,7 +19,7 @@ void Cartridge::main() {
}
void Cartridge::load() {
interface->loadRequest(ID::Manifest, "manifest.bml");
interface->loadRequest(ID::Manifest, "manifest.bml", true);
Board::load(information.markup); //this call will set Cartridge::board if successful
if(board == nullptr) return;

View File

@ -26,6 +26,8 @@ string Interface::sha256() {
unsigned Interface::group(unsigned id) {
switch(id) {
case ID::SystemManifest:
return 0;
case ID::Manifest:
case ID::ProgramROM:
case ID::ProgramRAM:
@ -48,7 +50,13 @@ void Interface::save() {
}
void Interface::load(unsigned id, const stream& stream) {
if(id == ID::Manifest) cartridge.information.markup = stream.text();
if(id == ID::SystemManifest) {
system.information.manifest = stream.text();
}
if(id == ID::Manifest) {
cartridge.information.markup = stream.text();
}
if(id == ID::ProgramROM) {
stream.read(cartridge.board->prgrom.data, min(cartridge.board->prgrom.size, stream.size()));

View File

@ -9,6 +9,8 @@ struct ID {
};
enum : unsigned {
SystemManifest,
Manifest,
ProgramROM,
ProgramRAM,

View File

@ -42,8 +42,8 @@ void System::runthreadtosave() {
}
void System::load() {
string manifest = string::read({interface->path(ID::System), "manifest.bml"});
auto document = BML::unserialize(manifest);
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
auto document = BML::unserialize(information.manifest);
serialize_init();
}

View File

@ -17,6 +17,10 @@ struct System {
void serialize_all(serializer&);
void serialize_init();
unsigned serialize_size;
struct Information {
string manifest;
} information;
};
extern System system;

View File

@ -35,7 +35,7 @@ void Cartridge::load(System::Revision revision) {
system.revision = revision; //needed for ID::Manifest to return correct group ID
if(revision != System::Revision::SuperGameBoy) {
interface->loadRequest(ID::Manifest, "manifest.bml");
interface->loadRequest(ID::Manifest, "manifest.bml", true);
}
information.mapper = Mapper::Unknown;
@ -74,8 +74,8 @@ void Cartridge::load(System::Revision revision) {
//Super Game Boy core loads memory from Super Famicom core
if(revision != System::Revision::SuperGameBoy) {
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name);
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name);
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, true);
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
}

View File

@ -38,6 +38,7 @@ string Interface::sha256() {
unsigned Interface::group(unsigned id) {
switch(id) {
case ID::SystemManifest:
case ID::GameBoyBootROM:
case ID::SuperGameBoyBootROM:
case ID::GameBoyColorBootROM:
@ -68,6 +69,10 @@ void Interface::save() {
}
void Interface::load(unsigned id, const stream& stream) {
if(id == ID::SystemManifest) {
system.information.manifest = stream.text();
}
if(id == ID::GameBoyBootROM) {
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
}
@ -80,7 +85,9 @@ void Interface::load(unsigned id, const stream& stream) {
stream.read(system.bootROM.cgb, min(2048u, stream.size()));
}
if(id == ID::Manifest) cartridge.information.markup = stream.text();
if(id == ID::Manifest) {
cartridge.information.markup = stream.text();
}
if(id == ID::ROM) {
stream.read(cartridge.romdata, min(cartridge.romsize, stream.size()));

View File

@ -11,6 +11,7 @@ struct ID {
};
enum : unsigned {
SystemManifest,
GameBoyBootROM,
SuperGameBoyBootROM,
GameBoyColorBootROM,

View File

@ -49,16 +49,14 @@ void System::load(Revision revision) {
serialize_init();
if(revision == Revision::SuperGameBoy) return; //Super Famicom core loads boot ROM for SGB
string manifest = string::read({interface->path(ID::System), "manifest.bml"});
auto document = BML::unserialize(manifest);
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
auto document = BML::unserialize(information.manifest);
auto bootROM = document["system/cpu/rom/name"].text();
interface->loadRequest(
revision == Revision::GameBoy ? ID::GameBoyBootROM : ID::GameBoyColorBootROM,
bootROM
);
if(!file::exists({interface->path(ID::System), bootROM})) {
interface->notify("Error: required Game Boy firmware boot.rom not found.\n");
if(auto bootROM = document["system/cpu/rom/name"].text()) {
interface->loadRequest(
revision == Revision::GameBoy ? ID::GameBoyBootROM : ID::GameBoyColorBootROM,
bootROM, true
);
}
}

View File

@ -42,6 +42,10 @@ struct System {
void serialize_init();
System();
struct Information {
string manifest;
} information;
};
#include <gb/interface/interface.hpp>

View File

@ -12,7 +12,7 @@ string Cartridge::title() {
}
void Cartridge::load() {
interface->loadRequest(ID::Manifest, "manifest.bml");
interface->loadRequest(ID::Manifest, "manifest.bml", true);
auto document = BML::unserialize(information.markup);
information.title = document["information/title"].text();
@ -20,7 +20,7 @@ void Cartridge::load() {
unsigned rom_size = 0;
if(document["cartridge/rom"]) {
auto info = document["cartridge/rom"];
interface->loadRequest(ID::ROM, info["name"].text());
interface->loadRequest(ID::ROM, info["name"].text(), true);
rom_size = info["size"].decimal();
for(unsigned addr = rom_size; addr < rom.size; addr++) {
rom.data[addr] = rom.data[Bus::mirror(addr, rom_size)];
@ -40,7 +40,7 @@ void Cartridge::load() {
ram.mask = ram.size - 1;
for(unsigned n = 0; n < ram.size; n++) ram.data[n] = 0xff;
interface->loadRequest(ID::RAM, info["name"].text());
interface->loadRequest(ID::RAM, info["name"].text(), false);
memory.append({ID::RAM, info["name"].text()});
}
@ -53,7 +53,7 @@ void Cartridge::load() {
eeprom.test = rom_size > 16 * 1024 * 1024 ? 0x0dffff00 : 0x0d000000;
for(unsigned n = 0; n < eeprom.size; n++) eeprom.data[n] = 0xff;
interface->loadRequest(ID::EEPROM, info["name"].text());
interface->loadRequest(ID::EEPROM, info["name"].text(), false);
memory.append({ID::EEPROM, info["name"].text()});
}
@ -68,7 +68,7 @@ void Cartridge::load() {
if(!flashrom.id && flashrom.size == 64 * 1024) flashrom.id = 0x1cc2;
if(!flashrom.id && flashrom.size == 128 * 1024) flashrom.id = 0x09c2;
interface->loadRequest(ID::FlashROM, info["name"].text());
interface->loadRequest(ID::FlashROM, info["name"].text(), false);
memory.append({ID::FlashROM, info["name"].text()});
}
}

View File

@ -22,6 +22,7 @@ bool Interface::loaded() {
unsigned Interface::group(unsigned id) {
switch(id) {
case ID::SystemManifest:
case ID::BIOS:
return ID::System;
case ID::Manifest:
@ -46,11 +47,17 @@ void Interface::save() {
}
void Interface::load(unsigned id, const stream& stream) {
if(id == ID::SystemManifest) {
system.information.manifest = stream.text();
}
if(id == ID::BIOS) {
stream.read(bios.data, min(bios.size, stream.size()));
}
if(id == ID::Manifest) cartridge.information.markup = stream.text();
if(id == ID::Manifest) {
cartridge.information.markup = stream.text();
}
if(id == ID::ROM) {
stream.read(cartridge.rom.data, min(cartridge.rom.size, stream.size()));

View File

@ -9,6 +9,7 @@ struct ID {
};
enum : unsigned {
SystemManifest,
BIOS,
Manifest,

View File

@ -24,13 +24,11 @@ void System::power() {
}
void System::load() {
string manifest = string::read({interface->path(ID::System), "manifest.bml"});
auto document = BML::unserialize(manifest);
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
auto document = BML::unserialize(information.manifest);
auto bios = document["system/cpu/rom/name"].text();
interface->loadRequest(ID::BIOS, bios);
if(!file::exists({interface->path(ID::System), bios})) {
interface->notify("Error: required Game Boy Advance firmware bios.rom not found.\n");
if(auto bios = document["system/cpu/rom/name"].text()) {
interface->loadRequest(ID::BIOS, bios, true);
}
serialize_init();

View File

@ -31,6 +31,10 @@ struct System {
void serialize(serializer&);
void serialize_all(serializer&);
void serialize_init();
struct Information {
string manifest;
} information;
};
extern BIOS bios;

View File

@ -1353,12 +1353,14 @@ struct mListViewItem : mObject {
auto backgroundColor() const -> Color;
auto cell(unsigned position) const -> ListViewCell;
auto cells() const -> unsigned;
auto checkable() const -> bool;
auto checked() const -> bool;
auto foregroundColor() const -> Color;
auto remove() -> type& override;
auto remove(sListViewCell cell) -> type&;
auto selected() const -> bool;
auto setBackgroundColor(Color color = {}) -> type&;
auto setCheckable(bool checkable = true) -> type&;
auto setChecked(bool checked = true) -> type&;
auto setFocused() -> type& override;
auto setForegroundColor(Color color = {}) -> type&;
@ -1369,6 +1371,7 @@ struct mListViewItem : mObject {
struct State {
Color backgroundColor;
vector<sListViewCell> cells;
bool checkable = true;
bool checked = false;
Color foregroundColor;
bool selected = false;

View File

@ -502,11 +502,13 @@ struct ListViewItem : sListViewItem {
auto backgroundColor() const { return self().backgroundColor(); }
auto cell(unsigned position) const { return self().cell(position); }
auto cells() const { return self().cells(); }
auto checkable() const { return self().checkable(); }
auto checked() const { return self().checked(); }
auto foregroundColor() const { return self().foregroundColor(); }
auto remove(sListViewCell cell) { return self().remove(cell), *this; }
auto selected() const { return self().selected(); }
auto setBackgroundColor(Color color = {}) { return self().setBackgroundColor(color), *this; }
auto setCheckable(bool checkable = true) { return self().setCheckable(checkable), *this; }
auto setChecked(bool checked = true) { return self().setChecked(checked), *this; }
auto setForegroundColor(Color color = {}) { return self().setForegroundColor(color), *this; }
auto setSelected(bool selected = true) { return self().setSelected(selected), *this; }

View File

@ -26,8 +26,12 @@ auto mListViewItem::cells() const -> unsigned {
return state.cells.size();
}
auto mListViewItem::checkable() const -> bool {
return state.checkable;
}
auto mListViewItem::checked() const -> bool {
return state.checked;
return state.checkable && state.checked;
}
auto mListViewItem::foregroundColor() const -> Color {
@ -59,6 +63,12 @@ auto mListViewItem::setBackgroundColor(Color color) -> type& {
return *this;
}
auto mListViewItem::setCheckable(bool checkable) -> type& {
state.checkable = checkable;
signal(setCheckable, checkable);
return *this;
}
auto mListViewItem::setChecked(bool checked) -> type& {
state.checked = checked;
signal(setChecked, checked);

View File

@ -163,7 +163,6 @@ auto BrowserDialogWindow::setPath(string path) -> void {
view.reset();
view.append(ListViewColumn().setExpandable());
view.append(ListViewColumn().setForegroundColor({192, 128, 128}));
auto contents = directory::icontents(path);
bool folderMode = state.action == "openFolder";
@ -175,7 +174,6 @@ auto BrowserDialogWindow::setPath(string path) -> void {
view.append(ListViewItem()
.append(ListViewCell().setText(content).setIcon(Icon::Emblem::Folder))
.append(ListViewCell().setText(octal(file_system_object::mode({path, content}) & 0777, 3L)))
);
}
@ -186,7 +184,6 @@ auto BrowserDialogWindow::setPath(string path) -> void {
view.append(ListViewItem()
.append(ListViewCell().setText(content).setIcon(folderMode ? Icon::Action::Open : Icon::Emblem::File))
.append(ListViewCell().setText(octal(file_system_object::mode({path, content}) & 0777, 3L)))
);
}

View File

@ -22,6 +22,7 @@ auto pListViewColumn::construct() -> void {
gtkCellToggle = gtk_cell_renderer_toggle_new();
gtk_tree_view_column_pack_start(gtkColumn, gtkCellToggle, false);
gtk_tree_view_column_set_attributes(gtkColumn, gtkCellToggle, "active", 0, nullptr);
gtk_tree_view_column_set_cell_data_func(gtkColumn, GTK_CELL_RENDERER(gtkCellToggle), (GtkTreeCellDataFunc)ListView_cellRendererToggleDataFunc, (gpointer)_parent(), nullptr);
}
gtkCellIcon = gtk_cell_renderer_pixbuf_new();

View File

@ -17,6 +17,9 @@ auto pListViewItem::remove(sListViewCell cell) -> void {
auto pListViewItem::setBackgroundColor(Color color) -> void {
}
auto pListViewItem::setCheckable(bool checkable) -> void {
}
auto pListViewItem::setChecked(bool checked) -> void {
if(auto parent = _parent()) {
gtk_list_store_set(parent->gtkListStore, &gtkIter, 0, checked, -1);

View File

@ -8,6 +8,7 @@ struct pListViewItem : pObject {
auto append(sListViewCell cell) -> void;
auto remove(sListViewCell cell) -> void;
auto setBackgroundColor(Color color) -> void;
auto setCheckable(bool checkable) -> void;
auto setChecked(bool checked) -> void;
auto setFocused() -> void;
auto setForegroundColor(Color color) -> void;

View File

@ -9,6 +9,8 @@ static auto ListView_edit(GtkCellRendererText* renderer, const char* path, const
static auto ListView_headerActivate(GtkTreeViewColumn* column, pListView* p) -> void { return p->_doHeaderActivate(column); }
static auto ListView_mouseMoveEvent(GtkWidget*, GdkEvent*, pListView* p) -> signed { return p->_doMouseMove(); }
static auto ListView_popup(GtkTreeView*, pListView* p) -> void { return p->_doContext(); }
static auto ListView_cellRendererToggleDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, pListView* p) -> void { return p->_doCellRendererToggleDataFunc(renderer, iter); }
static auto ListView_toggle(GtkCellRendererToggle*, const char* path, pListView* p) -> void { return p->_doToggle(path); }
auto pListView::construct() -> void {
@ -276,6 +278,15 @@ auto pListView::_doActivate() -> void {
if(!locked()) self().doActivate();
}
auto pListView::_doCellRendererToggleDataFunc(GtkCellRenderer* renderer, GtkTreeIter* iter) -> void {
auto path = gtk_tree_model_get_string_from_iter(gtkTreeModel, iter);
auto row = decimal(path);
if(auto item = self().item(row)) {
gtk_cell_renderer_set_visible(renderer, state().checkable && item->state.checkable);
}
g_free(path);
}
auto pListView::_doChange() -> void {
if(!locked()) _updateSelected();
}

View File

@ -31,6 +31,7 @@ struct pListView : pWidget {
auto _columnWidth(unsigned column) -> unsigned;
auto _createModel() -> void;
auto _doActivate() -> void;
auto _doCellRendererToggleDataFunc(GtkCellRenderer* renderer, GtkTreeIter* iter) -> void;
auto _doChange() -> void;
auto _doContext() -> void;
auto _doEdit(GtkCellRendererText* renderer, const char* path, const char* text) -> void;

View File

@ -18,7 +18,7 @@ auto pCheckLabel::destruct() -> void {
DestroyWindow(hwnd);
}
auto pCheckLabel::minimumSize() -> Size {
auto pCheckLabel::minimumSize() const -> Size {
auto size = pFont::size(hfont, state().text);
return {size.width() + 20, size.height() + 4};
}

View File

@ -5,7 +5,7 @@ namespace hiro {
struct pCheckLabel : pWidget {
Declare(CheckLabel, Widget)
auto minimumSize() -> Size;
auto minimumSize() const -> Size override;
auto setChecked(bool checked) -> void;
auto setText(const string& text) -> void;

View File

@ -17,6 +17,9 @@ auto pListViewItem::remove(sListViewCell cell) -> void {
auto pListViewItem::setBackgroundColor(Color color) -> void {
}
auto pListViewItem::setCheckable(bool checkable) -> void {
}
auto pListViewItem::setChecked(bool checked) -> void {
if(auto parent = _parent()) {
parent->lock();

View File

@ -8,6 +8,7 @@ struct pListViewItem : pObject {
auto append(sListViewCell cell) -> void;
auto remove(sListViewCell cell) -> void;
auto setBackgroundColor(Color color) -> void;
auto setCheckable(bool checkable) -> void;
auto setChecked(bool checked) -> void;
auto setFocused() -> void;
auto setForegroundColor(Color color) -> void;

View File

@ -275,7 +275,7 @@ auto pListView::onCustomDraw(LPARAM lparam) -> LRESULT {
HBRUSH brush = CreateSolidBrush(selected ? GetSysColor(COLOR_HIGHLIGHT) : CreateRGB(_backgroundColor(row, column)));
FillRect(hdc, &rc, brush);
DeleteObject(brush);
if(state().checkable && column == 0) {
if(state().checkable && self().item(row).checkable() && column == 0) {
if(auto htheme = OpenThemeData(hwnd, L"BUTTON")) {
unsigned state = checked ? CBS_CHECKEDNORMAL : CBS_UNCHECKEDNORMAL;
SIZE size;

View File

@ -1,3 +1,7 @@
# disable built-in rules and variables
MAKEFLAGS := Rr
.SUFFIXES:
[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z
[0-9] = 0 1 2 3 4 5 6 7 8 9

View File

@ -37,11 +37,15 @@ namespace nall {
#elif defined(__GNUC__)
#define COMPILER_GCC
auto Intrinsics::compiler() -> Compiler { return Compiler::GCC; }
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wswitch-bool"
#elif defined(_MSC_VER)
#define COMPILER_VISUALCPP
auto Intrinsics::compiler() -> Compiler { return Compiler::VisualCPP; }
#pragma warning(disable:4996) //disable libc "deprecation" warnings
#pragma warning(disable:4996) //libc "deprecation" warnings
#else
#warning "unable to detect compiler"
#define COMPILER_UNKNOWN

View File

@ -340,6 +340,7 @@ template<typename... P> auto append(lstring& self, const string& value, P&&... p
inline auto append(lstring& self) -> lstring&;
inline auto find(const lstring& self, const string& source) -> maybe<unsigned>;
inline auto ifind(const lstring& self, const string& source) -> maybe<unsigned>;
inline auto match(const lstring& self, const string& pattern) -> lstring;
inline auto merge(const lstring& self, const string& separator) -> string;
inline auto strip(lstring& self) -> lstring&;
@ -368,6 +369,7 @@ struct lstring : vector<string> {
template<typename... P> auto append(P&&... p) -> type& { return nall::append(*this, forward<P>(p)...); }
auto find(const string& source) const -> maybe<unsigned> { return nall::find(*this, source); }
auto ifind(const string& source) const -> maybe<unsigned> { return nall::ifind(*this, source); }
auto match(const string& pattern) const -> lstring { return nall::match(*this, pattern); }
auto merge(const string& separator) const -> string { return nall::merge(*this, separator); }
auto strip() -> type& { return nall::strip(*this); }

View File

@ -46,6 +46,14 @@ auto ifind(const lstring& self, const string& source) -> maybe<unsigned> {
return nothing;
}
auto match(const lstring& self, const string& pattern) -> lstring {
lstring result;
for(unsigned n = 0; n < self.size(); n++) {
if(self[n].match(pattern)) result.append(self[n]);
}
return result;
}
auto merge(const lstring& self, const string& separator) -> string {
string output;
for(unsigned n = 0; n < self.size(); n++) {

View File

@ -62,7 +62,7 @@ auto Cartridge::load() -> void {
information.title.sufamiTurboA = "";
information.title.sufamiTurboB = "";
interface->loadRequest(ID::Manifest, "manifest.bml");
interface->loadRequest(ID::Manifest, "manifest.bml", true);
parseMarkup(information.markup.cartridge);
//Super Game Boy
@ -115,7 +115,7 @@ auto Cartridge::load() -> void {
}
auto Cartridge::loadSuperGameBoy() -> void {
interface->loadRequest(ID::SuperGameBoyManifest, "manifest.bml");
interface->loadRequest(ID::SuperGameBoyManifest, "manifest.bml", true);
auto document = BML::unserialize(information.markup.gameBoy);
information.title.gameBoy = document["information/title"].text();
@ -125,13 +125,13 @@ auto Cartridge::loadSuperGameBoy() -> void {
GameBoy::cartridge.information.markup = information.markup.gameBoy;
GameBoy::cartridge.load(GameBoy::System::Revision::SuperGameBoy);
if(auto name = rom["name"].text()) interface->loadRequest(ID::SuperGameBoyROM, name);
if(auto name = ram["name"].text()) interface->loadRequest(ID::SuperGameBoyRAM, name);
if(auto name = rom["name"].text()) interface->loadRequest(ID::SuperGameBoyROM, name, true);
if(auto name = ram["name"].text()) interface->loadRequest(ID::SuperGameBoyRAM, name, false);
if(auto name = ram["name"].text()) memory.append({ID::SuperGameBoyRAM, name});
}
auto Cartridge::loadSatellaview() -> void {
interface->loadRequest(ID::SatellaviewManifest, "manifest.bml");
interface->loadRequest(ID::SatellaviewManifest, "manifest.bml", true);
auto document = BML::unserialize(information.markup.satellaview);
information.title.satellaview = document["information/title"].text();
@ -140,14 +140,14 @@ auto Cartridge::loadSatellaview() -> void {
if(rom["name"]) {
unsigned size = rom["size"].decimal();
satellaviewcartridge.memory.map(allocate<uint8>(size, 0xff), size);
interface->loadRequest(ID::SatellaviewROM, rom["name"].text());
interface->loadRequest(ID::SatellaviewROM, rom["name"].text(), true);
satellaviewcartridge.readonly = (rom["type"].text() == "MaskROM");
}
}
auto Cartridge::loadSufamiTurboA() -> void {
interface->loadRequest(ID::SufamiTurboSlotAManifest, "manifest.bml");
interface->loadRequest(ID::SufamiTurboSlotAManifest, "manifest.bml", true);
auto document = BML::unserialize(information.markup.sufamiTurboA);
information.title.sufamiTurboA = document["information/title"].text();
@ -157,23 +157,23 @@ auto Cartridge::loadSufamiTurboA() -> void {
if(rom["name"]) {
unsigned size = rom["size"].decimal();
sufamiturboA.rom.map(allocate<uint8>(size, 0xff), size);
interface->loadRequest(ID::SufamiTurboSlotAROM, rom["name"].text());
interface->loadRequest(ID::SufamiTurboSlotAROM, rom["name"].text(), true);
}
if(ram["name"]) {
unsigned size = ram["size"].decimal();
sufamiturboA.ram.map(allocate<uint8>(size, 0xff), size);
interface->loadRequest(ID::SufamiTurboSlotARAM, ram["name"].text());
interface->loadRequest(ID::SufamiTurboSlotARAM, ram["name"].text(), false);
memory.append({ID::SufamiTurboSlotARAM, ram["name"].text()});
}
if(document["cartridge/linkable"]) {
interface->loadRequest(ID::SufamiTurboSlotB, "Sufami Turbo - Slot B", "st");
interface->loadRequest(ID::SufamiTurboSlotB, "Sufami Turbo - Slot B", "st", false);
}
}
auto Cartridge::loadSufamiTurboB() -> void {
interface->loadRequest(ID::SufamiTurboSlotBManifest, "manifest.bml");
interface->loadRequest(ID::SufamiTurboSlotBManifest, "manifest.bml", true);
auto document = BML::unserialize(information.markup.sufamiTurboB);
information.title.sufamiTurboB = document["information/title"].text();
@ -183,13 +183,13 @@ auto Cartridge::loadSufamiTurboB() -> void {
if(rom["name"]) {
unsigned size = rom["size"].decimal();
sufamiturboB.rom.map(allocate<uint8>(size, 0xff), size);
interface->loadRequest(ID::SufamiTurboSlotBROM, rom["name"].text());
interface->loadRequest(ID::SufamiTurboSlotBROM, rom["name"].text(), true);
}
if(ram["name"]) {
unsigned size = ram["size"].decimal();
sufamiturboB.ram.map(allocate<uint8>(size, 0xff), size);
interface->loadRequest(ID::SufamiTurboSlotBRAM, ram["name"].text());
interface->loadRequest(ID::SufamiTurboSlotBRAM, ram["name"].text(), false);
memory.append({ID::SufamiTurboSlotBRAM, ram["name"].text()});
}
}

View File

@ -51,7 +51,7 @@ auto Cartridge::parseMarkupMemory(MappedRAM& ram, Markup::Node node, unsigned id
unsigned size = node["size"].decimal();
ram.map(allocate<uint8>(size, 0xff), size);
if(name) {
interface->loadRequest(id, name);
interface->loadRequest(id, name, !writable); //treat ROM as required; RAM as optional
if(writable) memory.append({id, name});
}
}
@ -83,10 +83,10 @@ auto Cartridge::parseMarkupICD2(Markup::Node root) -> void {
icd2.revision = max(1, root["revision"].decimal());
GameBoy::cartridge.load_empty(GameBoy::System::Revision::SuperGameBoy);
interface->loadRequest(ID::SuperGameBoy, "Game Boy", "gb");
interface->loadRequest(ID::SuperGameBoy, "Game Boy", "gb", false);
string bootROMName = root["rom/name"].text();
interface->loadRequest(ID::SuperGameBoyBootROM, bootROMName);
interface->loadRequest(ID::SuperGameBoyBootROM, bootROMName, true);
for(auto node : root.find("map")) {
if(node["id"].text() == "io") {
@ -309,10 +309,10 @@ auto Cartridge::parseMarkupARMDSP(Markup::Node root) -> void {
string dataROMName = rom(1)["name"].text();
string dataRAMName = ram(0)["name"].text();
interface->loadRequest(ID::ArmDSPPROM, programROMName);
interface->loadRequest(ID::ArmDSPDROM, dataROMName);
interface->loadRequest(ID::ArmDSPPROM, programROMName, true);
interface->loadRequest(ID::ArmDSPDROM, dataROMName, true);
if(dataRAMName.empty() == false) {
interface->loadRequest(ID::ArmDSPRAM, dataRAMName);
interface->loadRequest(ID::ArmDSPRAM, dataRAMName, false);
memory.append({ID::ArmDSPRAM, dataRAMName});
}
@ -344,9 +344,9 @@ auto Cartridge::parseMarkupHitachiDSP(Markup::Node root, unsigned roms) -> void
string dataROMName = rom(1)["name"].text();
string dataRAMName = ram(1)["name"].text();
interface->loadRequest(ID::HitachiDSPDROM, dataROMName);
interface->loadRequest(ID::HitachiDSPDROM, dataROMName, true);
if(dataRAMName.empty() == false) {
interface->loadRequest(ID::HitachiDSPDRAM, dataRAMName);
interface->loadRequest(ID::HitachiDSPDRAM, dataRAMName, false);
}
for(auto node : root.find("map")) {
@ -394,19 +394,19 @@ auto Cartridge::parseMarkupNECDSP(Markup::Node root) -> void {
string dataRAMName = ram(0)["name"].text();
if(necdsp.revision == NECDSP::Revision::uPD7725) {
interface->loadRequest(ID::Nec7725DSPPROM, programROMName);
interface->loadRequest(ID::Nec7725DSPDROM, dataROMName);
interface->loadRequest(ID::Nec7725DSPPROM, programROMName, true);
interface->loadRequest(ID::Nec7725DSPDROM, dataROMName, true);
if(dataRAMName.empty() == false) {
interface->loadRequest(ID::Nec7725DSPRAM, dataRAMName);
interface->loadRequest(ID::Nec7725DSPRAM, dataRAMName, false);
memory.append({ID::Nec7725DSPRAM, dataRAMName});
}
}
if(necdsp.revision == NECDSP::Revision::uPD96050) {
interface->loadRequest(ID::Nec96050DSPPROM, programROMName);
interface->loadRequest(ID::Nec96050DSPDROM, dataROMName);
interface->loadRequest(ID::Nec96050DSPPROM, programROMName, true);
interface->loadRequest(ID::Nec96050DSPDROM, dataROMName, true);
if(dataRAMName.empty() == false) {
interface->loadRequest(ID::Nec96050DSPRAM, dataRAMName);
interface->loadRequest(ID::Nec96050DSPRAM, dataRAMName, false);
memory.append({ID::Nec96050DSPRAM, dataRAMName});
}
}
@ -431,7 +431,7 @@ auto Cartridge::parseMarkupEpsonRTC(Markup::Node root) -> void {
hasEpsonRTC = true;
string name = root["ram/name"].text();
interface->loadRequest(ID::EpsonRTC, name);
interface->loadRequest(ID::EpsonRTC, name, false);
memory.append({ID::EpsonRTC, name});
for(auto node : root.find("map")) {
@ -447,7 +447,7 @@ auto Cartridge::parseMarkupSharpRTC(Markup::Node root) -> void {
hasSharpRTC = true;
string name = root["ram/name"].text();
interface->loadRequest(ID::SharpRTC, name);
interface->loadRequest(ID::SharpRTC, name, false);
memory.append({ID::SharpRTC, name});
for(auto node : root.find("map")) {

View File

@ -5,19 +5,19 @@ namespace SuperFamicom {
Cheat cheat;
void Cheat::reset() {
auto Cheat::reset() -> void {
codes.reset();
}
void Cheat::append(unsigned addr, unsigned data) {
auto Cheat::append(unsigned addr, unsigned data) -> void {
codes.append({addr, Unused, data});
}
void Cheat::append(unsigned addr, unsigned comp, unsigned data) {
auto Cheat::append(unsigned addr, unsigned comp, unsigned data) -> void {
codes.append({addr, comp, data});
}
maybe<unsigned> Cheat::find(unsigned addr, unsigned comp) {
auto Cheat::find(unsigned addr, unsigned comp) -> maybe<unsigned> {
//WRAM mirroring: $00-3f,80-bf:0000-1fff -> $7e:0000-1fff
if((addr & 0x40e000) == 0x000000) addr = 0x7e0000 | (addr & 0x1fff);
@ -26,6 +26,7 @@ maybe<unsigned> Cheat::find(unsigned addr, unsigned comp) {
return code.data;
}
}
return nothing;
}

View File

@ -1,17 +1,19 @@
struct Cheat {
enum : unsigned { Unused = ~0u };
alwaysinline auto enable() const -> bool { return codes.size() > 0; }
auto reset() -> void;
auto append(unsigned addr, unsigned data) -> void;
auto append(unsigned addr, unsigned comp, unsigned data) -> void;
auto find(unsigned addr, unsigned comp) -> maybe<unsigned>;
struct Code {
unsigned addr;
unsigned comp;
unsigned data;
};
vector<Code> codes;
enum : unsigned { Unused = ~0u };
alwaysinline bool enable() const { return codes.size() > 0; }
void reset();
void append(unsigned addr, unsigned data);
void append(unsigned addr, unsigned comp, unsigned data);
maybe<unsigned> find(unsigned addr, unsigned comp);
};
extern Cheat cheat;

View File

@ -10,21 +10,25 @@ namespace SuperFamicom {
#include "justifier/justifier.cpp"
#include "usart/usart.cpp"
void Controller::Enter() {
Controller::Controller(bool port) : port(port) {
if(!thread) create(Controller::Enter, 1);
}
auto Controller::Enter() -> void {
if(co_active() == input.port1->thread) input.port1->enter();
if(co_active() == input.port2->thread) input.port2->enter();
}
void Controller::enter() {
auto Controller::enter() -> void {
while(true) step(1);
}
void Controller::step(unsigned clocks) {
auto Controller::step(unsigned clocks) -> void {
clock += clocks * (uint64)cpu.frequency;
synchronize_cpu();
}
void Controller::synchronize_cpu() {
auto Controller::synchronize_cpu() -> void {
if(CPU::Threaded == true) {
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
} else {
@ -32,22 +36,18 @@ void Controller::synchronize_cpu() {
}
}
bool Controller::iobit() {
auto Controller::iobit() -> bool {
switch(port) {
case Controller::Port1: return cpu.pio() & 0x40;
case Controller::Port2: return cpu.pio() & 0x80;
}
}
void Controller::iobit(bool data) {
auto Controller::iobit(bool data) -> void {
switch(port) {
case Controller::Port1: bus.write(0x4201, (cpu.pio() & ~0x40) | (data << 6)); break;
case Controller::Port2: bus.write(0x4201, (cpu.pio() & ~0x80) | (data << 7)); break;
}
}
Controller::Controller(bool port) : port(port) {
if(!thread) create(Controller::Enter, 1);
}
}

View File

@ -13,18 +13,21 @@
struct Controller : Thread {
enum : bool { Port1 = 0, Port2 = 1 };
const bool port;
static void Enter();
virtual void enter();
void step(unsigned clocks);
void synchronize_cpu();
bool iobit();
void iobit(bool data);
virtual uint2 data() { return 0; }
virtual void latch(bool data) {}
Controller(bool port);
static auto Enter() -> void;
virtual auto enter() -> void;
auto step(unsigned clocks) -> void;
auto synchronize_cpu() -> void;
auto iobit() -> bool;
auto iobit(bool data) -> void;
virtual auto data() -> uint2 { return 0; }
virtual auto latch(bool data) -> void {}
const bool port;
};
#include "gamepad/gamepad.hpp"

View File

@ -4,31 +4,150 @@ namespace SuperFamicom {
Interface* interface = nullptr;
string Interface::title() {
Interface::Interface() {
interface = this;
system.init();
information.name = "Super Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::SuperFamicom, "Super Famicom", "sfc", true });
media.append({ID::SuperFamicom, "Game Boy", "gb", false});
media.append({ID::SuperFamicom, "BS-X Satellaview", "bs", false});
media.append({ID::SuperFamicom, "Sufami Turbo", "st", false});
{ Device device{0, ID::Port1 | ID::Port2, "Controller"};
device.input.append({ 0, 0, "B" });
device.input.append({ 1, 0, "Y" });
device.input.append({ 2, 0, "Select"});
device.input.append({ 3, 0, "Start" });
device.input.append({ 4, 0, "Up" });
device.input.append({ 5, 0, "Down" });
device.input.append({ 6, 0, "Left" });
device.input.append({ 7, 0, "Right" });
device.input.append({ 8, 0, "A" });
device.input.append({ 9, 0, "X" });
device.input.append({10, 0, "L" });
device.input.append({11, 0, "R" });
device.order = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
this->device.append(device);
}
{ Device device{1, ID::Port1 | ID::Port2, "Multitap"};
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}});
device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }});
device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }});
device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }});
device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }});
device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }});
device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }});
device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }});
device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }});
device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }});
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
}
this->device.append(device);
}
{ Device device{2, ID::Port1 | ID::Port2, "Mouse"};
device.input.append({0, 1, "X-axis"});
device.input.append({1, 1, "Y-axis"});
device.input.append({2, 0, "Left" });
device.input.append({3, 0, "Right" });
device.order = {0, 1, 2, 3};
this->device.append(device);
}
{ Device device{3, ID::Port2, "Super Scope"};
device.input.append({0, 1, "X-axis" });
device.input.append({1, 1, "Y-axis" });
device.input.append({2, 0, "Trigger"});
device.input.append({3, 0, "Cursor" });
device.input.append({4, 0, "Turbo" });
device.input.append({5, 0, "Pause" });
device.order = {0, 1, 2, 3, 4, 5};
this->device.append(device);
}
{ Device device{4, ID::Port2, "Justifier"};
device.input.append({0, 1, "X-axis" });
device.input.append({1, 1, "Y-axis" });
device.input.append({2, 0, "Trigger"});
device.input.append({3, 0, "Start" });
device.order = {0, 1, 2, 3};
this->device.append(device);
}
{ Device device{5, ID::Port2, "Justifiers"};
device.input.append({0, 1, "Port 1 - X-axis" });
device.input.append({1, 1, "Port 1 - Y-axis" });
device.input.append({2, 0, "Port 1 - Trigger"});
device.input.append({3, 0, "Port 1 - Start" });
device.order.append(0, 1, 2, 3);
device.input.append({4, 1, "Port 2 - X-axis" });
device.input.append({5, 1, "Port 2 - Y-axis" });
device.input.append({6, 0, "Port 2 - Trigger"});
device.input.append({7, 0, "Port 2 - Start" });
device.order.append(4, 5, 6, 7);
this->device.append(device);
}
{ Device device{6, ID::Port1, "Serial USART"};
this->device.append(device);
}
{ Device device{7, ID::Port1 | ID::Port2, "None"};
this->device.append(device);
}
port.append({0, "Port 1"});
port.append({1, "Port 2"});
for(auto& device : this->device) {
for(auto& port : this->port) {
if(device.portmask & (1 << port.id)) {
port.device.append(device);
}
}
}
}
auto Interface::title() -> string {
return cartridge.title();
}
double Interface::videoFrequency() {
auto Interface::videoFrequency() -> double {
switch(system.region()) { default:
case System::Region::NTSC: return system.cpu_frequency() / (262.0 * 1364.0 - 4.0);
case System::Region::PAL: return system.cpu_frequency() / (312.0 * 1364.0);
}
}
double Interface::audioFrequency() {
auto Interface::audioFrequency() -> double {
return system.apu_frequency() / 768.0;
}
bool Interface::loaded() {
auto Interface::loaded() -> bool {
return cartridge.loaded();
}
string Interface::sha256() {
auto Interface::sha256() -> string {
return cartridge.sha256();
}
unsigned Interface::group(unsigned id) {
auto Interface::group(unsigned id) -> unsigned {
switch(id) {
case ID::SystemManifest:
case ID::IPLROM:
return 0;
case ID::Manifest:
@ -94,7 +213,7 @@ unsigned Interface::group(unsigned id) {
throw;
}
void Interface::load(unsigned id) {
auto Interface::load(unsigned id) -> void {
if(id == ID::SuperFamicom) cartridge.load();
if(id == ID::SuperGameBoy) cartridge.loadSuperGameBoy();
if(id == ID::Satellaview) cartridge.loadSatellaview();
@ -102,13 +221,17 @@ void Interface::load(unsigned id) {
if(id == ID::SufamiTurboSlotB) cartridge.loadSufamiTurboB();
}
void Interface::save() {
auto Interface::save() -> void {
for(auto& memory : cartridge.memory) {
saveRequest(memory.id, memory.name);
}
}
void Interface::load(unsigned id, const stream& stream) {
auto Interface::load(unsigned id, const stream& stream) -> void {
if(id == ID::SystemManifest) {
system.information.manifest = stream.text();
}
if(id == ID::IPLROM) {
stream.read(smp.iplrom, min(64u, stream.size()));
}
@ -219,7 +342,7 @@ void Interface::load(unsigned id, const stream& stream) {
if(id == ID::SufamiTurboSlotBRAM) sufamiturboB.ram.read(stream);
}
void Interface::save(unsigned id, const stream& stream) {
auto Interface::save(unsigned id, const stream& stream) -> void {
if(id == ID::RAM) stream.write(cartridge.ram.data(), cartridge.ram.size());
if(id == ID::EventRAM) stream.write(event.ram.data(), event.ram.size());
if(id == ID::SA1IRAM) stream.write(sa1.iram.data(), sa1.iram.size());
@ -267,48 +390,48 @@ void Interface::save(unsigned id, const stream& stream) {
if(id == ID::SufamiTurboSlotBRAM) stream.write(sufamiturboB.ram.data(), sufamiturboB.ram.size());
}
void Interface::unload() {
auto Interface::unload() -> void {
save();
cartridge.unload();
}
void Interface::connect(unsigned port, unsigned device) {
auto Interface::connect(unsigned port, unsigned device) -> void {
input.connect(port, (Input::Device)device);
}
void Interface::power() {
auto Interface::power() -> void {
system.power();
}
void Interface::reset() {
auto Interface::reset() -> void {
system.reset();
}
void Interface::run() {
auto Interface::run() -> void {
system.run();
}
bool Interface::rtc() {
auto Interface::rtc() -> bool {
if(cartridge.hasEpsonRTC()) return true;
if(cartridge.hasSharpRTC()) return true;
return false;
}
void Interface::rtcsync() {
auto Interface::rtcsync() -> void {
if(cartridge.hasEpsonRTC()) epsonrtc.sync();
if(cartridge.hasSharpRTC()) sharprtc.sync();
}
serializer Interface::serialize() {
auto Interface::serialize() -> serializer {
system.runtosave();
return system.serialize();
}
bool Interface::unserialize(serializer& s) {
auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
void Interface::cheatSet(const lstring& list) {
auto Interface::cheatSet(const lstring& list) -> void {
cheat.reset();
//Super Game Boy
@ -336,134 +459,8 @@ void Interface::cheatSet(const lstring& list) {
}
}
void Interface::paletteUpdate(PaletteMode mode) {
auto Interface::paletteUpdate(PaletteMode mode) -> void {
video.generate_palette(mode);
}
Interface::Interface() {
interface = this;
system.init();
information.name = "Super Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::SuperFamicom, "Super Famicom", "sfc", true });
media.append({ID::SuperFamicom, "Game Boy", "gb", false});
media.append({ID::SuperFamicom, "BS-X Satellaview", "bs", false});
media.append({ID::SuperFamicom, "Sufami Turbo", "st", false});
{
Device device{0, ID::Port1 | ID::Port2, "Controller"};
device.input.append({ 0, 0, "B" });
device.input.append({ 1, 0, "Y" });
device.input.append({ 2, 0, "Select"});
device.input.append({ 3, 0, "Start" });
device.input.append({ 4, 0, "Up" });
device.input.append({ 5, 0, "Down" });
device.input.append({ 6, 0, "Left" });
device.input.append({ 7, 0, "Right" });
device.input.append({ 8, 0, "A" });
device.input.append({ 9, 0, "X" });
device.input.append({10, 0, "L" });
device.input.append({11, 0, "R" });
device.order = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
this->device.append(device);
}
{
Device device{1, ID::Port1 | ID::Port2, "Multitap"};
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}});
device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }});
device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }});
device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }});
device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }});
device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }});
device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }});
device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }});
device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }});
device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }});
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
}
this->device.append(device);
}
{
Device device{2, ID::Port1 | ID::Port2, "Mouse"};
device.input.append({0, 1, "X-axis"});
device.input.append({1, 1, "Y-axis"});
device.input.append({2, 0, "Left" });
device.input.append({3, 0, "Right" });
device.order = {0, 1, 2, 3};
this->device.append(device);
}
{
Device device{3, ID::Port2, "Super Scope"};
device.input.append({0, 1, "X-axis" });
device.input.append({1, 1, "Y-axis" });
device.input.append({2, 0, "Trigger"});
device.input.append({3, 0, "Cursor" });
device.input.append({4, 0, "Turbo" });
device.input.append({5, 0, "Pause" });
device.order = {0, 1, 2, 3, 4, 5};
this->device.append(device);
}
{
Device device{4, ID::Port2, "Justifier"};
device.input.append({0, 1, "X-axis" });
device.input.append({1, 1, "Y-axis" });
device.input.append({2, 0, "Trigger"});
device.input.append({3, 0, "Start" });
device.order = {0, 1, 2, 3};
this->device.append(device);
}
{
Device device{5, ID::Port2, "Justifiers"};
device.input.append({0, 1, "Port 1 - X-axis" });
device.input.append({1, 1, "Port 1 - Y-axis" });
device.input.append({2, 0, "Port 1 - Trigger"});
device.input.append({3, 0, "Port 1 - Start" });
device.order.append(0, 1, 2, 3);
device.input.append({4, 1, "Port 2 - X-axis" });
device.input.append({5, 1, "Port 2 - Y-axis" });
device.input.append({6, 0, "Port 2 - Trigger"});
device.input.append({7, 0, "Port 2 - Start" });
device.order.append(4, 5, 6, 7);
this->device.append(device);
}
{
Device device{6, ID::Port1, "Serial USART"};
this->device.append(device);
}
{
Device device{7, ID::Port1 | ID::Port2, "None"};
this->device.append(device);
}
port.append({0, "Port 1"});
port.append({1, "Port 2"});
for(auto& device : this->device) {
for(auto& port : this->port) {
if(device.portmask & (1 << port.id)) {
port.device.append(device);
}
}
}
}
}

View File

@ -13,6 +13,7 @@ struct ID {
SufamiTurboSlotB,
//memory (files)
SystemManifest,
IPLROM,
Manifest,
@ -89,36 +90,36 @@ struct ID {
};
struct Interface : Emulator::Interface {
string title();
double videoFrequency();
double audioFrequency();
bool loaded();
string sha256();
unsigned group(unsigned id);
void load(unsigned id);
void save();
void load(unsigned id, const stream& stream);
void save(unsigned id, const stream& stream);
void unload();
void connect(unsigned port, unsigned device);
void power();
void reset();
void run();
bool rtc();
void rtcsync();
serializer serialize();
bool unserialize(serializer&);
void cheatSet(const lstring&);
void paletteUpdate(PaletteMode mode);
Interface();
auto title() -> string;
auto videoFrequency() -> double;
auto audioFrequency() -> double;
auto loaded() -> bool;
auto sha256() -> string;
auto group(unsigned id) -> unsigned;
auto load(unsigned id) -> void;
auto save() -> void;
auto load(unsigned id, const stream& stream) -> void;
auto save(unsigned id, const stream& stream) -> void;
auto unload() -> void;
auto connect(unsigned port, unsigned device) -> void;
auto power() -> void;
auto reset() -> void;
auto run() -> void;
auto rtc() -> bool;
auto rtcsync() -> void;
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto cheatSet(const lstring&) -> void;
auto paletteUpdate(PaletteMode mode) -> void;
vector<Device> device;
};

View File

@ -1,23 +1,23 @@
//Memory
unsigned Memory::size() const { return 0; }
auto Memory::size() const -> unsigned { return 0; }
//StaticRAM
uint8* StaticRAM::data() { return data_; }
unsigned StaticRAM::size() const { return size_; }
uint8 StaticRAM::read(unsigned addr) { return data_[addr]; }
void StaticRAM::write(unsigned addr, uint8 n) { data_[addr] = n; }
uint8& StaticRAM::operator[](unsigned addr) { return data_[addr]; }
const uint8& StaticRAM::operator[](unsigned addr) const { return data_[addr]; }
StaticRAM::StaticRAM(unsigned n) : size_(n) { data_ = new uint8[size_]; }
StaticRAM::~StaticRAM() { delete[] data_; }
auto StaticRAM::data() -> uint8* { return data_; }
auto StaticRAM::size() const -> unsigned { return size_; }
auto StaticRAM::read(unsigned addr) -> uint8 { return data_[addr]; }
auto StaticRAM::write(unsigned addr, uint8 n) -> void { data_[addr] = n; }
auto StaticRAM::operator[](unsigned addr) -> uint8& { return data_[addr]; }
auto StaticRAM::operator[](unsigned addr) const -> const uint8& { return data_[addr]; }
//MappedRAM
void MappedRAM::reset() {
auto MappedRAM::reset() -> void {
if(data_) {
delete[] data_;
data_ = nullptr;
@ -26,13 +26,13 @@ void MappedRAM::reset() {
write_protect_ = false;
}
void MappedRAM::map(uint8* source, unsigned length) {
auto MappedRAM::map(uint8* source, unsigned length) -> void {
reset();
data_ = source;
size_ = data_ ? length : 0;
}
void MappedRAM::copy(const stream& memory) {
auto MappedRAM::copy(const stream& memory) -> void {
if(data_) delete[] data_;
//round size up to multiple of 256-bytes
size_ = (memory.size() & ~255) + ((bool)(memory.size() & 255) << 8);
@ -40,22 +40,21 @@ void MappedRAM::copy(const stream& memory) {
memory.read(data_, memory.size());
}
void MappedRAM::read(const stream& memory) {
auto MappedRAM::read(const stream& memory) -> void {
memory.read(data_, min(memory.size(), size_));
}
void MappedRAM::write_protect(bool status) { write_protect_ = status; }
uint8* MappedRAM::data() { return data_; }
unsigned MappedRAM::size() const { return size_; }
auto MappedRAM::write_protect(bool status) -> void { write_protect_ = status; }
auto MappedRAM::data() -> uint8* { return data_; }
auto MappedRAM::size() const -> unsigned { return size_; }
uint8 MappedRAM::read(unsigned addr) { return data_[addr]; }
void MappedRAM::write(unsigned addr, uint8 n) { if(!write_protect_) data_[addr] = n; }
const uint8& MappedRAM::operator[](unsigned addr) const { return data_[addr]; }
MappedRAM::MappedRAM() : data_(nullptr), size_(0), write_protect_(false) {}
auto MappedRAM::read(unsigned addr) -> uint8 { return data_[addr]; }
auto MappedRAM::write(unsigned addr, uint8 n) -> void { if(!write_protect_) data_[addr] = n; }
auto MappedRAM::operator[](unsigned addr) const -> const uint8& { return data_[addr]; }
//Bus
unsigned Bus::mirror(unsigned addr, unsigned size) {
auto Bus::mirror(unsigned addr, unsigned size) -> unsigned {
if(size == 0) return 0;
unsigned base = 0;
unsigned mask = 1 << 23;
@ -71,7 +70,7 @@ unsigned Bus::mirror(unsigned addr, unsigned size) {
return base + addr;
}
unsigned Bus::reduce(unsigned addr, unsigned mask) {
auto Bus::reduce(unsigned addr, unsigned mask) -> unsigned {
while(mask) {
unsigned bits = (mask & -mask) - 1;
addr = ((addr >> 1) & ~bits) | (addr & bits);
@ -80,7 +79,7 @@ unsigned Bus::reduce(unsigned addr, unsigned mask) {
return addr;
}
uint8 Bus::read(unsigned addr) {
auto Bus::read(unsigned addr) -> uint8 {
uint8 data = reader[lookup[addr]](target[addr]);
if(cheat.enable()) {
@ -90,6 +89,6 @@ uint8 Bus::read(unsigned addr) {
return data;
}
void Bus::write(unsigned addr, uint8 data) {
auto Bus::write(unsigned addr, uint8 data) -> void {
return writer[lookup[addr]](target[addr], data);
}

View File

@ -5,32 +5,17 @@ namespace SuperFamicom {
Bus bus;
void Bus::map(
const function<uint8 (unsigned)>& reader,
const function<void (unsigned, uint8)>& writer,
unsigned banklo, unsigned bankhi,
unsigned addrlo, unsigned addrhi,
unsigned size, unsigned base, unsigned mask
) {
assert(banklo <= bankhi && banklo <= 0xff);
assert(addrlo <= addrhi && addrlo <= 0xffff);
assert(idcount < 255);
unsigned id = idcount++;
this->reader[id] = reader;
this->writer[id] = writer;
for(unsigned bank = banklo; bank <= bankhi; bank++) {
for(unsigned addr = addrlo; addr <= addrhi; addr++) {
unsigned offset = reduce(bank << 16 | addr, mask);
if(size) offset = base + mirror(offset, size - base);
lookup[bank << 16 | addr] = id;
target[bank << 16 | addr] = offset;
}
}
Bus::Bus() {
lookup = new uint8 [16 * 1024 * 1024];
target = new uint32[16 * 1024 * 1024];
}
void Bus::map_reset() {
Bus::~Bus() {
delete[] lookup;
delete[] target;
}
auto Bus::reset() -> void {
function<uint8 (unsigned)> reader = [](unsigned) { return cpu.regs.mdr; };
function<void (unsigned, uint8)> writer = [](unsigned, uint8) {};
@ -38,7 +23,7 @@ void Bus::map_reset() {
map(reader, writer, 0x00, 0xff, 0x0000, 0xffff);
}
void Bus::map_xml() {
auto Bus::map() -> void {
for(auto& m : cartridge.mapping) {
lstring part = m.addr.split(":", 1L);
lstring banks = part(0).split(",");
@ -57,14 +42,29 @@ void Bus::map_xml() {
}
}
Bus::Bus() {
lookup = new uint8 [16 * 1024 * 1024];
target = new uint32[16 * 1024 * 1024];
}
auto Bus::map(
const function<uint8 (unsigned)>& reader,
const function<void (unsigned, uint8)>& writer,
unsigned banklo, unsigned bankhi,
unsigned addrlo, unsigned addrhi,
unsigned size, unsigned base, unsigned mask
) -> void {
assert(banklo <= bankhi && banklo <= 0xff);
assert(addrlo <= addrhi && addrlo <= 0xffff);
assert(idcount < 255);
Bus::~Bus() {
delete[] lookup;
delete[] target;
unsigned id = idcount++;
this->reader[id] = reader;
this->writer[id] = writer;
for(unsigned bank = banklo; bank <= bankhi; bank++) {
for(unsigned addr = addrlo; addr <= addrhi; addr++) {
unsigned offset = reduce(bank << 16 | addr, mask);
if(size) offset = base + mirror(offset, size - base);
lookup[bank << 16 | addr] = id;
target[bank << 16 | addr] = offset;
}
}
}
}

View File

@ -1,74 +1,72 @@
struct Memory {
virtual inline unsigned size() const;
virtual uint8 read(unsigned addr) = 0;
virtual void write(unsigned addr, uint8 data) = 0;
virtual inline auto size() const -> unsigned;
virtual auto read(unsigned addr) -> uint8 = 0;
virtual auto write(unsigned addr, uint8 data) -> void = 0;
};
struct StaticRAM : Memory {
inline uint8* data();
inline unsigned size() const;
inline uint8 read(unsigned addr);
inline void write(unsigned addr, uint8 n);
inline uint8& operator[](unsigned addr);
inline const uint8& operator[](unsigned addr) const;
inline StaticRAM(unsigned size);
inline ~StaticRAM();
inline auto data() -> uint8*;
inline auto size() const -> unsigned;
inline auto read(unsigned addr) -> uint8;
inline auto write(unsigned addr, uint8 n) -> void;
inline auto operator[](unsigned addr) -> uint8&;
inline auto operator[](unsigned addr) const -> const uint8&;
private:
uint8* data_;
unsigned size_;
uint8* data_ = nullptr;
unsigned size_ = 0;
};
struct MappedRAM : Memory {
inline void reset();
inline void map(uint8*, unsigned);
inline void copy(const stream& memory);
inline void read(const stream& memory);
inline auto reset() -> void;
inline auto map(uint8*, unsigned) -> void;
inline auto copy(const stream& memory) -> void;
inline auto read(const stream& memory) -> void;
inline void write_protect(bool status);
inline uint8* data();
inline unsigned size() const;
inline auto write_protect(bool status) -> void;
inline auto data() -> uint8*;
inline auto size() const -> unsigned;
inline uint8 read(unsigned addr);
inline void write(unsigned addr, uint8 n);
inline const uint8& operator[](unsigned addr) const;
inline MappedRAM();
inline auto read(unsigned addr) -> uint8;
inline auto write(unsigned addr, uint8 n) -> void;
inline auto operator[](unsigned addr) const -> const uint8&;
private:
uint8* data_;
unsigned size_;
bool write_protect_;
uint8* data_ = nullptr;
unsigned size_ = 0;
bool write_protect_ = false;
};
struct Bus {
alwaysinline static unsigned mirror(unsigned addr, unsigned size);
alwaysinline static unsigned reduce(unsigned addr, unsigned mask);
alwaysinline static auto mirror(unsigned addr, unsigned size) -> unsigned;
alwaysinline static auto reduce(unsigned addr, unsigned mask) -> unsigned;
alwaysinline uint8 read(unsigned addr);
alwaysinline void write(unsigned addr, uint8 data);
Bus();
~Bus();
uint8* lookup;
uint32* target;
alwaysinline auto read(unsigned addr) -> uint8;
alwaysinline auto write(unsigned addr, uint8 data) -> void;
unsigned idcount;
function<uint8 (unsigned)> reader[256];
function<void (unsigned, uint8)> writer[256];
void map(
auto reset() -> void;
auto map() -> void;
auto map(
const function<uint8 (unsigned)>& reader,
const function<void (unsigned, uint8)>& writer,
unsigned banklo, unsigned bankhi,
unsigned addrlo, unsigned addrhi,
unsigned size = 0, unsigned base = 0, unsigned mask = 0
);
) -> void;
void map_reset();
void map_xml();
uint8* lookup = nullptr;
uint32* target = nullptr;
Bus();
~Bus();
unsigned idcount = 0;
function<uint8 (unsigned)> reader[256];
function<void (unsigned, uint8)> writer[256];
};
extern Bus bus;

View File

@ -169,14 +169,13 @@ void PPU::Background::run(bool screen) {
if(hires == false) return;
}
if(regs.mode == Mode::Inactive) return;
if(regs.mode == Mode::Mode7) return run_mode7();
if(tile_counter-- == 0) {
tile_counter = 7;
get_tile();
}
if(regs.mode == Mode::Mode7) return run_mode7();
uint8 palette = get_tile_color();
if(x == 0) mosaic.hcounter = 1;
if(x >= 0 && --mosaic.hcounter == 0) {

View File

@ -2,31 +2,25 @@
Scheduler scheduler;
void Scheduler::enter() {
host_thread = co_active();
co_switch(thread);
}
void Scheduler::exit(ExitReason reason) {
exit_reason = reason;
thread = co_active();
co_switch(host_thread);
}
void Scheduler::debug() {
exit(ExitReason::DebuggerEvent);
}
void Scheduler::init() {
auto Scheduler::init() -> void {
host_thread = co_active();
thread = cpu.thread;
sync = SynchronizeMode::None;
}
Scheduler::Scheduler() {
host_thread = nullptr;
thread = nullptr;
exit_reason = ExitReason::UnknownEvent;
auto Scheduler::enter() -> void {
host_thread = co_active();
co_switch(thread);
}
auto Scheduler::exit(ExitReason reason) -> void {
exit_reason = reason;
thread = co_active();
co_switch(host_thread);
}
auto Scheduler::debug() -> void {
exit(ExitReason::DebuggerEvent);
}
#endif

View File

@ -1,17 +1,15 @@
struct Scheduler : property<Scheduler> {
struct Scheduler {
enum class SynchronizeMode : unsigned { None, CPU, All } sync;
enum class ExitReason : unsigned { UnknownEvent, FrameEvent, SynchronizeEvent, DebuggerEvent };
readonly<ExitReason> exit_reason;
cothread_t host_thread; //program thread (used to exit emulation)
cothread_t thread; //active emulation thread (used to enter emulation)
auto init() -> void;
auto enter() -> void;
auto exit(ExitReason) -> void;
auto debug() -> void;
void enter();
void exit(ExitReason);
void debug();
void init();
Scheduler();
cothread_t host_thread = nullptr; //program thread (used to exit emulation)
cothread_t thread = nullptr; //active emulation thread (used to enter emulation)
ExitReason exit_reason = ExitReason::UnknownEvent;
};
extern Scheduler scheduler;

View File

@ -5,23 +5,23 @@ namespace SuperFamicom {
SatellaviewCartridge satellaviewcartridge;
void SatellaviewCartridge::init() {
auto SatellaviewCartridge::init() -> void {
}
void SatellaviewCartridge::load() {
auto SatellaviewCartridge::load() -> void {
if(memory.size() == 0) {
memory.map(allocate<uint8>(1024 * 1024, 0xff), 1024 * 1024);
}
}
void SatellaviewCartridge::unload() {
auto SatellaviewCartridge::unload() -> void {
memory.reset();
}
void SatellaviewCartridge::power() {
auto SatellaviewCartridge::power() -> void {
}
void SatellaviewCartridge::reset() {
auto SatellaviewCartridge::reset() -> void {
regs.command = 0;
regs.write_old = 0x00;
regs.write_new = 0x00;
@ -32,11 +32,11 @@ void SatellaviewCartridge::reset() {
memory.write_protect(!regs.write_enable);
}
unsigned SatellaviewCartridge::size() const {
auto SatellaviewCartridge::size() const -> unsigned {
return memory.size();
}
uint8 SatellaviewCartridge::read(unsigned addr) {
auto SatellaviewCartridge::read(unsigned addr) -> uint8 {
if(readonly) return memory.read(bus.mirror(addr, memory.size()));
if(addr == 0x0002) {
@ -65,7 +65,7 @@ uint8 SatellaviewCartridge::read(unsigned addr) {
return memory.read(addr);
}
void SatellaviewCartridge::write(unsigned addr, uint8 data) {
auto SatellaviewCartridge::write(unsigned addr, uint8 data) -> void {
if(readonly) return;
if((addr & 0xff0000) == 0) {

View File

@ -1,17 +1,17 @@
struct SatellaviewCartridge : Memory {
auto init() -> void;
auto load() -> void;
auto unload() -> void;
auto power() -> void;
auto reset() -> void;
auto size() const -> unsigned;
auto read(unsigned addr) -> uint8;
auto write(unsigned addr, uint8 data) -> void;
MappedRAM memory;
bool readonly;
void init();
void load();
void unload();
void power();
void reset();
unsigned size() const;
uint8 read(unsigned addr);
void write(unsigned addr, uint8 data);
private:
struct {
unsigned command;

View File

@ -1,6 +1,6 @@
#ifdef SUFAMITURBO_CPP
void SufamiTurboCartridge::serialize(serializer& s) {
auto SufamiTurboCartridge::serialize(serializer& s) -> void {
s.array(ram.data(), ram.size());
}

View File

@ -7,10 +7,10 @@ namespace SuperFamicom {
SufamiTurboCartridge sufamiturboA;
SufamiTurboCartridge sufamiturboB;
void SufamiTurboCartridge::load() {
auto SufamiTurboCartridge::load() -> void {
}
void SufamiTurboCartridge::unload() {
auto SufamiTurboCartridge::unload() -> void {
rom.reset();
ram.reset();
}

View File

@ -1,10 +1,10 @@
struct SufamiTurboCartridge {
auto load() -> void;
auto unload() -> void;
auto serialize(serializer&) -> void;
MappedRAM rom;
MappedRAM ram;
void load();
void unload();
void serialize(serializer&);
};
extern SufamiTurboCartridge sufamiturboA;

View File

@ -18,7 +18,7 @@ void System::run() {
scheduler.sync = Scheduler::SynchronizeMode::None;
scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
video.update();
}
}
@ -54,8 +54,8 @@ void System::runtosave() {
void System::runthreadtosave() {
while(true) {
scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
video.update();
}
}
@ -93,13 +93,12 @@ void System::term() {
}
void System::load() {
string manifest = string::read({interface->path(ID::System), "manifest.bml"});
auto document = BML::unserialize(manifest);
//string manifest = string::read({interface->path(ID::System), "manifest.bml"});
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
auto document = BML::unserialize(information.manifest);
auto iplrom = document["system/smp/rom/name"].text();
interface->loadRequest(ID::IPLROM, iplrom);
if(!file::exists({interface->path(ID::System), iplrom})) {
interface->notify("Error: required Super Famicom firmware ipl.rom not found.\n");
if(auto iplrom = document["system/smp/rom/name"].text()) {
interface->loadRequest(ID::IPLROM, iplrom, true);
}
region = configuration.region;
@ -113,8 +112,8 @@ void System::load() {
audio.coprocessor_enable(false);
bus.map_reset();
bus.map_xml();
bus.reset();
bus.map();
cpu.enable();
ppu.enable();

View File

@ -29,6 +29,10 @@ struct System : property<System> {
System();
struct Information {
string manifest;
} information;
private:
void runthreadtosave();

View File

@ -1,5 +1,5 @@
//request from emulation core to load non-volatile media folder
auto Program::loadRequest(unsigned id, string name, string type) -> void {
auto Program::loadRequest(unsigned id, string name, string type, bool required) -> void {
string location = BrowserDialog()
.setTitle({"Load ", name})
.setPath({config->library.location, name})
@ -13,16 +13,38 @@ auto Program::loadRequest(unsigned id, string name, string type) -> void {
}
//request from emulation core to load non-volatile media file
auto Program::loadRequest(unsigned id, string path) -> void {
string location = {mediaPaths(emulator->group(id)), path};
if(!file::exists(location)) return;
mmapstream stream{location};
return emulator->load(id, stream);
auto Program::loadRequest(unsigned id, string filename, bool required) -> void {
string pathname = mediaPaths(emulator->group(id));
string location = {pathname, filename};
if(file::exists(location)) {
mmapstream stream{location};
return emulator->load(id, stream);
}
if(filename == "manifest.bml") {
string manifest;
if(auto fp = popen(string{"icarus -m \"", pathname, "\""}, "r")) {
while(true) {
auto byte = fgetc(fp);
if(byte == EOF) break;
manifest.append((char)byte);
}
pclose(fp);
}
if(manifest) {
memorystream stream{manifest.binary(), manifest.size()};
return emulator->load(id, stream);
}
}
if(required) MessageDialog().setTitle("higan").setText({
"Missing required file: ", location.filename(), "\n\n",
"From location:\n", location.pathname()
}).error();
}
//request from emulation core to save non-volatile media file
auto Program::saveRequest(unsigned id, string path) -> void {
string location = {mediaPaths(emulator->group(id)), path};
auto Program::saveRequest(unsigned id, string filename) -> void {
string pathname = mediaPaths(emulator->group(id));
string location = {pathname, filename};
filestream stream{location, file::mode::write};
return emulator->save(id, stream);
}

View File

@ -5,8 +5,8 @@ struct Program : Emulator::Interface::Bind {
auto quit() -> void;
//interface.cpp
auto loadRequest(unsigned id, string name, string type) -> void override;
auto loadRequest(unsigned id, string path) -> void override;
auto loadRequest(unsigned id, string name, string type, bool required) -> void override;
auto loadRequest(unsigned id, string path, bool required) -> void override;
auto saveRequest(unsigned id, string path) -> void override;
auto videoColor(unsigned source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 override;
auto videoRefresh(const uint32* palette, const uint32* data, unsigned pitch, unsigned width, unsigned height) -> void override;