Simplify dynamic options handling

This commit is contained in:
Jesse Talavera-Greenberg 2023-08-26 19:13:24 -04:00
parent cd12b5d4cc
commit f5d24fc5bd
5 changed files with 125 additions and 208 deletions

View File

@ -19,11 +19,13 @@
// This must come before <GPU.h>!
#include "PlatformOGLPrivate.h"
#include <algorithm>
#include <charconv>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <string_view>
#include <vector>
#include <file/file_path.h>
#include <libretro.h>
@ -45,11 +47,12 @@
#include "microphone.hpp"
#include "opengl.hpp"
#include "render.hpp"
#include "retro/dirent.hpp"
#include "screenlayout.hpp"
#include "tracy.hpp"
#include "dynamic.hpp"
using std::array;
using std::find_if;
using std::from_chars;
using std::from_chars_result;
using std::initializer_list;
@ -59,6 +62,7 @@ using std::optional;
using std::string;
using std::string_view;
using std::unique_ptr;
using std::vector;
constexpr unsigned AUTO_SDCARD_SIZE = 0;
constexpr unsigned DEFAULT_SDCARD_SIZE = 4096;
@ -81,8 +85,7 @@ namespace melonds::config {
static void set_core_options(
const optional<retro_game_info> &nds_info,
const optional<NDSHeader> &nds_header
) noexcept;
static bool _config_categories_supported;
);
namespace visibility {
static bool ShowDsiOptions = true;
static bool ShowDsiSdCardOptions = true;
@ -93,6 +96,7 @@ namespace melonds::config {
static bool ShowHybridOptions = true;
static bool ShowVerticalLayoutOptions = true;
static bool ShowCursorTimeout = true;
static bool ShowAlarm = true;
static unsigned NumberOfShownScreenLayouts = screen::MAX_SCREEN_LAYOUTS;
#ifdef JIT_ENABLED
@ -489,6 +493,9 @@ bool melonds::update_option_visibility() {
updated = true;
}
bool oldShowAlarm = ShowAlarm;
#ifdef JIT_ENABLED
// Show/hide JIT core options
bool oldShowJitOptions = ShowJitOptions;
@ -1421,23 +1428,101 @@ static void melonds::config::apply_screen_options(ScreenLayoutData& screenLayout
static void melonds::config::set_core_options(
const optional<retro_game_info> &nds_info,
const optional<NDSHeader> &nds_header
) noexcept {
) {
ZoneScopedN("retro::set_core_options");
_config_categories_supported = false;
array categories = definitions::OptionCategories<RETRO_LANGUAGE_ENGLISH>;
array definitions = definitions::CoreOptionDefinitions<RETRO_LANGUAGE_ENGLISH>;
DynamicCoreOptions options(
definitions.data(),
definitions.size(),
categories.data(),
categories.size()
);
retro_core_options_v2* optionsUs = options.GetOptions();
vector<string> bases;
retro::set_core_options(*optionsUs);
if (optional<string> subdir = retro::get_system_subdirectory(); subdir && path_is_directory(subdir->c_str())) {
bases.push_back(*subdir);
}
if (optional<string> fallback = retro::get_system_fallback_subdirectory(); fallback && path_is_directory(fallback->c_str())) {
bases.push_back(*fallback);
}
vector<string> dsiNandPaths;
vector<string> firmwarePaths;
optional<string> sysdir = retro::get_system_directory();
if (sysdir) {
for (const string& base : bases) {
for (const retro::dirent& d : retro::readdir(base, true)) {
if (IsDsiNandImage(d)) {
dsiNandPaths.emplace_back(d.path);
}
if (IsFirmwareImage(d)) {
firmwarePaths.emplace_back(d.path);
}
}
}
} else {
retro::set_error_message("Failed to get system directory, anything that needs it won't work.");
}
if (!dsiNandPaths.empty()) {
// If we found at least one DSi NAND image...
retro_core_option_v2_definition* dsiNandPathOption = find_if(definitions.begin(), definitions.end(), [](const auto& def) {
return string_is_equal(def.key, melonds::config::storage::DSI_NAND_PATH);
});
retro_assert(dsiNandPathOption != definitions.end());
memset(dsiNandPathOption->values, 0, sizeof(dsiNandPathOption->values));
int length = std::min((int)dsiNandPaths.size(), (int)RETRO_NUM_CORE_OPTION_VALUES_MAX - 1);
for (int i = 0; i < length; ++i) {
retro::debug("Found a DSi NAND image at \"%s\"", dsiNandPaths[i].c_str());
string_view path = dsiNandPaths[i];
path.remove_prefix(sysdir->size() + 1);
dsiNandPathOption->values[i].value = path.data();
dsiNandPathOption->values[i].label = nullptr;
}
dsiNandPathOption->default_value = dsiNandPaths[0].c_str();
}
if (!firmwarePaths.empty()) {
// If we found at least one firmware image...
retro_core_option_v2_definition* firmwarePathOption = find_if(definitions.begin(), definitions.end(), [](const auto& def) {
return string_is_equal(def.key, melonds::config::firmware::FIRMWARE_PATH);
});
retro_core_option_v2_definition* firmwarePathDsiOption = find_if(definitions.begin(), definitions.end(), [](const auto& def) {
return string_is_equal(def.key, melonds::config::firmware::FIRMWARE_DSI_PATH);
});
retro_assert(firmwarePathOption != definitions.end());
retro_assert(firmwarePathDsiOption != definitions.end());
// Keep the first element, it's for built-in firmware
// We subtract 2 because we need room for the terminating element and the built-in firmware
int length = std::min((int)firmwarePaths.size(), (int)RETRO_NUM_CORE_OPTION_VALUES_MAX - 2);
for (int i = 0; i < length; ++i) {
retro::debug("Found a firmware image at \"%s\"", firmwarePaths[i].c_str());
string_view path = firmwarePaths[i];
path.remove_prefix(sysdir->size() + 1);
firmwarePathOption->values[i + 1] = { path.data(), nullptr };
firmwarePathDsiOption->values[i + 1] = { path.data(), nullptr };
}
firmwarePathOption->values[length + 2] = { nullptr, nullptr };
firmwarePathDsiOption->values[length + 2] = { nullptr, nullptr };
firmwarePathOption->default_value = firmwarePathOption->values[0].value;
firmwarePathDsiOption->default_value = firmwarePathDsiOption->values[0].value;
retro_assert(firmwarePathOption->values[0].value != nullptr);
retro_assert(firmwarePathDsiOption->values[0].value != nullptr);
}
retro_core_options_v2 optionsUs = {
.categories = categories.data(),
.definitions = definitions.data(),
};
if (!retro::set_core_options(optionsUs)) {
throw emulator_exception("Failed to set core options");
}
}
using namespace melonds::config;

View File

@ -16,12 +16,15 @@
#include "constants.hpp"
#include <algorithm>
#include <string>
#include <net/net_compat.h>
#include <string/stdstring.h>
#include "environment.hpp"
#include "retro/dirent.hpp"
using std::find;
using std::optional;
using std::nullopt;
using std::string;
@ -148,4 +151,16 @@ optional<SPI_Firmware::IpAddress> melonds::config::ParseIpAddress(const char* va
}
return nullopt;
}
bool melonds::config::IsDsiNandImage(const retro::dirent &file) noexcept {
// TODO: Validate the NoCash footer
return file.is_regular_file() && file.size == DSI_NAND_SIZE;
}
bool melonds::config::IsFirmwareImage(const retro::dirent& file) noexcept {
return
file.is_regular_file() &&
find(FIRMWARE_SIZES.begin(), FIRMWARE_SIZES.end(), file.size) != FIRMWARE_SIZES.end() &&
!string_ends_with(file.path, ".bak");
}

View File

@ -29,6 +29,10 @@
#include "../config.hpp"
namespace retro {
struct dirent;
}
namespace melonds::config {
constexpr unsigned DS_NAME_LIMIT = 10;
@ -57,6 +61,8 @@ namespace melonds::config {
static constexpr const char *const BIRTH_DAY = "melonds_firmware_birth_day";
static constexpr const char *const ENABLE_ALARM = "melonds_firmware_enable_alarm";
static constexpr const char *const FAVORITE_COLOR = "melonds_firmware_favorite_color";
static constexpr const char *const FIRMWARE_PATH = "melonds_firmware_nds_path";
static constexpr const char *const FIRMWARE_DSI_PATH = "melonds_firmware_dsi_path";
static constexpr const char *const LANGUAGE = "melonds_firmware_language";
static constexpr const char *const USERNAME = "melonds_firmware_username";
static constexpr const char *const WFC_DNS = "melonds_firmware_wfc_dns";
@ -160,6 +166,7 @@ namespace melonds::config {
static constexpr const char *const BOTTOM_TOP = "bottom-top";
static constexpr const char *const BOTH = "both";
static constexpr const char *const BOTTOM = "bottom";
static constexpr const char *const BUILT_IN = "builtin";
static constexpr const char *const COSINE = "cosine";
static constexpr const char *const CUBIC = "cubic";
static constexpr const char *const DEDICATED = "dedicated";
@ -255,7 +262,11 @@ namespace melonds::config {
std::optional<SPI_Firmware::IpAddress> ParseIpAddress(const char* value) noexcept;
constexpr size_t DSI_NAND_SIZE = 251658304;
constexpr std::array<size_t, 3> FIRMWARE_SIZES = { 131072, 262144, 524288 };
bool IsDsiNandImage(const retro::dirent &file) noexcept;
bool IsFirmwareImage(const retro::dirent &file) noexcept;
}
#endif //MELONDS_DS_CONSTANTS_HPP

View File

@ -1,127 +0,0 @@
/*
Copyright 2023 Jesse Talavera-Greenberg
melonDS DS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS DS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS DS. If not, see http://www.gnu.org/licenses/.
*/
#include "dynamic.hpp"
#include "constants.hpp"
#include "environment.hpp"
#include "retro/dirent.hpp"
#include "tracy.hpp"
#include <algorithm>
#include <string_view>
#include <utility>
#include <retro_dirent.h>
#include <retro_assert.h>
#include <string/stdstring.h>
#include <vfs/vfs_implementation.h>
using std::find_if;
using std::min;
using std::optional;
using std::pair;
using std::string;
using std::string_view;
using std::vector;
constexpr size_t DSI_NAND_SIZE = 251658304;
static vector<string> GetNandPaths() noexcept;
melonds::config::DynamicCoreOptions::DynamicCoreOptions(
const retro_core_option_v2_definition *definitions,
size_t definitions_length,
const retro_core_option_v2_category *categories,
size_t categories_length
) {
ZoneScopedN("melonds::config::DynamicCoreOptions::DynamicCoreOptions");
// TODO: Generate an option for selecting the wi-fi interface
// TODO: Generate an option for selecting the DS BIOS7 file
// TODO: Generate an option for selecting the DS BIOS9 file
// TODO: Generate an option for selecting the DS firmware file
// TODO: Generate an option for selecting the DSi BIOS7 file
// TODO: Generate an option for selecting the DSi BIOS9 file
// TODO: Generate an option for selecting the DSi firmware file
// TODO: Generate an option for selecting the homebrew SD card
// TODO: Generate an option for selecting the DSi SD card
_optionDefs = new retro_core_option_v2_definition[definitions_length];
_optionDefsEnd = &_optionDefs[definitions_length - 1];
_optionDefsLength = definitions_length;
memcpy(_optionDefs, definitions, definitions_length * sizeof(retro_core_option_v2_definition));
_optionCategories = new retro_core_option_v2_category[categories_length];
memcpy(_optionCategories, categories, categories_length * sizeof(retro_core_option_v2_category));
{
retro_core_option_v2_definition* dsiNandPathOption = find_if(_optionDefs, _optionDefsEnd, [](const auto& def) {
return string_is_equal(def.key, melonds::config::storage::DSI_NAND_PATH);
});
retro_assert(dsiNandPathOption != _optionDefsEnd);
for (const string& path : GetNandPaths()) {
_dsiNandPaths.push_back(std::move(path));
}
if (!_dsiNandPaths.empty()) {
memset(dsiNandPathOption->values, 0, sizeof(dsiNandPathOption->values));
int length = min((int)_dsiNandPaths.size(), (int)RETRO_NUM_CORE_OPTION_VALUES_MAX - 1);
for (int i = 0; i < length; ++i) {
dsiNandPathOption->values[i].value = _dsiNandPaths[i].c_str();
dsiNandPathOption->values[i].label = nullptr;
}
dsiNandPathOption->default_value = _dsiNandPaths[0].c_str();
}
}
_options.categories = _optionCategories;
_options.definitions = _optionDefs;
}
melonds::config::DynamicCoreOptions::~DynamicCoreOptions() noexcept {
delete[] _optionCategories;
delete[] _optionDefs;
}
static vector<string> GetNandPaths() noexcept {
vector<string> paths;
optional<string> sysdir = retro::get_system_directory();
auto appendPaths = [&paths, &sysdir](optional<string> base) {
if (base) {
for (const retro::dirent& d : retro::readdir(*base, true)) {
retro_assert(retro::is_regular_file(d.flags));
if (d.size == DSI_NAND_SIZE) {
// If this is a regular file...
string_view path = d.path;
retro_assert(path.size() > sysdir->size());
path.remove_prefix(sysdir->size() + 1);
paths.emplace_back(path);
}
}
}
};
appendPaths(sysdir);
appendPaths(retro::get_system_subdirectory());
appendPaths(retro::get_system_fallback_subdirectory());
return paths;
}

View File

@ -1,67 +0,0 @@
/*
Copyright 2023 Jesse Talavera-Greenberg
melonDS DS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS DS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS DS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef MELONDS_DS_DYNAMIC_HPP
#define MELONDS_DS_DYNAMIC_HPP
#include <optional>
#include <string>
#include <vector>
#include <libretro.h>
struct NDSHeader;
namespace melonds::config {
class DynamicCoreOptions {
public:
DynamicCoreOptions(
const retro_core_option_v2_definition *definitions,
size_t definitions_length,
const retro_core_option_v2_category *categories,
size_t categories_length
);
~DynamicCoreOptions() noexcept;
const retro_core_option_v2_definition* GetDefinitions() const noexcept {
return _optionDefs;
}
retro_core_option_v2_definition* GetDefinitions() noexcept {
return _optionDefs;
}
const retro_core_options_v2* GetOptions() const noexcept {
return &_options;
}
retro_core_options_v2* GetOptions() noexcept {
return &_options;
}
private:
retro_core_option_v2_definition *_optionDefs;
retro_core_option_v2_definition *_optionDefsEnd;
retro_core_option_v2_category *_optionCategories;
size_t _optionDefsLength; // Excluding the null option at the end
retro_core_options_v2 _options;
std::vector<std::string> _dsiNandPaths;
};
}
#endif //MELONDS_DS_DYNAMIC_HPP