diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 0bd42f24b7..71aae2ecab 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -4841,7 +4841,7 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_CONTENT_FAVORITES_SIZE, - "Limit the number of entries in the favorites playlist. Once limit is reached, new additions will be prevented until old entries are removed. Setting a value of -1 allows 'unlimited' (99999) entries. WARNING: Reducing the value will delete existing entries!" + "Limit the number of entries in the favorites playlist. Once limit is reached, new additions will be prevented until old entries are removed. Setting a value of -1 allows 'unlimited' entries. WARNING: Reducing the value will delete existing entries!" ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_RENAME, diff --git a/libretro-common/include/array/rbuf.h b/libretro-common/include/array/rbuf.h new file mode 100644 index 0000000000..388542beba --- /dev/null +++ b/libretro-common/include/array/rbuf.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (rbuf.h). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#ifndef __LIBRETRO_SDK_ARRAY_RBUF_H__ +#define __LIBRETRO_SDK_ARRAY_RBUF_H__ + +/* + * This file implements stretchy buffers as invented (?) by Sean Barrett. + * Based on the implementation from the public domain Bitwise project + * by Per Vognsen - https://github.com/pervognsen/bitwise + * + * It's a super simple type safe dynamic array for C with no need + * to predeclare any type or anything. + * The first time an element is added, memory for 16 elements are allocated. + * Then every time length is about to exceed capacity, capacity is doubled. + * + * Sample usage: + * + * mytype_t* buf = NULL; + * RBUF_PUSH(buf, some_element); + * RBUF_PUSH(buf, other_element); + * -- now RBUF_LEN(buf) == 2, buf[0] == some_element, buf[1] == other_element + * + * -- Free allocated memory: + * RBUF_FREE(buf); + * -- now buf == NULL, RBUF_LEN(buf) == 0, RBUF_CAP(buf) == 0 + * + * -- Explicitly increase allocated memory and set capacity: + * RBUF_FIT(buf, 100); + * -- now RBUF_LEN(buf) == 0, RBUF_CAP(buf) == 100 + * + * -- Resize buffer (does not initialize or zero memory!) + * RBUF_RESIZE(buf, 200); + * -- now RBUF_LEN(buf) == 200, RBUF_CAP(buf) == 200 + * + * -- To handle running out of memory: + * bool ran_out_of_memory = !RBUF_TRYFIT(buf, 1000); + * -- before RESIZE or PUSH. When out of memory, buf will stay unmodified. + */ + +#include /* for MAX */ +#include /* for malloc, realloc */ + +#define RBUF__HDR(b) (((struct rbuf__hdr *)(b))-1) + +#define RBUF_LEN(b) ((b) ? RBUF__HDR(b)->len : 0) +#define RBUF_CAP(b) ((b) ? RBUF__HDR(b)->cap : 0) +#define RBUF_END(b) ((b) + RBUF_LEN(b)) +#define RBUF_SIZEOF(b) ((b) ? RBUF_LEN(b)*sizeof(*b) : 0) + +#define RBUF_FREE(b) ((b) ? (free(RBUF__HDR(b)), (b) = NULL) : 0) +#define RBUF_FIT(b, n) ((size_t)(n) <= RBUF_CAP(b) ? 0 : (*(void**)(&(b)) = rbuf__grow((b), (n), sizeof(*(b))))) +#define RBUF_PUSH(b, val) (RBUF_FIT((b), 1 + RBUF_LEN(b)), (b)[RBUF__HDR(b)->len++] = (val)) +#define RBUF_POP(b) (b)[--RBUF__HDR(b)->len] +#define RBUF_RESIZE(b, sz) (RBUF_FIT((b), (sz)), ((b) ? RBUF__HDR(b)->len = (sz) : 0)) +#define RBUF_CLEAR(b) ((b) ? RBUF__HDR(b)->len = 0 : 0) +#define RBUF_TRYFIT(b, n) (RBUF_FIT((b), (n)), (((b) && RBUF_CAP(b) >= (size_t)(n)) || !(n))) + +struct rbuf__hdr +{ + size_t len; + size_t cap; +}; + +static void *rbuf__grow(void *buf, + size_t new_len, size_t elem_size) +{ + struct rbuf__hdr *new_hdr; + size_t new_cap = MAX(2 * RBUF_CAP(buf), MAX(new_len, 16)); + size_t new_size = sizeof(struct rbuf__hdr) + new_cap*elem_size; + if (buf) + { + new_hdr = (struct rbuf__hdr *)realloc(RBUF__HDR(buf), new_size); + if (!new_hdr) + return buf; /* out of memory, return unchanged */ + } + else + { + new_hdr = (struct rbuf__hdr *)malloc(new_size); + if (!new_hdr) + return NULL; /* out of memory */ + new_hdr->len = 0; + } + new_hdr->cap = new_cap; + return new_hdr + 1; +} + +#endif diff --git a/menu/menu_explore.c b/menu/menu_explore.c index f8089eaf08..96db855619 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -23,21 +23,7 @@ #include "../libretro-db/libretrodb.h" #include #include - -/* Stretchy buffers, invented (?) by Sean Barrett */ -#define EX_BUF__HDR(b) (((struct ex_buf_hdr *)(b))-1) - -#define EX_BUF_LEN(b) ((b) ? EX_BUF__HDR(b)->len : 0) -#define EX_BUF_CAP(b) ((b) ? EX_BUF__HDR(b)->cap : 0) -#define EX_BUF_END(b) ((b) + EX_BUF_LEN(b)) -#define EX_BUF_SIZEOF(b) ((b) ? EX_BUF_LEN(b)*sizeof(*b) : 0) - -#define EX_BUF_FREE(b) ((b) ? (free(EX_BUF__HDR(b)), (b) = NULL) : 0) -#define EX_BUF_FIT(b, n) ((size_t)(n) <= EX_BUF_CAP(b) ? 0 : (*(void**)(&(b)) = ex_buf__grow((b), (n), sizeof(*(b))))) -#define EX_BUF_PUSH(b, val) (EX_BUF_FIT((b), 1 + EX_BUF_LEN(b)), (b)[EX_BUF__HDR(b)->len++] = (val)) -#define EX_BUF_POP(b) (b)[--EX_BUF__HDR(b)->len] -#define EX_BUF_RESIZE(b, sz) (EX_BUF_FIT((b), (sz)), ((b) ? EX_BUF__HDR(b)->len = (sz) : 0)) -#define EX_BUF_CLEAR(b) ((b) ? EX_BUF__HDR(b)->len = 0 : 0) +#include #define EX_ARENA_ALIGNMENT 8 #define EX_ARENA_BLOCK_SIZE (64 * 1024) @@ -70,12 +56,6 @@ enum EXPLORE_TYPE_FIRSTITEM = EXPLORE_TYPE_FIRSTCATEGORY + EXPLORE_CAT_COUNT }; -struct ex_buf_hdr -{ - size_t len; - size_t cap; -}; - /* Arena allocator */ typedef struct ex_arena { @@ -147,30 +127,13 @@ explore_by_info[EXPLORE_CAT_COUNT] = /* TODO/FIXME - static global */ static explore_state_t* explore_state; -static void *ex_buf__grow(const void *buf, - size_t new_len, size_t elem_size) -{ - struct ex_buf_hdr *new_hdr; - size_t new_cap = MAX(2 * EX_BUF_CAP(buf), MAX(new_len, 16)); - size_t new_size = sizeof(struct ex_buf_hdr) + new_cap*elem_size; - if (buf) - new_hdr = (struct ex_buf_hdr *)realloc(EX_BUF__HDR(buf), new_size); - else - { - new_hdr = (struct ex_buf_hdr *)malloc(new_size); - new_hdr->len = 0; - } - new_hdr->cap = new_cap; - return new_hdr + 1; -} - static void ex_arena_grow(ex_arena *arena, size_t min_size) { size_t size = EX_ARENA_ALIGN_UP( MAX(min_size, EX_ARENA_BLOCK_SIZE), EX_ARENA_ALIGNMENT); arena->ptr = (char *)malloc(size); arena->end = arena->ptr + size; - EX_BUF_PUSH(arena->blocks, arena->ptr); + RBUF_PUSH(arena->blocks, arena->ptr); } static void *ex_arena_alloc(ex_arena *arena, size_t size) @@ -190,10 +153,10 @@ static void ex_arena_free(ex_arena *arena) { char **it; - for (it = arena->blocks; it != EX_BUF_END(arena->blocks); it++) + for (it = arena->blocks; it != RBUF_END(arena->blocks); it++) free(*it); - EX_BUF_FREE(arena->blocks); + RBUF_FREE(arena->blocks); arena->ptr = NULL; arena->end = NULL; arena->blocks = NULL; @@ -459,14 +422,14 @@ static void explore_add_unique_string( sizeof(explore_string_t) + len); memcpy(entry->str, str, len); entry->str[len] = '\0'; - EX_BUF_PUSH(explore->by[cat], entry); + RBUF_PUSH(explore->by[cat], entry); ex_hashmap32_setptr(&maps[cat], hash, entry); } if (!e->by[cat]) e->by[cat] = entry; else - EX_BUF_PUSH(*split_buf, entry); + RBUF_PUSH(*split_buf, entry); if (*p_next == '\0') return; @@ -493,7 +456,7 @@ static void explore_unload_icons(explore_state_t *state) unsigned i; if (!state) return; - for (i = 0; i != EX_BUF_LEN(state->icons); i++) + for (i = 0; i != RBUF_LEN(state->icons); i++) if (state->icons[i]) video_driver_texture_unload(&state->icons[i]); } @@ -504,16 +467,16 @@ static void explore_free(explore_state_t *state) if (!state) return; for (i = 0; i != EXPLORE_CAT_COUNT; i++) - EX_BUF_FREE(state->by[i]); + RBUF_FREE(state->by[i]); - EX_BUF_FREE(state->entries); + RBUF_FREE(state->entries); - for (i = 0; i != EX_BUF_LEN(state->playlists); i++) + for (i = 0; i != RBUF_LEN(state->playlists); i++) playlist_free(state->playlists[i]); - EX_BUF_FREE(state->playlists); + RBUF_FREE(state->playlists); explore_unload_icons(state); - EX_BUF_FREE(state->icons); + RBUF_FREE(state->icons); ex_arena_free(&state->arena); } @@ -525,8 +488,14 @@ static void explore_load_icons(explore_state_t *state) if (!state) return; - system_count = EX_BUF_LEN(state->by[EXPLORE_BY_SYSTEM]); - EX_BUF_RESIZE(state->icons, system_count); + system_count = RBUF_LEN(state->by[EXPLORE_BY_SYSTEM]); + + /* unload any icons that could exist from a previous call to this */ + explore_unload_icons(state); + + /* RBUF_RESIZE leaves memory uninitialised, have to zero it 'manually' */ + RBUF_RESIZE(state->icons, system_count); + memset(state->icons, 0, RBUF_SIZEOF(state->icons)); fill_pathname_application_special(path, sizeof(path), APPLICATION_SPECIAL_DIRECTORY_ASSETS_SYSICONS); @@ -539,7 +508,6 @@ static void explore_load_icons(explore_state_t *state) for (i = 0; i != system_count; i++) { struct texture_image ti; - state->icons[i] = 0; strlcpy(path + pathlen, state->by[EXPLORE_BY_SYSTEM][i]->str, sizeof(path) - pathlen); @@ -689,8 +657,8 @@ static explore_state_t *explore_build_list(void) continue; } - EX_BUF_PUSH(rdbs, newrdb); - rdb_num = (uintptr_t)EX_BUF_LEN(rdbs); + RBUF_PUSH(rdbs, newrdb); + rdb_num = (uintptr_t)RBUF_LEN(rdbs); ex_hashmap32_setnum(&rdb_indices, rdb_hash, rdb_num); } @@ -715,14 +683,14 @@ static explore_state_t *explore_build_list(void) } if (used_entries) - EX_BUF_PUSH(explore->playlists, playlist); + RBUF_PUSH(explore->playlists, playlist); else playlist_free(playlist); } /* Loop through all RDBs referenced in the playlists * and load meta data strings */ - for (i = 0; i != EX_BUF_LEN(rdbs); i++) + for (i = 0; i != RBUF_LEN(rdbs); i++) { struct rmsgpack_dom_value item; struct explore_rdb* rdb = &rdbs[i]; @@ -841,19 +809,19 @@ static explore_state_t *explore_build_list(void) } #endif - if (EX_BUF_LEN(split_buf)) + if (RBUF_LEN(split_buf)) { size_t len; - EX_BUF_PUSH(split_buf, NULL); /* terminator */ - len = EX_BUF_SIZEOF(split_buf); + RBUF_PUSH(split_buf, NULL); /* terminator */ + len = RBUF_SIZEOF(split_buf); e.split = (explore_string_t **) ex_arena_alloc(&explore->arena, len); memcpy(e.split, split_buf, len); - EX_BUF_CLEAR(split_buf); + RBUF_CLEAR(split_buf); } - EX_BUF_PUSH(explore->entries, e); + RBUF_PUSH(explore->entries, e); /* if all entries have found connections, we can leave early */ if (--rdb->count == 0) @@ -870,14 +838,14 @@ static explore_state_t *explore_build_list(void) ex_hashmap32_free(&rdb->playlist_crcs); ex_hashmap32_free(&rdb->playlist_names); } - EX_BUF_FREE(split_buf); + RBUF_FREE(split_buf); ex_hashmap32_free(&rdb_indices); - EX_BUF_FREE(rdbs); + RBUF_FREE(rdbs); for (i = 0; i != EXPLORE_CAT_COUNT; i++) { uint32_t idx; - size_t len = EX_BUF_LEN(explore->by[i]); + size_t len = RBUF_LEN(explore->by[i]); if (explore->by[i]) qsort(explore->by[i], len, sizeof(*explore->by[i]), @@ -889,7 +857,7 @@ static explore_state_t *explore_build_list(void) ex_hashmap32_free(&cat_maps[i]); } qsort(explore->entries, - EX_BUF_LEN(explore->entries), + RBUF_LEN(explore->entries), sizeof(*explore->entries), explore_qsort_func_entries); return explore; } @@ -1085,7 +1053,7 @@ unsigned menu_displaylist_explore(file_list_t *list) explore_string_t **entries = explore_state->by[cat]; size_t tmplen; - if (!EX_BUF_LEN(entries)) + if (!RBUF_LEN(entries)) continue; for (i = 1; i < depth; i++) @@ -1100,14 +1068,14 @@ unsigned menu_displaylist_explore(file_list_t *list) if (explore_by_info[cat].is_numeric) { snprintf(tmp + tmplen, sizeof(tmp) - tmplen, " (%s - %s)", - entries[0]->str, entries[EX_BUF_LEN(entries) - 1]->str); + entries[0]->str, entries[RBUF_LEN(entries) - 1]->str); } else { strlcat(tmp, " (", sizeof(tmp)); snprintf(tmp + tmplen + 2, sizeof(tmp) - tmplen - 2, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_EXPLORE_ITEMS_COUNT), - (unsigned)EX_BUF_LEN(entries)); + (unsigned)RBUF_LEN(entries)); strlcat(tmp, ")", sizeof(tmp)); } } @@ -1133,7 +1101,7 @@ SKIP_EXPLORE_BY_CATEGORY:; { /* List all items in a selected explore by category */ explore_string_t **entries = explore_state->by[current_cat]; - unsigned i_last = EX_BUF_LEN(entries) - 1; + unsigned i_last = RBUF_LEN(entries) - 1; for (i = 0; i <= i_last; i++) explore_menu_entry(list, explore_state, entries[i]->str, EXPLORE_TYPE_FIRSTITEM + i); @@ -1223,7 +1191,7 @@ SKIP_EXPLORE_BY_CATEGORY:; } e = explore_state->entries; - e_end = EX_BUF_END(explore_state->entries); + e_end = RBUF_END(explore_state->entries); for (; e != e_end; e++) { @@ -1307,7 +1275,7 @@ SKIP_ENTRY:; strlcpy(explore_state->title, pl_entry->label, sizeof(explore_state->title)); - for (pl_idx = 0; pl_idx != EX_BUF_LEN(explore_state->playlists); pl_idx++) + for (pl_idx = 0; pl_idx != RBUF_LEN(explore_state->playlists); pl_idx++) { menu_displaylist_info_t info; const struct playlist_entry* pl_first = NULL; @@ -1348,12 +1316,12 @@ uintptr_t menu_explore_get_entry_icon(unsigned type) if (explore_state->show_icons == EXPLORE_ICONS_CONTENT) { explore_entry_t* e = &explore_state->entries[i]; - if (e < EX_BUF_END(explore_state->entries)) + if (e < RBUF_END(explore_state->entries)) return explore_state->icons[e->by[EXPLORE_BY_SYSTEM]->idx]; } else if (explore_state->show_icons == EXPLORE_ICONS_SYSTEM_CATEGORY) { - if (i < EX_BUF_LEN(explore_state->icons)) + if (i < RBUF_LEN(explore_state->icons)) return explore_state->icons[i]; } return 0; diff --git a/playlist.c b/playlist.c index 16d3305f45..e97c5895c5 100644 --- a/playlist.c +++ b/playlist.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "playlist.h" #include "verbosity.h" @@ -63,8 +64,6 @@ struct content_playlist enum playlist_thumbnail_mode left_thumbnail_mode; enum playlist_sort_mode sort_mode; - size_t size; - char *default_core_path; char *default_core_name; char *base_content_directory; @@ -78,6 +77,7 @@ typedef struct bool in_items; bool in_subsystem_roms; bool capacity_exceeded; + bool out_of_memory; unsigned array_depth; unsigned object_depth; @@ -355,7 +355,7 @@ uint32_t playlist_get_size(playlist_t *playlist) { if (!playlist) return 0; - return (uint32_t)playlist->size; + return (uint32_t)RBUF_LEN(playlist->entries); } char *playlist_get_conf_path(playlist_t *playlist) @@ -379,7 +379,7 @@ void playlist_get_index(playlist_t *playlist, size_t idx, const struct playlist_entry **entry) { - if (!playlist || !entry || (idx >= playlist->size)) + if (!playlist || !entry || (idx >= RBUF_LEN(playlist->entries))) return; *entry = &playlist->entries[idx]; @@ -452,16 +452,16 @@ static void playlist_free_entry(struct playlist_entry *entry) void playlist_delete_index(playlist_t *playlist, size_t idx) { - struct playlist_entry *entry_to_delete = NULL; + size_t len; + struct playlist_entry *entry_to_delete; if (!playlist) return; - if (idx >= playlist->size) + len = RBUF_LEN(playlist->entries); + if (idx >= len) return; - playlist->size = playlist->size - 1; - /* Free unwanted entry */ entry_to_delete = (struct playlist_entry *)(playlist->entries + idx); if (entry_to_delete) @@ -469,7 +469,9 @@ void playlist_delete_index(playlist_t *playlist, /* Shift remaining entries to fill the gap */ memmove(playlist->entries + idx, playlist->entries + idx + 1, - (playlist->size - idx) * sizeof(struct playlist_entry)); + (len - 1 - idx) * sizeof(struct playlist_entry)); + + RBUF_RESIZE(playlist->entries, len - 1); playlist->modified = true; } @@ -497,7 +499,7 @@ void playlist_delete_by_path(playlist_t *playlist, strlcpy(real_search_path, search_path, sizeof(real_search_path)); path_resolve_realpath(real_search_path, sizeof(real_search_path), true); - while (i < playlist->size) + while (i < RBUF_LEN(playlist->entries)) { if (!playlist_path_equal(real_search_path, playlist->entries[i].path, &playlist->config)) @@ -518,7 +520,7 @@ void playlist_get_index_by_path(playlist_t *playlist, const char *search_path, const struct playlist_entry **entry) { - size_t i; + size_t i, len; char real_search_path[PATH_MAX_LENGTH]; real_search_path[0] = '\0'; @@ -530,7 +532,7 @@ void playlist_get_index_by_path(playlist_t *playlist, strlcpy(real_search_path, search_path, sizeof(real_search_path)); path_resolve_realpath(real_search_path, sizeof(real_search_path), true); - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { if (!playlist_path_equal(real_search_path, playlist->entries[i].path, &playlist->config)) @@ -545,7 +547,7 @@ void playlist_get_index_by_path(playlist_t *playlist, bool playlist_entry_exists(playlist_t *playlist, const char *path) { - size_t i; + size_t i, len; char real_search_path[PATH_MAX_LENGTH]; real_search_path[0] = '\0'; @@ -557,7 +559,7 @@ bool playlist_entry_exists(playlist_t *playlist, strlcpy(real_search_path, path, sizeof(real_search_path)); path_resolve_realpath(real_search_path, sizeof(real_search_path), true); - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) if (playlist_path_equal(real_search_path, playlist->entries[i].path, &playlist->config)) return true; @@ -570,7 +572,7 @@ void playlist_update(playlist_t *playlist, size_t idx, { struct playlist_entry *entry = NULL; - if (!playlist || idx > playlist->size) + if (!playlist || idx >= RBUF_LEN(playlist->entries)) return; entry = &playlist->entries[idx]; @@ -631,7 +633,7 @@ void playlist_update_runtime(playlist_t *playlist, size_t idx, { struct playlist_entry *entry = NULL; - if (!playlist || idx > playlist->size) + if (!playlist || idx >= RBUF_LEN(playlist->entries)) return; entry = &playlist->entries[idx]; @@ -736,7 +738,7 @@ void playlist_update_runtime(playlist_t *playlist, size_t idx, bool playlist_push_runtime(playlist_t *playlist, const struct playlist_entry *entry) { - size_t i; + size_t i, len; char real_path[PATH_MAX_LENGTH]; char real_core_path[PATH_MAX_LENGTH]; @@ -771,7 +773,8 @@ bool playlist_push_runtime(playlist_t *playlist, return false; } - for (i = 0; i < playlist->size; i++) + len = RBUF_LEN(playlist->entries); + for (i = 0; i < len; i++) { struct playlist_entry tmp; const char *entry_path = playlist->entries[i].path; @@ -801,19 +804,27 @@ bool playlist_push_runtime(playlist_t *playlist, goto success; } - if (playlist->size == playlist->config.capacity) - { - struct playlist_entry *last_entry = &playlist->entries[playlist->config.capacity - 1]; + if (playlist->config.capacity == 0) + return false; - if (last_entry) - playlist_free_entry(last_entry); - playlist->size--; + if (len == playlist->config.capacity) + { + struct playlist_entry *last_entry = &playlist->entries[len - 1]; + playlist_free_entry(last_entry); + len--; + } + else + { + /* Allocate memory to fit one more item and resize the buffer */ + if (!RBUF_TRYFIT(playlist->entries, len + 1)) + return false; /* out of memory */ + RBUF_RESIZE(playlist->entries, len + 1); } if (playlist->entries) { memmove(playlist->entries + 1, playlist->entries, - (playlist->config.capacity - 1) * sizeof(struct playlist_entry)); + len * sizeof(struct playlist_entry)); playlist->entries[0].path = NULL; playlist->entries[0].core_path = NULL; @@ -843,8 +854,6 @@ bool playlist_push_runtime(playlist_t *playlist, playlist->entries[0].last_played_str = strdup(entry->last_played_str); } - playlist->size++; - success: playlist->modified = true; @@ -904,7 +913,7 @@ void playlist_resolve_path(enum playlist_file_mode mode, bool playlist_push(playlist_t *playlist, const struct playlist_entry *entry) { - size_t i; + size_t i, len; char real_path[PATH_MAX_LENGTH]; char real_core_path[PATH_MAX_LENGTH]; const char *core_name = entry->core_name; @@ -954,7 +963,8 @@ bool playlist_push(playlist_t *playlist, } } - for (i = 0; i < playlist->size; i++) + len = RBUF_LEN(playlist->entries); + for (i = 0; i < len; i++) { struct playlist_entry tmp; const char *entry_path = playlist->entries[i].path; @@ -1068,20 +1078,27 @@ bool playlist_push(playlist_t *playlist, goto success; } - if (playlist->size == playlist->config.capacity) - { - struct playlist_entry *last_entry = - &playlist->entries[playlist->config.capacity - 1]; + if (playlist->config.capacity == 0) + return false; - if (last_entry) - playlist_free_entry(last_entry); - playlist->size--; + if (len == playlist->config.capacity) + { + struct playlist_entry *last_entry = &playlist->entries[len - 1]; + playlist_free_entry(last_entry); + len--; + } + else + { + /* Allocate memory to fit one more item and resize the buffer */ + if (!RBUF_TRYFIT(playlist->entries, len + 1)) + return false; /* out of memory */ + RBUF_RESIZE(playlist->entries, len + 1); } if (playlist->entries) { memmove(playlist->entries + 1, playlist->entries, - (playlist->config.capacity - 1) * sizeof(struct playlist_entry)); + len * sizeof(struct playlist_entry)); playlist->entries[0].path = NULL; playlist->entries[0].label = NULL; @@ -1132,8 +1149,6 @@ bool playlist_push(playlist_t *playlist, } } - playlist->size++; - success: playlist->modified = true; @@ -1170,7 +1185,7 @@ static void JSONLogError(JSONContext *pCtx) void playlist_write_runtime_file(playlist_t *playlist) { - size_t i; + size_t i, len; intfstream_t *file = NULL; JSONContext context = {0}; @@ -1218,7 +1233,7 @@ void playlist_write_runtime_file(playlist_t *playlist) JSON_Writer_WriteStartArray(context.writer); JSON_Writer_WriteNewLine(context.writer); - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { JSON_Writer_WriteSpace(context.writer, 4); JSON_Writer_WriteStartObject(context.writer); @@ -1379,7 +1394,7 @@ void playlist_write_runtime_file(playlist_t *playlist) JSON_Writer_WriteSpace(context.writer, 4); JSON_Writer_WriteEndObject(context.writer); - if (i < playlist->size - 1) + if (i < len - 1) JSON_Writer_WriteComma(context.writer); JSON_Writer_WriteNewLine(context.writer); @@ -1416,7 +1431,7 @@ static JSON_Status JSON_CALL JSON_Writer_WriteSpace_NULL(JSON_Writer writer, siz void playlist_write_file(playlist_t *playlist) { - size_t i; + size_t i, len; intfstream_t *file = NULL; bool compressed = false; @@ -1457,7 +1472,7 @@ void playlist_write_file(playlist_t *playlist) #ifdef RARCH_INTERNAL if (playlist->config.old_format) { - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) intfstream_printf(file, "%s\n%s\n%s\n%s\n%s\n%s\n", playlist->entries[i].path ? playlist->entries[i].path : "", playlist->entries[i].label ? playlist->entries[i].label : "", @@ -1640,7 +1655,7 @@ void playlist_write_file(playlist_t *playlist) JSON_Writer_WriteStartArray(context.writer); json_write_new_line(context.writer); - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { json_write_space(context.writer, 4); JSON_Writer_WriteStartObject(context.writer); @@ -1813,7 +1828,7 @@ void playlist_write_file(playlist_t *playlist) json_write_space(context.writer, 4); JSON_Writer_WriteEndObject(context.writer); - if (i < playlist->size - 1) + if (i < len - 1) JSON_Writer_WriteComma(context.writer); json_write_new_line(context.writer); @@ -1846,7 +1861,7 @@ end: */ void playlist_free(playlist_t *playlist) { - size_t i; + size_t i, len; if (!playlist) return; @@ -1865,7 +1880,7 @@ void playlist_free(playlist_t *playlist) if (playlist->entries) { - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { struct playlist_entry *entry = &playlist->entries[i]; @@ -1873,8 +1888,7 @@ void playlist_free(playlist_t *playlist) playlist_free_entry(entry); } - free(playlist->entries); - playlist->entries = NULL; + RBUF_FREE(playlist->entries); } free(playlist); @@ -1888,18 +1902,18 @@ void playlist_free(playlist_t *playlist) **/ void playlist_clear(playlist_t *playlist) { - size_t i; + size_t i, len; if (!playlist) return; - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { struct playlist_entry *entry = &playlist->entries[i]; if (entry) playlist_free_entry(entry); } - playlist->size = 0; + RBUF_CLEAR(playlist->entries); } /** @@ -1913,7 +1927,7 @@ size_t playlist_size(playlist_t *playlist) { if (!playlist) return 0; - return playlist->size; + return RBUF_LEN(playlist->entries); } /** @@ -1993,8 +2007,19 @@ static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser) { if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded) { - if (pCtx->playlist->size < pCtx->playlist->config.capacity) - pCtx->current_entry = &pCtx->playlist->entries[pCtx->playlist->size]; + size_t len = RBUF_LEN(pCtx->playlist->entries); + if (len < pCtx->playlist->config.capacity) + { + /* Allocate memory to fit one more item but don't resize the + * buffer just yet, wait until JSONEndObjectHandler for that */ + if (!RBUF_TRYFIT(pCtx->playlist->entries, len + 1)) + { + pCtx->out_of_memory = true; + return JSON_Parser_Abort; + } + pCtx->current_entry = &pCtx->playlist->entries[len]; + memset(pCtx->current_entry, 0, sizeof(*pCtx->current_entry)); + } else { /* Hit max item limit. @@ -2022,7 +2047,8 @@ static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser) if (pCtx->in_items && pCtx->object_depth == 2) { if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded) - pCtx->playlist->size++; + RBUF_RESIZE(pCtx->playlist->entries, + RBUF_LEN(pCtx->playlist->entries) + 1); } retro_assert(pCtx->object_depth > 0); @@ -2281,6 +2307,8 @@ static bool playlist_read_file(playlist_t *playlist) { unsigned i; int test_char; + bool res = true; + #if defined(HAVE_ZLIB) /* Always use RZIP interface when reading playlists * > this will automatically handle uncompressed @@ -2386,6 +2414,12 @@ static bool playlist_read_file(playlist_t *playlist) if (!JSON_Parser_Parse(context.parser, chunk, (size_t)length, JSON_False)) { + if (context.out_of_memory) + { + RARCH_WARN("Ran out of memory while parsing JSON playlist\n"); + res = false; + goto json_cleanup; + } /* Note: Chunk may not be null-terminated. * It is therefore dangerous to print its contents. * Setting a size limit here mitigates the issue, but @@ -2418,6 +2452,7 @@ json_cleanup: } else { + size_t len = RBUF_LEN(playlist->entries); char line_buf[PLAYLIST_ENTRIES][PATH_MAX_LENGTH] = {{0}}; /* Unnecessary, but harmless */ @@ -2425,8 +2460,7 @@ json_cleanup: line_buf[i][0] = '\0'; /* Read playlist entries */ - playlist->size = 0; - while (playlist->size < playlist->config.capacity) + while (len < playlist->config.capacity) { size_t i; size_t lines_read = 0; @@ -2454,11 +2488,17 @@ json_cleanup: * is a valid playlist entry */ if (lines_read >= PLAYLIST_ENTRIES) { - struct playlist_entry *entry = - &playlist->entries[playlist->size]; + struct playlist_entry* entry; - if (!entry) - continue; + if (!RBUF_TRYFIT(playlist->entries, len + 1)) + { + res = false; /* out of memory */ + goto end; + } + RBUF_RESIZE(playlist->entries, len + 1); + entry = &playlist->entries[len++]; + + memset(entry, 0, sizeof(*entry)); /* path */ if (!string_is_empty(line_buf[0])) @@ -2483,8 +2523,6 @@ json_cleanup: /* db_name */ if (!string_is_empty(line_buf[5])) entry->db_name = strdup(line_buf[5]); - - playlist->size++; } /* If fewer than 'PLAYLIST_ENTRIES' lines were * read, then this is metadata */ @@ -2611,7 +2649,7 @@ json_cleanup: end: intfstream_close(file); free(file); - return true; + return res; } void playlist_free_cached(void) @@ -2658,18 +2696,8 @@ bool playlist_init_cached(const playlist_config_t *config) **/ playlist_t *playlist_init(const playlist_config_t *config) { - struct playlist_entry *entries = NULL; playlist_t *playlist = (playlist_t*)malloc(sizeof(*playlist)); - - /* Cache configuration parameters */ - if (!playlist || !playlist_config_copy(config, &playlist->config)) - goto error; - - /* Create entries array */ - entries = (struct playlist_entry*)calloc( - playlist->config.capacity, sizeof(*entries)); - - if (!entries) + if (!playlist) goto error; /* Set initial values */ @@ -2677,18 +2705,22 @@ playlist_t *playlist_init(const playlist_config_t *config) playlist->old_format = false; playlist->compressed = false; playlist->cached_external = false; - playlist->size = 0; playlist->default_core_name = NULL; playlist->default_core_path = NULL; playlist->base_content_directory = NULL; - playlist->entries = entries; + playlist->entries = NULL; playlist->label_display_mode = LABEL_DISPLAY_MODE_DEFAULT; playlist->right_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; playlist->left_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; playlist->sort_mode = PLAYLIST_SORT_MODE_DEFAULT; + /* Cache configuration parameters */ + if (!playlist_config_copy(config, &playlist->config)) + goto error; + /* Attempt to read any existing playlist file */ - playlist_read_file(playlist); + if (!playlist_read_file(playlist)) + goto error; /* Try auto-fixing paths if enabled, and playlist * base content directory is different */ @@ -2697,11 +2729,10 @@ playlist_t *playlist_init(const playlist_config_t *config) !string_is_equal(playlist->base_content_directory, playlist->config.base_content_directory)) { - size_t i; - size_t j; + size_t i, j, len; char tmp_entry_path[PATH_MAX_LENGTH]; - for (i = 0; i < playlist->size; i++) + for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) { struct playlist_entry *entry = &playlist->entries[i]; @@ -2847,10 +2878,11 @@ void playlist_qsort(playlist_t *playlist) /* Avoid inadvertent sorting if 'sort mode' * has been set explicitly to PLAYLIST_SORT_MODE_OFF */ if (!playlist || - (playlist->sort_mode == PLAYLIST_SORT_MODE_OFF)) + (playlist->sort_mode == PLAYLIST_SORT_MODE_OFF) || + !playlist->entries) return; - qsort(playlist->entries, playlist->size, + qsort(playlist->entries, RBUF_LEN(playlist->entries), sizeof(struct playlist_entry), (int (*)(const void *, const void *))playlist_qsort_func); } @@ -2890,7 +2922,7 @@ bool playlist_index_is_valid(playlist_t *playlist, size_t idx, if (!playlist) return false; - if (idx >= playlist->size) + if (idx >= RBUF_LEN(playlist->entries)) return false; return string_is_equal(playlist->entries[idx].path, path) && @@ -2944,7 +2976,7 @@ bool playlist_entries_are_equal( void playlist_get_crc32(playlist_t *playlist, size_t idx, const char **crc32) { - if (!playlist) + if (!playlist || idx >= RBUF_LEN(playlist->entries)) return; if (crc32) @@ -2954,7 +2986,7 @@ void playlist_get_crc32(playlist_t *playlist, size_t idx, void playlist_get_db_name(playlist_t *playlist, size_t idx, const char **db_name) { - if (!playlist) + if (!playlist || idx >= RBUF_LEN(playlist->entries)) return; if (db_name) diff --git a/playlist.h b/playlist.h index d68692666b..88aa5fa190 100644 --- a/playlist.h +++ b/playlist.h @@ -29,7 +29,7 @@ RETRO_BEGIN_DECLS /* Default maximum playlist size */ -#define COLLECTION_SIZE 99999 +#define COLLECTION_SIZE 0x7FFFFFFF typedef struct content_playlist playlist_t;