Skip to content

Commit

Permalink
Decouple data save paths from Emulator
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
twvd committed Dec 26, 2024
1 parent 8cc9bfb commit 80d8337
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 57 deletions.
6 changes: 5 additions & 1 deletion include/emulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include "gl/context.h"
#endif

static const std::string EmulatorConfigFilename = "config.toml";

struct SDL_Window;

enum class ROMType {
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ void EmulatorConfig::load() {
return;
}

printf("Loading existing configuration file %s\n", path.string().c_str());
toml::value data;

try {
Expand Down
58 changes: 18 additions & 40 deletions src/emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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() {}

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/hydra_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
12 changes: 11 additions & 1 deletion src/jni_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand All @@ -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");
Expand Down
14 changes: 2 additions & 12 deletions src/libretro_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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() {
Expand Down
5 changes: 4 additions & 1 deletion src/panda_qt/main_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
9 changes: 8 additions & 1 deletion src/panda_sdl/frontend_sdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down

0 comments on commit 80d8337

Please sign in to comment.