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.
This commit is contained in:
Thomas 2024-12-26 21:43:42 +01:00
parent 8cc9bfbb36
commit 80d8337592
8 changed files with 50 additions and 57 deletions

View file

@ -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<std::filesystem::path> configSearchPaths, std::filesystem::path appDataPath);
~Emulator();
void step();

View file

@ -27,6 +27,7 @@ void EmulatorConfig::load() {
return;
}
printf("Loading existing configuration file %s\n", path.string().c_str());
toml::value data;
try {

View file

@ -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<std::filesystem::path> paths) {
for (std::filesystem::path p: paths) {
if (std::filesystem::exists(p)) {
return p;
}
}
return paths.back();
}
Emulator::Emulator(std::vector<std::filesystem::path> 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

View file

@ -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");
}

View file

@ -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<Emulator>();
auto appPath = getAndroidAppPath();
emulator = std::make_unique<Emulator>({ appPath / EmulatorConfigFilename }, appPath);
if (emulator->getRendererType() != RendererType::OpenGL) {
return throwException(env, "Renderer type is not OpenGL");

View file

@ -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> 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>();
emulator = std::make_unique<Emulator>(({ std::filesystem::path(savePath / EmulatorConfigFilename) }, std::filesystem::path(savePath / "Emulator Files")));
}
void retro_deinit() {

View file

@ -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"));

View file

@ -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");
}