You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
407 lines
16 KiB
407 lines
16 KiB
/*
|
|
Copyright 2020-2021 Brad Parker
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#include <QGuiApplication>
|
|
#include <QRegularExpression>
|
|
#include <QScreen>
|
|
#include <iostream>
|
|
#include "libretro.h"
|
|
#include "core.h"
|
|
#include "../common/video.h"
|
|
#include "../frontend/mainwindow.h"
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
unsigned cmd;
|
|
} EnvCommand;
|
|
|
|
static EnvCommand s_envCommands[] = {
|
|
{"RETRO_ENVIRONMENT_SET_ROTATION", 1},
|
|
{"RETRO_ENVIRONMENT_GET_OVERSCAN", 2},
|
|
{"RETRO_ENVIRONMENT_GET_CAN_DUPE", 3},
|
|
{"RETRO_ENVIRONMENT_SET_MESSAGE", 6},
|
|
{"RETRO_ENVIRONMENT_SHUTDOWN", 7},
|
|
{"RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL", 8},
|
|
{"RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY", 9},
|
|
{"RETRO_ENVIRONMENT_SET_PIXEL_FORMAT", 10},
|
|
{"RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS", 11},
|
|
{"RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK", 12},
|
|
{"RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE", 13},
|
|
{"RETRO_ENVIRONMENT_SET_HW_RENDER", 14},
|
|
{"RETRO_ENVIRONMENT_GET_VARIABLE", 15},
|
|
{"RETRO_ENVIRONMENT_SET_VARIABLES", 16},
|
|
{"RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE", 17},
|
|
{"RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME", 18},
|
|
{"RETRO_ENVIRONMENT_GET_LIBRETRO_PATH", 19},
|
|
{"RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK", 21},
|
|
{"RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK", 22},
|
|
{"RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE", 23},
|
|
{"RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES", 24},
|
|
{"RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE", (25 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE", (26 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_LOG_INTERFACE", 27},
|
|
{"RETRO_ENVIRONMENT_GET_PERF_INTERFACE", 28},
|
|
{"RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE", 29},
|
|
{"RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY", 30},
|
|
{"RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY", 30},
|
|
{"RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY", 31},
|
|
{"RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO", 32},
|
|
{"RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK", 33},
|
|
{"RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO", 34},
|
|
{"RETRO_ENVIRONMENT_SET_CONTROLLER_INFO", 35},
|
|
{"RETRO_ENVIRONMENT_SET_MEMORY_MAPS", (36 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_SET_GEOMETRY", 37},
|
|
{"RETRO_ENVIRONMENT_GET_USERNAME", 38},
|
|
{"RETRO_ENVIRONMENT_GET_LANGUAGE", 39},
|
|
{"RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER", (40 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE", (41 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS", (42 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE", (43 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS", 44},
|
|
{"RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT", (44 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_VFS_INTERFACE", (45 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_LED_INTERFACE", (46 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE", (47 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_MIDI_INTERFACE", (48 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_FASTFORWARDING", (49 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE", (50 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_INPUT_BITMASKS", (51 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
|
|
{"RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION", 52},
|
|
{"RETRO_ENVIRONMENT_SET_CORE_OPTIONS", 53},
|
|
{"RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL", 54},
|
|
{"RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY", 55},
|
|
{"RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER", 56},
|
|
{"RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION", 57},
|
|
{"RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE", 58},
|
|
};
|
|
|
|
static void core_log(enum retro_log_level level, const char *fmt, ...) {
|
|
char buffer[4093] = {0};
|
|
char buffer2[4096] = {0};
|
|
static const char *levelstr[] = {"debug", "info", "warn", "error"};
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
vsnprintf(buffer, sizeof(buffer), fmt, va);
|
|
va_end(va);
|
|
if(level == 0)
|
|
return;
|
|
sprintf(buffer2, "[%s] %s", levelstr[level], buffer);
|
|
fprintf(stdout, "%s", buffer2);
|
|
}
|
|
|
|
bool Core::environment(unsigned cmd, void *data) {
|
|
for(unsigned i = 0; i < sizeof(s_envCommands) / sizeof(s_envCommands[0]); ++i) {
|
|
if(s_envCommands[i].cmd == cmd) {
|
|
switch(cmd) {
|
|
case RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT:
|
|
return true;
|
|
case RETRO_ENVIRONMENT_SET_HW_RENDER:
|
|
{
|
|
auto *cb = reinterpret_cast<struct retro_hw_render_callback*>(data);
|
|
|
|
if(cb->context_type == RETRO_HW_CONTEXT_OPENGL || cb->context_type == RETRO_HW_CONTEXT_OPENGL_CORE) {
|
|
cb->get_current_framebuffer = []() { return Core::instance()->getCurrentFramebuffer(); };
|
|
|
|
void* (*getProcAddressPtr)(const char *sym) = [](const char *sym) -> void* { return Core::instance()->getProcAddress(sym); };
|
|
|
|
cb->get_proc_address = reinterpret_cast<retro_hw_get_proc_address_t>(getProcAddressPtr);
|
|
|
|
m_retroHWContextReset = cb->context_reset;
|
|
m_isHWRender = true;
|
|
m_bottomLeftOrigin = cb->bottom_left_origin ? true : false;
|
|
std::cout << "core uses bottom left origin? " << (m_bottomLeftOrigin ? "yes" : "no") << std::endl;
|
|
return true;
|
|
}else{
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
// tells the frontend what buttons it supports on what kind of devices, for however many users
|
|
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
|
|
{
|
|
auto *descs = reinterpret_cast<const struct retro_input_descriptor*>(data);
|
|
|
|
/* TODO: FIXME: I used an arbitrary limit of 100 */
|
|
for(unsigned j = 0; j < 100; ++j) {
|
|
if(descs[j].port == 0 && descs[j].device == 0 && descs[j].index == 0 && descs[j].id == 0 && descs[j].description == nullptr)
|
|
break;
|
|
|
|
if(descs[j].description && *(descs[j].description))
|
|
std::cout << "got input descriptor at port " << descs[j].port << " device " << descs[j].device << " index " << descs[j].index << " id " << descs[j].id << ": " << descs[j].description << std::endl;
|
|
else
|
|
std::cout << "got input descriptor at port " << descs[j].port << " device " << descs[j].device << " index " << descs[j].index << " id " << descs[j].id << ": (no description)" << std::endl;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// whether frontend supports the newer advanced core option interfaces
|
|
case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION:
|
|
{
|
|
unsigned *ver = reinterpret_cast<unsigned*>(data);
|
|
// not yet
|
|
if(ver)
|
|
*ver = 0;
|
|
return true;
|
|
}
|
|
// asks the frontend if it changed any core variables
|
|
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
|
|
{
|
|
bool *update = reinterpret_cast<bool*>(data);
|
|
|
|
if(update)
|
|
*update = m_variablesChanged;
|
|
|
|
if(m_variablesChanged)
|
|
m_variablesChanged = false;
|
|
|
|
return true;
|
|
}
|
|
// tells the frontend we support running without any content loaded
|
|
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
|
{
|
|
bool *nogame = reinterpret_cast<bool*>(data);
|
|
|
|
m_noGame = nogame;
|
|
|
|
return true;
|
|
}
|
|
// tells the frontend what core options are available
|
|
case RETRO_ENVIRONMENT_SET_VARIABLES:
|
|
{
|
|
auto *vars = reinterpret_cast<const struct retro_variable*>(data);
|
|
|
|
std::cout << "core is sending its variables" << std::endl;
|
|
|
|
if(vars) {
|
|
int i = 0;
|
|
|
|
for(;;) {
|
|
if(vars[i].key && *(vars[i].key) && vars[i].value && *(vars[i].value)) {
|
|
//std::cout << "storing variable " << vars[i].key << ": " << vars[i].value << std::endl;
|
|
|
|
QStringList name_values = QString(vars[i].value).split(";");
|
|
|
|
if(name_values.size() > 0) {
|
|
CoreOption op{};
|
|
op.key = vars[i].key;
|
|
op.name = name_values.at(0);
|
|
|
|
QStringList values = name_values.at(1).split("|");
|
|
|
|
for(int j = 0; j < values.size(); ++j) {
|
|
QString val = values.at(j);
|
|
val = val.replace(QRegularExpression("^\\s+"), "");
|
|
val = val.replace(QRegularExpression("\\s+$"), "");
|
|
op.values.append(val);
|
|
}
|
|
|
|
// use first value as the default
|
|
op.val = op.values.at(0);
|
|
op.valArray = op.val.toUtf8();
|
|
op.valData = op.valArray.constData();
|
|
|
|
m_options[vars[i].key] = op;
|
|
}else{
|
|
//std::cerr << "no options found for variable " << vars[i].key << std::endl;
|
|
}
|
|
}else{
|
|
break;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
emit coreOptionsChanged();
|
|
|
|
return true;
|
|
}
|
|
// asks the frontend for the current value of a variable
|
|
case RETRO_ENVIRONMENT_GET_VARIABLE:
|
|
{
|
|
auto *var = reinterpret_cast<struct retro_variable*>(data);
|
|
|
|
if(!var->key || !*(var->key)) {
|
|
static QString allEnvs;
|
|
static QByteArray allEnvsArray;
|
|
static const char *allEnvsData = nullptr;
|
|
|
|
std::cout << "got env " << cmd << " (" << s_envCommands[i].name << ") with no key, sending everything we have" << std::endl;
|
|
|
|
allEnvs.clear();
|
|
|
|
const QList<Core::CoreOption> options = m_options.values();
|
|
|
|
for(const CoreOption &op : options) {
|
|
allEnvs += op.key + "=" + op.val + ";";
|
|
}
|
|
|
|
allEnvsArray = allEnvs.toUtf8();
|
|
allEnvsData = allEnvsArray.constData();
|
|
|
|
var->key = allEnvsData;
|
|
var->value = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
std::cout << "got env " << cmd << " (" << s_envCommands[i].name << ") with key \"" << var->key << "\"" << std::endl;
|
|
|
|
if(m_options.contains(var->key)) {
|
|
const CoreOption &op = m_options.value(var->key);
|
|
|
|
var->value = op.valData;
|
|
|
|
std::cout << "frontend to core: variable " << var->key << " = " << var->value << std::endl;
|
|
}else{
|
|
var->value = NULL;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// tells the frontend there was a major change in the A/V setup of the core, in RetroArch's case this constitutes an entire teardown/reinit of the whole application, but we don't need to
|
|
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
|
|
{
|
|
auto *info = reinterpret_cast<const struct retro_system_av_info*>(data);
|
|
|
|
std::cout << "got env " << cmd << " (" << s_envCommands[i].name << ")" << std::endl;
|
|
std::cout << "desired AV info: " << info->geometry.base_width << "x" << info->geometry.base_height << " (max " << info->geometry.max_width << "x" << info->geometry.max_height << ") aspect " << info->geometry.aspect_ratio << " fps " << info->timing.fps << std::endl;
|
|
// TODO: FIXME: not implemented: changing of audio parameters
|
|
auto videoState = VideoState::instance();
|
|
struct retro_system_av_info *avInfo = videoState->avInfo();
|
|
videoState->setAspect(info->geometry.aspect_ratio);
|
|
videoState->setBaseWidth(info->geometry.base_width);
|
|
videoState->setBaseHeight(info->geometry.base_height);
|
|
avInfo->geometry.aspect_ratio = info->geometry.aspect_ratio;
|
|
avInfo->geometry.base_width = info->geometry.base_width;
|
|
avInfo->geometry.base_height = info->geometry.base_height;
|
|
avInfo->geometry.max_width = info->geometry.max_width;
|
|
avInfo->geometry.max_height = info->geometry.max_height;
|
|
avInfo->timing.fps = info->timing.fps;
|
|
avInfo->timing.sample_rate = info->timing.sample_rate;
|
|
|
|
float screenRefresh = 0;
|
|
|
|
QScreen *screen = QGuiApplication::primaryScreen();
|
|
|
|
if(screen)
|
|
screenRefresh = screen->refreshRate();
|
|
|
|
// if core refresh rate is within half a percent of the screen's, just call it equal, it's hopefully close enough to not be noticeable
|
|
if((screenRefresh - (screenRefresh * 0.005)) <= avInfo->timing.fps && (screenRefresh + (screenRefresh * 0.005)) >= avInfo->timing.fps) {
|
|
avInfo->timing.fps = screenRefresh;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// if the core needs to handle saving on its own, this asks the frontend for a path to do so in
|
|
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
|
|
{
|
|
const char **save_dir = reinterpret_cast<const char**>(data);
|
|
*save_dir = m_savePath;
|
|
std::cout << "core requesting current save directory: " << *save_dir << std::endl;
|
|
return true;
|
|
}
|
|
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
|
|
{
|
|
const char **system_dir = reinterpret_cast<const char**>(data);
|
|
*system_dir = m_systemPath;
|
|
std::cout << "core requesting current assets directory: " << *system_dir << std::endl;
|
|
return true;
|
|
}
|
|
// if the core needs to load a BIOS or other support files, this asks the frontend for a path to find them in
|
|
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
|
|
{
|
|
const char **system_dir = reinterpret_cast<const char**>(data);
|
|
*system_dir = m_systemPath;
|
|
std::cout << "core requesting current system directory: " << *system_dir << std::endl;
|
|
return true;
|
|
}
|
|
// asks the frontend for a callback to send core log messages to
|
|
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
|
{
|
|
auto *cb = reinterpret_cast<struct retro_log_callback*>(data);
|
|
cb->log = core_log;
|
|
return true;
|
|
}
|
|
case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE:
|
|
break;
|
|
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
|
|
{
|
|
const auto *info = reinterpret_cast<const struct retro_controller_info*>(data);
|
|
|
|
std::cout << "core has set the controller info as:" << std::endl;
|
|
|
|
unsigned i = 0;
|
|
|
|
for(;;) {
|
|
const auto &desc = info->types[i];
|
|
|
|
if(i == info->num_types || (!desc.desc || !*(desc.desc)))
|
|
break;
|
|
|
|
std::cout << desc.desc << " (" << desc.id << ")" << std::endl;
|
|
|
|
m_controllerInfo[desc.desc] = desc.id;
|
|
|
|
++i;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
case RETRO_ENVIRONMENT_SET_GEOMETRY:
|
|
{
|
|
auto *geom = reinterpret_cast<struct retro_game_geometry*>(data);
|
|
std::cout << "core requesting geometry change: base " << geom->base_width << "x" << geom->base_height << " max " << geom->max_width << "x" << geom->max_height << " aspect " << geom->aspect_ratio << std::endl;
|
|
auto videoState = VideoState::instance();
|
|
videoState->setAspect(geom->aspect_ratio);
|
|
videoState->setBaseWidth(geom->base_width);
|
|
videoState->setBaseHeight(geom->base_height);
|
|
return true;
|
|
}
|
|
// tells the frontend what pixel format the core wants to use, might be called multiple times to find a preferred format if the frontend rejects anything.
|
|
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
|
|
{
|
|
auto *pixfmt = reinterpret_cast<const enum retro_pixel_format*>(data);
|
|
|
|
m_pixFmt = *pixfmt;
|
|
|
|
if(m_pixFmt == RETRO_PIXEL_FORMAT_XRGB8888) {
|
|
std::cout << "core requesting pixel format " << *pixfmt << " (XRGB8888)" << std::endl;
|
|
return true;
|
|
}else if(m_pixFmt == RETRO_PIXEL_FORMAT_RGB565) {
|
|
std::cout << "core requesting pixel format " << *pixfmt << " (RGB565)" << std::endl;
|
|
return true;
|
|
}
|
|
|
|
// for now we only support XRGB8888 and RGB565
|
|
std::cout << "core requesting unsupported pixel format " << pixfmt << std::endl;
|
|
|
|
return false;
|
|
}
|
|
default:
|
|
//std::cout << "got unhandled env " << cmd << " (" << s_envCommands[i].name << ")" << std::endl;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|