Files
RetroArch/msg_hash.c
T
LibretroAdmin a86d86a22c HAVE_RGIF: GIF images through image_texture, image_transfer and the tasks
rgif now loads GIFs wherever RetroArch loads images: thumbnails and
backgrounds through task_image, the image viewer core, the file browser
and the image_texture/image_transfer layer underneath them all.

rgif:
- the image_transfer interface the other decoders have:
  rgif_set_buf_ptr, rgif_set_avail, rgif_header_ready and
  rgif_process_image, giving a 32-bit texture of its own, ARGB32 or
  ABGR32 in native byte order
- RGIF_ABGR, for ABGR32 on big-endian targets
- a file not all read yet gives IMAGE_PROCESS_WAIT and is not touched;
  the image is decoded in one call once it is all in

Wiring, as HAVE_RTGA is wired:
- IMAGE_TYPE_GIF (image.h) and NBIO_TYPE_GIF (task_file_transfer.h),
  both appended so no other value moves
- image_texture_get_type: .gif
- image_transfer: new, free, start, is_valid, set_buffer_ptr, process,
  set_avail; the support matrix gains a GIF column
- task_image and task_file_transfer: GIF is an image task
- FILE_TYPE_GIF ("gif", "GIF"): opened as image content
- the file browser's image extensions, the image viewer core's
  extensions, the System Information list ("GIF (RGIF)") and
  --features ("RGIF"); SUPPORTS_RGIF in config.features.h
- griffin.c, Makefile.common (rgif.o), configure (HAVE_RGIF=yes), and
  next to HAVE_RTGA in every platform Makefile, the Android, Apple,
  MSVC, UWP and QNX projects and the CI compile matrix
- tools/settings_check.py and tools/stack_budget.py check with it

Also: menu_displaylist's image viewer extension list wrote "tga"
without moving its length on, so "webp" and "dds" landed on top of it
and TGA dropped out of the list; it moves on now.

Tests: rgif_test checks the texture in both byte orders against
rgif_decode, the wait before the whole file is in, and a refused file;
image_decode_fuzz sweeps GIF too (every byte of a small GIF set to
edge values, and every truncation), under ASan and UBSan. The griffin
unity build with every codec, the touched objects in a configured
build, the MinGW/C89 syntax check, the overlap check and the stack
budget pass.
2026-11-10 20:00:24 +01:00

829 lines
28 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2016-2019 - Brad Parker
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <compat/strl.h>
#include <string/stdstring.h> /* for string_replace_substring */
#include <libretro.h>
#include <retro_miscellaneous.h> /* ARRAY_SIZE */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "msg_hash.h"
#if defined(_MSC_VER) && !defined(_XBOX) && (_MSC_VER >= 1500 && _MSC_VER < 1900)
#if (_MSC_VER >= 1700)
/* https://support.microsoft.com/en-us/kb/980263 */
#pragma execution_character_set("utf-8")
#endif
#pragma warning(disable:4566)
#endif
/* TODO/FIXME - static public global variable */
static unsigned uint_user_language;
int msg_hash_get_help_enum(enum msg_hash_enums msg, char *s, size_t len)
{
int ret = msg_hash_get_help_us_enum(msg, s, len);
/* Replace line-breaks with "empty line-breaks" for readability */
const char *temp = string_replace_substring(s, strlen(s),
"\n", (sizeof("\n")-1),
"\n \n", (sizeof("\n \n")-1));
if (temp)
{
strlcpy(s, temp, len);
free((void*)temp);
}
return ret;
}
#if defined(MSG_HASH_HAVE_STRTAB)
void msg_hash_strtab_index_build(msg_hash_strtab_index_t *idx,
const msg_hash_strtab_t *tab)
{
uint32_t i;
uint32_t *id_slots;
if (idx->tab == tab && idx->id_slots)
return;
if (!tab || tab->count == 0)
return;
id_slots = (uint32_t*)calloc(MSG_LAST, sizeof(uint32_t));
if (!id_slots)
return;
{
/* Rows are contiguous NUL-terminated strings; recover each
* row's offset with one walk. */
const char *p = tab->blob;
for (i = 0; i < tab->count; i++)
{
if (tab->ids[i] < (uint32_t)MSG_LAST)
id_slots[tab->ids[i]] = (uint32_t)(p - tab->blob) + 1;
while (*p)
p++;
p++;
}
}
/* Publish the fully built index before marking it valid, so a
* concurrent reader either sees the complete index or takes the
* fallback path below. */
if (idx->id_slots)
free(idx->id_slots);
idx->id_slots = id_slots;
idx->tab = tab;
}
const char *msg_hash_strtab_lookup(const msg_hash_strtab_t *tab,
msg_hash_strtab_index_t *idx, uint32_t id)
{
if (!tab || tab->count == 0)
return NULL;
if (idx->tab != tab || !idx->id_slots)
msg_hash_strtab_index_build(idx, tab);
if (idx->tab == tab && idx->id_slots)
{
if (id < (uint32_t)MSG_LAST)
{
uint32_t slot = idx->id_slots[id];
if (slot)
return tab->blob + (slot - 1);
}
return NULL;
}
/* Allocation-failure path only. */
{
uint32_t i;
const char *p = tab->blob;
for (i = 0; i < tab->count; i++)
{
if (tab->ids[i] == id)
return p;
while (*p)
p++;
p++;
}
}
return NULL;
}
#endif /* MSG_HASH_HAVE_STRTAB */
const char *get_user_language_iso639_1(bool limit)
{
switch (uint_user_language)
{
case RETRO_LANGUAGE_FRENCH:
return "fr";
case RETRO_LANGUAGE_GERMAN:
return "de";
case RETRO_LANGUAGE_SPANISH:
return "es";
case RETRO_LANGUAGE_ITALIAN:
return "it";
case RETRO_LANGUAGE_PORTUGUESE_BRAZIL:
if (limit)
return "pt";
return "pt_br";
case RETRO_LANGUAGE_PORTUGUESE_PORTUGAL:
if (limit)
return "pt";
return "pt_pt";
case RETRO_LANGUAGE_DUTCH:
return "nl";
case RETRO_LANGUAGE_ESPERANTO:
return "eo";
case RETRO_LANGUAGE_POLISH:
return "pl";
case RETRO_LANGUAGE_JAPANESE:
return "ja";
case RETRO_LANGUAGE_KOREAN:
return "ko";
case RETRO_LANGUAGE_VIETNAMESE:
return "vi";
case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
if (limit)
return "zh";
return "zh_cn";
case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
if (limit)
return "zh";
return "zh_tw";
case RETRO_LANGUAGE_ARABIC:
return "ar";
case RETRO_LANGUAGE_GREEK:
return "el";
case RETRO_LANGUAGE_TURKISH:
return "tr";
case RETRO_LANGUAGE_SLOVAK:
return "sk";
case RETRO_LANGUAGE_RUSSIAN:
return "ru";
case RETRO_LANGUAGE_PERSIAN:
return "fa";
case RETRO_LANGUAGE_HEBREW:
return "he";
case RETRO_LANGUAGE_ASTURIAN:
return "ast";
case RETRO_LANGUAGE_FINNISH:
return "fi";
case RETRO_LANGUAGE_INDONESIAN:
return "id";
case RETRO_LANGUAGE_SWEDISH:
return "sv";
case RETRO_LANGUAGE_UKRAINIAN:
return "uk";
case RETRO_LANGUAGE_CZECH:
return "cs";
case RETRO_LANGUAGE_CATALAN_VALENCIA:
if (limit)
return "ca";
return "ca_ES@valencia";
case RETRO_LANGUAGE_CATALAN:
return "ca";
case RETRO_LANGUAGE_BRITISH_ENGLISH:
if (limit)
return "en";
return "en_gb";
case RETRO_LANGUAGE_HUNGARIAN:
return "hu";
case RETRO_LANGUAGE_BELARUSIAN:
return "be";
case RETRO_LANGUAGE_GALICIAN:
return "gl";
case RETRO_LANGUAGE_NORWEGIAN:
return "no";
case RETRO_LANGUAGE_IRISH:
return "ga";
case RETRO_LANGUAGE_THAI:
return "th";
}
return "en";
}
#ifdef HAVE_LANGEXTRA
/* Per-language packed string tables, generated by intl/json2h.py.
* Each header defines msg_hash_<lang>_blob / _ids; see
* msg_hash.h for the encoding notes. */
#include "intl/msg_hash_he.h"
#include "intl/msg_hash_sk.h"
#include "intl/msg_hash_uk.h"
#include "intl/msg_hash_eo.h"
#include "intl/msg_hash_pl.h"
#include "intl/msg_hash_fi.h"
#include "intl/msg_hash_hu.h"
#include "intl/msg_hash_be.h"
#include "intl/msg_hash_en.h"
#include "intl/msg_hash_it.h"
#include "intl/msg_hash_fa.h"
#include "intl/msg_hash_ast.h"
#include "intl/msg_hash_nl.h"
#include "intl/msg_hash_sv.h"
#include "intl/msg_hash_id.h"
#include "intl/msg_hash_cs.h"
#include "intl/msg_hash_ar.h"
#include "intl/msg_hash_fr.h"
#include "intl/msg_hash_cht.h"
#include "intl/msg_hash_de.h"
#include "intl/msg_hash_es.h"
#include "intl/msg_hash_ca.h"
#include "intl/msg_hash_el.h"
#include "intl/msg_hash_ja.h"
#include "intl/msg_hash_ko.h"
#include "intl/msg_hash_pt_pt.h"
#include "intl/msg_hash_ru.h"
#include "intl/msg_hash_tr.h"
#include "intl/msg_hash_val.h"
#include "intl/msg_hash_vn.h"
#include "intl/msg_hash_chs.h"
#include "intl/msg_hash_pt_br.h"
#include "intl/msg_hash_gl.h"
#include "intl/msg_hash_no.h"
#include "intl/msg_hash_ga.h"
#include "intl/msg_hash_th.h"
static const msg_hash_strtab_t msg_hash_strtab_he =
{ msg_hash_he_ids,
(uint32_t)(sizeof(msg_hash_he_ids) / sizeof(msg_hash_he_ids[0])),
(const char*)&msg_hash_he_blob };
static const msg_hash_strtab_t msg_hash_strtab_sk =
{ msg_hash_sk_ids,
(uint32_t)(sizeof(msg_hash_sk_ids) / sizeof(msg_hash_sk_ids[0])),
(const char*)&msg_hash_sk_blob };
static const msg_hash_strtab_t msg_hash_strtab_uk =
{ msg_hash_uk_ids,
(uint32_t)(sizeof(msg_hash_uk_ids) / sizeof(msg_hash_uk_ids[0])),
(const char*)&msg_hash_uk_blob };
static const msg_hash_strtab_t msg_hash_strtab_eo =
{ msg_hash_eo_ids,
(uint32_t)(sizeof(msg_hash_eo_ids) / sizeof(msg_hash_eo_ids[0])),
(const char*)&msg_hash_eo_blob };
static const msg_hash_strtab_t msg_hash_strtab_pl =
{ msg_hash_pl_ids,
(uint32_t)(sizeof(msg_hash_pl_ids) / sizeof(msg_hash_pl_ids[0])),
(const char*)&msg_hash_pl_blob };
static const msg_hash_strtab_t msg_hash_strtab_fi =
{ msg_hash_fi_ids,
(uint32_t)(sizeof(msg_hash_fi_ids) / sizeof(msg_hash_fi_ids[0])),
(const char*)&msg_hash_fi_blob };
static const msg_hash_strtab_t msg_hash_strtab_hu =
{ msg_hash_hu_ids,
(uint32_t)(sizeof(msg_hash_hu_ids) / sizeof(msg_hash_hu_ids[0])),
(const char*)&msg_hash_hu_blob };
static const msg_hash_strtab_t msg_hash_strtab_be =
{ msg_hash_be_ids,
(uint32_t)(sizeof(msg_hash_be_ids) / sizeof(msg_hash_be_ids[0])),
(const char*)&msg_hash_be_blob };
static const msg_hash_strtab_t msg_hash_strtab_en =
{ msg_hash_en_ids,
(uint32_t)(sizeof(msg_hash_en_ids) / sizeof(msg_hash_en_ids[0])),
(const char*)&msg_hash_en_blob };
static const msg_hash_strtab_t msg_hash_strtab_it =
{ msg_hash_it_ids,
(uint32_t)(sizeof(msg_hash_it_ids) / sizeof(msg_hash_it_ids[0])),
(const char*)&msg_hash_it_blob };
static const msg_hash_strtab_t msg_hash_strtab_fa =
{ msg_hash_fa_ids,
(uint32_t)(sizeof(msg_hash_fa_ids) / sizeof(msg_hash_fa_ids[0])),
(const char*)&msg_hash_fa_blob };
static const msg_hash_strtab_t msg_hash_strtab_ast =
{ msg_hash_ast_ids,
(uint32_t)(sizeof(msg_hash_ast_ids) / sizeof(msg_hash_ast_ids[0])),
(const char*)&msg_hash_ast_blob };
static const msg_hash_strtab_t msg_hash_strtab_nl =
{ msg_hash_nl_ids,
(uint32_t)(sizeof(msg_hash_nl_ids) / sizeof(msg_hash_nl_ids[0])),
(const char*)&msg_hash_nl_blob };
static const msg_hash_strtab_t msg_hash_strtab_sv =
{ msg_hash_sv_ids,
(uint32_t)(sizeof(msg_hash_sv_ids) / sizeof(msg_hash_sv_ids[0])),
(const char*)&msg_hash_sv_blob };
static const msg_hash_strtab_t msg_hash_strtab_id =
{ msg_hash_id_ids,
(uint32_t)(sizeof(msg_hash_id_ids) / sizeof(msg_hash_id_ids[0])),
(const char*)&msg_hash_id_blob };
static const msg_hash_strtab_t msg_hash_strtab_cs =
{ msg_hash_cs_ids,
(uint32_t)(sizeof(msg_hash_cs_ids) / sizeof(msg_hash_cs_ids[0])),
(const char*)&msg_hash_cs_blob };
static const msg_hash_strtab_t msg_hash_strtab_ar =
{ msg_hash_ar_ids,
(uint32_t)(sizeof(msg_hash_ar_ids) / sizeof(msg_hash_ar_ids[0])),
(const char*)&msg_hash_ar_blob };
static const msg_hash_strtab_t msg_hash_strtab_fr =
{ msg_hash_fr_ids,
(uint32_t)(sizeof(msg_hash_fr_ids) / sizeof(msg_hash_fr_ids[0])),
(const char*)&msg_hash_fr_blob };
static const msg_hash_strtab_t msg_hash_strtab_cht =
{ msg_hash_cht_ids,
(uint32_t)(sizeof(msg_hash_cht_ids) / sizeof(msg_hash_cht_ids[0])),
(const char*)&msg_hash_cht_blob };
static const msg_hash_strtab_t msg_hash_strtab_de =
{ msg_hash_de_ids,
(uint32_t)(sizeof(msg_hash_de_ids) / sizeof(msg_hash_de_ids[0])),
(const char*)&msg_hash_de_blob };
static const msg_hash_strtab_t msg_hash_strtab_es =
{ msg_hash_es_ids,
(uint32_t)(sizeof(msg_hash_es_ids) / sizeof(msg_hash_es_ids[0])),
(const char*)&msg_hash_es_blob };
static const msg_hash_strtab_t msg_hash_strtab_ca =
{ msg_hash_ca_ids,
(uint32_t)(sizeof(msg_hash_ca_ids) / sizeof(msg_hash_ca_ids[0])),
(const char*)&msg_hash_ca_blob };
static const msg_hash_strtab_t msg_hash_strtab_el =
{ msg_hash_el_ids,
(uint32_t)(sizeof(msg_hash_el_ids) / sizeof(msg_hash_el_ids[0])),
(const char*)&msg_hash_el_blob };
static const msg_hash_strtab_t msg_hash_strtab_jp =
{ msg_hash_ja_ids,
(uint32_t)(sizeof(msg_hash_ja_ids) / sizeof(msg_hash_ja_ids[0])),
(const char*)&msg_hash_ja_blob };
static const msg_hash_strtab_t msg_hash_strtab_ko =
{ msg_hash_ko_ids,
(uint32_t)(sizeof(msg_hash_ko_ids) / sizeof(msg_hash_ko_ids[0])),
(const char*)&msg_hash_ko_blob };
static const msg_hash_strtab_t msg_hash_strtab_pt_pt =
{ msg_hash_pt_pt_ids,
(uint32_t)(sizeof(msg_hash_pt_pt_ids) / sizeof(msg_hash_pt_pt_ids[0])),
(const char*)&msg_hash_pt_pt_blob };
static const msg_hash_strtab_t msg_hash_strtab_ru =
{ msg_hash_ru_ids,
(uint32_t)(sizeof(msg_hash_ru_ids) / sizeof(msg_hash_ru_ids[0])),
(const char*)&msg_hash_ru_blob };
static const msg_hash_strtab_t msg_hash_strtab_tr =
{ msg_hash_tr_ids,
(uint32_t)(sizeof(msg_hash_tr_ids) / sizeof(msg_hash_tr_ids[0])),
(const char*)&msg_hash_tr_blob };
static const msg_hash_strtab_t msg_hash_strtab_val =
{ msg_hash_val_ids,
(uint32_t)(sizeof(msg_hash_val_ids) / sizeof(msg_hash_val_ids[0])),
(const char*)&msg_hash_val_blob };
static const msg_hash_strtab_t msg_hash_strtab_vn =
{ msg_hash_vn_ids,
(uint32_t)(sizeof(msg_hash_vn_ids) / sizeof(msg_hash_vn_ids[0])),
(const char*)&msg_hash_vn_blob };
static const msg_hash_strtab_t msg_hash_strtab_chs =
{ msg_hash_chs_ids,
(uint32_t)(sizeof(msg_hash_chs_ids) / sizeof(msg_hash_chs_ids[0])),
(const char*)&msg_hash_chs_blob };
static const msg_hash_strtab_t msg_hash_strtab_pt_br =
{ msg_hash_pt_br_ids,
(uint32_t)(sizeof(msg_hash_pt_br_ids) / sizeof(msg_hash_pt_br_ids[0])),
(const char*)&msg_hash_pt_br_blob };
static const msg_hash_strtab_t msg_hash_strtab_gl =
{ msg_hash_gl_ids,
(uint32_t)(sizeof(msg_hash_gl_ids) / sizeof(msg_hash_gl_ids[0])),
(const char*)&msg_hash_gl_blob };
static const msg_hash_strtab_t msg_hash_strtab_no =
{ msg_hash_no_ids,
(uint32_t)(sizeof(msg_hash_no_ids) / sizeof(msg_hash_no_ids[0])),
(const char*)&msg_hash_no_blob };
static const msg_hash_strtab_t msg_hash_strtab_ga =
{ msg_hash_ga_ids,
(uint32_t)(sizeof(msg_hash_ga_ids) / sizeof(msg_hash_ga_ids[0])),
(const char*)&msg_hash_ga_blob };
static const msg_hash_strtab_t msg_hash_strtab_th =
{ msg_hash_th_ids,
(uint32_t)(sizeof(msg_hash_th_ids) / sizeof(msg_hash_th_ids[0])),
(const char*)&msg_hash_th_blob };
static msg_hash_strtab_index_t msg_hash_lang_index;
static const msg_hash_strtab_t *msg_hash_lang_strtab(unsigned lang)
{
switch (lang)
{
case RETRO_LANGUAGE_FRENCH:
return &msg_hash_strtab_fr;
case RETRO_LANGUAGE_GERMAN:
return &msg_hash_strtab_de;
case RETRO_LANGUAGE_SPANISH:
return &msg_hash_strtab_es;
case RETRO_LANGUAGE_ITALIAN:
return &msg_hash_strtab_it;
case RETRO_LANGUAGE_PORTUGUESE_BRAZIL:
return &msg_hash_strtab_pt_br;
case RETRO_LANGUAGE_PORTUGUESE_PORTUGAL:
return &msg_hash_strtab_pt_pt;
case RETRO_LANGUAGE_DUTCH:
return &msg_hash_strtab_nl;
case RETRO_LANGUAGE_ESPERANTO:
return &msg_hash_strtab_eo;
case RETRO_LANGUAGE_POLISH:
return &msg_hash_strtab_pl;
case RETRO_LANGUAGE_RUSSIAN:
return &msg_hash_strtab_ru;
case RETRO_LANGUAGE_JAPANESE:
return &msg_hash_strtab_jp;
case RETRO_LANGUAGE_KOREAN:
return &msg_hash_strtab_ko;
case RETRO_LANGUAGE_VIETNAMESE:
return &msg_hash_strtab_vn;
case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
return &msg_hash_strtab_chs;
case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
return &msg_hash_strtab_cht;
case RETRO_LANGUAGE_ARABIC:
return &msg_hash_strtab_ar;
case RETRO_LANGUAGE_GREEK:
return &msg_hash_strtab_el;
case RETRO_LANGUAGE_TURKISH:
return &msg_hash_strtab_tr;
case RETRO_LANGUAGE_SLOVAK:
return &msg_hash_strtab_sk;
case RETRO_LANGUAGE_PERSIAN:
return &msg_hash_strtab_fa;
case RETRO_LANGUAGE_HEBREW:
return &msg_hash_strtab_he;
case RETRO_LANGUAGE_ASTURIAN:
return &msg_hash_strtab_ast;
case RETRO_LANGUAGE_FINNISH:
return &msg_hash_strtab_fi;
case RETRO_LANGUAGE_INDONESIAN:
return &msg_hash_strtab_id;
case RETRO_LANGUAGE_SWEDISH:
return &msg_hash_strtab_sv;
case RETRO_LANGUAGE_UKRAINIAN:
return &msg_hash_strtab_uk;
case RETRO_LANGUAGE_CZECH:
return &msg_hash_strtab_cs;
case RETRO_LANGUAGE_CATALAN_VALENCIA:
return &msg_hash_strtab_val;
case RETRO_LANGUAGE_CATALAN:
return &msg_hash_strtab_ca;
case RETRO_LANGUAGE_BRITISH_ENGLISH:
return &msg_hash_strtab_en;
case RETRO_LANGUAGE_HUNGARIAN:
return &msg_hash_strtab_hu;
case RETRO_LANGUAGE_BELARUSIAN:
return &msg_hash_strtab_be;
case RETRO_LANGUAGE_GALICIAN:
return &msg_hash_strtab_gl;
case RETRO_LANGUAGE_NORWEGIAN:
return &msg_hash_strtab_no;
case RETRO_LANGUAGE_IRISH:
return &msg_hash_strtab_ga;
case RETRO_LANGUAGE_THAI:
return &msg_hash_strtab_th;
default:
break;
}
return NULL;
}
#endif
const char *msg_hash_to_str(enum msg_hash_enums msg)
{
const char *ret = NULL;
#ifdef HAVE_LANGEXTRA
{
const msg_hash_strtab_t *tab =
msg_hash_lang_strtab(uint_user_language);
if (tab)
ret = msg_hash_strtab_lookup(tab,
&msg_hash_lang_index, (uint32_t)msg);
}
#endif
/* No strtab row can hold the untranslated marker: json2h.py drops
* "null" rows at pack time, since a missing row and a "null" row both
* mean "fall back to the base table" and are indistinguishable at
* lookup. Verified exhaustively over all 36 language tables x every
* enum id. So a non-NULL ret is always a real translation, and the
* strcmp that used to guard this return was dead weight paid on
* roughly 40 percent of calls for every non-English user. */
if (ret)
return ret;
return msg_hash_to_str_us(msg);
}
/* String-table lookup for menu entry values and file extensions.
*
* Historically this dispatched through DJB2 hashes of the value
* string against ~80 precomputed magic hex constants — readable
* only via reverse-engineering, prone to silent collisions if a
* new extension hashed to the same value as an existing one, and
* carried 79 TOC entries on PPC64 (which contributed to the PS3
* griffin build crossing the 64K TOC limit).
*
* The hash dispatch saved ~2 us/frame in menu-mode file browsing
* vs a linear strcmp scan — not enough to justify the obscurity.
*
* The table below stays that plain declarative list; the speed is
* recovered instead by a first-byte bucket index built once from the
* table itself, so there is still exactly one source of truth, no
* magic constants to collide, and no added TOC entries.
*
* Callers passing case-insensitively must lowercase their input
* before calling this; only the lowercase entries below are
* checked for those cases (jpg, png, etc. include uppercase
* variants because the menu callers may receive either). */
enum msg_file_type msg_hash_to_file_type(const char *value)
{
size_t i;
unsigned short slot;
struct ft_entry {
const char *value;
enum msg_file_type type;
};
/* Order roughly by expected hit frequency to minimize average
* compare count for the common cases (game ROMs hitting CHD,
* ISO, CUE, ZIP first; image viewers hitting PNG/JPG). */
static const struct ft_entry table[] = {
/* disc / archive containers — top of file-browser traffic */
{ "chd", FILE_TYPE_CHD },
{ "iso", FILE_TYPE_ISO },
{ "ISO", FILE_TYPE_ISO },
{ "cue", FILE_TYPE_CUE },
{ "CUE", FILE_TYPE_CUE },
{ "gdi", FILE_TYPE_GDI },
{ "GDI", FILE_TYPE_GDI },
{ "zip", FILE_TYPE_COMPRESSED },
{ "ZIP", FILE_TYPE_COMPRESSED },
{ "7z", FILE_TYPE_COMPRESSED },
{ "7Z", FILE_TYPE_COMPRESSED },
{ "apk", FILE_TYPE_COMPRESSED },
{ "(COMP)", FILE_TYPE_COMPRESSED },
{ "lutro", FILE_TYPE_LUTRO },
/* menu placeholder values from displaylist enumeration */
{ "(CFILE)", FILE_TYPE_IN_CARCHIVE },
{ "...", FILE_TYPE_MORE },
{ "(CORE)", FILE_TYPE_CORE },
{ "(RDB)", FILE_TYPE_RDB },
{ "(FILE)", FILE_TYPE_PLAIN },
{ "(DIR)", FILE_TYPE_DIRECTORY },
{ "(MUSIC)", FILE_TYPE_MUSIC },
{ "(IMAGE)", FILE_TYPE_IMAGE },
{ "(MOVIE)", FILE_TYPE_MOVIE },
{ "ON", FILE_TYPE_BOOL_ON },
{ "OFF", FILE_TYPE_BOOL_OFF },
/* shader source extensions */
{ "glsl", FILE_TYPE_SHADER_GLSL },
{ "hlsl", FILE_TYPE_SHADER_HLSL },
{ "cg", FILE_TYPE_SHADER_CG },
{ "slang", FILE_TYPE_SHADER_SLANG },
{ "glslp", FILE_TYPE_SHADER_PRESET_GLSLP },
{ "hlslp", FILE_TYPE_SHADER_PRESET_HLSLP },
{ "cgp", FILE_TYPE_SHADER_PRESET_CGP },
{ "slangp", FILE_TYPE_SHADER_PRESET_SLANGP },
/* checksum / hash labels */
{ "crc", FILE_TYPE_CRC },
{ "sha1", FILE_TYPE_SHA1 },
{ "md5", FILE_TYPE_MD5 },
#if defined(HAVE_WEBMPLAYER) && !defined(HAVE_FFMPEG) && !defined(HAVE_MPV)
/* video containers the built-in WebM/MP4 player handles */
{ "mkv", FILE_TYPE_MKV },
{ "webm", FILE_TYPE_WEBM },
#ifdef HAVE_RMP4
{ "mp4", FILE_TYPE_MP4 },
{ "m4v", FILE_TYPE_MP4 },
{ "mov", FILE_TYPE_MOV },
#endif
#endif
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
/* video containers */
{ "ogm", FILE_TYPE_OGM },
{ "mkv", FILE_TYPE_MKV },
{ "avi", FILE_TYPE_AVI },
{ "mp4", FILE_TYPE_MP4 },
{ "m4v", FILE_TYPE_MP4 },
{ "flv", FILE_TYPE_FLV },
{ "webm", FILE_TYPE_WEBM },
{ "3gp", FILE_TYPE_3GP },
{ "f4f", FILE_TYPE_F4F },
{ "f4v", FILE_TYPE_F4V },
{ "mov", FILE_TYPE_MOV },
{ "wmv", FILE_TYPE_WMV },
{ "m4a", FILE_TYPE_M4A },
{ "3g2", FILE_TYPE_3G2 },
{ "mpg", FILE_TYPE_MPG },
{ "mpeg", FILE_TYPE_MPEG },
{ "vob", FILE_TYPE_VOB },
{ "asf", FILE_TYPE_ASF },
{ "divx", FILE_TYPE_DIVX },
{ "m2p", FILE_TYPE_M2P },
{ "m2ts", FILE_TYPE_M2TS },
{ "ps", FILE_TYPE_PS },
{ "ts", FILE_TYPE_TS },
{ "mxf", FILE_TYPE_MXF },
#endif
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV) || defined(HAVE_RWMA)
{ "wma", FILE_TYPE_WMA },
#endif
#ifdef HAVE_RWMA
{ "xwma", FILE_TYPE_WMA },
{ "xma", FILE_TYPE_WMA },
#endif
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV) || defined(HAVE_AUDIOMIXER)
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_RMP3)
{ "mp3", FILE_TYPE_MP3 },
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_RVORBIS)
{ "ogg", FILE_TYPE_OGG },
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_RFLAC)
{ "flac", FILE_TYPE_FLAC },
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_RWAV)
{ "wav", FILE_TYPE_WAV },
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_RMODTRACKER)
{ "mod", FILE_TYPE_MOD },
{ "s3m", FILE_TYPE_S3M },
{ "xm", FILE_TYPE_XM },
#endif
#if defined(HAVE_AUDIOMIXER) && defined(HAVE_RAAC)
#ifdef HAVE_RMP4
{ "m4a", FILE_TYPE_M4A },
#endif
{ "aac", FILE_TYPE_AAC },
{ "ac3", FILE_TYPE_AC3 },
{ "eac3", FILE_TYPE_AC3 },
{ "ec3", FILE_TYPE_AC3 },
{ "lpcm", FILE_TYPE_LPCM },
{ "pcm", FILE_TYPE_LPCM },
#endif
#if defined(HAVE_AUDIOMIXER) && defined(HAVE_ROPUS)
{ "opus", FILE_TYPE_OPUS },
#endif
#if defined(HAVE_AUDIOMIXER) && defined(HAVE_RWEBM) \
&& (defined(HAVE_ROPUS) || defined(HAVE_RVORBIS))
{ "weba", FILE_TYPE_WEBA },
#endif
#endif
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
/* Further media formats; with FFmpeg, only routed to the player
* if its build can open them. Extensions claimed above win. */
{ "ogv", FILE_TYPE_MOVIE_OTHER },
{ "mk3d", FILE_TYPE_MOVIE_OTHER },
{ "mts", FILE_TYPE_MOVIE_OTHER },
{ "m2t", FILE_TYPE_MOVIE_OTHER },
{ "m2v", FILE_TYPE_MOVIE_OTHER },
{ "rm", FILE_TYPE_MOVIE_OTHER },
{ "rmvb", FILE_TYPE_MOVIE_OTHER },
{ "nut", FILE_TYPE_MOVIE_OTHER },
{ "dv", FILE_TYPE_MOVIE_OTHER },
{ "ivf", FILE_TYPE_MOVIE_OTHER },
{ "oga", FILE_TYPE_MUSIC_OTHER },
{ "opus", FILE_TYPE_MUSIC_OTHER },
{ "spx", FILE_TYPE_MUSIC_OTHER },
{ "mka", FILE_TYPE_MUSIC_OTHER },
{ "weba", FILE_TYPE_MUSIC_OTHER },
{ "m4b", FILE_TYPE_MUSIC_OTHER },
{ "aac", FILE_TYPE_MUSIC_OTHER },
{ "ac3", FILE_TYPE_MUSIC_OTHER },
{ "eac3", FILE_TYPE_MUSIC_OTHER },
{ "dts", FILE_TYPE_MUSIC_OTHER },
{ "mp2", FILE_TYPE_MUSIC_OTHER },
{ "aif", FILE_TYPE_MUSIC_OTHER },
{ "aiff", FILE_TYPE_MUSIC_OTHER },
{ "wv", FILE_TYPE_MUSIC_OTHER },
{ "ape", FILE_TYPE_MUSIC_OTHER },
{ "tta", FILE_TYPE_MUSIC_OTHER },
{ "mpc", FILE_TYPE_MUSIC_OTHER },
{ "amr", FILE_TYPE_MUSIC_OTHER },
{ "au", FILE_TYPE_MUSIC_OTHER },
{ "caf", FILE_TYPE_MUSIC_OTHER },
#endif
#if defined(HAVE_IMAGEVIEWER) || defined(HAVE_MENU)
{ "jpg", FILE_TYPE_JPEG },
{ "JPG", FILE_TYPE_JPEG },
{ "jpeg", FILE_TYPE_JPEG },
{ "JPEG", FILE_TYPE_JPEG },
{ "png", FILE_TYPE_PNG },
{ "PNG", FILE_TYPE_PNG },
{ "tga", FILE_TYPE_TGA },
{ "bmp", FILE_TYPE_BMP },
{ "webp", FILE_TYPE_WEBP },
{ "WEBP", FILE_TYPE_WEBP },
{ "gif", FILE_TYPE_GIF },
{ "GIF", FILE_TYPE_GIF },
#endif
#ifdef HAVE_EASTEREGG
{ "gong", FILE_TYPE_GONG },
{ "GONG", FILE_TYPE_GONG },
#endif
};
/* First-byte bucket index over table[].
*
* ft_head[c] is a 1-based index of the first row whose value starts
* with byte c; ft_next[] chains the remainder. 0 is the
* end-of-chain sentinel, so the zero-initialised (BSS) state of
* both arrays reads as "every bucket empty": a reader that observes
* a partially built index can only miss - never follow a bogus row,
* never cycle - and every surviving candidate is still compared in
* full below before it is returned. The build is idempotent, so
* concurrent builders store identical bytes. */
static unsigned short ft_head[256];
static unsigned short ft_next[ARRAY_SIZE(table)];
static unsigned char ft_ready;
if (!value || !*value)
return FILE_TYPE_NONE;
if (!ft_ready)
{
for (i = ARRAY_SIZE(table); i > 0; i--)
{
unsigned char c = (unsigned char)table[i - 1].value[0];
ft_next[i - 1] = ft_head[c];
ft_head[c] = (unsigned short)i;
}
/* Publish only once every bucket is populated. */
ft_ready = 1;
}
for (slot = ft_head[(unsigned char)*value]; slot; slot = ft_next[slot - 1])
{
/* Inline short string compare: most entries are 2-6 bytes so
* the strcmp/string_is_equal overhead would dominate. Bail
* on first mismatch. The first byte already matched via the
* bucket, so start at the second. */
const char *a = value + 1;
const char *b = table[slot - 1].value + 1;
while (*a && *a == *b) { a++; b++; }
if (!*a && !*b)
return table[slot - 1].type;
}
return FILE_TYPE_NONE;
}
unsigned *msg_hash_get_uint(enum msg_hash_action type)
{
switch (type)
{
case MSG_HASH_USER_LANGUAGE:
return &uint_user_language;
case MSG_HASH_NONE:
break;
}
return NULL;
}
void msg_hash_set_uint(enum msg_hash_action type, unsigned val)
{
switch (type)
{
case MSG_HASH_USER_LANGUAGE:
uint_user_language = val;
#if defined(MSG_HASH_HAVE_STRTAB)
msg_hash_us_index_init();
#ifdef HAVE_LANGEXTRA
msg_hash_strtab_index_build(&msg_hash_lang_index,
msg_hash_lang_strtab(val));
#endif
#endif
break;
case MSG_HASH_NONE:
break;
}
}
const char *msg_hash_get_wideglyph_str(void)
{
#ifdef HAVE_LANGEXTRA
switch (uint_user_language)
{
case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
return "菜";
case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
return "主";
case RETRO_LANGUAGE_JAPANESE:
return "漢";
case RETRO_LANGUAGE_KOREAN:
return "메";
default:
break;
}
#endif
return NULL;
}