Skip to content

str: do not take the frontend's LC_NUMERIC with us on Windows - #133

Closed
WizzardSK wants to merge 1 commit into
libretro:mainfrom
WizzardSK:win-locale-numeric
Closed

WizzardSK wants to merge 1 commit into
libretro:mainfrom
WizzardSK:win-locale-numeric

Conversation

@WizzardSK

Copy link
Copy Markdown
Contributor

Str_Init() calls setlocale(LC_ALL, "") on Windows so that the Atari character set can be converted to whatever the host uses. Everything it does that for - wctomb(), mbtowc(), MB_CUR_MAX - is LC_CTYPE. LC_ALL takes LC_NUMERIC along with it, and in a core the process belongs to the frontend.

On a machine whose locale writes a decimal comma, that turns two things in RetroArch inside out from the moment content is loaded:

  • #pragma parameter DO_TEST "..." 0.0 0.0 12.0 1.0 is read with strtod() in gfx/drivers_shader/slang_process.c. The first call now stops at the ., the second is handed ".0 0.0 12.0 1.0" and fails, and the preset is dropped whole with Invalid #pragma parameter line;
  • config floats are written with snprintf("%f"), so retroarch.cfg gets input_axis_threshold = 0,500000, which RetroArch's own locale-independent reader truncates to 0 on the next start - after which input behaves as if every axis were held.

Measured under sk_SK.utf8, running RetroArch's exact strtod sequence over that pragma line:

after setlocale(LC_ALL,"")   | 0 | field 2 REJECTED at ".0 0.0 12.0 1.0" || snprintf("%f",0.5) = 0,500000
after setlocale(LC_CTYPE,"") | 0 | 0 | 12 | 1                            || snprintf("%f",0.5) = 0.500000

This is #131, and it accounts for every observation in that thread: Windows only, because the call is behind #if defined(WIN32); only the Atari cores, because they are the ones calling it; not the 2014 core, which never calls Str_Init() at all; hatariB too, which does; and not reproducible for anyone whose locale writes a decimal point, which is why it looked like a RetroArch regression at first - it is not, RetroArch parses those lines correctly in the locale it is entitled to expect.

Only the libretro build changes. Standalone Hatari owns its process and keeps LC_ALL.

Not verified on Windows hardware - I have none here. What is verified: the core builds, and the locale behaviour above is measured rather than assumed.

@cscd98

cscd98 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Can you change the comment in the code to something like "Use LC_CTYPE instead of LC_ALL so character handling follows the user's locale without changing numeric formatting, preventing locale-specific decimal separators (e.g. 1,0000) in RetroArch configuration files."

@cscd98

cscd98 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Also change it to this:

#if defined(LIBRETRO)
 <your comment>
	<your code>
#elif defined(WIN32) || defined(USE_LOCALE_CHARSET)
	/* Change libc from default "C" locale to one
	 * specified by the program environment. Needed
	 * only for Windows, as Unix based OSes (are
	 * assumed to) use UTF-8 based locales nowadays.
	 */
	setlocale(LC_ALL, "");
#endif

Str_Init() switches libc away from the "C" locale so that the Atari
character set can be converted to whatever the host uses. The conversion
functions it does that for - wctomb(), mbtowc(), MB_CUR_MAX - are all
LC_CTYPE. LC_ALL brings LC_NUMERIC along, and in a core the process is
the frontend's.

On a machine whose locale writes a decimal comma, that turns two things
in RetroArch inside out from the moment a game is loaded:

- "#pragma parameter DO_TEST "..." 0.0 0.0 12.0 1.0" is parsed with
  strtod() in gfx/drivers_shader/slang_process.c. The first call now
  stops at the '.', the second is handed ".0 0.0 12.0 1.0" and fails,
  and the whole preset is dropped with "Invalid #pragma parameter line";
- config floats are written with snprintf("%f"), so retroarch.cfg gets
  "input_axis_threshold = 0,500000", which its own locale-independent
  reader truncates to 0 on the next start - after which input behaves as
  if every axis were held.

Measured under sk_SK.utf8, running RetroArch's own strtod sequence over
that pragma line: with LC_ALL the second field is rejected and 0.5 prints
as "0,500000"; with LC_CTYPE the four fields read 0, 0, 12, 1 and 0.5
prints as "0.500000", with the character set still the user's.

Only the libretro build changes; standalone Hatari owns its process.

Reported in libretro#131, on Windows 11, where the same happens with hatariB -
which calls Str_Init() too. The 2014 core never called it, which is why
that one is unaffected.
@WizzardSK

Copy link
Copy Markdown
Contributor Author

Done - your wording, and the #if defined(LIBRETRO) / #elif shape.

One deviation, and say the word if you would rather have it exactly as written: I kept the Windows condition on the libretro branch as well, #if defined(LIBRETRO) && (defined(WIN32) || defined(USE_LOCALE_CHARSET)). With a bare defined(LIBRETRO) first, a Linux or Android core would start calling setlocale(LC_CTYPE, "") where it calls nothing today - those builds compile the UTF-8 conversion path, which never looks at the locale, so it would change the frontend's LC_CTYPE for no gain. This way only the builds that already touched the locale still do.

@cscd98

cscd98 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Thanks not quite correct. I think this is better. So it only applies when not libretro.

#if !defined(LIBRETRO) && (defined(WIN32) || defined(USE_LOCALE_CHARSET))
	/* Change libc from default "C" locale to one
	 * specified by the program environment. Needed
	 * only for Windows, as Unix based OSes (are
	 * assumed to) use UTF-8 based locales nowadays.
	 */
	setlocale(LC_ALL, "");
#endif

@cscd98

cscd98 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

I want to watch a film so quickly made the change. Thanks for the fix! much appreciated.

@cscd98 cscd98 closed this Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants