shithub: choc

Download patch

ref: 9f14544070d0f7418d5025955526ea363d7f905d
parent: 98300208b663ef1b0882e032aa8d07f4af126eb9
author: Roman Fomin <rfomin@gmail.com>
date: Wed Jun 8 18:19:42 EDT 2022

Add support for non-latin paths and filenames on Windows (#1463)

* Add support for non-latin paths and filenames on Windows

* add missed newline

* fix memory leaks

* remove redundant declarations

* rewrite to use explicit wrappers

* don't call M_fopen() in droplay.c

* attempt to fix clang warning

* make conversion failure to UTF 8 non-fatal

* forgot newline

* fix return value, add comment

* set errno if conversion to UTF8 fails

* update HACKING.md

--- a/HACKING.md
+++ b/HACKING.md
@@ -216,6 +216,18 @@
 `(i / (1 << n))`.  However, most processors handle bitshifts of signed
 integers properly, so it’s not a huge problem.
 
+Due to Microsoft implementation of the Unicode support in C standard
+library you should use these wrappers to support non-latin paths and
+filenames on Windows.
+
+C library function |   Wrapper
+-------------------|-------------------
+`fopen()`          |  `M_fopen()`
+`remove()`         |  `M_remove()`
+`rename()`         |  `M_rename()`
+`stat()`           |  `M_stat()`
+`mkdir()`          |  `M_MakeDirectory()`
+
 ## GNU GPL and licensing
 
 All code submitted to the project must be licensed under the GNU GPLv2 or a
--- a/src/deh_io.c
+++ b/src/deh_io.c
@@ -88,7 +88,7 @@
     FILE *fstream;
     deh_context_t *context;
 
-    fstream = fopen(filename, "r");
+    fstream = M_fopen(filename, "r");
 
     if (fstream == NULL)
         return NULL;
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -1586,7 +1586,7 @@
 	 
     gameaction = ga_nothing; 
 	 
-    save_stream = fopen(savename, "rb");
+    save_stream = M_fopen(savename, "rb");
 
     if (save_stream == NULL)
     {
@@ -1656,7 +1656,7 @@
     // and then rename it at the end if it was successfully written.
     // This prevents an existing savegame from being overwritten by
     // a corrupted one, or if a savegame buffer overrun occurs.
-    save_stream = fopen(temp_savegame_file, "wb");
+    save_stream = M_fopen(temp_savegame_file, "wb");
 
     if (save_stream == NULL)
     {
@@ -1663,7 +1663,7 @@
         // Failed to save the game, so we're going to have to abort. But
         // to be nice, save to somewhere else before we call I_Error().
         recovery_savegame_file = M_TempFile("recovery.dsg");
-        save_stream = fopen(recovery_savegame_file, "wb");
+        save_stream = M_fopen(recovery_savegame_file, "wb");
         if (save_stream == NULL)
         {
             I_Error("Failed to open either '%s' or '%s' to write savegame.",
@@ -1707,8 +1707,8 @@
     // Now rename the temporary savegame file to the actual savegame
     // file, overwriting the old savegame if there was one there.
 
-    remove(savegame_file);
-    rename(temp_savegame_file, savegame_file);
+    M_remove(savegame_file);
+    M_rename(temp_savegame_file, savegame_file);
 
     gameaction = ga_nothing;
     M_StringCopy(savedescription, "", sizeof(savedescription));
--- a/src/doom/m_menu.c
+++ b/src/doom/m_menu.c
@@ -510,7 +510,7 @@
         int retval;
         M_StringCopy(name, P_SaveGameFile(i), sizeof(name));
 
-	handle = fopen(name, "rb");
+	handle = M_fopen(name, "rb");
         if (handle == NULL)
         {
             M_StringCopy(savegamestrings[i], EMPTYSTRING, SAVESTRINGSIZE);
--- a/src/doom/statdump.c
+++ b/src/doom/statdump.c
@@ -26,6 +26,7 @@
 #include "d_player.h"
 #include "d_mode.h"
 #include "m_argv.h"
+#include "m_misc.h"
 
 #include "statdump.h"
 
@@ -335,7 +336,7 @@
 
         if (strcmp(myargv[i + 1], "-") != 0)
         {
-            dumpfile = fopen(myargv[i + 1], "w");
+            dumpfile = M_fopen(myargv[i + 1], "w");
         }
         else
         {
--- a/src/gusconf.c
+++ b/src/gusconf.c
@@ -229,7 +229,7 @@
     FILE *fstream;
     unsigned int i;
 
-    fstream = fopen(path, "w");
+    fstream = M_fopen(path, "w");
 
     if (fstream == NULL)
     {
--- a/src/heretic/d_main.c
+++ b/src/heretic/d_main.c
@@ -238,7 +238,7 @@
     {
         char filename[20];
         M_snprintf(filename, sizeof(filename), "debug%i.txt", consoleplayer);
-        debugfile = fopen(filename, "w");
+        debugfile = M_fopen(filename, "w");
     }
     I_GraphicsCheckCommandLine();
     I_SetGrabMouseCallback(D_GrabMouseCallback);
--- a/src/heretic/mn_menu.c
+++ b/src/heretic/mn_menu.c
@@ -644,7 +644,7 @@
     {
         int retval;
         filename = SV_Filename(i);
-        fp = fopen(filename, "rb+");
+        fp = M_fopen(filename, "rb+");
 	free(filename);
 
         if (!fp)
--- a/src/heretic/p_saveg.c
+++ b/src/heretic/p_saveg.c
@@ -60,12 +60,12 @@
 
 void SV_Open(char *fileName)
 {
-    SaveGameFP = fopen(fileName, "wb");
+    SaveGameFP = M_fopen(fileName, "wb");
 }
 
 void SV_OpenRead(char *filename)
 {
-    SaveGameFP = fopen(filename, "rb");
+    SaveGameFP = M_fopen(filename, "rb");
 
     if (SaveGameFP == NULL)
     {
--- a/src/hexen/h2_main.c
+++ b/src/hexen/h2_main.c
@@ -822,7 +822,7 @@
     {
         char filename[20];
         M_snprintf(filename, sizeof(filename), "debug%i.txt", consoleplayer);
-        debugfile = fopen(filename, "w");
+        debugfile = M_fopen(filename, "w");
     }
     I_SetWindowTitle(gamedescription);
     I_GraphicsCheckCommandLine();
--- a/src/hexen/mn_menu.c
+++ b/src/hexen/mn_menu.c
@@ -680,7 +680,7 @@
 
     M_snprintf(name, sizeof(name), "%shex%d.hxs", SavePath, slot);
 
-    fp = fopen(name, "rb");
+    fp = M_fopen(name, "rb");
 
     if (fp == NULL)
     {
--- a/src/hexen/sv_save.c
+++ b/src/hexen/sv_save.c
@@ -3199,10 +3199,10 @@
     {
         M_snprintf(fileName, sizeof(fileName),
                    "%shex%d%02d.hxs", SavePath, slot, i);
-        remove(fileName);
+        M_remove(fileName);
     }
     M_snprintf(fileName, sizeof(fileName), "%shex%d.hxs", SavePath, slot);
-    remove(fileName);
+    M_remove(fileName);
 }
 
 //==========================================================================
@@ -3262,7 +3262,7 @@
     FILE *read_handle, *write_handle;
     int buf_count, read_count, write_count;
 
-    read_handle = fopen(source_name, "rb");
+    read_handle = M_fopen(source_name, "rb");
     if (read_handle == NULL)
     {
         I_Error ("Couldn't read file %s", source_name);
@@ -3281,7 +3281,7 @@
         Z_Free(buffer);
     }
 
-    write_handle = fopen(dest_name, "wb");
+    write_handle = M_fopen(dest_name, "wb");
     if (write_handle == NULL)
     {
         I_Error ("Couldn't read file %s", dest_name);
@@ -3327,7 +3327,7 @@
 {
     FILE *fp;
 
-    if ((fp = fopen(name, "rb")) != NULL)
+    if ((fp = M_fopen(name, "rb")) != NULL)
     {
         fclose(fp);
         return true;
@@ -3346,7 +3346,7 @@
 
 static void SV_OpenRead(char *fileName)
 {
-    SavingFP = fopen(fileName, "rb");
+    SavingFP = M_fopen(fileName, "rb");
 
     // Should never happen, only if hex6.hxs cannot ever be created.
     if (SavingFP == NULL)
@@ -3357,7 +3357,7 @@
 
 static void SV_OpenWrite(char *fileName)
 {
-    SavingFP = fopen(fileName, "wb");
+    SavingFP = M_fopen(fileName, "wb");
 }
 
 //==========================================================================
--- a/src/i_glob.c
+++ b/src/i_glob.c
@@ -27,7 +27,6 @@
 #if defined(_MSC_VER)
 // For Visual C++, we need to include the win_opendir module.
 #include <win_opendir.h>
-#include <sys/stat.h>
 #define S_ISDIR(m)      (((m)& S_IFMT) == S_IFDIR)
 #elif defined(HAVE_DIRENT_H)
 #include <dirent.h>
@@ -60,7 +59,7 @@
         int result;
 
         filename = M_StringJoin(dir, DIR_SEPARATOR_S, de->d_name, NULL);
-        result = stat(filename, &sb);
+        result = M_stat(filename, &sb);
         free(filename);
 
         if (result != 0)
--- a/src/i_musicpack.c
+++ b/src/i_musicpack.c
@@ -580,7 +580,7 @@
     metadata->start_time = 0;
     metadata->end_time = -1;
 
-    fs = fopen(filename, "rb");
+    fs = M_fopen(filename, "rb");
 
     if (fs == NULL)
     {
@@ -1017,7 +1017,7 @@
     unsigned int lumpnum;
     size_t h;
 
-    fs = fopen(filename, "w");
+    fs = M_fopen(filename, "w");
 
     if (fs == NULL)
     {
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -1673,7 +1673,7 @@
 
     // remove file now
 
-    remove(filename);
+    M_remove(filename);
     free(filename);
 
     return result;
--- a/src/i_sdlmusic.c
+++ b/src/i_sdlmusic.c
@@ -78,7 +78,7 @@
         return false;
     }
 
-    fstream = fopen(write_path, "w");
+    fstream = M_fopen(write_path, "w");
 
     if (fstream == NULL)
     {
@@ -144,7 +144,7 @@
 {
     if (temp_timidity_cfg != NULL)
     {
-        remove(temp_timidity_cfg);
+        M_remove(temp_timidity_cfg);
         free(temp_timidity_cfg);
     }
 }
@@ -514,7 +514,7 @@
 
         if (strlen(snd_musiccmd) == 0)
         {
-            remove(filename);
+            M_remove(filename);
         }
     }
 
--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -552,7 +552,7 @@
     unsigned int i;
     unsigned short s;
 
-    wav = fopen(filename, "wb");
+    wav = M_fopen(filename, "wb");
 
     // Header
 
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -86,7 +86,7 @@
     int i, k;
 
     // Read the response file into memory
-    handle = fopen(filename, "rb");
+    handle = M_fopen(filename, "rb");
 
     if (handle == NULL)
     {
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -1853,7 +1853,7 @@
     int i, v;
     FILE *f;
 	
-    f = fopen (collection->filename, "w");
+    f = M_fopen(collection->filename, "w");
     if (!f)
 	return; // can't write the file, but don't complain
 
@@ -2049,7 +2049,7 @@
     char strparm[100];
 
     // read the file in, overriding any set defaults
-    f = fopen(collection->filename, "r");
+    f = M_fopen(collection->filename, "r");
 
     if (f == NULL)
     {
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -32,7 +32,6 @@
 #include <direct.h>
 #endif
 #else
-#include <sys/stat.h>
 #include <sys/types.h>
 #endif
 
@@ -48,6 +47,159 @@
 #include "w_wad.h"
 #include "z_zone.h"
 
+#ifdef _WIN32
+static wchar_t* ConvertToUtf8(const char *str)
+{
+    wchar_t *wstr = NULL;
+    int wlen = 0;
+
+    wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
+
+    if (!wlen)
+    {
+        errno = EINVAL;
+        printf("Warning: Failed to convert path to UTF8\n");
+        return NULL;
+    }
+
+    wstr = malloc(sizeof(wchar_t) * wlen);
+
+    if (!wstr)
+    {
+        I_Error("ConvertToUtf8: Failed to allocate new string");
+        return NULL;
+    }
+
+    if (MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen) == 0)
+    {
+        errno = EINVAL;
+        printf("Warning: Failed to convert path to UTF8\n");
+        free(wstr);
+        return NULL;
+    }
+
+    return wstr;
+}
+#endif
+
+FILE* M_fopen(const char *filename, const char *mode)
+{
+#ifdef _WIN32
+    FILE *file;
+    wchar_t *wname = NULL;
+    wchar_t *wmode = NULL;
+
+    wname = ConvertToUtf8(filename);
+
+    if (!wname)
+    {
+        return NULL;
+    }
+
+    wmode = ConvertToUtf8(mode);
+
+    if (!wmode)
+    {
+        free(wname);
+        return NULL;
+    }
+
+    file = _wfopen(wname, wmode);
+
+    free(wname);
+    free(wmode);
+
+    return file;
+#else
+    return fopen(filename, mode);
+#endif
+}
+
+int M_remove(const char *path)
+{
+#ifdef _WIN32
+    wchar_t *wpath = NULL;
+    int ret;
+
+    wpath = ConvertToUtf8(path);
+
+    if (!wpath)
+    {
+        return 0;
+    }
+
+    ret = _wremove(wpath);
+
+    free(wpath);
+
+    return ret;
+#else
+    return remove(path);
+#endif
+}
+
+int M_rename(const char *oldname, const char *newname)
+{
+#ifdef _WIN32
+    wchar_t *wold = NULL;
+    wchar_t *wnew = NULL;
+    int ret;
+
+    wold = ConvertToUtf8(oldname);
+
+    if (!wold)
+    {
+        return 0;
+    }
+
+    wnew = ConvertToUtf8(newname);
+
+    if (!wnew)
+    {
+        free(wold);
+        return 0;
+    }
+
+    ret = _wrename(wold, wnew);
+
+    free(wold);
+    free(wnew);
+
+    return ret;
+#else
+    return rename(oldname, newname);
+#endif
+}
+
+int M_stat(const char *path, struct stat *buf)
+{
+#ifdef _WIN32
+    wchar_t *wpath = NULL;
+    struct _stat wbuf;
+    int ret;
+
+    wpath = ConvertToUtf8(path);
+
+    if (!wpath)
+    {
+        return -1;
+    }
+
+    ret = _wstat(wpath, &wbuf);
+
+    // The _wstat() function expects a struct _stat* parameter that is
+    // incompatible with struct stat*. We copy only the required compatible
+    // field.
+    buf->st_mode = wbuf.st_mode;
+
+    free(wpath);
+
+    return ret;
+#else
+    return stat(path, buf);
+#endif
+}
+
 //
 // Create a directory
 //
@@ -55,7 +207,18 @@
 void M_MakeDirectory(const char *path)
 {
 #ifdef _WIN32
-    mkdir(path);
+    wchar_t *wdir;
+
+    wdir = ConvertToUtf8(path);
+
+    if (!wdir)
+    {
+        return;
+    }
+
+    _wmkdir(wdir);
+
+    free(wdir);
 #else
     mkdir(path, 0755);
 #endif
@@ -67,7 +230,7 @@
 {
     FILE *fstream;
 
-    fstream = fopen(filename, "r");
+    fstream = M_fopen(filename, "r");
 
     if (fstream != NULL)
     {
@@ -183,7 +346,7 @@
     FILE *handle;
     int	count;
 	
-    handle = fopen(name, "wb");
+    handle = M_fopen(name, "wb");
 
     if (handle == NULL)
 	return false;
@@ -208,7 +371,7 @@
     int	count, length;
     byte *buf;
 	
-    handle = fopen(name, "rb");
+    handle = M_fopen(name, "rb");
     if (handle == NULL)
 	I_Error ("Couldn't read file %s", name);
 
@@ -654,25 +817,6 @@
     va_end(args);
     return result;
 }
-
-#ifdef _WIN32
-
-char *M_OEMToUTF8(const char *oem)
-{
-    unsigned int len = strlen(oem) + 1;
-    wchar_t *tmp;
-    char *result;
-
-    tmp = malloc(len * sizeof(wchar_t));
-    MultiByteToWideChar(CP_OEMCP, 0, oem, len, tmp, len);
-    result = malloc(len * 4);
-    WideCharToMultiByte(CP_UTF8, 0, tmp, len, result, len * 4, NULL, NULL);
-    free(tmp);
-
-    return result;
-}
-
-#endif
 
 //
 // M_NormalizeSlashes
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -22,9 +22,14 @@
 
 #include <stdio.h>
 #include <stdarg.h>
+#include <sys/stat.h>
 
 #include "doomtype.h"
 
+FILE* M_fopen(const char *filename, const char *mode);
+int M_remove(const char *path);
+int M_rename(const char *oldname, const char *newname);
+int M_stat(const char *path, struct stat *buf);
 boolean M_WriteFile(const char *name, const void *source, int length);
 int M_ReadFile(const char *name, byte **buffer);
 void M_MakeDirectory(const char *dir);
@@ -49,7 +54,6 @@
 boolean M_StringEndsWith(const char *s, const char *suffix);
 int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args);
 int M_snprintf(char *buf, size_t buf_len, const char *s, ...) PRINTF_ATTR(3, 4);
-char *M_OEMToUTF8(const char *ansi);
 void M_NormalizeSlashes(char *str);
 
 #endif
--- a/src/midifile.c
+++ b/src/midifile.c
@@ -23,6 +23,7 @@
 #include "doomtype.h"
 #include "i_swap.h"
 #include "i_system.h"
+#include "m_misc.h"
 #include "midifile.h"
 
 #define HEADER_CHUNK_ID "MThd"
@@ -598,7 +599,7 @@
 
     // Open file
 
-    stream = fopen(filename, "rb");
+    stream = M_fopen(filename, "rb");
 
     if (stream == NULL)
     {
--- a/src/net_common.c
+++ b/src/net_common.c
@@ -24,6 +24,7 @@
 #include "i_system.h"
 #include "i_timer.h"
 #include "m_argv.h"
+#include "m_misc.h"
 
 #include "net_common.h"
 #include "net_io.h"
@@ -469,7 +470,7 @@
     p = M_CheckParmWithArgs("-netlog", 1);
     if (p > 0)
     {
-        net_debug = fopen(myargv[p + 1], "w");
+        net_debug = M_fopen(myargv[p + 1], "w");
         if (net_debug == NULL)
         {
             I_Error("Failed to open %s to write debug log.", myargv[p + 1]);
--- a/src/setup/execute.c
+++ b/src/setup/execute.c
@@ -119,7 +119,7 @@
     result = malloc(sizeof(execute_context_t));
     
     result->response_file = TempFile("chocolat.rsp");
-    result->stream = fopen(result->response_file, "w");
+    result->stream = M_fopen(result->response_file, "w");
 
     if (result->stream == NULL)
     {
@@ -368,7 +368,7 @@
     free(response_file_arg);
 
     // Destroy context
-    remove(context->response_file);
+    M_remove(context->response_file);
     free(context->response_file);
     free(context);
 
@@ -406,8 +406,8 @@
 
     // Delete the temporary config files
 
-    remove(main_cfg);
-    remove(extra_cfg);
+    M_remove(main_cfg);
+    M_remove(extra_cfg);
     free(main_cfg);
     free(extra_cfg);
 }
--- a/src/strife/g_game.c
+++ b/src/strife/g_game.c
@@ -1682,7 +1682,7 @@
 
     gameaction = ga_nothing;
 
-    save_stream = fopen(loadpath, "rb");
+    save_stream = M_fopen(loadpath, "rb");
 
     // [STRIFE] If the file does not exist, G_DoLoadLevel is called.
     if (save_stream == NULL)
@@ -1828,7 +1828,7 @@
     // This prevents an existing savegame from being overwritten by 
     // a corrupted one, or if a savegame buffer overrun occurs.
 
-    save_stream = fopen(temp_savegame_file, "wb");
+    save_stream = M_fopen(temp_savegame_file, "wb");
 
     if (save_stream == NULL)
     {
@@ -1862,8 +1862,8 @@
     // Now rename the temporary savegame file to the actual savegame
     // file, overwriting the old savegame if there was one there.
 
-    remove(savegame_file);
-    rename(temp_savegame_file, savegame_file);
+    M_remove(savegame_file);
+    M_rename(temp_savegame_file, savegame_file);
     
     // haleyjd: free the savegame_file path
     Z_Free(savegame_file);
--- a/src/strife/m_menu.c
+++ b/src/strife/m_menu.c
@@ -552,7 +552,7 @@
             Z_Free(fname);
         fname = M_SafeFilePath(savegamedir, M_MakeStrifeSaveDir(i, "\\name"));
 
-        handle = fopen(fname, "rb");
+        handle = M_fopen(fname, "rb");
         if(handle == NULL)
         {
             M_StringCopy(savegamestrings[i], DEH_String(EMPTYSTRING),
--- a/src/strife/m_saves.c
+++ b/src/strife/m_saves.c
@@ -68,7 +68,7 @@
         {
             break;
         }
-        remove(path);
+        M_remove(path);
     }
 
     I_EndGlob(glob);
@@ -99,7 +99,7 @@
             break;
         }
 
-        remove(filepath);
+        M_remove(filepath);
     }
 
     I_EndGlob(glob);
@@ -207,8 +207,8 @@
     // haleyjd: use M_FileExists, not access
     if(M_FileExists(mapsave))
     {
-        remove(heresave);
-        rename(mapsave, heresave);
+        M_remove(heresave);
+        M_rename(mapsave, heresave);
     }
 
     Z_Free(mapsave);
@@ -234,8 +234,8 @@
 
     if(M_FileExists(heresave))
     {
-        remove(mapsave);
-        rename(heresave, mapsave);
+        M_remove(mapsave);
+        M_rename(heresave, mapsave);
     }
 
     Z_Free(mapsave);
@@ -273,7 +273,7 @@
     // haleyjd: use M_SafeFilePath, not sprintf
     srcpath = M_SafeFilePath(savepathtemp, "mis_obj");
 
-    if((f = fopen(srcpath, "rb")))
+    if((f = M_fopen(srcpath, "rb")))
     {
         int retval = fread(mission_objective, 1, OBJECTIVE_LEN, f);
         fclose(f);
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -752,7 +752,7 @@
         h_factor = 1;
     }
 
-    handle = fopen(filename, "wb");
+    handle = M_fopen(filename, "wb");
     if (!handle)
     {
         return;
--- a/src/w_file_stdc.c
+++ b/src/w_file_stdc.c
@@ -35,7 +35,7 @@
     stdc_wad_file_t *result;
     FILE *fstream;
 
-    fstream = fopen(path, "rb");
+    fstream = M_fopen(path, "rb");
 
     if (fstream == NULL)
     {
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -508,7 +508,7 @@
     }
     profilecount++;
 	
-    f = fopen ("waddump.txt","w");
+    f = M_fopen ("waddump.txt","w");
     name[8] = 0;
 
     for (i=0 ; i<numlumps ; i++)