ares-openbsd/desktop-ui/emulator/emulator.cpp

250 行
7.9 KiB
C++

#include "../desktop-ui.hpp"
#include "emulators.cpp"
vector<shared_pointer<Emulator>> emulators;
shared_pointer<Emulator> emulator;
auto Emulator::enumeratePorts(string name) -> vector<InputPort>& {
for(auto& emulator : emulators) {
if(emulator->name == name && emulator->ports) return emulator->ports;
}
static vector<InputPort> ports;
if(!ports) {
for(auto id : range(5)) {
InputPort port{string{"Controller Port ", 1 + id}};
port.append(virtualPorts[id].pad);
port.append(virtualPorts[id].mouse);
ports.append(port);
}
}
return ports;
}
auto Emulator::location() -> string {
return {Path::userData(), "ares/Saves/", name, "/"};
}
auto Emulator::locate(const string& location, const string& suffix, const string& path, maybe<string> system) -> string {
if(!system) system = root->name();
//game path
if(!path) return {Location::notsuffix(location), suffix};
//path override
string pathname = {path, *system, "/"};
directory::create(pathname);
return {pathname, Location::prefix(location), suffix};
}
//handles region selection when games support multiple regions
auto Emulator::region() -> string {
if(game && game->pak) {
if(auto regions = game->pak->attribute("region").split(",").strip()) {
if(settings.boot.prefer == "NTSC-U" && regions.find("NTSC-U")) return "NTSC-U";
if(settings.boot.prefer == "NTSC-J" && regions.find("NTSC-J")) return "NTSC-J";
if(settings.boot.prefer == "NTSC-U" && regions.find("NTSC" )) return "NTSC";
if(settings.boot.prefer == "NTSC-J" && regions.find("NTSC" )) return "NTSC";
if(settings.boot.prefer == "PAL" && regions.find("PAL" )) return "PAL";
if(regions.first()) return regions.first();
}
}
if(settings.boot.prefer == "NTSC-J") return "NTSC-J";
if(settings.boot.prefer == "NTSC-U") return "NTSC-U";
if(settings.boot.prefer == "PAL" ) return "PAL";
return {};
}
auto Emulator::load(const string& location) -> bool {
if(inode::exists(location)) locationQueue.append(location);
if(!load()) return false;
setBoolean("Color Emulation", settings.video.colorEmulation);
setBoolean("Deep Black Boost", settings.video.deepBlackBoost);
setBoolean("Interframe Blending", settings.video.interframeBlending);
setOverscan(settings.video.overscan);
setColorBleed(settings.video.colorBleed);
latch = {};
root->power();
return true;
}
auto Emulator::load(shared_pointer<mia::Pak> pak, string& path) -> string {
string location;
if(locationQueue) {
location = locationQueue.takeFirst(); //pull from the game queue if an entry is available
} else if(program.startGameLoad) {
location = program.startGameLoad.takeFirst(); //pull from the command line if an entry is available
} else if(!program.noFilePrompt) {
BrowserDialog dialog;
dialog.setTitle({"Load ", pak->name(), " Game"});
dialog.setPath(path ? path : Path::desktop());
dialog.setAlignment(presentation);
string filters{"*.zip:"};
for(auto& extension : pak->extensions()) {
filters.append("*.", extension, ":");
}
//support both uppercase and lowercase extensions
filters.append(string{filters}.upcase());
filters.trimRight(":", 1L);
filters.prepend(pak->name(), "|");
dialog.setFilters({filters, "All|*"});
location = program.openFile(dialog);
}
if(location) {
path = Location::dir(location);
return location;
}
return {};
}
auto Emulator::loadFirmware(const Firmware& firmware) -> shared_pointer<vfs::file> {
if(firmware.location.iendsWith(".zip")) {
Decode::ZIP archive;
if(archive.open(firmware.location) && archive.file) {
auto image = archive.extract(archive.file.first());
return vfs::memory::open(image);
}
} else if(auto image = file::read(firmware.location)) {
return vfs::memory::open(image);
}
return {};
}
auto Emulator::unload() -> void {
save();
root->unload();
game = {};
system = {};
root.reset();
locationQueue.reset();
}
auto Emulator::load(mia::Pak& node, string name) -> bool {
if(auto fp = node.pak->read(name)) {
if(auto memory = file::read({node.location, name})) {
fp->read(memory);
return true;
}
}
return false;
}
auto Emulator::save(mia::Pak& node, string name) -> bool {
if(auto memory = node.pak->write(name)) {
return file::write({node.location, name}, {memory->data(), memory->size()});
}
return false;
}
auto Emulator::refresh() -> void {
if(auto screen = root->scan<ares::Node::Video::Screen>("Screen")) {
screen->refresh();
}
}
auto Emulator::setBoolean(const string& name, bool value) -> bool {
if(auto node = root->scan<ares::Node::Setting::Boolean>(name)) {
node->setValue(value); //setValue() will not call modify() if value has not changed;
node->modify(value); //but that may prevent the initial setValue() from working
return true;
}
return false;
}
auto Emulator::setOverscan(bool value) -> bool {
if(auto screen = root->scan<ares::Node::Video::Screen>("Screen")) {
screen->setOverscan(value);
return true;
}
return false;
}
auto Emulator::setColorBleed(bool value) -> bool {
if(auto screen = root->scan<ares::Node::Video::Screen>("Screen")) {
screen->setColorBleed(screen->height() < 720 ? value : false); //only apply to sub-HD content
return true;
}
return false;
}
auto Emulator::error(const string& text) -> void {
MessageDialog().setTitle("Error").setText(text).setAlignment(presentation).error();
}
auto Emulator::errorFirmware(const Firmware& firmware, string system) -> void {
if(!system) system = emulator->name;
if(MessageDialog().setText({
"Error: firmware is missing or invalid.\n",
system, " - ", firmware.type, " (", firmware.region, ") is required to play this game.\n"
"Would you like to configure firmware settings now?"
}).question() == "Yes") {
settingsWindow.show("Firmware");
firmwareSettings.select(system, firmware.type, firmware.region);
}
}
auto Emulator::input(ares::Node::Input::Input input) -> void {
//looking up inputs is very time-consuming; skip call if input was called too recently
//note: allow rumble to be polled at full speed to prevent missed motor events
if(!input->cast<ares::Node::Input::Rumble>()) {
auto thisPoll = chrono::millisecond();
if(thisPoll - input->lastPoll < 5) return;
input->lastPoll = thisPoll;
}
auto device = ares::Node::parent(input);
if(!device) return;
auto port = ares::Node::parent(device);
if(!port) return;
for(auto& inputPort : ports) {
if(inputPort.name != port->name()) continue;
for(auto& inputDevice : inputPort.devices) {
if(inputDevice.name != device->name()) continue;
for(auto& inputNode : inputDevice.inputs) {
if(inputNode.name != input->name()) continue;
if(auto button = input->cast<ares::Node::Input::Button>()) {
auto pressed = inputNode.mapping->pressed();
return button->setValue(pressed);
}
if(auto axis = input->cast<ares::Node::Input::Axis>()) {
auto value = inputNode.mapping->value();
return axis->setValue(value);
}
if(auto rumble = input->cast<ares::Node::Input::Rumble>()) {
if(auto target = dynamic_cast<InputRumble*>(inputNode.mapping)) {
return target->rumble(rumble->strongValue(), rumble->weakValue());
}
}
}
for(auto& inputPair : inputDevice.pairs) {
if(inputPair.name != input->name()) continue;
if(auto axis = input->cast<ares::Node::Input::Axis>()) {
auto value = inputPair.mappingHi->value() - inputPair.mappingLo->value();
return axis->setValue(value);
}
}
}
}
}
auto Emulator::inputKeyboard(string name) -> bool {
for (auto& device : inputManager.devices) {
if (!device->isKeyboard()) continue;
auto keyboard = (shared_pointer<HID::Keyboard>)device;
auto key = keyboard->buttons().find(name);
if (!key) return false;
return keyboard->buttons().input(*key).value();
}
return false;
}