Update to v106r15 release.

byuu says:

Changelog:

  - Super Game Boy: fixed loading of boot ROM
  - hiro: added ComboEdit::setEditable(bool = true);
  - tomoko: added new systems settings panel

Note!!: this release will not compile on Windows or macOS due to the
missing ComboEdit control! I'll try to merge in hex's implementation
for the Windows release here soon. macOS users will probably be out of
luck for a while, sorry.

The new systems panel is an idea I've been meaning to implement for
quite a while, but finally got around to starting on it. It's still
fairly unpolished, but the basic idea is there for Linux/BSD users to
try out now.

So imagine the Super Game Boy, BS-X Satellaview, Sufami Turbo, and the
associated BS Memory Pack-slotted SNES cartridges. To play any of those,
you needed to choose Nintendo→Super Famicom, and then select the
relevant cartridge, and then select any slotted cartridges to play with
it.

This was acceptable-ish, if not ideal. But now imagine in the future if
we wanted to support the Famicom Disk System, which is technically a
cartridge that plugs into the Famicom deck. Or the PC Engine CD, which
has one of three special HuCards that must be inserted (ignoring the
Turbo Duo where it's built-in—I'm going to be emulating the Super CD
as if you're using a stock PCE CD.) Or the Mega CD, where there are
probably a half dozen or more BIOS + hardware revisions that are
region-specific, which connect to an expansion port that is identical to
the cartridge port save for the Mega Drive seeing an I/O register bit
toggled here.

In all of these cases, it's going to be a real pain to have to choose
the 'BIOS' every time you want to play a game for them.

I can't distribute these BIOSes with higan due to copyright
restrictions, and trying to ship dummy folders for every possible
combination would become quite odious, and difficult for people to use
(compare to setting up the Game Boy Advance system BIOS.)

And so I've created the new systems settings panel. Here, you can manage
a list of systems that show up under the higan library menu (now renamed
to “System”), where each entry contains name, boot, and hidden
parameters.

The name parameter is what shows up in the system menu. You can call any
system higan emulates whatever you like here. Don't like “Super
Famicom”? Change it to “SNES”, then.

The boot parameter is a combo edit with a dropdown for all of the
systems higan emulates. If you choose one of these, then the higan
system menu option will work exactly like in previous releases, and
prompt you for a cartridge. But if you choose the browse button next to
the combo edit control, you'll get to pick any gamepak from the higan
library of your choosing.

So you could choose the SGB2 BIOS, and name the menu option “Super Game
Boy 2”, and when you choose the menu option, it will load the SFC core,
load the SGB2 BIOS, and only prompt you for the Game Boy game you wish
to play on it. The same deal goes for the FDS, PCE-CD, Mega CD, Mega
Drive Sonic & Knuckles lock-on cartridge, BS-X Satellaview, SD Gundam
G-Next, etc. Whatever you want to be in the menu, you can put in there
by pointing higan at the appropriate 'BIOS' gamepak to load.

Astute readers have probably already noticed, but you can technically
use this on non-slotted games as well, thus creating instant boot
options for your absolute favorite games, if you so wanted. Point it at
Zelda 3, and you can boot it instantly from the main menu, without any
need for file selection.

The hidden option is a way to hide the system entries from the system
menu. Primarily this would be a fast way for users to disable emulation
cores they never use in higan, without having to remove the options.

The major concession with this change is the collapsing of the
per-manufacturer submenus. What this means is you will now have all
twelve higan emulated systems in the main menu by default. This makes
the list rather long, but ... oh well. I may try to offer some form of
grouping in the future, but the grouping defeats the “list order =
display order” design, and I'm not willing to auto-sort the list. I want
people to be able to control the ordering of the system menu, and have
added (as yet non-functional) sorting arrows for that purpose. I also
don't have a combined tree+table view widget in higan to try to and
group things. But ... we'll see how things go in the future.

Another idea is to add a specialty load option that opens up the user's
Emulation library path, and lets you pick a gamepak for any system,
which would boot the same way as when you drop a gamepak onto the higan
executable or main window. So say you almost never play Wonderswan
games, this would be a way to play them without them cluttering your
system menu list.

The “import ROM files” option has been removed. All it does is launch
icarus directly. I would rather users become familiar with using icarus.
The “load ROM file” option remains.

Anyway, this is all still a work in progress, so please give it time and
don't overload me with too many suggested changes right now, thanks :3
This commit is contained in:
Tim Allen 2018-04-16 18:58:13 +10:00
parent 8f61c267c5
commit 0ea17abfea
17 changed files with 258 additions and 48 deletions

View File

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

View File

@ -57,8 +57,8 @@ auto System::load(Emulator::Interface* interface, Model model_, maybe<uint> syst
} else return false;
auto document = BML::unserialize(information.manifest);
if(auto name = document["game/memory[1]/name"].text()) {
if(auto fp = platform->open(systemID(), name, File::Read, File::Required)) {
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]}) {
if(auto fp = platform->open(systemID(), memory.name(), File::Read, File::Required)) {
fp->read(bootROM.sgb, 256);
}
}

View File

@ -59,6 +59,8 @@ Settings::Settings() {
set("Emulation/AutoSaveMemory/Enable", true);
set("Emulation/AutoSaveMemory/Interval", 30);
set("Systems", "");
set("Crashed", false);
}

View File

@ -6,42 +6,9 @@ unique_pointer<Presentation> presentation;
Presentation::Presentation() {
presentation = this;
libraryMenu.setText("Library");
string_vector manufacturers;
for(auto& emulator : program->emulators) {
if(!manufacturers.find(emulator->information.manufacturer)) {
manufacturers.append(emulator->information.manufacturer);
}
}
for(auto& manufacturer : manufacturers) {
Menu manufacturerMenu{&libraryMenu};
manufacturerMenu.setText(manufacturer);
for(auto& emulator : program->emulators) {
if(emulator->information.manufacturer != manufacturer) continue;
for(auto& medium : emulator->media) {
auto item = new MenuItem{&manufacturerMenu};
item->setText({medium.name, " ..."}).onActivate([=] {
program->loadMedium(*emulator, medium);
});
}
}
}
//add icarus menu options -- but only if icarus binary is present
if(execute("icarus", "--name").output.strip() == "icarus") {
libraryMenu.append(MenuSeparator());
libraryMenu.append(MenuItem().setText("Load ROM File ...").onActivate([&] {
audio->clear();
if(auto location = execute("icarus", "--import")) {
program->mediumQueue.append(location.output.strip());
program->loadMedium();
}
}));
libraryMenu.append(MenuItem().setText("Import ROM Files ...").onActivate([&] {
invoke("icarus");
}));
}
libraryMenu.setText("System");
systemMenu.setText("System").setVisible(false);
systemMenu.setVisible(false);
resetSystem.setText("Soft Reset").onActivate([&] { program->softReset(); });
powerSystem.setText("Power Cycle").onActivate([&] { program->powerCycle(); });
unloadSystem.setText("Unload").onActivate([&] { program->unloadMedium(); });
@ -100,8 +67,9 @@ Presentation::Presentation() {
statusBar.setVisible(showStatusBar.checked());
if(visible()) resizeViewport();
});
showVideoSettings.setText("Video ...").onActivate([&] { settingsManager->show(0); });
showAudioSettings.setText("Audio ...").onActivate([&] { settingsManager->show(1); });
showSystemSettings.setText("Systems ...").onActivate([&] { settingsManager->show(0); });
showVideoSettings.setText("Video ...").onActivate([&] { settingsManager->show(1); });
showAudioSettings.setText("Audio ...").onActivate([&] { settingsManager->show(2); });
showInputSettings.setText("Input ...").onActivate([&] {
if(emulator) {
//default input panel to current core's input settings
@ -113,10 +81,10 @@ Presentation::Presentation() {
}
}
}
settingsManager->show(2);
settingsManager->show(3);
});
showHotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsManager->show(3); });
showAdvancedSettings.setText("Advanced ...").onActivate([&] { settingsManager->show(4); });
showHotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsManager->show(4); });
showAdvancedSettings.setText("Advanced ...").onActivate([&] { settingsManager->show(5); });
toolsMenu.setText("Tools").setVisible(false);
saveQuickStateMenu.setText("Save Quick State");
@ -335,6 +303,42 @@ auto Presentation::toggleFullScreen() -> void {
resizeViewport();
}
auto Presentation::loadSystems() -> void {
libraryMenu.reset();
for(auto system : settings.find("Systems/System")) {
if(system["Hidden"].boolean()) continue;
MenuItem item;
string boot = system["Boot"].text();
item.setText({system["Name"].text(), " ..."}).onActivate([=] {
bool booted = false;
for(auto& emulator : program->emulators) {
if(boot == emulator->information.name) {
program->loadMedium(*emulator, emulator->media(0));
booted = true;
break;
}
}
if(!booted && directory::exists(boot)) {
program->mediumQueue.append(boot);
program->loadMedium();
}
});
libraryMenu.append(item);
}
//add icarus menu option -- but only if icarus binary is present
if(execute("icarus", "--name").output.strip() == "icarus") {
libraryMenu.append(MenuSeparator());
libraryMenu.append(MenuItem().setText("Load ROM File ...").onActivate([&] {
audio->clear();
if(auto location = execute("icarus", "--import")) {
program->mediumQueue.append(location.output.strip());
program->loadMedium();
}
}));
}
}
auto Presentation::loadShaders() -> void {
auto pathname = locate("Video Shaders/");

View File

@ -14,6 +14,7 @@ struct Presentation : Window {
auto clearViewport() -> void;
auto resizeViewport(bool resizeWindow = true) -> void;
auto toggleFullScreen() -> void;
auto loadSystems() -> void;
auto loadShaders() -> void;
MenuBar menuBar{this};
@ -45,6 +46,7 @@ struct Presentation : Window {
MenuCheckItem muteAudio{&settingsMenu};
MenuCheckItem showStatusBar{&settingsMenu};
MenuSeparator settingsSeparator{&settingsMenu};
MenuItem showSystemSettings{&settingsMenu};
MenuItem showVideoSettings{&settingsMenu};
MenuItem showAudioSettings{&settingsMenu};
MenuItem showInputSettings{&settingsMenu};

View File

@ -51,6 +51,7 @@ Program::Program(string_vector args) {
settings.save();
new InputManager;
new SystemProperties;
new SettingsManager;
new CheatDatabase;
new ToolsManager;

View File

@ -1,4 +1,9 @@
#include "../tomoko.hpp"
#include "system-properties.cpp"
unique_pointer<SystemProperties> systemProperties;
#include "systems.cpp"
#include "video.cpp"
#include "audio.cpp"
#include "input.cpp"

View File

@ -1,3 +1,39 @@
struct SystemProperties : Window {
SystemProperties();
auto append() -> void;
auto modify(Markup::Node) -> void;
VerticalLayout layout{this};
HorizontalLayout nameLayout{&layout, Size{~0, 0}};
Label nameLabel{&nameLayout, Size{40, 0}};
LineEdit nameEdit{&nameLayout, Size{~0, 0}};
HorizontalLayout bootLayout{&layout, Size{~0, 0}};
Label bootLabel{&bootLayout, Size{40, 0}};
ComboEdit bootEdit{&bootLayout, Size{~0, 0}};
Button bootBrowse{&bootLayout, Size{80, 0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Widget spacer{&controlLayout, Size{40, 0}};
CheckLabel hiddenOption{&controlLayout, Size{~0, 0}};
Button acceptButton{&controlLayout, Size{80, 0}};
Button cancelButton{&controlLayout, Size{80, 0}};
};
struct SystemSettings : TabFrameItem {
SystemSettings(TabFrame*);
auto reload() -> void;
auto acceptProperties() -> void;
VerticalLayout layout{this};
TableView systemList{&layout, Size{~0, ~0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Button upButton{&controlLayout, Size{0, 0}};
Button downButton{&controlLayout, Size{0, 0}};
Widget spacer{&controlLayout, Size{~0, 0}};
Button appendButton{&controlLayout, Size{80, 0}};
Button modifyButton{&controlLayout, Size{80, 0}};
Button removeButton{&controlLayout, Size{80, 0}};
};
struct VideoSettings : TabFrameItem {
VideoSettings(TabFrame*);
@ -150,6 +186,7 @@ struct SettingsManager : Window {
VerticalLayout layout{this};
TabFrame panel{&layout, Size{~0, ~0}};
SystemSettings systems{&panel};
VideoSettings video{&panel};
AudioSettings audio{&panel};
InputSettings input{&panel};
@ -161,4 +198,5 @@ struct SettingsManager : Window {
auto show(uint setting) -> void;
};
extern unique_pointer<SystemProperties> systemProperties;
extern unique_pointer<SettingsManager> settingsManager;

View File

@ -0,0 +1,50 @@
SystemProperties::SystemProperties() {
systemProperties = this;
layout.setMargin(5);
nameLabel.setText("Name:");
bootLabel.setText("Boot:");
bootEdit.setEditable(false);
for(auto& emulator : program->emulators) {
bootEdit.append(ComboEditItem().setText(emulator->information.name));
}
bootBrowse.setText("Browse ...").onActivate([&] {
if(auto location = BrowserDialog().setTitle("Select Boot Game").setPath(settings["Library/Location"].text()).selectFolder()) {
bootEdit.setText(location);
}
});
hiddenOption.setText("Hidden");
acceptButton.onActivate([&] {
setVisible(false);
settingsManager->systems.acceptProperties();
});
cancelButton.setText("Cancel").onActivate([&] {
setVisible(false);
});
setTitle("System Properties");
setSize({480, layout.minimumSize().height()});
setDismissable();
}
auto SystemProperties::append() -> void {
setCentered(*settingsManager);
nameEdit.setText("");
bootEdit.setText("");
hiddenOption.setChecked(false);
acceptButton.setText("Append");
setFocused();
setVisible();
nameEdit.setFocused();
}
auto SystemProperties::modify(Markup::Node system) -> void {
setCentered(*settingsManager);
nameEdit.setText(system["Name"].text());
bootEdit.setText(system["Boot"].text());
hiddenOption.setChecked(system["Hidden"].boolean());
acceptButton.setText("Modify");
setFocused();
setVisible();
nameEdit.setFocused();
}

View File

@ -0,0 +1,85 @@
SystemSettings::SystemSettings(TabFrame* parent) : TabFrameItem(parent) {
setIcon(Icon::Device::Storage);
setText("Systems");
layout.setMargin(5);
systemList.onActivate([&] {
modifyButton.doActivate();
});
systemList.onChange([&] {
auto selected = (bool)systemList.selected();
upButton.setEnabled(selected);
downButton.setEnabled(selected);
modifyButton.setEnabled(selected);
removeButton.setEnabled(selected);
});
upButton.setIcon(Icon::Go::Up);
downButton.setIcon(Icon::Go::Down);
appendButton.setText("Append").onActivate([&] {
systemProperties->append();
});
modifyButton.setText("Modify").onActivate([&] {
if(auto item = systemList.selected()) {
if(auto system = settings.find("Systems/System")[item.offset()]) {
systemProperties->modify(system);
}
}
});
removeButton.setText("Remove").onActivate([&] {
if(auto item = systemList.selected()) {
if(auto system = settings.find("Systems/System")[item.offset()]) {
if(MessageDialog().setParent(*settingsManager).setText({
"Are you sure you want to delete this system?\n\n"
"Name: ", system["Name"].text()
}).question() == "Yes") {
settings["Systems"].remove(system);
reload();
}
}
}
});
reload();
}
auto SystemSettings::reload() -> void {
systemList.reset();
systemList.append(TableViewHeader().setVisible()
.append(TableViewColumn().setText("Name"))
.append(TableViewColumn().setText("Boot").setExpandable())
);
for(auto system : settings.find("Systems/System")) {
systemList.append(TableViewItem()
.append(TableViewCell().setText(system["Name"].text()))
.append(TableViewCell().setText(Location::base(system["Boot"].text()).trimRight("/", 1L)))
.setForegroundColor(system["Hidden"].boolean() ? Color{160, 160, 160} : Color{})
);
}
systemList.resizeColumns().doChange();
presentation->loadSystems();
}
auto SystemSettings::acceptProperties() -> void {
if(systemProperties->acceptButton.text() == "Append") {
Markup::Node system{"System"};
system.append({"Name", systemProperties->nameEdit.text()});
system.append({"Boot", systemProperties->bootEdit.text()});
system.append({"Hidden", systemProperties->hiddenOption.checked()});
settings["Systems"].append(system);
} else if(systemProperties->acceptButton.text() == "Modify") {
if(auto item = systemList.selected()) {
if(auto system = settings.find("Systems/System")[item.offset()]) {
system("Name").setValue(systemProperties->nameEdit.text());
system("Boot").setValue(systemProperties->bootEdit.text());
system("Hidden").setValue(systemProperties->hiddenOption.checked());
}
}
}
reload();
}

View File

@ -1,10 +1,12 @@
#include "../tomoko.hpp"
#include "cheat-database.cpp"
unique_pointer<CheatDatabase> cheatDatabase;
#include "cheat-editor.cpp"
#include "state-manager.cpp"
#include "manifest-viewer.cpp"
#include "game-notes.cpp"
unique_pointer<CheatDatabase> cheatDatabase;
unique_pointer<ToolsManager> toolsManager;
ToolsManager::ToolsManager() {

View File

@ -1144,6 +1144,7 @@ struct mComboEdit : mWidget {
auto backgroundColor() const -> Color;
auto doActivate() const -> void;
auto doChange() const -> void;
auto editable() const -> bool;
auto foregroundColor() const -> Color;
auto item(uint position) const -> ComboEditItem;
auto itemCount() const -> uint;
@ -1153,6 +1154,7 @@ struct mComboEdit : mWidget {
auto remove(sComboEditItem item) -> type&;
auto reset() -> type&;
auto setBackgroundColor(Color color = {}) -> type&;
auto setEditable(bool editable = true) -> type&;
auto setForegroundColor(Color color = {}) -> type&;
auto setParent(mObject* parent = nullptr, int offset = -1) -> type& override;
auto setText(const string& text = "") -> type&;
@ -1161,6 +1163,7 @@ struct mComboEdit : mWidget {
//private:
struct State {
Color backgroundColor;
bool editable = true;
Color foregroundColor;
vector<sComboEditItem> items;
function<void ()> onActivate;

View File

@ -342,6 +342,7 @@ struct ComboEdit : sComboEdit {
auto backgroundColor() const { return self().backgroundColor(); }
auto doActivate() const { return self().doActivate(); }
auto doChange() const { return self().doChange(); }
auto editable() const { return self().editable(); }
auto foregroundColor() const { return self().foregroundColor(); }
auto item(uint position) const { return self().item(position); }
auto itemCount() const { return self().itemCount(); }
@ -351,6 +352,7 @@ struct ComboEdit : sComboEdit {
auto remove(sComboEditItem item) { return self().remove(item), *this; }
auto reset() { return self().reset(), *this; }
auto setBackgroundColor(Color color = {}) { return self().setBackgroundColor(color), *this; }
auto setEditable(bool editable = true) { return self().setEditable(editable), *this; }
auto setForegroundColor(Color color = {}) { return self().setForegroundColor(color), *this; }
auto setText(const string& text = "") { return self().setText(text), *this; }
auto text() const { return self().text(); }

View File

@ -29,6 +29,10 @@ auto mComboEdit::doChange() const -> void {
if(state.onChange) return state.onChange();
}
auto mComboEdit::editable() const -> bool {
return state.editable;
}
auto mComboEdit::foregroundColor() const -> Color {
return state.foregroundColor;
}
@ -81,6 +85,12 @@ auto mComboEdit::setBackgroundColor(Color color) -> type& {
return *this;
}
auto mComboEdit::setEditable(bool editable) -> type& {
state.editable = editable;
signal(setEditable, editable);
return *this;
}
auto mComboEdit::setForegroundColor(Color color) -> type& {
state.foregroundColor = color;
signal(setForegroundColor, color);

View File

@ -32,6 +32,7 @@ auto pComboEdit::construct() -> void {
close(stdErr);
setBackgroundColor(state().backgroundColor);
setEditable(state().editable);
setForegroundColor(state().foregroundColor);
for(auto& item : state().items) append(item);
@ -86,6 +87,10 @@ auto pComboEdit::setBackgroundColor(Color color) -> void {
gtk_widget_modify_base(gtk_bin_get_child(GTK_BIN(gtkComboBox)), GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
}
auto pComboEdit::setEditable(bool editable) -> void {
gtk_editable_set_editable(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(gtkComboBox))), editable);
}
auto pComboEdit::setForegroundColor(Color color) -> void {
GdkColor gdkColor = CreateColor(color);
gtk_widget_modify_text(gtk_bin_get_child(GTK_BIN(gtkComboBox)), GTK_STATE_NORMAL, color ? &gdkColor : nullptr);

View File

@ -10,6 +10,7 @@ struct pComboEdit : pWidget {
auto remove(sComboEditItem item) -> void;
auto reset() -> void;
auto setBackgroundColor(Color color) -> void;
auto setEditable(bool editable) -> void;
auto setForegroundColor(Color color) -> void;
auto setFont(const Font& font) -> void override;
auto setText(const string& text) -> void;

View File

@ -8,8 +8,8 @@ using SharedNode = shared_pointer<ManagedNode>;
struct ManagedNode {
ManagedNode() = default;
explicit ManagedNode(const string& name) : _name(name) {}
explicit ManagedNode(const string& name, const string& value) : _name(name), _value(value) {}
ManagedNode(const string& name) : _name(name) {}
ManagedNode(const string& name, const string& value) : _name(name), _value(value) {}
auto clone() const -> SharedNode {
SharedNode clone{new ManagedNode(_name, _value)};
@ -46,8 +46,8 @@ protected:
struct Node {
Node() : shared(new ManagedNode) {}
Node(const SharedNode& source) : shared(source ? source : new ManagedNode) {}
explicit Node(const string& name) : shared(new ManagedNode(name)) {}
explicit Node(const string& name, const string& value) : shared(new ManagedNode(name, value)) {}
Node(const string& name) : shared(new ManagedNode(name)) {}
Node(const string& name, const string& value) : shared(new ManagedNode(name, value)) {}
auto unique() const -> bool { return shared.unique(); }
auto clone() const -> Node { return shared->clone(); }