SDL_GetPrefPath and fopen()

Hi,

I have a problem reported by a couple of players playing my game on Windows. They have Windows usernames with non-ASCII characters. I get the user dir properly with SDL_GetPrefPath, but when I try to create a file in that directory with fopen() it fails.

I assume the problem is that the character set returned by SDL_GetPrefPath is different than the one that fopen() expects. So, my questions are:

What is the character set of the string that SDL_GetPrefPath returns?

Has anyone had the same problem and how did you get it to work?

Thanks.

it returns utf-8. https://wiki.libsdl.org/SDL_GetPrefPath#Return_Value

We always return UTF-8 strings, and (arguably) this is what fopen() should want. It’s possible your C runtime wants this string in the current locale (EDIT: “codepage”?) or the C runtime is broken if not using ASCII file names.

Maybe you’ll have better luck if you convert the string to UTF-16 with SDL_iconv, and use wfopen on Windows?

AFAIK Windows doesn’t support UTF-8, only something UTF-16-ish with the
"w" functions and extended ASCII in current codepage in the “a” (or in
case of fopen() probably default) functions.
So I guess you’d either have to convert the UTF-8 to the current
codepage (ISO-8859-ish on Western systems, I think?) or to the 8.3 DOS
format that (afaik) somehow folds file/dir names to uppercase ASCII-only
8chars with ~1 in it; see


for details, also GetShortPathName()
(https://msdn.microsoft.com/en-us/library/aa364989.aspx). Unfortunately
neither source is explicit about what happens to non-ASCII chars (I
think they’re just dropped, but not sure).
It seems like you need to use GetShortPathName() to create the 8.3
name anyway, because the renaming doesn’t seem to be deterministic (from
a users point of view). e.g. if you have two filenames
"LongFileNameFoo.txt" and “LongFileNameBar.txt” in the same directory,
they’d be shorted to sth like “LONGFI~1.TXT” and “LONGFI~2.TXT”, but you
wouldn’t know which is which.

TL;DR: It sucks, have to convert from UTF-8 to UTF-16 and use _wfopen().
Maybe try using this wrapper:


(I haven’t tried it myself, but it looks good on first sight)

Cheers,
Daniel

2 Likes

Thanks for all the replies.

It seems like that Wrapper does the job (the player who had the problem tested and now it works for him). I threw out the FOPEN_RB and other defines as it seems to me that other systems simply ignore fopen “b” flag.

[mbabuskov] mbabuskov https://discourse.libsdl.org/u/mbabuskov
June 19

Thanks for all the replies.

It seems like that Wrapper does the job (the player who had the problem
tested and now it works for him). I threw out the FOPEN_RB and other
defines as it seems to me that other systems simply ignore fopen “b” flag.

Yep, those shouldn’t be needed, just always add “b”.
Another thing is, I’m not sure if UNICODE must be defined, but that code
silently falls back to standard fopen() if it doesn’t - that seems very
error-prone to me - it would probably be better to do it like:

#include <stdio.h>
#ifdef _WIN32

#ifndef UNICODE // FIXME: not sure this check is even needed at all
#error Make sure UNICODE is defined for fopenu() to work on Windows!
#endif

#include <winnls.h>
inline FILE *fopenu(const char *path,const char *opt){
wchar_t wpath[1024];
wchar_t wopt[8];
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, 1024);
MultiByteToWideChar(CP_UTF8, 0, opt, -1, wopt, 8);

 return _wfopen(wpath, wopt);

}

#else // not Windows => just use standard fopen()
#define fopenu fopen
#endif

Cheers,
Daniel

Damn, looks like discourse fucked the formatting up (I sent via email,
but it applied Markdown or sth, which might not be the best choice)

Anyway, trying again, hoping it understands Github-style three-backticks
codeblocks:

#include <stdio.h>
#ifdef _WIN32

  #ifndef UNICODE // FIXME: not sure this check is even needed at all
    #error Make sure UNICODE is defined for fopenu() to work on Windows!
  #endif

#include <winnls.h>
inline FILE *fopenu(const char *path,const char *opt){
     wchar_t wpath[1024];
     wchar_t wopt[8];
     MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, 1024);
     MultiByteToWideChar(CP_UTF8, 0, opt, -1, wopt, 8);

     return _wfopen(wpath, wopt);
}

#else // not Windows => just use standard fopen()
#define fopenu fopen
#endif

I’m not sure what defines UNICODE? I don’t have a #define in my code nor in compiler invocation. And it works.

I just tested, UNICODE is definitely not defined in the version that works properly. So, #error is definitely a bad idea.

BTW, when I used winnls.h my code would not compile. I had to include windows.h instead.