/* Copyright (C) 2010-2020 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (disk_index_file.c). * --------------------------------------------------------------------------------------- * * Permission is hereby granted, free of charge, * to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include "file_path_special.h" #include "verbosity.h" #include "msg_hash.h" #include "disk_index_file.h" /****************/ /* JSON Helpers */ /****************/ typedef struct { unsigned *current_entry_uint_val; char **current_entry_str_val; unsigned image_index; char *image_path; } DCifJSONContext; static bool DCifJSONObjectMemberHandler(void* context, const char *pValue, size_t length) { DCifJSONContext *pCtx = (DCifJSONContext*)context; /* something went wrong */ if (pCtx->current_entry_str_val) return false; if (length) { if (string_is_equal(pValue, "image_index")) pCtx->current_entry_uint_val = &pCtx->image_index; else if (string_is_equal(pValue, "image_path")) pCtx->current_entry_str_val = &pCtx->image_path; /* ignore unknown members */ } return true; } static bool DCifJSONNumberHandler(void* context, const char *pValue, size_t length) { DCifJSONContext *pCtx = (DCifJSONContext*)context; if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue)) *pCtx->current_entry_uint_val = string_to_unsigned(pValue); /* ignore unknown members */ pCtx->current_entry_uint_val = NULL; return true; } static bool DCifJSONStringHandler(void* context, const char *pValue, size_t length) { DCifJSONContext *pCtx = (DCifJSONContext*)context; if (pCtx->current_entry_str_val && length && !string_is_empty(pValue)) { if (*pCtx->current_entry_str_val) free(*pCtx->current_entry_str_val); *pCtx->current_entry_str_val = strdup(pValue); } /* ignore unknown members */ pCtx->current_entry_str_val = NULL; return true; } /******************/ /* Initialisation */ /******************/ /* Parses disk index file referenced by * disk_index_file->file_path. * Does nothing if disk index file does not exist. */ static bool disk_index_file_read(disk_index_file_t *disk_index_file) { const char *file_path = NULL; bool success = false; DCifJSONContext context = {0}; RFILE *file = NULL; rjson_t* parser; /* Sanity check */ if (!disk_index_file) return false; file_path = disk_index_file->file_path; if ( string_is_empty(file_path) || !path_is_valid(file_path) ) return false; /* Attempt to open disk index file */ file = filestream_open( file_path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (!file) { RARCH_ERR( "[disk index file] Failed to open disk index record file: %s\n", file_path); return false; } /* Initialise JSON parser */ if (!(parser = rjson_open_rfile(file))) { RARCH_ERR("[disk index file] Failed to create JSON parser.\n"); goto end; } /* Configure parser */ rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM); /* Read file */ if (rjson_parse(parser, &context, DCifJSONObjectMemberHandler, DCifJSONStringHandler, DCifJSONNumberHandler, NULL, NULL, NULL, NULL, /* unused object/array handlers */ NULL, NULL) /* unused boolean/null handlers */ != RJSON_DONE) { if (rjson_get_source_context_len(parser)) { RARCH_ERR( "[disk index file] Error parsing chunk of disk index file: %s\n---snip---\n%.*s\n---snip---\n", file_path, rjson_get_source_context_len(parser), rjson_get_source_context_buf(parser)); } RARCH_WARN( "[disk index file] Error parsing disk index file: %s\n", file_path); RARCH_ERR("[disk index file] Error: Invalid JSON at line %d, column %d - %s.\n", (int)rjson_get_source_line(parser), (int)rjson_get_source_column(parser), (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error")); } /* Free parser */ rjson_free(parser); /* Copy values read from JSON file */ disk_index_file->image_index = context.image_index; if (!string_is_empty(context.image_path)) strlcpy( disk_index_file->image_path, context.image_path, sizeof(disk_index_file->image_path)); else disk_index_file->image_path[0] = '\0'; success = true; end: /* Clean up leftover strings */ if (context.image_path) free(context.image_path); /* Close log file */ filestream_close(file); return success; } /* Initialises existing disk index record, loading * current parameters if a record file exists. * Returns false if arguments are invalid. */ bool disk_index_file_init( disk_index_file_t *disk_index_file, const char *content_path, const char *dir_savefile) { size_t len; char content_name[NAME_MAX_LENGTH]; char disk_index_file_dir[DIR_MAX_LENGTH]; /* Sanity check */ if (!disk_index_file) return false; /* Disk index records are only valid when loading * content (i.e. they do not apply to contentless * cores) */ if (string_is_empty(content_path)) goto error; /* Build disk index file path */ fill_pathname(content_name, path_basename(content_path), "", sizeof(content_name)); if (string_is_empty(content_name)) goto error; /* > Get disk index file directory */ if (!string_is_empty(dir_savefile)) strlcpy(disk_index_file_dir, dir_savefile, sizeof(disk_index_file_dir)); else { /* Use content directory */ strlcpy(disk_index_file_dir, content_path, sizeof(disk_index_file_dir)); path_basedir(disk_index_file_dir); } /* > Create directory, if required */ if ( !path_is_directory(disk_index_file_dir) && !path_mkdir(disk_index_file_dir)) { RARCH_ERR( "[disk index file] failed to create directory for disk index file: %s\n", disk_index_file_dir); goto error; } /* > Generate final path */ len = fill_pathname_join_special( disk_index_file->file_path, disk_index_file_dir, content_name, sizeof(disk_index_file->file_path)); strlcpy(disk_index_file->file_path + len, ".ldci", sizeof(disk_index_file->file_path) - len); /* All is well - reset disk_index_file_t and * attempt to load values from file */ disk_index_file->modified = false; disk_index_file->image_index = 0; disk_index_file->image_path[0] = '\0'; /* > If file does not exist (or some other * error occurs) then this is a new record * - in this case, 'modified' flag should * be set to 'true' */ if (!disk_index_file_read(disk_index_file)) disk_index_file->modified = true; return true; error: disk_index_file->modified = false; disk_index_file->image_index = 0; disk_index_file->image_path[0] = '\0'; disk_index_file->file_path[0] = '\0'; return false; } /***********/ /* Setters */ /***********/ /* Sets image index and path */ void disk_index_file_set( disk_index_file_t *disk_index_file, unsigned image_index, const char *image_path) { if (!disk_index_file) return; /* Check whether image index should be updated */ if (disk_index_file->image_index != image_index) { disk_index_file->image_index = image_index; disk_index_file->modified = true; } /* Check whether image path should be updated */ if (!string_is_empty(image_path)) { if (!string_is_equal(disk_index_file->image_path, image_path)) { strlcpy( disk_index_file->image_path, image_path, sizeof(disk_index_file->image_path)); disk_index_file->modified = true; } } else if (!string_is_empty(disk_index_file->image_path)) { disk_index_file->image_path[0] = '\0'; disk_index_file->modified = true; } } /**********/ /* Saving */ /**********/ /* Saves specified disk index file to disk */ bool disk_index_file_save(disk_index_file_t *disk_index_file) { const char *file_path; rjsonwriter_t* writer; RFILE *file = NULL; bool success = false; /* Sanity check */ if (!disk_index_file) return false; /* > Only save file if record has been modified. * We return true in this case - since there * was nothing to write, there can be no * 'failure' */ if (!disk_index_file->modified) return true; file_path = disk_index_file->file_path; if (string_is_empty(file_path)) return false; RARCH_LOG( "[disk index file] Saving disk index file: %s\n", file_path); /* Attempt to open disk index file */ if (!(file = filestream_open( file_path, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE))) { RARCH_ERR( "[disk index file] Failed to open disk index file: %s\n", file_path); return false; } /* Initialise JSON writer */ if (!(writer = rjsonwriter_open_rfile(file))) { RARCH_ERR("[disk index file] Failed to create JSON writer.\n"); goto end; } /* Write output file */ rjsonwriter_raw(writer, "{", 1); rjsonwriter_raw(writer, "\n", 1); /* > Version entry */ rjsonwriter_add_spaces(writer, 2); rjsonwriter_add_string(writer, "version"); rjsonwriter_raw(writer, ":", 1); rjsonwriter_raw(writer, " ", 1); rjsonwriter_add_string(writer, "1.0"); rjsonwriter_raw(writer, ",", 1); rjsonwriter_raw(writer, "\n", 1); /* > image index entry */ rjsonwriter_add_spaces(writer, 2); rjsonwriter_add_string(writer, "image_index"); rjsonwriter_raw(writer, ":", 1); rjsonwriter_raw(writer, " ", 1); rjsonwriter_rawf(writer, "%u", disk_index_file->image_index); rjsonwriter_raw(writer, ",", 1); rjsonwriter_raw(writer, "\n", 1); /* > image path entry */ rjsonwriter_add_spaces(writer, 2); rjsonwriter_add_string(writer, "image_path"); rjsonwriter_raw(writer, ":", 1); rjsonwriter_raw(writer, " ", 1); rjsonwriter_add_string(writer, disk_index_file->image_path); rjsonwriter_raw(writer, "\n", 1); /* > Finalise */ rjsonwriter_raw(writer, "}", 1); rjsonwriter_raw(writer, "\n", 1); /* Free JSON writer */ if (!rjsonwriter_free(writer)) { RARCH_ERR("[disk index file] Error writing disk index file: %s\n", file_path); } /* Changes have been written - record * is no longer considered to be in a * 'modified' state */ disk_index_file->modified = false; success = true; end: /* Close disk index file */ filestream_close(file); return success; }