/* 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 #include #include #include #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(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(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(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(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(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(data); m_noGame = nogame; return true; } // tells the frontend what core options are available case RETRO_ENVIRONMENT_SET_VARIABLES: { auto *vars = reinterpret_cast(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(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 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(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(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(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(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(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(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(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(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; }