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++)