config/data folders improvements

linux: look for legacy ~/.reicast and ~/.reicast/data
else look for ~/.config/flycast and ~/.local/share/flycast
and ~/.config/reicast and ~/.local/share/reicast
(defaults to flycast)

look for bios files in home folder and data folders (android, windows
,macos), then in game folder.
on linux, search in /usr/share/flycast and /usr/local/share/flycast and
legacy locations
This commit is contained in:
Flyinghead 2020-11-26 16:45:57 +01:00
parent d485da19b7
commit 8f77a5482a
25 changed files with 292 additions and 214 deletions

View File

@ -81,7 +81,7 @@ bool cfgOpen()
// Config dir not set (android onboarding) // Config dir not set (android onboarding)
return false; return false;
const char* filename = "/emu.cfg"; const char* filename = "emu.cfg";
std::string config_path_read = get_readonly_config_path(filename); std::string config_path_read = get_readonly_config_path(filename);
cfgPath = get_writable_config_path(filename); cfgPath = get_writable_config_path(filename);

View File

@ -4,7 +4,6 @@
void AICA_Sample(); void AICA_Sample();
void AICA_Sample32(); void AICA_Sample32();
//u32 ReadChannelReg(u32 channel,u32 reg);
void WriteChannelReg(u32 channel, u32 reg, int size); void WriteChannelReg(u32 channel, u32 reg, int size);
void sgc_Init(); void sgc_Init();

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <cmath> #include <cmath>
#include "types.h" #include "types.h"
#include "stdclass.h"
struct MemChip struct MemChip
{ {
@ -75,54 +76,41 @@ struct MemChip
} }
} }
bool Load(const std::string& root, const std::string& prefix, const std::string& names_ro, const std::string& title) bool Load(const std::string& prefix, const std::string& names_ro, const std::string& title)
{ {
char base[512]; const size_t npos = std::string::npos;
char temp[512]; size_t start = 0;
char names[512]; while (start < names_ro.size())
// FIXME: Data loss if buffer is too small
strncpy(names,names_ro.c_str(), sizeof(names));
names[sizeof(names) - 1] = '\0';
sprintf(base,"%s",root.c_str());
char* curr=names;
char* next;
do
{ {
next=strstr(curr,";"); size_t semicolon = names_ro.find(';', start);
if(next) *next=0; std::string name = names_ro.substr(start, semicolon == npos ? semicolon : semicolon - start);
if (curr[0]=='%')
{
sprintf(temp,"%s%s%s",base,prefix.c_str(),curr+1);
}
else
{
sprintf(temp,"%s%s",base,curr);
}
curr=next+1;
if (Load(temp)) size_t percent = name.find('%');
if (percent != npos)
name = name.replace(percent, 1, prefix);
std::string fullpath = get_readonly_data_path(name);
if (file_exists(fullpath) && Load(fullpath))
{ {
INFO_LOG(FLASHROM, "Loaded %s as %s", temp, title.c_str()); INFO_LOG(FLASHROM, "Loaded %s as %s", fullpath.c_str(), title.c_str());
return true; return true;
} }
} while(next);
start = semicolon;
if (start != npos)
start++;
}
return false; return false;
} }
void Save(const std::string& root, const std::string& prefix, const std::string& name_ro, const std::string& title)
{
char path[512];
sprintf(path,"%s%s%s",root.c_str(),prefix.c_str(),name_ro.c_str()); void Save(const std::string& prefix, const std::string& name_ro, const std::string& title)
{
std::string path = get_writable_data_path(prefix + name_ro);
Save(path); Save(path);
INFO_LOG(FLASHROM, "Saved %s as %s", path, title.c_str()); INFO_LOG(FLASHROM, "Saved %s as %s", path.c_str(), title.c_str());
} }
virtual void Reset() {} virtual void Reset() {}
virtual bool Serialize(void **data, unsigned int *total_size) { return true; } virtual bool Serialize(void **data, unsigned int *total_size) { return true; }
virtual bool Unserialize(void **data, unsigned int *total_size) { return true; } virtual bool Unserialize(void **data, unsigned int *total_size) { return true; }

View File

@ -164,11 +164,11 @@ void FixUpFlash()
} }
} }
static bool nvmem_load(const std::string& root) static bool nvmem_load()
{ {
bool rc; bool rc;
if (settings.platform.system == DC_PLATFORM_DREAMCAST) if (settings.platform.system == DC_PLATFORM_DREAMCAST)
rc = sys_nvmem->Load(root, getRomPrefix(), "%nvmem.bin", "nvram"); rc = sys_nvmem->Load(getRomPrefix(), "%nvmem.bin", "nvram");
else else
rc = sys_nvmem->Load(get_game_save_prefix() + ".nvmem"); rc = sys_nvmem->Load(get_game_save_prefix() + ".nvmem");
if (!rc) if (!rc)
@ -180,12 +180,12 @@ static bool nvmem_load(const std::string& root)
return true; return true;
} }
bool LoadRomFiles(const std::string& root) bool LoadRomFiles()
{ {
nvmem_load(root); nvmem_load();
if (settings.platform.system != DC_PLATFORM_ATOMISWAVE) if (settings.platform.system != DC_PLATFORM_ATOMISWAVE)
{ {
if (sys_rom->Load(root, getRomPrefix(), "%boot.bin;%boot.bin.bin;%bios.bin;%bios.bin.bin", "bootrom")) if (sys_rom->Load(getRomPrefix(), "%boot.bin;%boot.bin.bin;%bios.bin;%bios.bin.bin", "bootrom"))
bios_loaded = true; bios_loaded = true;
else if (settings.platform.system == DC_PLATFORM_DREAMCAST) else if (settings.platform.system == DC_PLATFORM_DREAMCAST)
return false; return false;
@ -194,19 +194,19 @@ bool LoadRomFiles(const std::string& root)
return true; return true;
} }
void SaveRomFiles(const std::string& root) void SaveRomFiles()
{ {
if (settings.platform.system == DC_PLATFORM_DREAMCAST) if (settings.platform.system == DC_PLATFORM_DREAMCAST)
sys_nvmem->Save(root, getRomPrefix(), "nvmem.bin", "nvmem"); sys_nvmem->Save(getRomPrefix(), "nvmem.bin", "nvmem");
else else
sys_nvmem->Save(get_game_save_prefix() + ".nvmem"); sys_nvmem->Save(get_game_save_prefix() + ".nvmem");
if (settings.platform.system == DC_PLATFORM_ATOMISWAVE) if (settings.platform.system == DC_PLATFORM_ATOMISWAVE)
sys_rom->Save(get_game_save_prefix() + ".nvmem2"); sys_rom->Save(get_game_save_prefix() + ".nvmem2");
} }
bool LoadHle(const std::string& root) bool LoadHle()
{ {
if (!nvmem_load(root)) if (!nvmem_load())
WARN_LOG(FLASHROM, "No nvmem loaded\n"); WARN_LOG(FLASHROM, "No nvmem loaded\n");
reios_reset(sys_rom->data); reios_reset(sys_rom->data);

View File

@ -7,4 +7,9 @@ void map_area0(u32 base);
//Init/Res/Term //Init/Res/Term
void sh4_area0_Init(); void sh4_area0_Init();
void sh4_area0_Reset(bool Manual); void sh4_area0_Reset(bool Manual);
void sh4_area0_Term(); void sh4_area0_Term();
bool LoadRomFiles();
void SaveRomFiles();
bool LoadHle();
void FixUpFlash();

View File

@ -482,8 +482,11 @@ struct maple_sega_vmu: maple_base
memset(flash_data, 0, sizeof(flash_data)); memset(flash_data, 0, sizeof(flash_data));
memset(lcd_data, 0, sizeof(lcd_data)); memset(lcd_data, 0, sizeof(lcd_data));
char tempy[512]; char tempy[512];
sprintf(tempy, "/vmu_save_%s.bin", logical_port); sprintf(tempy, "vmu_save_%s.bin", logical_port);
std::string apath = get_writable_data_path(tempy); // VMU saves used to be stored in .reicast, not in .reicast/data
std::string apath = get_writable_config_path(tempy);
if (!file_exists(apath))
apath = get_writable_data_path(tempy);
file = fopen(apath.c_str(), "rb+"); file = fopen(apath.c_str(), "rb+");
if (!file) if (!file)
@ -492,25 +495,19 @@ struct maple_sega_vmu: maple_base
file = fopen(apath.c_str(), "wb"); file = fopen(apath.c_str(), "wb");
if (file) { if (file) {
if (!init_emptyvmu()) if (!init_emptyvmu())
INFO_LOG(MAPLE, "Failed to initialize an empty VMU, you should reformat it using the BIOS"); WARN_LOG(MAPLE, "Failed to initialize an empty VMU, you should reformat it using the BIOS");
fwrite(flash_data, sizeof(flash_data), 1, file); fwrite(flash_data, sizeof(flash_data), 1, file);
fseek(file, 0, SEEK_SET); fseek(file, 0, SEEK_SET);
} }
else else
{ {
INFO_LOG(MAPLE, "Unable to create VMU!"); ERROR_LOG(MAPLE, "Failed to create VMU save file \"%s\"", apath.c_str());
} }
} }
if (!file) if (file != nullptr)
{
INFO_LOG(MAPLE, "Failed to create VMU save file \"%s\"", apath.c_str());
}
else
{
fread(flash_data, 1, sizeof(flash_data), file); fread(flash_data, 1, sizeof(flash_data), file);
}
u8 sum = 0; u8 sum = 0;
for (u32 i = 0; i < sizeof(flash_data); i++) for (u32 i = 0; i < sizeof(flash_data); i++)
@ -521,20 +518,15 @@ struct maple_sega_vmu: maple_base
if (init_emptyvmu()) if (init_emptyvmu())
{ {
if (!file) if (file != nullptr)
file = fopen(apath.c_str(), "wb"); {
if (file) {
fwrite(flash_data, sizeof(flash_data), 1, file); fwrite(flash_data, sizeof(flash_data), 1, file);
fseek(file, 0, SEEK_SET); fseek(file, 0, SEEK_SET);
} }
else {
INFO_LOG(MAPLE, "Unable to create VMU!");
}
} }
else else
{ {
INFO_LOG(MAPLE, "Failed to initialize an empty VMU, you should reformat it using the BIOS"); WARN_LOG(MAPLE, "Failed to initialize an empty VMU, you should reformat it using the BIOS");
} }
} }
@ -3001,7 +2993,7 @@ u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_ou
jvs_length = length - 2; jvs_length = length - 2;
u8 calc_crc = 0; u8 calc_crc = 0;
for (int i = 1; i < length; i++) for (u32 i = 1; i < length; i++)
calc_crc = ((calc_crc + buffer_out[i]) & 0xFF); calc_crc = ((calc_crc + buffer_out[i]) & 0xFF);
JVS_OUT(calc_crc); JVS_OUT(calc_crc);

View File

@ -76,8 +76,12 @@ static bool naomi_LoadBios(const char *filename, Archive *child_archive, Archive
struct BIOS_t *bios = &BIOS[biosid]; struct BIOS_t *bios = &BIOS[biosid];
std::string basepath = get_readonly_data_path(DATA_PATH); std::string arch_name(filename);
std::unique_ptr<Archive> bios_archive(OpenArchive((basepath + filename).c_str())); std::string path = get_readonly_data_path(arch_name + ".zip");
if (!file_exists(path.c_str()))
path = get_readonly_data_path(arch_name + ".7z");
DEBUG_LOG(NAOMI, "Loading BIOS from %s", path.c_str());
std::unique_ptr<Archive> bios_archive(OpenArchive(path.c_str()));
bool found_region = false; bool found_region = false;
@ -232,7 +236,7 @@ static void naomi_cart_LoadZip(const char *filename)
{ {
// If a specific BIOS is needed for this game, fail. // If a specific BIOS is needed for this game, fail.
if (game->bios != NULL || !bios_loaded) if (game->bios != NULL || !bios_loaded)
throw NaomiCartException(std::string("Error: cannot load BIOS ") + (game->bios != NULL ? game->bios : "naomi.zip") + " in " + get_readonly_data_path(DATA_PATH)); throw NaomiCartException(std::string("Error: cannot load BIOS ") + (game->bios != NULL ? game->bios : "naomi.zip"));
// otherwise use the default BIOS // otherwise use the default BIOS
} }

View File

@ -652,7 +652,7 @@ void print_blocks()
if (print_stats) if (print_stats)
{ {
f=fopen(get_writable_data_path("/blkmap.lst").c_str(),"w"); f=fopen(get_writable_data_path("blkmap.lst").c_str(),"w");
print_stats=0; print_stats=0;
INFO_LOG(DYNAREC, "Writing blocks to %p", f); INFO_LOG(DYNAREC, "Writing blocks to %p", f);
@ -680,9 +680,7 @@ void print_blocks()
fprintf(f,"host_opcodes: %d\n",blk->host_opcodes); fprintf(f,"host_opcodes: %d\n",blk->host_opcodes);
fprintf(f,"il_opcodes: %zd\n",blk->oplist.size()); fprintf(f,"il_opcodes: %zd\n",blk->oplist.size());
u32 hcode=0;
s32 gcode=-1; s32 gcode=-1;
u8* pucode=(u8*)blk->code;
size_t j=0; size_t j=0;

View File

@ -223,12 +223,6 @@ void mem_Term()
sh4_mmr_term(); sh4_mmr_term();
sh4_area0_Term(); sh4_area0_Term();
// done by emulator thread
//SaveRomFiles(get_writable_data_path(DATA_PATH));
//mem_b.Term(); // handled by vmem
//vmem
_vmem_term(); _vmem_term();
} }

View File

@ -85,8 +85,4 @@ u8* GetMemPtr(u32 Addr,u32 size);
bool IsOnRam(u32 addr); bool IsOnRam(u32 addr);
bool LoadRomFiles(const std::string& root);
void SaveRomFiles(const std::string& root);
bool LoadHle(const std::string& root);
void FixUpFlash();
void SetMemoryHandlers(); void SetMemoryHandlers();

View File

@ -205,7 +205,7 @@ std::shared_ptr<InputMapping> InputMapping::LoadMapping(const char *name)
if (it != loaded_mappings.end()) if (it != loaded_mappings.end())
return it->second; return it->second;
std::string path = get_writable_config_path((std::string("/mappings/") + name).c_str()); std::string path = get_readonly_config_path((std::string("mappings/") + name).c_str());
FILE *fp = fopen(path.c_str(), "r"); FILE *fp = fopen(path.c_str(), "r");
if (fp == NULL) if (fp == NULL)
return NULL; return NULL;
@ -222,9 +222,9 @@ bool InputMapping::save(const char *name)
if (!dirty) if (!dirty)
return true; return true;
std::string path = get_writable_config_path("/mappings/"); std::string path = get_writable_config_path("mappings/");
make_directory(path); make_directory(path);
path = get_writable_config_path((std::string("/mappings/") + name).c_str()); path = get_writable_config_path((std::string("mappings/") + name).c_str());
FILE *fp = fopen(path.c_str(), "w"); FILE *fp = fopen(path.c_str(), "w");
if (fp == NULL) if (fp == NULL)
{ {

View File

@ -151,89 +151,139 @@ void* rend_thread(void* p);
} }
#endif #endif
// Find the user config directory.
// The following folders are checked in this order:
// $HOME/.reicast
// $HOME/.config/flycast
// $HOME/.config/reicast
// If no folder exists, $HOME/.config/flycast is created and used.
std::string find_user_config_dir() std::string find_user_config_dir()
{ {
struct stat info; struct stat info;
std::string home = ""; std::string xdg_home;
if(getenv("HOME") != NULL) if (getenv("HOME") != NULL)
{ {
// Support for the legacy config dir at "$HOME/.reicast" // Support for the legacy config dir at "$HOME/.reicast"
std::string legacy_home = (std::string)getenv("HOME") + "/.reicast"; std::string legacy_home = (std::string)getenv("HOME") + "/.reicast/";
if((stat(legacy_home.c_str(), &info) == 0) && (info.st_mode & S_IFDIR)) if (stat(legacy_home.c_str(), &info) == 0 && (info.st_mode & S_IFDIR))
{
// "$HOME/.reicast" already exists, let's use it! // "$HOME/.reicast" already exists, let's use it!
return legacy_home; return legacy_home;
}
/* If $XDG_CONFIG_HOME is not set, we're supposed to use "$HOME/.config" instead. /* If $XDG_CONFIG_HOME is not set, we're supposed to use "$HOME/.config" instead.
* Consult the XDG Base Directory Specification for details: * Consult the XDG Base Directory Specification for details:
* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables
*/ */
home = (std::string)getenv("HOME") + "/.config/reicast"; xdg_home = (std::string)getenv("HOME") + "/.config";
} }
if(getenv("XDG_CONFIG_HOME") != NULL) if (getenv("XDG_CONFIG_HOME") != NULL)
{
// If XDG_CONFIG_HOME is set explicitly, we'll use that instead of $HOME/.config // If XDG_CONFIG_HOME is set explicitly, we'll use that instead of $HOME/.config
home = (std::string)getenv("XDG_CONFIG_HOME") + "/reicast"; xdg_home = (std::string)getenv("XDG_CONFIG_HOME");
}
if(!home.empty()) if (!xdg_home.empty())
{ {
if((stat(home.c_str(), &info) != 0) || !(info.st_mode & S_IFDIR)) std::string fullpath = xdg_home + "/flycast/";
{ if (stat(fullpath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR))
// If the directory doesn't exist yet, create it! // Found .config/flycast
mkdir(home.c_str(), 0755); return fullpath;
} fullpath = xdg_home + "/reicast/";
return home; if (stat(fullpath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR))
// Found .config/reicast
return fullpath;
// Create .config/flycast
fullpath = xdg_home + "/flycast/";
mkdir(fullpath.c_str(), 0755);
return fullpath;
} }
// Unable to detect config dir, use the current folder // Unable to detect config dir, use the current folder
return "."; return ".";
} }
// Find the user data directory.
// The following folders are checked in this order:
// $HOME/.reicast/data
// $HOME/.local/share/flycast
// $HOME/.local/share/reicast
// If no folder exists, $HOME/.local/share/flycast is created and used.
std::string find_user_data_dir() std::string find_user_data_dir()
{ {
struct stat info; struct stat info;
std::string data = ""; std::string xdg_home;
if(getenv("HOME") != NULL) if (getenv("HOME") != NULL)
{ {
// Support for the legacy config dir at "$HOME/.reicast" // Support for the legacy config dir at "$HOME/.reicast/data"
std::string legacy_data = (std::string)getenv("HOME") + "/.reicast"; std::string legacy_data = (std::string)getenv("HOME") + "/.reicast/data/";
if((stat(legacy_data.c_str(), &info) == 0) && (info.st_mode & S_IFDIR)) if (stat(legacy_data.c_str(), &info) == 0 && (info.st_mode & S_IFDIR))
{ // "$HOME/.reicast/data" already exists, let's use it!
// "$HOME/.reicast" already exists, let's use it!
return legacy_data; return legacy_data;
}
/* If $XDG_DATA_HOME is not set, we're supposed to use "$HOME/.local/share" instead. /* If $XDG_DATA_HOME is not set, we're supposed to use "$HOME/.local/share" instead.
* Consult the XDG Base Directory Specification for details: * Consult the XDG Base Directory Specification for details:
* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables
*/ */
data = (std::string)getenv("HOME") + "/.local/share/reicast"; xdg_home = (std::string)getenv("HOME") + "/.local/share";
} }
if(getenv("XDG_DATA_HOME") != NULL) if (getenv("XDG_DATA_HOME") != NULL)
// If XDG_DATA_HOME is set explicitly, we'll use that instead of $HOME/.local/share
xdg_home = (std::string)getenv("XDG_DATA_HOME");
if (!xdg_home.empty())
{ {
// If XDG_DATA_HOME is set explicitly, we'll use that instead of $HOME/.config std::string fullpath = xdg_home + "/flycast/";
data = (std::string)getenv("XDG_DATA_HOME") + "/reicast"; if (stat(fullpath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR))
// Found .local/share/flycast
return fullpath;
fullpath = xdg_home + "/reicast/";
if (stat(fullpath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR))
// Found .local/share/reicast
return fullpath;
// Create .local/share/flycast
fullpath = xdg_home + "/flycast/";
mkdir(fullpath.c_str(), 0755);
return fullpath;
} }
if(!data.empty()) // Unable to detect data dir, use the current folder
{
if((stat(data.c_str(), &info) != 0) || !(info.st_mode & S_IFDIR))
{
// If the directory doesn't exist yet, create it!
mkdir(data.c_str(), 0755);
}
return data;
}
// Unable to detect config dir, use the current folder
return "."; return ".";
} }
// Find a file in the user and system config directories.
// The following folders are checked in this order:
// $HOME/.reicast
// $HOME/.config/flycast
// $HOME/.config/reicast
// if XDG_CONFIG_DIRS is defined:
// <$XDG_CONFIG_DIRS>/flycast
// <$XDG_CONFIG_DIRS>/reicast
// else
// /etc/flycast/
// /etc/xdg/flycast/
// .
std::vector<std::string> find_system_config_dirs() std::vector<std::string> find_system_config_dirs()
{ {
std::vector<std::string> dirs; std::vector<std::string> dirs;
std::string xdg_home;
if (getenv("HOME") != NULL)
{
// Support for the legacy config dir at "$HOME/.reicast"
dirs.push_back((std::string)getenv("HOME") + "/.reicast/");
xdg_home = (std::string)getenv("HOME") + "/.config";
}
if (getenv("XDG_CONFIG_HOME") != NULL)
// If XDG_CONFIG_HOME is set explicitly, we'll use that instead of $HOME/.config
xdg_home = (std::string)getenv("XDG_CONFIG_HOME");
if (!xdg_home.empty())
{
// XDG config locations
dirs.push_back(xdg_home + "/flycast/");
dirs.push_back(xdg_home + "/reicast/");
}
if (getenv("XDG_CONFIG_DIRS") != NULL) if (getenv("XDG_CONFIG_DIRS") != NULL)
{ {
std::string s = (std::string)getenv("XDG_CONFIG_DIRS"); std::string s = (std::string)getenv("XDG_CONFIG_DIRS");
@ -242,24 +292,61 @@ std::vector<std::string> find_system_config_dirs()
std::string::size_type n = s.find(':', pos); std::string::size_type n = s.find(':', pos);
while(n != std::string::npos) while(n != std::string::npos)
{ {
dirs.push_back(s.substr(pos, n-pos) + "/reicast"); dirs.push_back(s.substr(pos, n-pos) + "/flycast/");
dirs.push_back(s.substr(pos, n-pos) + "/reicast/");
pos = n + 1; pos = n + 1;
n = s.find(':', pos); n = s.find(':', pos);
} }
// Separator not found // Separator not found
dirs.push_back(s.substr(pos) + "/reicast"); dirs.push_back(s.substr(pos) + "/flycast/");
dirs.push_back(s.substr(pos) + "/reicast/");
} }
else else
{ {
dirs.push_back("/etc/reicast"); // This isn't part of the XDG spec, but much more common than /etc/xdg/ dirs.push_back("/etc/flycast/"); // This isn't part of the XDG spec, but much more common than /etc/xdg/
dirs.push_back("/etc/xdg/reicast"); dirs.push_back("/etc/xdg/flycast/");
} }
dirs.push_back("./");
return dirs; return dirs;
} }
// Find a file in the user data directories.
// The following folders are checked in this order:
// $HOME/.reicast/data
// $HOME/.local/share/flycast
// $HOME/.local/share/reicast
// if XDG_DATA_DIRS is defined:
// <$XDG_DATA_DIRS>/flycast
// <$XDG_DATA_DIRS>/reicast
// else
// /usr/local/share/flycast
// /usr/share/flycast
// /usr/local/share/reicast
// /usr/share/reicast
// ./data
std::vector<std::string> find_system_data_dirs() std::vector<std::string> find_system_data_dirs()
{ {
std::vector<std::string> dirs; std::vector<std::string> dirs;
std::string xdg_home;
if (getenv("HOME") != NULL)
{
// Support for the legacy data dir at "$HOME/.reicast/data"
dirs.push_back((std::string)getenv("HOME") + "/.reicast/data/");
xdg_home = (std::string)getenv("HOME") + "/.local/share";
}
if (getenv("XDG_DATA_HOME") != NULL)
// If XDG_DATA_HOME is set explicitly, we'll use that instead of $HOME/.local/share
xdg_home = (std::string)getenv("XDG_DATA_HOME");
if (!xdg_home.empty())
{
// XDG data locations
dirs.push_back(xdg_home + "/flycast/");
dirs.push_back(xdg_home + "/reicast/");
dirs.push_back(xdg_home + "/reicast/data/");
}
if (getenv("XDG_DATA_DIRS") != NULL) if (getenv("XDG_DATA_DIRS") != NULL)
{ {
std::string s = (std::string)getenv("XDG_DATA_DIRS"); std::string s = (std::string)getenv("XDG_DATA_DIRS");
@ -268,18 +355,25 @@ std::vector<std::string> find_system_data_dirs()
std::string::size_type n = s.find(':', pos); std::string::size_type n = s.find(':', pos);
while(n != std::string::npos) while(n != std::string::npos)
{ {
dirs.push_back(s.substr(pos, n-pos) + "/reicast"); dirs.push_back(s.substr(pos, n-pos) + "/flycast/");
dirs.push_back(s.substr(pos, n-pos) + "/reicast/");
pos = n + 1; pos = n + 1;
n = s.find(':', pos); n = s.find(':', pos);
} }
// Separator not found // Separator not found
dirs.push_back(s.substr(pos) + "/reicast"); dirs.push_back(s.substr(pos) + "/flycast/");
dirs.push_back(s.substr(pos) + "/reicast/");
} }
else else
{ {
dirs.push_back("/usr/local/share/reicast"); dirs.push_back("/usr/local/share/flycast/");
dirs.push_back("/usr/share/reicast"); dirs.push_back("/usr/share/flycast/");
dirs.push_back("/usr/local/share/reicast/");
dirs.push_back("/usr/share/reicast/");
} }
dirs.push_back("./");
dirs.push_back("data/");
return dirs; return dirs;
} }
@ -291,22 +385,15 @@ int main(int argc, char* argv[])
signal(SIGKILL, clean_exit); signal(SIGKILL, clean_exit);
#endif #endif
/* Set directories */ // Set directories
set_user_config_dir(find_user_config_dir()); set_user_config_dir(find_user_config_dir());
set_user_data_dir(find_user_data_dir()); set_user_data_dir(find_user_data_dir());
std::vector<std::string> dirs; for (const auto& dir : find_system_config_dirs())
dirs = find_system_config_dirs(); add_system_config_dir(dir);
for (std::size_t i = 0; i < dirs.size(); i++) for (const auto& dir : find_system_data_dirs())
{ add_system_data_dir(dir);
add_system_data_dir(dirs[i]); INFO_LOG(BOOT, "Config dir is: %s", get_writable_config_path("").c_str());
} INFO_LOG(BOOT, "Data dir is: %s", get_writable_data_path("").c_str());
dirs = find_system_data_dirs();
for (std::size_t i = 0; i < dirs.size(); i++)
{
add_system_data_dir(dirs[i]);
}
INFO_LOG(BOOT, "Config dir is: %s", get_writable_config_path("/").c_str());
INFO_LOG(BOOT, "Data dir is: %s", get_writable_data_path("/").c_str());
#if defined(USE_SDL) #if defined(USE_SDL)
// init video now: on rpi3 it installs a sigsegv handler(?) // init video now: on rpi3 it installs a sigsegv handler(?)

View File

@ -121,7 +121,7 @@ static int allocate_shared_filemem(unsigned size) {
// if shmem does not work (or using OSX) fallback to a regular file on disk // if shmem does not work (or using OSX) fallback to a regular file on disk
if (fd < 0) { if (fd < 0) {
std::string path = get_writable_data_path("/dcnzorz_mem"); std::string path = get_writable_data_path("dcnzorz_mem");
fd = open(path.c_str(), O_CREAT|O_RDWR|O_TRUNC, S_IRWXU|S_IRWXG|S_IRWXO); fd = open(path.c_str(), O_CREAT|O_RDWR|O_TRUNC, S_IRWXU|S_IRWXG|S_IRWXO);
unlink(path.c_str()); unlink(path.c_str());
} }

View File

@ -131,7 +131,7 @@ LogManager::LogManager()
if (cfgLoadBool("log", "LogToFile", false)) if (cfgLoadBool("log", "LogToFile", false))
{ {
#ifdef __ANDROID__ #ifdef __ANDROID__
std::string logPath = get_writable_data_path("/flycast.log"); std::string logPath = get_writable_data_path("flycast.log");
#else #else
std::string logPath = "flycast.log"; std::string logPath = "flycast.log";
#endif #endif

View File

@ -15,6 +15,7 @@
#include "hw/maple/maple_cfg.h" #include "hw/maple/maple_cfg.h"
#include "hw/sh4/sh4_mem.h" #include "hw/sh4/sh4_mem.h"
#include "hw/holly/sb_mem.h"
#include "hw/naomi/naomi_cart.h" #include "hw/naomi/naomi_cart.h"
#include "reios/reios.h" #include "reios/reios.h"
@ -551,15 +552,14 @@ static void dc_start_game(const char *path)
dc_reset(true); dc_reset(true);
LoadSettings(false); LoadSettings(false);
std::string data_path = get_readonly_data_path(DATA_PATH);
if (settings.platform.system == DC_PLATFORM_DREAMCAST) if (settings.platform.system == DC_PLATFORM_DREAMCAST)
{ {
if ((settings.bios.UseReios && !forced_bios_file) || !LoadRomFiles(data_path)) if ((settings.bios.UseReios && !forced_bios_file) || !LoadRomFiles())
{ {
if (forced_bios_file) if (forced_bios_file)
throw ReicastException("No BIOS file found"); throw ReicastException("No BIOS file found");
if (!LoadHle(data_path)) if (!LoadHle())
throw ReicastException("Failed to initialize HLE BIOS"); throw ReicastException("Failed to initialize HLE BIOS");
NOTICE_LOG(BOOT, "Did not load BIOS, using reios"); NOTICE_LOG(BOOT, "Did not load BIOS, using reios");
@ -567,7 +567,7 @@ static void dc_start_game(const char *path)
} }
else else
{ {
LoadRomFiles(data_path); LoadRomFiles();
} }
if (settings.platform.system == DC_PLATFORM_DREAMCAST) if (settings.platform.system == DC_PLATFORM_DREAMCAST)
{ {
@ -591,7 +591,7 @@ static void dc_start_game(const char *path)
// Content load failed. Boot the BIOS // Content load failed. Boot the BIOS
settings.imgread.ImagePath[0] = '\0'; settings.imgread.ImagePath[0] = '\0';
forced_bios_file = true; forced_bios_file = true;
if (!LoadRomFiles(data_path)) if (!LoadRomFiles())
throw ReicastException("No BIOS file found"); throw ReicastException("No BIOS file found");
InitDrive(); InitDrive();
} }
@ -656,7 +656,7 @@ void* dc_run(void*)
sh4_cpu.Run(); sh4_cpu.Run();
SaveRomFiles(get_writable_data_path(DATA_PATH)); SaveRomFiles();
if (reset_requested) if (reset_requested)
{ {
dc_reset(false); dc_reset(false);
@ -933,7 +933,7 @@ static void LoadCustom()
if (*p == '\0') if (*p == '\0')
return; return;
} }
else if (settings.platform.system == DC_PLATFORM_NAOMI || settings.platform.system == DC_PLATFORM_ATOMISWAVE) else
{ {
reios_id = naomi_game_id; reios_id = naomi_game_id;
} }
@ -951,9 +951,9 @@ void SaveSettings()
{ {
cfgSetAutoSave(false); cfgSetAutoSave(false);
cfgSaveBool("config", "Dynarec.Enabled", settings.dynarec.Enable); cfgSaveBool("config", "Dynarec.Enabled", settings.dynarec.Enable);
if (forced_game_cable == -1 || forced_game_cable != settings.dreamcast.cable) if (forced_game_cable == -1 || forced_game_cable != (int)settings.dreamcast.cable)
cfgSaveInt("config", "Dreamcast.Cable", settings.dreamcast.cable); cfgSaveInt("config", "Dreamcast.Cable", settings.dreamcast.cable);
if (forced_game_region == -1 || forced_game_region != settings.dreamcast.region) if (forced_game_region == -1 || forced_game_region != (int)settings.dreamcast.region)
cfgSaveInt("config", "Dreamcast.Region", settings.dreamcast.region); cfgSaveInt("config", "Dreamcast.Region", settings.dreamcast.region);
cfgSaveInt("config", "Dreamcast.Broadcast", settings.dreamcast.broadcast); cfgSaveInt("config", "Dreamcast.Broadcast", settings.dreamcast.broadcast);
cfgSaveBool("config", "Dreamcast.ForceWindowsCE", settings.dreamcast.ForceWindowsCE); cfgSaveBool("config", "Dreamcast.ForceWindowsCE", settings.dreamcast.ForceWindowsCE);
@ -1064,7 +1064,7 @@ static void cleanup_serialize(void *data)
free(data) ; free(data) ;
} }
static std::string get_savestate_file_path() static std::string get_savestate_file_path(bool writable)
{ {
std::string state_file = settings.imgread.ImagePath; std::string state_file = settings.imgread.ImagePath;
size_t lastindex = state_file.find_last_of('/'); size_t lastindex = state_file.find_last_of('/');
@ -1081,7 +1081,10 @@ static std::string get_savestate_file_path()
if (lastindex != std::string::npos) if (lastindex != std::string::npos)
state_file = state_file.substr(0, lastindex); state_file = state_file.substr(0, lastindex);
state_file = state_file + ".state"; state_file = state_file + ".state";
return get_writable_data_path(DATA_PATH) + state_file; if (writable)
return get_writable_data_path(state_file);
else
return get_readonly_data_path(state_file);
} }
void dc_savestate() void dc_savestate()
@ -1121,7 +1124,7 @@ void dc_savestate()
return; return;
} }
filename = get_savestate_file_path(); filename = get_savestate_file_path(true);
f = fopen(filename.c_str(), "wb") ; f = fopen(filename.c_str(), "wb") ;
if ( f == NULL ) if ( f == NULL )
@ -1150,7 +1153,7 @@ void dc_loadstate()
dc_stop(); dc_stop();
filename = get_savestate_file_path(); filename = get_savestate_file_path(false);
f = fopen(filename.c_str(), "rb") ; f = fopen(filename.c_str(), "rb") ;
if ( f == NULL ) if ( f == NULL )

View File

@ -739,7 +739,7 @@ void reios_reset(u8* rom)
// 7078 24 × 24 pixels (72 bytes) characters // 7078 24 × 24 pixels (72 bytes) characters
// 129 32 × 32 pixels (128 bytes) characters // 129 32 × 32 pixels (128 bytes) characters
memset(pFont, 0, 536496); memset(pFont, 0, 536496);
FILE *font = fopen(get_readonly_data_path(DATA_PATH "font.bin").c_str(), "rb"); FILE *font = fopen(get_readonly_data_path("font.bin").c_str(), "rb");
if (font == NULL) if (font == NULL)
{ {
INFO_LOG(REIOS, "font.bin not found. Using built-in font"); INFO_LOG(REIOS, "font.bin not found. Using built-in font");

View File

@ -102,7 +102,7 @@ bool CustomTexture::Init()
std::string game_id = GetGameId(); std::string game_id = GetGameId();
if (game_id.length() > 0) if (game_id.length() > 0)
{ {
textures_path = get_readonly_data_path(DATA_PATH) + "textures/" + game_id + "/"; textures_path = get_readonly_data_path("textures/" + game_id) + "/";
DIR *dir = opendir(textures_path.c_str()); DIR *dir = opendir(textures_path.c_str());
if (dir != NULL) if (dir != NULL)
@ -156,7 +156,7 @@ void CustomTexture::LoadCustomTextureAsync(BaseTextureCacheData *texture_data)
void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, void *src_buffer) void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, void *src_buffer)
{ {
std::string base_dump_dir = get_writable_data_path(DATA_PATH "texdump/"); std::string base_dump_dir = get_writable_data_path("texdump/");
if (!file_exists(base_dump_dir)) if (!file_exists(base_dump_dir))
make_directory(base_dump_dir); make_directory(base_dump_dir);
std::string game_id = GetGameId(); std::string game_id = GetGameId();

View File

@ -1674,8 +1674,10 @@ static void systemdir_selected_callback(bool cancelled, std::string selection)
{ {
if (!cancelled) if (!cancelled)
{ {
selection += "/";
set_user_config_dir(selection); set_user_config_dir(selection);
set_user_data_dir(selection); add_system_data_dir(selection);
set_user_data_dir(selection + "data/");
if (cfgOpen()) if (cfgOpen())
{ {
LoadSettings(false); LoadSettings(false);

View File

@ -150,7 +150,7 @@ u8 *loadOSDButtons(int &width, int &height)
{ {
int n; int n;
stbi_set_flip_vertically_on_load(1); stbi_set_flip_vertically_on_load(1);
u8 *image_data = stbi_load(get_readonly_data_path(DATA_PATH "buttons.png").c_str(), &width, &height, &n, STBI_rgb_alpha); u8 *image_data = stbi_load(get_readonly_data_path("buttons.png").c_str(), &width, &height, &n, STBI_rgb_alpha);
if (image_data == nullptr) if (image_data == nullptr)
{ {
if (DefaultOSDButtons.empty()) if (DefaultOSDButtons.empty())

View File

@ -33,7 +33,7 @@
VulkanContext *VulkanContext::contextInstance; VulkanContext *VulkanContext::contextInstance;
static const char *PipelineCacheFileName = DATA_PATH "vulkan_pipeline.cache"; static const char *PipelineCacheFileName = "vulkan_pipeline.cache";
#ifndef __ANDROID__ #ifndef __ANDROID__
VKAPI_ATTR static VkBool32 VKAPI_CALL debugUtilsMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, VKAPI_ATTR static VkBool32 VKAPI_CALL debugUtilsMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes,
@ -450,7 +450,7 @@ bool VulkanContext::InitDevice()
10000, ARRAY_SIZE(pool_sizes), pool_sizes)); 10000, ARRAY_SIZE(pool_sizes), pool_sizes));
std::string cachePath = get_writable_data_path(PipelineCacheFileName); std::string cachePath = get_readonly_data_path(PipelineCacheFileName);
FILE *f = fopen(cachePath.c_str(), "rb"); FILE *f = fopen(cachePath.c_str(), "rb");
if (f == nullptr) if (f == nullptr)
pipelineCache = device->createPipelineCacheUnique(vk::PipelineCacheCreateInfo()); pipelineCache = device->createPipelineCacheUnique(vk::PipelineCacheCreateInfo());

View File

@ -20,10 +20,10 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
std::string user_config_dir; static std::string user_config_dir;
std::string user_data_dir; static std::string user_data_dir;
std::vector<std::string> system_config_dirs; static std::vector<std::string> system_config_dirs;
std::vector<std::string> system_data_dirs; static std::vector<std::string> system_data_dirs;
bool file_exists(const std::string& filename) bool file_exists(const std::string& filename)
{ {
@ -55,24 +55,20 @@ std::string get_writable_config_path(const std::string& filename)
/* Only stuff in the user_config_dir is supposed to be writable, /* Only stuff in the user_config_dir is supposed to be writable,
* so we always return that. * so we always return that.
*/ */
return (user_config_dir + filename); return user_config_dir + filename;
} }
std::string get_readonly_config_path(const std::string& filename) std::string get_readonly_config_path(const std::string& filename)
{ {
std::string user_filepath = get_writable_config_path(filename); std::string user_filepath = get_writable_config_path(filename);
if(file_exists(user_filepath)) if (file_exists(user_filepath))
{
return user_filepath; return user_filepath;
}
std::string filepath; for (const auto& config_dir : system_config_dirs)
for (size_t i = 0; i < system_config_dirs.size(); i++) { {
filepath = system_config_dirs[i] + filename; std::string filepath = config_dir + filename;
if (file_exists(filepath)) if (file_exists(filepath))
{
return filepath; return filepath;
}
} }
// Not found, so we return the user variant // Not found, so we return the user variant
@ -84,31 +80,31 @@ std::string get_writable_data_path(const std::string& filename)
/* Only stuff in the user_data_dir is supposed to be writable, /* Only stuff in the user_data_dir is supposed to be writable,
* so we always return that. * so we always return that.
*/ */
return (user_data_dir + filename); return user_data_dir + filename;
} }
std::string get_readonly_data_path(const std::string& filename) std::string get_readonly_data_path(const std::string& filename)
{ {
std::string user_filepath = get_writable_data_path(filename); std::string user_filepath = get_writable_data_path(filename);
if(file_exists(user_filepath)) if (file_exists(user_filepath))
{
return user_filepath; return user_filepath;
}
std::string filepath; for (const auto& data_dir : system_data_dirs)
for (size_t i = 0; i < system_data_dirs.size(); i++) { {
filepath = system_data_dirs[i] + filename; std::string filepath = data_dir + filename;
if (file_exists(filepath)) if (file_exists(filepath))
{
return filepath; return filepath;
}
} }
// Try the game directory
std::string filepath = get_game_dir() + filename;
if (file_exists(filepath))
return filepath;
// Not found, so we return the user variant // Not found, so we return the user variant
return user_filepath; return user_filepath;
} }
static size_t get_last_slash_pos(const std::string& path) size_t get_last_slash_pos(const std::string& path)
{ {
size_t lastindex = path.find_last_of('/'); size_t lastindex = path.find_last_of('/');
#ifdef _WIN32 #ifdef _WIN32
@ -127,7 +123,7 @@ std::string get_game_save_prefix()
size_t lastindex = get_last_slash_pos(save_file); size_t lastindex = get_last_slash_pos(save_file);
if (lastindex != std::string::npos) if (lastindex != std::string::npos)
save_file = save_file.substr(lastindex + 1); save_file = save_file.substr(lastindex + 1);
return get_writable_data_path(DATA_PATH) + save_file; return get_writable_data_path(save_file);
} }
std::string get_game_basename() std::string get_game_basename()

View File

@ -49,12 +49,6 @@ public :
void Wait(); //Wait for signal , then reset[if auto] void Wait(); //Wait for signal , then reset[if auto]
}; };
#if !defined(TARGET_IPHONE)
#define DATA_PATH "/data/"
#else
#define DATA_PATH "/"
#endif
//Set the path ! //Set the path !
void set_user_config_dir(const std::string& dir); void set_user_config_dir(const std::string& dir);
void set_user_data_dir(const std::string& dir); void set_user_data_dir(const std::string& dir);
@ -72,6 +66,7 @@ bool make_directory(const std::string& path);
std::string get_game_save_prefix(); std::string get_game_save_prefix();
std::string get_game_basename(); std::string get_game_basename();
std::string get_game_dir(); std::string get_game_dir();
size_t get_last_slash_pos(const std::string& path);
bool mem_region_lock(void *start, std::size_t len); bool mem_region_lock(void *start, std::size_t len);
bool mem_region_unlock(void *start, std::size_t len); bool mem_region_unlock(void *start, std::size_t len);

View File

@ -192,11 +192,16 @@ LONG ExeptionHandler(EXCEPTION_POINTERS *ExceptionInfo)
void SetupPath() void SetupPath()
{ {
char fname[512]; char fname[512];
GetModuleFileName(0,fname,512); GetModuleFileName(0, fname, sizeof(fname));
std::string fn = std::string(fname); std::string fn = std::string(fname);
fn=fn.substr(0,fn.find_last_of('\\')); size_t pos = get_last_slash_pos(fn);
if (pos != std::string::npos)
fn = fn.substr(0, pos) + "\\";
else
fn = ".\\";
set_user_config_dir(fn); set_user_config_dir(fn);
set_user_data_dir(fn); add_system_data_dir(fn);
set_user_data_dir(fn + "data\\");
} }
static Win32KeyboardDevice keyboard(0); static Win32KeyboardDevice keyboard(0);

View File

@ -195,13 +195,22 @@ JNIEXPORT jstring JNICALL Java_com_reicast_emulator_emu_JNIdc_initEnvironment(JN
saveAndroidSettingsMid = env->GetMethodID(env->GetObjectClass(emulator), "SaveAndroidSettings", "(Ljava/lang/String;)V"); saveAndroidSettingsMid = env->GetMethodID(env->GetObjectClass(emulator), "SaveAndroidSettings", "(Ljava/lang/String;)V");
} }
// Set home directory based on User config // Set home directory based on User config
const char* path = homeDirectory != NULL ? env->GetStringUTFChars(homeDirectory, 0) : ""; if (homeDirectory != NULL)
set_user_config_dir(path); {
set_user_data_dir(path); const char *jchar = env->GetStringUTFChars(homeDirectory, 0);
std::string path = jchar;
if (!path.empty())
{
if (path.back() != '/')
path += '/';
set_user_config_dir(path);
add_system_data_dir(path);
set_user_data_dir(path + "data/");
}
env->ReleaseStringUTFChars(homeDirectory, jchar);
}
INFO_LOG(BOOT, "Config dir is: %s", get_writable_config_path("").c_str()); INFO_LOG(BOOT, "Config dir is: %s", get_writable_config_path("").c_str());
INFO_LOG(BOOT, "Data dir is: %s", get_writable_data_path("").c_str()); INFO_LOG(BOOT, "Data dir is: %s", get_writable_data_path("").c_str());
if (homeDirectory != NULL)
env->ReleaseStringUTFChars(homeDirectory, path);
if (first_init) if (first_init)
{ {

View File

@ -134,27 +134,32 @@ extern "C" void emu_gles_init(int width, int height) {
char *home = getenv("HOME"); char *home = getenv("HOME");
if (home != NULL) if (home != NULL)
{ {
std::string config_dir = std::string(home) + "/.reicast"; std::string config_dir = std::string(home) + "/.reicast/";
if (!file_exists(config_dir))
config_dir = std::string(home) + "/.flycast/";
int instanceNumber = (int)[[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.reicast.Flycast"] count]; int instanceNumber = (int)[[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.reicast.Flycast"] count];
if (instanceNumber > 1){ if (instanceNumber > 1){
config_dir += "/" + std::to_string(instanceNumber); config_dir += std::to_string(instanceNumber) + "/";
[[NSApp dockTile] setBadgeLabel:@(instanceNumber).stringValue]; [[NSApp dockTile] setBadgeLabel:@(instanceNumber).stringValue];
} }
mkdir(config_dir.c_str(), 0755); // create the directory if missing mkdir(config_dir.c_str(), 0755); // create the directory if missing
set_user_config_dir(config_dir); set_user_config_dir(config_dir);
add_system_data_dir(config_dir);
config_dir += "data/";
mkdir(config_dir.c_str(), 0755);
set_user_data_dir(config_dir); set_user_data_dir(config_dir);
} }
else else
{ {
set_user_config_dir("."); set_user_config_dir("./");
set_user_data_dir("."); set_user_data_dir("./");
} }
// Add bundle resources path // Add bundle resources path
CFBundleRef mainBundle = CFBundleGetMainBundle(); CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle); CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
char path[PATH_MAX]; char path[PATH_MAX];
if (CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX)) if (CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX))
add_system_data_dir(std::string(path)); add_system_data_dir(std::string(path) + "/");
CFRelease(resourcesURL); CFRelease(resourcesURL);
CFRelease(mainBundle); CFRelease(mainBundle);