# HG changeset patch # User Michael Pavone # Date 1460770179 25200 # Node ID 229c23b3ab7366fdc793e918293e4ee4753cede3 # Parent cbc5b39e5518b52caba9d05c55d8d6a9f71eef80 Switch to storing SRAM/EEPROM and save states in a per-game directory rather than next to the ROM (for SRAM/EEPROM) or in the current working directory (for save states) diff -r cbc5b39e5518 -r 229c23b3ab73 blastem.c --- a/blastem.c Tue Apr 12 22:50:31 2016 -0700 +++ b/blastem.c Fri Apr 15 18:29:39 2016 -0700 @@ -192,6 +192,7 @@ int break_on_sync = 0; int save_state = 0; +char *save_state_path; //#define DO_DEBUG_PRINT #ifdef DO_DEBUG_PRINT @@ -309,8 +310,8 @@ { sync_z80(z_context, z_context->current_cycle + MCLKS_PER_Z80); } - save_gst(gen, "savestate.gst", address); - puts("Saved state to savestate.gst"); + save_gst(gen, save_state_path, address); + printf("Saved state to %s\n", save_state_path); } else if(save_state) { context->sync_cycle = context->current_cycle + 1; } @@ -974,6 +975,25 @@ } } +void setup_saves(char *fname, rom_info *info) +{ + char * barename = basename_no_extension(fname); + char const * parts[3] = {get_save_dir(), "/", barename}; + char *save_dir = alloc_concat_m(3, parts); + if (!ensure_dir_exists(save_dir)) { + warning("Failed to create save directory %s\n", save_dir); + } + parts[0] = save_dir; + parts[2] = info->save_type == SAVE_I2C ? "save.eeprom" : "save.sram"; + free(save_filename); + save_filename = alloc_concat_m(3, parts); + parts[2] = "quicksave.gst"; + free(save_state_path); + save_state_path = alloc_concat_m(3, parts); + info->save_dir = save_dir; + free(barename); +} + int main(int argc, char ** argv) { set_exe_str(argv[0]); @@ -1150,21 +1170,7 @@ if (!headless) { render_init(width, height, title, fps, fullscreen); } - int fname_size = strlen(romfname); - char * ext = info.save_type == SAVE_I2C ? "eeprom" : "sram"; - save_filename = malloc(fname_size+strlen(ext) + 2); - memcpy(save_filename, romfname, fname_size); - int i; - for (i = fname_size-1; fname_size >= 0; --i) { - if (save_filename[i] == '.') { - strcpy(save_filename + i + 1, ext); - break; - } - } - if (i < 0) { - save_filename[fname_size] = '.'; - strcpy(save_filename + fname_size + 1, ext); - } + setup_saves(romfname, &info); genesis = alloc_init_genesis(&info, fps, (ym_log && !menu) ? YM_OPT_WAVE_LOG : 0); if (menu) { @@ -1189,7 +1195,7 @@ genesis = menu_context; } free(game_context->cart); - free(save_filename); + free(info.save_dir); base_map[0].buffer = ram = game_context->work_ram; } else { base_map[0].buffer = ram = malloc(RAM_WORDS * sizeof(uint16_t)); @@ -1202,20 +1208,7 @@ byteswap_rom(rom_size); set_region(&info, force_version); update_title(info.name); - fname_size = strlen(menu_context->next_rom); - ext = info.save_type == SAVE_I2C ? "eeprom" : "sram"; - save_filename = malloc(fname_size+strlen(ext) + 2); - memcpy(save_filename, menu_context->next_rom, fname_size); - for (i = fname_size-1; fname_size >= 0; --i) { - if (save_filename[i] == '.') { - strcpy(save_filename + i + 1, ext); - break; - } - } - if (i < 0) { - save_filename[fname_size] = '.'; - strcpy(save_filename + fname_size + 1, ext); - } + setup_saves(menu_context->next_rom, &info); if (!game_context) { //start a new arena and save old one in suspended genesis context genesis->arena = start_new_arena(); diff -r cbc5b39e5518 -r 229c23b3ab73 romdb.h --- a/romdb.h Tue Apr 12 22:50:31 2016 -0700 +++ b/romdb.h Fri Apr 15 18:29:39 2016 -0700 @@ -37,6 +37,7 @@ typedef struct { char *name; + char *save_dir; memmap_chunk *map; uint8_t *save_buffer; eeprom_map *eeprom_map; diff -r cbc5b39e5518 -r 229c23b3ab73 util.c --- a/util.c Tue Apr 12 22:50:31 2016 -0700 +++ b/util.c Fri Apr 15 18:29:39 2016 -0700 @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef __ANDROID__ #include @@ -93,6 +94,32 @@ return text+1; } +char * basename_no_extension(char *path) +{ + char *lastdot = NULL; + char *lastslash = NULL; + char *cur; + for (cur = path; *cur; cur++) + { + if (*cur == '.') { + lastdot = cur; + } else if (*cur == '/') { + lastslash = cur + 1; + } + } + if (!lastdot) { + lastdot = cur; + } + if (!lastslash) { + lastslash = path; + } + char *barename = malloc(lastdot-lastslash+1); + memcpy(barename, lastslash, lastdot-lastslash); + barename[lastdot-lastslash] = 0; + + return barename; +} + uint32_t nearest_pow2(uint32_t val) { uint32_t ret = 1; @@ -339,6 +366,32 @@ free(list); } +int ensure_dir_exists(char *path) +{ + struct stat st; + if (stat(path, &st)) { + if (errno == ENOENT) { + char *parent = strdup(path); + char *sep = strrchr(parent, '/'); + if (sep && sep != parent) { + *sep = 0; + if (!ensure_dir_exists(parent)) { + free(parent); + return 0; + } + free(parent); + } + return mkdir(path, 0777) == 0; + } else { + char buf[80]; + strerror_r(errno, buf, sizeof(buf)); + warning("stat failed with error: %s", buf); + return 0; + } + } + return S_ISDIR(st.st_mode); +} + #endif #ifdef __ANDROID__ @@ -377,6 +430,11 @@ return SDL_AndroidGetInternalStoragePath(); } +char const *get_save_dir() +{ + return SDL_AndroidGetInternalStoragePath(); +} + #else char *read_bundled_file(char *name, long *sizeret) @@ -430,4 +488,16 @@ return confdir; } +char const *get_save_dir() +{ + static char* savedir; + if (!savedir) { + char *homedir = get_home_dir(); + if (homedir) { + savedir = alloc_concat(homedir, "/.local/share/blastem"); + } + } + return savedir; +} + #endif diff -r cbc5b39e5518 -r 229c23b3ab73 util.h --- a/util.h Tue Apr 12 22:50:31 2016 -0700 +++ b/util.h Fri Apr 15 18:29:39 2016 -0700 @@ -20,6 +20,8 @@ char * strip_ws(char * text); //Inserts a null after the first word, returns a pointer to the second word char * split_keyval(char * text); +//Returns the basename of a path with th extension (if any) stripped +char * basename_no_extension(char *path); //Gets the smallest power of two that is >= a certain value, won't work for values > 0x80000000 uint32_t nearest_pow2(uint32_t val); //Should be called by main with the value of argv[0] for use by get_exe_dir @@ -30,12 +32,16 @@ char * get_home_dir(); //Returns an appropriate path for storing config files char const *get_config_dir(); +//Returns an appropriate path for saving non-config data like savestates +char const *get_save_dir(); //Reads a file bundled with the executable char *read_bundled_file(char *name, long *sizeret); //Retunrs an array of normal files and directories residing in a directory dir_entry *get_dir_list(char *path, size_t *numret); //Frees a dir list returned by get_dir_list void free_dir_list(dir_entry *list, size_t numentries); +//Recusrively creates a directory if it does not exist +int ensure_dir_exists(char *path); //Returns the contents of a symlink in a newly allocated string char * readlink_alloc(char * path); //Prints an error message to stderr and to a message box if not in headless mode and then exits