Files
RetroArch/msg_hash.c
T
2c7d46deef FFmpeg core: no hang on video gaps, decoder detection, more formats (#19753)
* ffmpeg core: don't hang on gaps in the video track

Playing a file whose video track has a gap of more than about two
seconds (frames dropped while recording, or any variable frame rate
source) froze RetroArch at that point. The main thread waits for the
next frame, and the decode thread only decodes a video packet once
audio has caught up to it, so across the gap it decodes audio that
nobody takes until the audio queue is full, then blocks too.

Wait for the next frame as before, but give up once the decoder has a
second or more of audio buffered ahead, which only happens across a
gap: hold the frame on screen, keep playing audio, and pick the next
frame up when the decoder reaches it. The interpolation factor is
clamped, since a held frame would push it outside [0, 1].

ffmpeg_sweep_test plays a clip with a 4 s hole in its video track and
checks audio and video both carry on past it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* ffmpeg core: detect working decoders instead of taking the first

avcodec_find_decoder() returns one decoder per codec id, whichever the
build registered first, so which decoder played a file depended on
registration order and on what that platform's FFmpeg includes. A
platform's hardware video decoder (h264_nvv4l2, h264_v4l2m2m,
h264_rkmpp, h264_cuvid, h264_mediacodec, ...) was only used if the
build moved it to the front (the L4T FFmpeg patches do, to make every
application use nvv4l2), and a default decoder that can't work there
failed the file even when another decoder for the codec was built in.

For every audio, video and subtitle stream, collect the
non-experimental decoders for its codec and try them in order:
  1. video only: platform hardware decoders (AV_CODEC_CAP_HARDWARE),
     unless disabled by the new core option 'Use platform hardware
     decoders' (ffmpeg_hw_wrappers, default enabled);
  2. the default decoder (still libdav1d first for AV1), with the
     existing hwaccel set-up for video;
  3. any other software decoder.
For audio and video, open each and decode from the start until a
frame comes out, then rewind; keep the first whose frame this core can
use (video: a format swscale accepts, or the hwaccel format it set up,
so a wrapper that only returns DRM-PRIME surfaces is skipped; audio: a
sample format swresample converts). Subtitles, inputs that can't be
rewound, and codecs with a single decoder take the first decoder that
opens. The hwaccel configuration now comes from the decoder actually
being opened. The chosen decoder is logged, and a missing one names
the codec.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* ffmpeg core: claim more media formats, but only those the build can open

The player claimed a fixed list of 28 extensions, and the frontend's
media type table only routed those to it, so common files (.ogv, .mka,
.opus, .aac, .ac3, .aiff, .wv, .ape, .mts, ...) didn't open with the
built-in player at all.

- The core keeps its list and adds 31 more extensions, each only if
  this build has the demuxer and, where the extension implies a codec
  (.ogv Theora, .ape APE, .wv WavPack, .ac3 AC-3, ...), the decoder.
  RetroArch's platforms ship very different FFmpeg builds, so
  valid_extensions is built once, at runtime.
- libretro_ffmpeg_ffmpeg_core_supports_extension() exposes the same
  check; msg_hash.c lists the extra extensions as MOVIE_OTHER /
  MUSIC_OTHER (after everything else, so existing claims win) and
  path_is_media_type() only routes them to the player when it passes.
  MPV builds route them unconditionally.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* ffmpeg core: an unload frees its codec contexts and a hardware device has one owner

The codec contexts are freed at unload whatever the FFmpeg version, not
only closed, and a hardware device is handed to the codec context that
uses it rather than referenced from it, so freeing that context closes
the device - every decoder the probing opens and drops included.

ffmpeg_sweep_test runs its ASan pass with leak detection: every load and
unload in it must give back what it took.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 18:19:47 +02:00

843 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;
if (tab->blob)
{
/* Packed encoding: 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++;
}
}
else
{
for (i = 0; i < tab->count; i++)
{
if (tab->ids[i] < (uint32_t)MSG_LAST)
id_slots[tab->ids[i]] = i + 1;
}
}
/* 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)
{
if (tab->strs)
return tab->strs[slot - 1];
return tab->blob + (slot - 1);
}
}
return NULL;
}
/* Allocation-failure path only. */
{
uint32_t i;
if (tab->strs)
{
for (i = 0; i < tab->count; i++)
if (tab->ids[i] == id)
return tab->strs[i];
}
else
{
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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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, NULL,
(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 },
{ "wma", 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
#ifdef HAVE_IMAGEVIEWER
{ "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 },
#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;
}