From a9f76da2710b074d5c6c7952c5b9a0730f53dcb9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20=C5=A0imek?= <jsimek.cz@gmail.com>
Date: Fri, 17 Jun 2022 16:34:18 +0200
Subject: [PATCH] Compatibility layer for file handling on linux

See #17
---
 src/automap.cc         |  2 +-
 src/dfile.cc           |  4 +--
 src/file_find.cc       | 76 ++++++++++++++++++++++++++++--------------
 src/file_find.h        | 12 ++++---
 src/file_utils.cc      | 23 ++++++++-----
 src/game_sound.cc      |  6 ++--
 src/loadsave.cc        | 22 ++++++------
 src/platform_compat.cc | 31 +++++++++++++++--
 src/platform_compat.h  |  4 +++
 src/xfile.cc           | 14 ++++----
 10 files changed, 128 insertions(+), 66 deletions(-)

diff --git a/src/automap.cc b/src/automap.cc
index 1808aac..80a25dd 100644
--- a/src/automap.cc
+++ b/src/automap.cc
@@ -237,7 +237,7 @@ void automapExit()
     if (configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) {
         char path[COMPAT_MAX_PATH];
         sprintf(path, "%s\\%s\\%s", masterPatchesPath, "MAPS", AUTOMAP_DB);
-        remove(path);
+        compat_remove(path);
     }
 }
 
diff --git a/src/dfile.cc b/src/dfile.cc
index 9d71446..cb1cb63 100644
--- a/src/dfile.cc
+++ b/src/dfile.cc
@@ -16,7 +16,7 @@ DBase* dbaseOpen(const char* filePath)
 {
     assert(filePath); // "filename", "dfile.c", 74
 
-    FILE* stream = fopen(filePath, "rb");
+    FILE* stream = compat_fopen(filePath, "rb");
     if (stream == NULL) {
         return NULL;
     }
@@ -635,7 +635,7 @@ DFile* dfileOpenInternal(DBase* dbase, const char* filePath, const char* mode, D
     dfile->entry = entry;
 
     // Open stream to .DAT file.
-    dfile->stream = fopen(dbase->path, "rb");
+    dfile->stream = compat_fopen(dbase->path, "rb");
     if (dfile->stream == NULL) {
         goto err;
     }
diff --git a/src/file_find.cc b/src/file_find.cc
index 59c3f13..380ba9d 100644
--- a/src/file_find.cc
+++ b/src/file_find.cc
@@ -1,7 +1,50 @@
 #include "file_find.h"
+#ifndef _WIN32
+#include <regex>
+#endif
 
 #include <stddef.h>
 
+#ifndef _WIN32
+bool findFileRegex(DirectoryFileFindData* findData) {
+    bool findingNextFile = (findData->filename[0] != '\0');
+
+    std::string filenameRegex = findData->searchPath.filename().string();
+
+    auto wildcardPosition = filenameRegex.find("*");
+    if (wildcardPosition != std::string::npos) {
+        filenameRegex = filenameRegex.insert(wildcardPosition, ".");
+    }
+
+    try {
+        const std::filesystem::directory_iterator end;
+        for (std::filesystem::directory_iterator iter{findData->searchPath.parent_path()}; iter != end; iter++) {
+
+            std::string currentFilename = iter->path().filename().string();
+
+            if (findingNextFile) {
+                if (currentFilename == findData->filename) {
+                    findingNextFile = false;
+                }
+                continue;
+            }
+
+            if (std::regex_match(currentFilename, std::regex(filenameRegex))) {
+
+                memset(findData->filename, 0, sizeof findData->filename);
+                currentFilename.copy(findData->filename, currentFilename.length());
+                findData->filename[currentFilename.length()] = '\0';
+                return true;
+            }
+        }
+    }
+    catch (std::exception&) {}
+
+    memset(findData->filename, 0, sizeof findData->filename);
+    return false;
+}
+#endif
+
 // 0x4E6380
 bool fileFindFirst(const char* path, DirectoryFileFindData* findData)
 {
@@ -10,20 +53,12 @@ bool fileFindFirst(const char* path, DirectoryFileFindData* findData)
     if (findData->hFind == INVALID_HANDLE_VALUE) {
         return false;
     }
-#else
-    findData->dir = opendir(path);
-    if (findData->dir == NULL) {
-        return false;
-    }
-
-    findData->entry = readdir(findData->dir);
-    if (findData->entry == NULL) {
-        closedir(findData->dir);
-        return false;
-    }
-#endif
-
     return true;
+#else
+    memset(findData->filename, 0, sizeof findData->filename);
+    findData->searchPath = compat_convertPathSeparators(path);
+    return findFileRegex(findData);
+#endif
 }
 
 // 0x4E63A8
@@ -33,15 +68,10 @@ bool fileFindNext(DirectoryFileFindData* findData)
     if (!FindNextFileA(findData->hFind, &(findData->ffd))) {
         return false;
     }
-#else
-    findData->entry = readdir(findData->dir);
-    if (findData->entry == NULL) {
-        closedir(findData->dir);
-        return false;
-    }
-#endif
-
     return true;
+#else
+    return findFileRegex(findData);
+#endif
 }
 
 // 0x4E63CC
@@ -49,10 +79,6 @@ bool findFindClose(DirectoryFileFindData* findData)
 {
 #if defined(_MSC_VER)
     FindClose(findData->hFind);
-#else
-    if (closedir(findData->dir) != 0) {
-        return false;
-    }
 #endif
 
     return true;
diff --git a/src/file_find.h b/src/file_find.h
index 2bba6ed..e8c5c4c 100644
--- a/src/file_find.h
+++ b/src/file_find.h
@@ -6,7 +6,9 @@
 #define NOMINMAX
 #include <windows.h>
 #else
-#include <dirent.h>
+#include "platform_compat.h"
+#include <filesystem>
+#include <string.h>
 #endif
 
 // NOTE: This structure is significantly different from what was in the
@@ -33,8 +35,8 @@ typedef struct DirectoryFileFindData {
     HANDLE hFind;
     WIN32_FIND_DATAA ffd;
 #else
-    DIR* dir;
-    struct dirent* entry;
+    std::filesystem::path searchPath;
+    char filename[COMPAT_MAX_FNAME];
 #endif
 } DirectoryFileFindData;
 
@@ -49,7 +51,7 @@ static inline bool fileFindIsDirectory(DirectoryFileFindData* findData)
 #elif defined(__WATCOMC__)
     return (findData->entry->d_attr & _A_SUBDIR) != 0;
 #else
-    return findData->entry->d_type == DT_DIR;
+    return std::filesystem::is_directory(findData->searchPath);
 #endif
 }
 
@@ -58,7 +60,7 @@ static inline char* fileFindGetName(DirectoryFileFindData* findData)
 #if defined(_WIN32)
     return findData->ffd.cFileName;
 #else
-    return findData->entry->d_name;
+    return findData->filename;
 #endif
 }
 
diff --git a/src/file_utils.cc b/src/file_utils.cc
index 7edba4b..a719db6 100644
--- a/src/file_utils.cc
+++ b/src/file_utils.cc
@@ -2,6 +2,7 @@
 // of regular __usercall.
 
 #include "file_utils.h"
+#include "platform_compat.h"
 
 #include <stdio.h>
 #include <zlib.h>
@@ -11,7 +12,7 @@
 // 0x452740
 int fileCopyDecompressed(const char* existingFilePath, const char* newFilePath)
 {
-    FILE* stream = fopen(existingFilePath, "rb");
+    FILE* stream = compat_fopen(existingFilePath, "rb");
     if (stream == NULL) {
         return -1;
     }
@@ -22,8 +23,8 @@ int fileCopyDecompressed(const char* existingFilePath, const char* newFilePath)
     fclose(stream);
 
     if (magic[0] == 0x1F && magic[1] == 0x8B) {
-        gzFile inStream = gzopen(existingFilePath, "rb");
-        FILE* outStream = fopen(newFilePath, "wb");
+        gzFile inStream = gzopen(compat_convertPathSeparators(existingFilePath).c_str(), "rb");
+        FILE* outStream = compat_fopen(newFilePath, "wb");
 
         if (inStream != NULL && outStream != NULL) {
             for (;;) {
@@ -58,7 +59,7 @@ int fileCopyDecompressed(const char* existingFilePath, const char* newFilePath)
 // 0x452804
 int fileCopyCompressed(const char* existingFilePath, const char* newFilePath)
 {
-    FILE* inStream = fopen(existingFilePath, "rb");
+    FILE* inStream = compat_fopen(existingFilePath, "rb");
     if (inStream == NULL) {
         return -1;
     }
@@ -74,7 +75,7 @@ int fileCopyCompressed(const char* existingFilePath, const char* newFilePath)
         fclose(inStream);
         fileCopy(existingFilePath, newFilePath, true);
     } else {
-        gzFile outStream = gzopen(newFilePath, "wb");
+        gzFile outStream = gzopen(compat_convertPathSeparators(newFilePath).c_str(), "wb");
         if (outStream == NULL) {
             fclose(inStream);
             return -1;
@@ -100,7 +101,7 @@ int fileCopyCompressed(const char* existingFilePath, const char* newFilePath)
 // TODO: Check, implementation looks odd.
 int _gzdecompress_file(const char* existingFilePath, const char* newFilePath)
 {
-    FILE* stream = fopen(existingFilePath, "rb");
+    FILE* stream = compat_fopen(existingFilePath, "rb");
     if (stream == NULL) {
         return -1;
     }
@@ -112,12 +113,12 @@ int _gzdecompress_file(const char* existingFilePath, const char* newFilePath)
 
     // TODO: Is it broken?
     if (magic[0] != 0x1F || magic[1] != 0x8B) {
-        gzFile gzstream = gzopen(existingFilePath, "rb");
+        gzFile gzstream = gzopen(compat_convertPathSeparators(existingFilePath).c_str(), "rb");
         if (gzstream == NULL) {
             return -1;
         }
 
-        stream = fopen(newFilePath, "wb");
+        stream = compat_fopen(newFilePath, "wb");
         if (stream == NULL) {
             gzclose(gzstream);
             return -1;
@@ -149,5 +150,9 @@ void fileCopy(const char* existingFilePath, const char* newFilePath, bool overwr
     std::filesystem::copy_options options = overwrite
         ? std::filesystem::copy_options::overwrite_existing
         : std::filesystem::copy_options::none;
-    std::filesystem::copy_file(std::filesystem::path(existingFilePath), std::filesystem::path(newFilePath), options, ec);
+
+    std::string compatExistingFilePath = compat_convertPathSeparators(existingFilePath);
+    std::string compatNewFilePath = compat_convertPathSeparators(newFilePath);
+
+    std::filesystem::copy_file(std::filesystem::path(compatExistingFilePath), std::filesystem::path(compatNewFilePath), options, ec);
 }
diff --git a/src/game_sound.cc b/src/game_sound.cc
index 47eab43..be2198d 100644
--- a/src/game_sound.cc
+++ b/src/game_sound.cc
@@ -1703,7 +1703,7 @@ int gameSoundFindBackgroundSoundPathWithCopy(char* dest, const char* src)
     char inPath[COMPAT_MAX_PATH];
     sprintf(inPath, "%s%s%s", _sound_music_path2, src, ".ACM");
 
-    FILE* inStream = fopen(inPath, "rb");
+    FILE* inStream = compat_fopen(inPath, "rb");
     if (inStream == NULL) {
         if (gGameSoundDebugEnabled) {
             debugPrint("Unable to find music file %s to copy down.\n", src);
@@ -1712,7 +1712,7 @@ int gameSoundFindBackgroundSoundPathWithCopy(char* dest, const char* src)
         return -1;
     }
 
-    FILE* outStream = fopen(outPath, "wb");
+    FILE* outStream = compat_fopen(outPath, "wb");
     if (outStream == NULL) {
         if (gGameSoundDebugEnabled) {
             debugPrint("Unable to open music file %s for copying to.", src);
@@ -2026,7 +2026,7 @@ Sound* _gsound_get_sound_ready_for_effect()
 // 0x4524E0
 bool _gsound_file_exists_f(const char* fname)
 {
-    FILE* f = fopen(fname, "rb");
+    FILE* f = compat_fopen(fname, "rb");
     if (f == NULL) {
         return false;
     }
diff --git a/src/loadsave.cc b/src/loadsave.cc
index fefd03f..8e023ac 100644
--- a/src/loadsave.cc
+++ b/src/loadsave.cc
@@ -2177,7 +2177,7 @@ int _GameMap2Slot(File* stream)
     sprintf(_gmpath, "%s\\%s\\%s%.2d\\", _patches, "SAVEGAME", "SLOT", _slot_cursor + 1);
     _strmfe(_str0, "AUTOMAP.DB", "SAV");
     strcat(_gmpath, _str0);
-    remove(_gmpath);
+    compat_remove(_gmpath);
 
     for (int index = 0; index < fileNameListLength; index += 1) {
         char* string = fileNameList[index];
@@ -2204,7 +2204,7 @@ int _GameMap2Slot(File* stream)
         return -1;
     }
 
-    sprintf(_str0, "%s\\%s", "MAPS", "AUTOMAP.DB");
+    sprintf(_str0, "%s\\%s", "MAPS", AUTOMAP_DB);
     File* inStream = fileOpen(_str0, "rb");
     if (inStream == NULL) {
         return -1;
@@ -2266,7 +2266,7 @@ int _SlotMap2Game(File* stream)
     }
 
     sprintf(_str0, "%s\\%s\\%s", _patches, "MAPS", "AUTOMAP.DB");
-    remove(_str0);
+    compat_remove(_str0);
 
     if (gPartyMemberDescriptionsLength > 1) {
         for (int index = 1; index < gPartyMemberDescriptionsLength; index += 1) {
@@ -2323,7 +2323,7 @@ int _SlotMap2Game(File* stream)
     }
 
     if (mapLoadSaved(_LSData[_slot_cursor].file_name) == -1) {
-        debugPrint("LOADSAVE: returning 13\n");
+        debugPrint("\nLOADSAVE: returning 13\n");
         return -1;
     }
 
@@ -2448,7 +2448,7 @@ int _MapDirErase(const char* relativePath, const char* extension)
     int fileListLength = fileNameListInit(path, &fileList, 0, 0);
     while (--fileListLength >= 0) {
         sprintf(path, "%s\\%s%s", _patches, relativePath, fileList[fileListLength]);
-        remove(path);
+        compat_remove(path);
     }
     fileNameListFree(&fileList, 0);
 
@@ -2461,7 +2461,7 @@ int _MapDirEraseFile_(const char* a1, const char* a2)
     char path[COMPAT_MAX_PATH];
 
     sprintf(path, "%s\\%s%s", _patches, a1, a2);
-    if (remove(path) != 0) {
+    if (compat_remove(path) != 0) {
         return -1;
     }
 
@@ -2550,7 +2550,7 @@ int _RestoreSave()
     strcpy(_str0, _gmpath);
     strcat(_str0, "SAVE.DAT");
     _strmfe(_str1, _str0, "BAK");
-    remove(_str0);
+    compat_remove(_str0);
 
     if (rename(_str1, _str0) != 0) {
         _EraseSave();
@@ -2578,7 +2578,7 @@ int _RestoreSave()
         strcpy(_str0, _gmpath);
         strcat(_str0, fileList[index]);
         _strmfe(_str1, _str0, "SAV");
-        remove(_str1);
+        compat_remove(_str1);
         if (rename(_str0, _str1) != 0) {
             // FIXME: Probably leaks fileList.
             _EraseSave();
@@ -2637,7 +2637,7 @@ int _EraseSave()
     sprintf(_gmpath, "%s\\%s\\%s%.2d\\", _patches, "SAVEGAME", "SLOT", _slot_cursor + 1);
     strcpy(_str0, _gmpath);
     strcat(_str0, "SAVE.DAT");
-    remove(_str0);
+    compat_remove(_str0);
 
     sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
     sprintf(_str0, "%s*.%s", _gmpath, "SAV");
@@ -2652,7 +2652,7 @@ int _EraseSave()
     for (int index = fileListLength - 1; index >= 0; index--) {
         strcpy(_str0, _gmpath);
         strcat(_str0, fileList[index]);
-        remove(_str0);
+        compat_remove(_str0);
     }
 
     fileNameListFree(&fileList, 0);
@@ -2663,7 +2663,7 @@ int _EraseSave()
     strcpy(_str0, _gmpath);
     strcat(_str0, v1);
 
-    remove(_str0);
+    compat_remove(_str0);
 
     return 0;
 }
diff --git a/src/platform_compat.cc b/src/platform_compat.cc
index 69da2c5..5ac14b3 100644
--- a/src/platform_compat.cc
+++ b/src/platform_compat.cc
@@ -2,7 +2,6 @@
 
 #include <SDL.h>
 #include <string.h>
-
 #include <filesystem>
 
 #ifdef _WIN32
@@ -16,6 +15,7 @@
 #include <stdio.h>
 #else
 #include <unistd.h>
+#include <algorithm>
 #endif
 
 #ifdef _WIN32
@@ -54,7 +54,8 @@ void compat_splitpath(const char* path, char* drive, char* dir, char* fname, cha
 #if defined(_WIN32)
     _splitpath(path, drive, dir, fname, ext);
 #else
-    std::filesystem::path p(path);
+
+    std::filesystem::path p(compat_convertPathSeparators(path));
     
     if (drive != NULL) {
         strcpy(drive, p.root_name().string().substr(0, COMPAT_MAX_DRIVE - 1).c_str());
@@ -132,8 +133,10 @@ long compat_filelength(int fd)
 
 int compat_mkdir(const char* path)
 {
+    auto platformPath = compat_convertPathSeparators(path);
+
     std::error_code ec;
-    if (std::filesystem::create_directory(std::filesystem::path(path), ec)) {
+    if (std::filesystem::create_directory(platformPath, ec)) {
         return 0;
     }
 
@@ -150,3 +153,25 @@ unsigned int compat_timeGetTime()
     return static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count());
 #endif
 }
+
+std::string compat_convertPathSeparators(const char* path) {
+
+    auto compatPath = std::string(path);
+
+#ifndef _WIN32
+    std::replace(compatPath.begin(), compatPath.end(), '\\','/');
+    std::transform(compatPath.begin(), compatPath.end(), compatPath.begin(), ::tolower);
+#endif
+
+    return compatPath;
+}
+
+FILE* compat_fopen(const char* filename, const char* mode)
+{
+    return fopen(compat_convertPathSeparators(filename).c_str(), mode);
+}
+
+int compat_remove(const char* filename)
+{
+    return remove(compat_convertPathSeparators(filename).c_str());
+}
diff --git a/src/platform_compat.h b/src/platform_compat.h
index 77b762d..8149dcb 100644
--- a/src/platform_compat.h
+++ b/src/platform_compat.h
@@ -2,6 +2,7 @@
 #define PLATFORM_COMPAT_H
 
 #include <stddef.h>
+#include <string>
 
 // TODO: This is compatibility cross-platform layer. Designed to have minimal
 // impact on the codebase. Remove once it's no longer needed.
@@ -31,5 +32,8 @@ long compat_tell(int fileHandle);
 long compat_filelength(int fd);
 int compat_mkdir(const char* path);
 unsigned int compat_timeGetTime();
+FILE* compat_fopen(const char* filename, const char* mode);
+int compat_remove(const char* filename);
+std::string compat_convertPathSeparators(const char* path);
 
 #endif /* PLATFORM_COMPAT_H */
diff --git a/src/xfile.cc b/src/xfile.cc
index 27589db..97962dd 100644
--- a/src/xfile.cc
+++ b/src/xfile.cc
@@ -65,14 +65,14 @@ XFile* xfileOpen(const char* filePath, const char* mode)
     char path[COMPAT_MAX_PATH];
     if (drive[0] != '\0' || dir[0] == '\\' || dir[0] == '/' || dir[0] == '.') {
         // [filePath] is an absolute path. Attempt to open as plain stream.
-        stream->file = fopen(filePath, mode);
+        stream->file = compat_fopen(filePath, mode);
         if (stream->file == NULL) {
             free(stream);
             return NULL;
         }
 
         stream->type = XFILE_TYPE_FILE;
-        sprintf(path, filePath);
+        sprintf(path, "%s", filePath);
     } else {
         // [filePath] is a relative path. Loop thru open xbases and attempt to
         // open [filePath] from appropriate xbase.
@@ -83,7 +83,7 @@ XFile* xfileOpen(const char* filePath, const char* mode)
                 stream->dfile = dfileOpen(curr->dbase, filePath, mode);
                 if (stream->dfile != NULL) {
                     stream->type = XFILE_TYPE_DFILE;
-                    sprintf(path, filePath);
+                    sprintf(path, "%s", filePath);
                     break;
                 }
             } else {
@@ -91,7 +91,7 @@ XFile* xfileOpen(const char* filePath, const char* mode)
                 sprintf(path, "%s\\%s", curr->path, filePath);
 
                 // Attempt to open plain stream.
-                stream->file = fopen(path, mode);
+                stream->file = compat_fopen(path, mode);
                 if (stream->file != NULL) {
                     stream->type = XFILE_TYPE_FILE;
                     break;
@@ -103,14 +103,14 @@ XFile* xfileOpen(const char* filePath, const char* mode)
         if (stream->file == NULL) {
             // File was not opened during the loop above. Attempt to open file
             // relative to the current working directory.
-            stream->file = fopen(filePath, mode);
+            stream->file = compat_fopen(filePath, mode);
             if (stream->file == NULL) {
                 free(stream);
                 return NULL;
             }
 
             stream->type = XFILE_TYPE_FILE;
-            sprintf(path, filePath);
+            sprintf(path, "%s", filePath);
         }
     }
 
@@ -125,7 +125,7 @@ XFile* xfileOpen(const char* filePath, const char* mode)
             fclose(stream->file);
 
             stream->type = XFILE_TYPE_GZFILE;
-            stream->gzfile = gzopen(path, mode);
+            stream->gzfile = gzopen(compat_convertPathSeparators(path).c_str(), mode);
         } else {
             // File is not gzipped.
             rewind(stream->file);