From a7c8cc88e5f16f931c831b08cb9ab469a528fab8 Mon Sep 17 00:00:00 2001 From: Brad Parker Date: Sat, 14 Oct 2017 23:13:24 -0400 Subject: [PATCH] win32: support local multibyte encoding for file paths --- configuration.c | 6 +- input/input_remapping.c | 2 +- libretro-common/encodings/encoding_utf.c | 88 ++++++++++++++++++++++++ libretro-common/file/file_path.c | 39 +++++++++-- libretro-common/file/retro_dirent.c | 15 +++- libretro-common/include/encodings/utf.h | 10 +++ libretro-common/include/file/file_path.h | 2 + libretro-common/streams/file_stream.c | 18 +++++ menu/cbs/menu_cbs_ok.c | 4 +- menu/menu_setting.c | 2 +- tasks/task_content.c | 2 +- 11 files changed, 175 insertions(+), 13 deletions(-) diff --git a/configuration.c b/configuration.c index 9f0261e063..ed096b10ae 100644 --- a/configuration.c +++ b/configuration.c @@ -3701,15 +3701,15 @@ bool config_save_file(const char *path) if (settings->bools.ssh_enable) fclose(fopen(LAKKA_SSH_PATH, "w")); else - remove(LAKKA_SSH_PATH); + path_file_remove(LAKKA_SSH_PATH); if (settings->bools.samba_enable) fclose(fopen(LAKKA_SAMBA_PATH, "w")); else - remove(LAKKA_SAMBA_PATH); + path_file_remove(LAKKA_SAMBA_PATH); if (settings->bools.bluetooth_enable) fclose(fopen(LAKKA_BLUETOOTH_PATH, "w")); else - remove(LAKKA_BLUETOOTH_PATH); + path_file_remove(LAKKA_BLUETOOTH_PATH); #endif for (i = 0; i < MAX_USERS; i++) diff --git a/input/input_remapping.c b/input/input_remapping.c index 83629a40b3..68e0d7363e 100644 --- a/input/input_remapping.c +++ b/input/input_remapping.c @@ -241,7 +241,7 @@ bool input_remapping_remove_file(const char *path) fill_pathname_noext(remap_file, buf, ".rmp", path_size); - ret = remove(remap_file) == 0 ? true : false;; + ret = path_file_remove(remap_file) == 0 ? true : false;; free(buf); free(remap_file); return ret; diff --git a/libretro-common/encodings/encoding_utf.c b/libretro-common/encodings/encoding_utf.c index 8220361124..dafeabb381 100644 --- a/libretro-common/encodings/encoding_utf.c +++ b/libretro-common/encodings/encoding_utf.c @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -31,6 +32,15 @@ #include +#if defined(_WIN32) && !defined(_XBOX) +#include + +/* Starting with Windows 8: MultiByteToWideChar is declared in stringapiset.h. Before Windows 8, it was declared in winnls.h (which windows.h includes for us). */ +#ifndef MultiByteToWideChar +#include +#endif +#endif + static INLINE unsigned leading_ones(uint8_t c) { unsigned ones = 0; @@ -260,3 +270,81 @@ bool utf16_to_char_string(const uint16_t *in, char *s, size_t len) return ret; } + +/* Returned pointer MUST be freed by the caller if non-NULL. */ +static char* mb_to_mb_string_alloc(const char *str, enum CodePage cp_in, enum CodePage cp_out) +{ + char *path_buf = NULL; + wchar_t *path_buf_wide = NULL; + int path_buf_len = 0; + int path_buf_wide_len = 0; + + if (!str || !*str) + return NULL; + +#if !defined(_WIN32) || defined(_XBOX) + /* assume string needs no modification if not on Windows */ + return strdup(str); +#else +#ifdef UNICODE + /* TODO/FIXME: Not implemented. */ + return strdup(str); +#else + + path_buf_wide_len = MultiByteToWideChar(cp_in, 0, str, -1, NULL, 0); + + if (path_buf_wide_len) + { + path_buf_wide = (wchar_t*)calloc(path_buf_wide_len + sizeof(wchar_t), sizeof(wchar_t)); + + if (path_buf_wide) + { + MultiByteToWideChar(cp_in, 0, str, -1, path_buf_wide, path_buf_wide_len); + + if (*path_buf_wide) + { + path_buf_len = WideCharToMultiByte(cp_out, 0, path_buf_wide, -1, NULL, 0, NULL, NULL); + + if (path_buf_len) + { + path_buf = (char*)calloc(path_buf_len + sizeof(char), sizeof(char)); + + if (path_buf) + { + WideCharToMultiByte(cp_out, 0, path_buf_wide, -1, path_buf, path_buf_len, NULL, NULL); + + free(path_buf_wide); + + if (*path_buf) + return path_buf; + else + { + free(path_buf); + return NULL; + } + } + } + } + } + } +#endif +#endif + + if (path_buf_wide) + free(path_buf_wide); + + return NULL; +} + +/* Returned pointer MUST be freed by the caller if non-NULL. */ +char* utf8_to_local_string_alloc(const char *str) +{ + return mb_to_mb_string_alloc(str, CODEPAGE_UTF8, CODEPAGE_LOCAL); +} + +/* Returned pointer MUST be freed by the caller if non-NULL. */ +char* local_to_utf8_string_alloc(const char *str) +{ + return mb_to_mb_string_alloc(str, CODEPAGE_LOCAL, CODEPAGE_UTF8); +} + diff --git a/libretro-common/file/file_path.c b/libretro-common/file/file_path.c index 2740397074..81b8c83114 100644 --- a/libretro-common/file/file_path.c +++ b/libretro-common/file/file_path.c @@ -37,6 +37,7 @@ #endif #include #include +#include #if defined(_WIN32) #ifdef _MSC_VER @@ -115,9 +116,18 @@ static bool path_stat(const char *path, enum stat_mode mode, int32_t *size) return false; #elif defined(_WIN32) struct _stat buf; - DWORD file_info = GetFileAttributes(path); - _stat(path, &buf); + if (!path || !*path) + return false; + + char *path_local = utf8_to_local_string_alloc(path); + + DWORD file_info = GetFileAttributes(path_local); + + _stat(path_local, &buf); + + if (path_local) + free(path_local); if (file_info == INVALID_FILE_ATTRIBUTES) return false; @@ -365,8 +375,8 @@ bool path_is_compressed_file(const char* path) * Returns: true (1) if file already exists, otherwise false (0). */ bool path_file_exists(const char *path) -{ - FILE *dummy; +{ + FILE *dummy; if (!path || !*path) return false; @@ -884,3 +894,24 @@ void fill_short_pathname_representation_noext(char* out_rep, fill_short_pathname_representation(out_rep, in_path, size); path_remove_extension(out_rep); } + +int path_file_remove(const char *path) +{ + if (!path || !*path) + return false; +#if defined(_WIN32) && !defined(_XBOX) + char *path_local = utf8_to_local_string_alloc(path); + + if (path_local) + { + bool ret = remove(path_local); + free(path_local); + + return ret; + } +#else + return remove(path); +#endif + + return -1; +} diff --git a/libretro-common/file/retro_dirent.c b/libretro-common/file/retro_dirent.c index 26f3e8a897..11de94c533 100644 --- a/libretro-common/file/retro_dirent.c +++ b/libretro-common/file/retro_dirent.c @@ -28,6 +28,8 @@ #include #include +#include +#include #if defined(_WIN32) # ifdef _MSC_VER @@ -89,6 +91,7 @@ struct RDIR *retro_opendir(const char *name) { #if defined(_WIN32) char path_buf[1024]; + char *path_local = NULL; #endif struct RDIR *rdir = (struct RDIR*)calloc(1, sizeof(*rdir)); @@ -98,7 +101,11 @@ struct RDIR *retro_opendir(const char *name) #if defined(_WIN32) path_buf[0] = '\0'; snprintf(path_buf, sizeof(path_buf), "%s\\*", name); - rdir->directory = FindFirstFile(path_buf, &rdir->entry); + path_local = utf8_to_local_string_alloc(path_buf); + rdir->directory = FindFirstFile(path_local, &rdir->entry); + + if (path_local) + free(path_local); #elif defined(VITA) || defined(PSP) rdir->directory = sceIoDopen(name); #elif defined(_3DS) @@ -149,6 +156,12 @@ int retro_readdir(struct RDIR *rdir) const char *retro_dirent_get_name(struct RDIR *rdir) { #if defined(_WIN32) + char *name_local = local_to_utf8_string_alloc(rdir->entry.cFileName); + memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName)); + strlcpy(rdir->entry.cFileName, name_local, sizeof(rdir->entry.cFileName)); + + if (name_local) + free(name_local); return rdir->entry.cFileName; #elif defined(VITA) || defined(PSP) || defined(__CELLOS_LV2__) return rdir->entry.d_name; diff --git a/libretro-common/include/encodings/utf.h b/libretro-common/include/encodings/utf.h index cb96a53066..b8991d0dc4 100644 --- a/libretro-common/include/encodings/utf.h +++ b/libretro-common/include/encodings/utf.h @@ -32,6 +32,12 @@ RETRO_BEGIN_DECLS +enum CodePage +{ + CODEPAGE_LOCAL = 0, /* CP_ACP */ + CODEPAGE_UTF8 = 65001 /* CP_UTF8 */ +}; + size_t utf8_conv_utf32(uint32_t *out, size_t out_chars, const char *in, size_t in_size); @@ -48,6 +54,10 @@ uint32_t utf8_walk(const char **string); bool utf16_to_char_string(const uint16_t *in, char *s, size_t len); +char* utf8_to_local_string_alloc(const char *str); + +char* local_to_utf8_string_alloc(const char *str); + RETRO_END_DECLS #endif diff --git a/libretro-common/include/file/file_path.h b/libretro-common/include/file/file_path.h index c92e35cf26..04da118ac0 100644 --- a/libretro-common/include/file/file_path.h +++ b/libretro-common/include/file/file_path.h @@ -466,6 +466,8 @@ bool path_is_valid(const char *path); int32_t path_get_size(const char *path); +int path_file_remove(const char *path); + RETRO_END_DECLS #endif diff --git a/libretro-common/streams/file_stream.c b/libretro-common/streams/file_stream.c index c2ba594f13..77178b9fc9 100644 --- a/libretro-common/streams/file_stream.c +++ b/libretro-common/streams/file_stream.c @@ -65,6 +65,7 @@ #include #include #include +#include struct RFILE { @@ -140,6 +141,9 @@ RFILE *filestream_open(const char *path, unsigned mode, ssize_t len) const char *mode_str = NULL; #endif RFILE *stream = (RFILE*)calloc(1, sizeof(*stream)); +#if defined(_WIN32) && !defined(_XBOX) + char *path_local = NULL; +#endif if (!stream) return NULL; @@ -228,7 +232,14 @@ RFILE *filestream_open(const char *path, unsigned mode, ssize_t len) #if defined(HAVE_BUFFERED_IO) if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0 && mode_str) { +#if defined(_WIN32) && !defined(_XBOX) + path_local = utf8_to_local_string_alloc(path); + stream->fp = fopen(path_local, mode_str); + if (path_local) + free(path_local); +#else stream->fp = fopen(path, mode_str); +#endif if (!stream->fp) goto error; } @@ -236,7 +247,14 @@ RFILE *filestream_open(const char *path, unsigned mode, ssize_t len) #endif { /* FIXME: HAVE_BUFFERED_IO is always 1, but if it is ever changed, open() needs to be changed to _wopen() for WIndows. */ +#if defined(_WIN32) && !defined(_XBOX) + path_local = utf8_to_local_string_alloc(path); + stream->fd = open(path_local, flags, mode_int); + if (path_local) + free(path_local); +#else stream->fd = open(path, flags, mode_int); +#endif if (stream->fd == -1) goto error; #ifdef HAVE_MMAP diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index a310c385cf..34f8bbbab9 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -2643,7 +2643,7 @@ static void cb_decompressed(void *task_data, void *user_data, const char *err) if (dec) { if (path_file_exists(dec->source_file)) - remove(dec->source_file); + path_file_remove(dec->source_file); free(dec->source_file); free(dec); @@ -4242,7 +4242,7 @@ static int action_ok_core_delete(const char *path, generic_action_ok_command(CMD_EVENT_UNLOAD_CORE); menu_entries_flush_stack(0, 0); - if (remove(core_path) != 0) { } + if (path_file_remove(core_path) != 0) { } free(core_path); diff --git a/menu/menu_setting.c b/menu/menu_setting.c index b725d6ac1c..2de7f238fe 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -1860,7 +1860,7 @@ static void systemd_service_toggle(const char *path, char *unit, bool enable) if (enable) fclose(fopen(path, "w")); else - remove(path); + path_file_remove(path); if (pid == 0) execvp(args[0], args); diff --git a/tasks/task_content.c b/tasks/task_content.c index 8208f8b4ed..64f139a639 100644 --- a/tasks/task_content.c +++ b/tasks/task_content.c @@ -1717,7 +1717,7 @@ void content_deinit(void) RARCH_LOG("%s: %s.\n", msg_hash_to_str(MSG_REMOVING_TEMPORARY_CONTENT_FILE), path); - if (remove(path) < 0) + if (path_file_remove(path) < 0) RARCH_ERR("%s: %s.\n", msg_hash_to_str(MSG_FAILED_TO_REMOVE_TEMPORARY_FILE), path);