From 80d8337592948c888947c3c93aa6a1b87d121d1b Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 26 Dec 2024 21:43:42 +0100 Subject: [PATCH] Decouple data save paths from Emulator This change allows the frontend to select the appdata and configuration file locations when instantiating the emulator, allowing for more flexibility in placing the files. This also fixes configuration not working in MacOS app bundles, as it will now look for config.toml in the user's "Application Support" directory and default there. --- include/emulator.hpp | 6 +++- src/config.cpp | 1 + src/emulator.cpp | 58 +++++++++++----------------------- src/hydra_core.cpp | 2 +- src/jni_driver.cpp | 12 ++++++- src/libretro_core.cpp | 14 ++------ src/panda_qt/main_window.cpp | 5 ++- src/panda_sdl/frontend_sdl.cpp | 9 +++++- 8 files changed, 50 insertions(+), 57 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index cf231328f..9846a34af 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -28,6 +28,8 @@ #include "gl/context.h" #endif +static const std::string EmulatorConfigFilename = "config.toml"; + struct SDL_Window; enum class ROMType { @@ -51,6 +53,8 @@ class Emulator { MiniAudioDevice audioDevice; Cheats cheats; + std::filesystem::path appDataPath; + public: static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens @@ -85,7 +89,7 @@ class Emulator { // Used in CPU::runFrame bool frameDone = false; - Emulator(); + Emulator(std::vector configSearchPaths, std::filesystem::path appDataPath); ~Emulator(); void step(); diff --git a/src/config.cpp b/src/config.cpp index bfe84193e..9b2627444 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -27,6 +27,7 @@ void EmulatorConfig::load() { return; } + printf("Loading existing configuration file %s\n", path.string().c_str()); toml::value data; try { diff --git a/src/emulator.cpp b/src/emulator.cpp index 86adbf223..01902ed2e 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -18,14 +18,29 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; } #endif -Emulator::Emulator() - : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), - cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig), lua(*this), running(false) +std::filesystem::path findConfig(std::vector paths) { + for (std::filesystem::path p: paths) { + if (std::filesystem::exists(p)) { + return p; + } + } + return paths.back(); +} + +Emulator::Emulator(std::vector configSearchPaths, std::filesystem::path appDataPath) + : config(findConfig(configSearchPaths)), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), + cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig), lua(*this), running(false), appDataPath(appDataPath) #ifdef PANDA3DS_ENABLE_HTTP_SERVER , httpServer(this) #endif { + if (config.usePortableBuild) { + auto appData = SDL_GetBasePath(); + appDataPath = std::filesystem::path(appData) / "Emulator Files"; + SDL_free(appData); + } + DSPService& dspService = kernel.getServiceManager().getDSP(); dsp = Audio::makeDSPCore(config, memory, scheduler, dspService); @@ -91,25 +106,6 @@ void Emulator::reset(ReloadOption reload) { } } -#ifndef __LIBRETRO__ -std::filesystem::path Emulator::getAndroidAppPath() { - // SDL_GetPrefPath fails to get the path due to no JNI environment - std::ifstream cmdline("/proc/self/cmdline"); - std::string applicationName; - std::getline(cmdline, applicationName, '\0'); - - return std::filesystem::path("/data") / "data" / applicationName / "files"; -} - -std::filesystem::path Emulator::getConfigPath() { - if constexpr (Helpers::isAndroid()) { - return getAndroidAppPath() / "config.toml"; - } else { - return std::filesystem::current_path() / "config.toml"; - } -} -#endif - void Emulator::step() {} void Emulator::render() {} @@ -188,31 +184,13 @@ void Emulator::pollScheduler() { } } -#ifndef __LIBRETRO__ // Get path for saving files (AppData on Windows, /home/user/.local/share/ApplicationName on Linux, etc) // Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in // %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart. // If the portable build setting is enabled, then those saves go in the executable directory instead std::filesystem::path Emulator::getAppDataRoot() { - std::filesystem::path appDataPath; - -#ifdef __ANDROID__ - appDataPath = getAndroidAppPath(); -#else - char* appData; - if (!config.usePortableBuild) { - appData = SDL_GetPrefPath(nullptr, "Alber"); - appDataPath = std::filesystem::path(appData); - } else { - appData = SDL_GetBasePath(); - appDataPath = std::filesystem::path(appData) / "Emulator Files"; - } - SDL_free(appData); -#endif - return appDataPath; } -#endif bool Emulator::loadROM(const std::filesystem::path& path) { // Reset the emulator if we've already loaded a ROM diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 078b8a6c9..79209c2d3 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -50,7 +50,7 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, void* getProcAddress = nullptr; }; -HydraCore::HydraCore() : emulator(new Emulator) { +HydraCore::HydraCore() : emulator(new Emulator({ std::filesystem::current_path() / EmulatorConfigFilename }, std::filesystem::current_path())) { if (emulator->getRendererType() != RendererType::OpenGL) { throw std::runtime_error("HydraCore: Renderer is not OpenGL"); } diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 60bbc6806..451ac5327 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -38,6 +38,15 @@ JNIEnv* jniEnv() { return env; } +std::filesystem::path getAndroidAppPath() { + // SDL_GetPrefPath fails to get the path due to no JNI environment + std::ifstream cmdline("/proc/self/cmdline"); + std::string applicationName; + std::getline(cmdline, applicationName, '\0'); + + return std::filesystem::path("/data") / "data" / applicationName / "files"; +} + extern "C" { #define MAKE_SETTING(functionName, type, settingName) \ @@ -64,7 +73,8 @@ AlberFunction(void, Pause)(JNIEnv* env, jobject obj) { emulator->pause(); } AlberFunction(void, Resume)(JNIEnv* env, jobject obj) { emulator->resume(); } AlberFunction(void, Initialize)(JNIEnv* env, jobject obj) { - emulator = std::make_unique(); + auto appPath = getAndroidAppPath(); + emulator = std::make_unique({ appPath / EmulatorConfigFilename }, appPath); if (emulator->getRendererType() != RendererType::OpenGL) { return throwException(env, "Renderer type is not OpenGL"); diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index fe3cb6c48..2253adaa6 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -15,21 +15,12 @@ static retro_input_poll_t inputPollCallback; static retro_input_state_t inputStateCallback; static retro_hw_render_callback hwRender; -static std::filesystem::path savePath; static bool screenTouched; std::unique_ptr emulator; RendererGL* renderer; -std::filesystem::path Emulator::getConfigPath() { - return std::filesystem::path(savePath / "config.toml"); -} - -std::filesystem::path Emulator::getAppDataRoot() { - return std::filesystem::path(savePath / "Emulator Files"); -} - static void* getGLProcAddress(const char* name) { return (void*)hwRender.get_proc_address(name); } @@ -276,15 +267,14 @@ void retro_init() { envCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &xrgb888); char* saveDir = nullptr; + std::filesystem::path savePath = std::filesystem::path(saveDir); if (!envCallback(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &saveDir) || saveDir == nullptr) { Helpers::warn("No save directory provided by LibRetro."); savePath = std::filesystem::current_path(); - } else { - savePath = std::filesystem::path(saveDir); } - emulator = std::make_unique(); + emulator = std::make_unique(({ std::filesystem::path(savePath / EmulatorConfigFilename) }, std::filesystem::path(savePath / "Emulator Files"))); } void retro_deinit() { diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 881dc02d2..b61d87834 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -14,7 +14,10 @@ #include "version.hpp" MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings::defaultKeyboardMappings()) { - emu = new Emulator(); + QCoreApplication::setApplicationName("Alber"); + + const std::filesystem::path appData(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString()); + emu = new Emulator({ std::filesystem::current_path() / EmulatorConfigFilename, appData / EmulatorConfigFilename }, appData); loadTranslation(); setWindowTitle(tr("Alber")); diff --git a/src/panda_sdl/frontend_sdl.cpp b/src/panda_sdl/frontend_sdl.cpp index 1c07c25e4..6fff5e94d 100644 --- a/src/panda_sdl/frontend_sdl.cpp +++ b/src/panda_sdl/frontend_sdl.cpp @@ -6,7 +6,14 @@ #include "sdl_sensors.hpp" #include "version.hpp" -FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMappings()) { +std::filesystem::path getAppDataPath() { + auto appData = SDL_GetPrefPath(nullptr, "Alber"); + auto appDataPath = std::filesystem::path(appData); + SDL_free(appData); + return appDataPath; +} + +FrontendSDL::FrontendSDL() : emu({ std::filesystem::current_path() / EmulatorConfigFilename, getAppDataPath() / EmulatorConfigFilename }, getAppDataPath()), keyboardMappings(InputMappings::defaultKeyboardMappings()) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { Helpers::panic("Failed to initialize SDL2"); }