From ac0da8862c244fd626b44280443e5f77339606bc Mon Sep 17 00:00:00 2001 From: Marek Benc Date: Mon, 7 Sep 2026 23:04:21 +0200 Subject: [PATCH 1/2] Update FileClass code to build and work on Unix. --- code/always.h | 79 ++++++++ code/blowfish.h | 2 + code/cdfile.cpp | 105 +--------- code/cdfile.h | 4 - code/file.cpp | 84 ++++++++ code/file.h | 39 ++++ code/file_posix.cpp | 145 ++++++++++++++ code/file_win.cpp | 88 ++++++++ code/gamedirs.cpp | 6 +- code/ini.cpp | 3 + code/ini.h | 7 + code/mixfile.cpp | 13 +- code/mixfile.h | 2 +- code/rawfile.cpp | 277 ++++++++++++++++++-------- code/rawfile.h | 37 ++-- code/session.cpp | 1 + code/sha.cpp | 2 +- code/sha.h | 2 +- code/vqa.h | 1 + code/wwfile.h | 7 - tests/deploymentconfig/CMakeLists.txt | 3 + tests/gamedirs/CMakeLists.txt | 3 + 22 files changed, 685 insertions(+), 225 deletions(-) create mode 100644 code/file.cpp create mode 100644 code/file.h create mode 100644 code/file_posix.cpp create mode 100644 code/file_win.cpp diff --git a/code/always.h b/code/always.h index 042ded5c2..ae7cc1631 100644 --- a/code/always.h +++ b/code/always.h @@ -91,3 +91,82 @@ #ifndef _stricmp #define _stricmp stricmp #endif + + +/* +** Define some Windows specific values that are used throghout the games +*/ +#ifndef _WIN32 + +#define _MAX_FNAME 255 +#define _MAX_EXT 8 +#define _MAX_PATH 512 +#define MAX_PATH _MAX_PATH +#define _CONTROL 0x20 // space, first non-control character in ASCII + +#undef _stricmp +#define stricmp strcasecmp +#define _stricmp strcasecmp +#define strnicmp strncasecmp +#define memicmp strncasecmp +#define __cdecl + +#include +#include +#include + +inline static void _makepath(char* path, const char* drive, const char* dir, const char* fname, const char* ext) +{ + if (!path || !fname || !ext) { + return; + } + + sprintf(path, "%s%s%s", fname, (ext[0] == '.' ? "" : "."), ext); +} + +inline static void _splitpath(const char* path, char* drive, char* dir, char* fname, char* ext) +{ + if (!path || !ext) { + return; + } + + while (*path != '\0') { + if (*path == '.') { + strcpy(ext, path + 1); + break; + } + + ++path; + } +} + +inline static char* strupr(char* str) +{ + char* ret = str; + while (*str != '\0') { + *str = toupper(*str); + ++str; + } + return(ret); +} + +inline static void strrev(char* str) +{ + int len = strlen(str); + + for (int i = 0; i < len / 2; i++) { + char c = str[i]; + str[i] = str[len - i - 1]; + str[len - i - 1] = c; + } +} + +inline static void _strlwr(char* str) +{ + while (*str != '\0') { + *str = tolower(*str); + ++str; + } +} + +#endif // not _WIN32 diff --git a/code/blowfish.h b/code/blowfish.h index 3cbc2d7c6..191781549 100644 --- a/code/blowfish.h +++ b/code/blowfish.h @@ -31,7 +31,9 @@ #pragma once +#ifdef _WIN32 #include "win.h" +#endif /// Names and comments from TLBs diff --git a/code/cdfile.cpp b/code/cdfile.cpp index 11ef777ac..0c3194616 100644 --- a/code/cdfile.cpp +++ b/code/cdfile.cpp @@ -42,6 +42,7 @@ #include "cdfile.h" #include +#include /* ** Pointer to the first search path record. @@ -180,7 +181,7 @@ void CDFileClass::Set_User_Path(char const * path) break; default: - UserPath += '\\'; + UserPath += std::filesystem::path::preferred_separator; break; } } @@ -459,105 +460,3 @@ int CDFileClass::Delete(void) return(BASECLASS::Delete()); } - - -HANDLE FindFileHandle = INVALID_HANDLE_VALUE; - -/// -/// Begins a search for the files matching the wildcard specified. -/// This routine will look in the current directory first and then work along the search -/// drive list, settling on the first drive that has a match. Only ordinary files qualify; -/// directories and system, hidden, or temporary files are passed over. Any search still -/// in progress is closed off first. -/// -/// The wildcard to search for; filled in with the file found. -/// bool; Was a matching file found? -/// Be sure that the buffer is big enough to hold the filename returned. -bool CDFileClass::Find_First_File(char *fname) -{ - WIN32_FIND_DATAA fb; - char scan_path[MAX_PATH]; - SearchDriveType *entry; - - if (fname) { - - Find_Close(); - - strcpy(scan_path, fname); - - HANDLE file_handle = ::FindFirstFile(scan_path, &fb); - if (file_handle != INVALID_HANDLE_VALUE && !(fb.dwFileAttributes & (FILE_ATTRIBUTE_TEMPORARY|FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))) { - - strcpy(fname, fb.cFileName); - FindFileHandle = file_handle; - - return(true); - } - - entry = First; - - if (entry != NULL) { - - while (true) { - - strcpy(scan_path, entry->Path); - strcat(scan_path, fname); - - file_handle = ::FindFirstFile(scan_path, &fb); - if (file_handle != INVALID_HANDLE_VALUE && !(fb.dwFileAttributes & (FILE_ATTRIBUTE_TEMPORARY|FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))) { - break; - } - - entry = (SearchDriveType *)entry->Next; - if (entry == NULL) { - return(false); - } - } - - strcpy(fname, fb.cFileName); - FindFileHandle = file_handle; - - return(true); - } - } - return(false); -} - - -/// -/// Fetches the next file that matches the search in progress. -/// This routine continues the scan begun by Find_First_File, working through the rest of -/// the matches on whichever drive that routine settled upon. -/// -/// Buffer to fill in with the name of the file found. -/// bool; Was another matching file found? -/// Be sure that the buffer is big enough to hold the filename returned. -bool CDFileClass::Find_Next_File(char *buffer) -{ - WIN32_FIND_DATAA fb; - - if (buffer) { - - if (FindFileHandle != INVALID_HANDLE_VALUE && ::FindNextFile(FindFileHandle, &fb) == TRUE) { - strcpy(buffer, fb.cFileName); - return(true); - } - - buffer[0] = '\0'; - } - return(false); -} - - -/// -/// Closes off the file search that is in progress. -/// Call this routine when the results of a Find_First_File scan are no longer wanted, so -/// that the search handle held on the game's behalf is given back to the system. -/// -void CDFileClass::Find_Close(void) -{ - if (FindFileHandle != INVALID_HANDLE_VALUE) { - FindClose(FindFileHandle); - FindFileHandle = INVALID_HANDLE_VALUE; - } -} diff --git a/code/cdfile.h b/code/cdfile.h index 9f1b34a16..913968b70 100644 --- a/code/cdfile.h +++ b/code/cdfile.h @@ -72,10 +72,6 @@ class CDFileClass : public BufferIOFileClass static void Set_User_Path(char const * path); static char const * User_Path(void); - static bool Find_First_File(char *buffer); - static bool Find_Next_File(char *buffer); - static void Find_Close(void); - private: char const * Capture_Name(char const * filename); diff --git a/code/file.cpp b/code/file.cpp new file mode 100644 index 000000000..bd306a5bd --- /dev/null +++ b/code/file.cpp @@ -0,0 +1,84 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2020-2024 Vanilla Conquer contributors + * Copyright 2026 OpenTS contributors + * + * Contains material derived from Vanilla Conquer (https://github.com/TheAssemblyArmada/Vanilla-Conquer). + * Modified by OpenTS contributors, 2026. + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "file.h" +#include + +#ifndef _WIN32 +static void Resolve_File_Single(char* fname) +{ + Find_File_Data* ffblk; + + ffblk = Find_File_Data::CreateFindData(); + + if (ffblk == nullptr) { + return; + } + + size_t name_len = strlen(fname); + + if (ffblk->FindFirst(fname) && name_len == strlen(ffblk->GetFullName())) { + strncpy(fname, ffblk->GetFullName(), name_len + 1); + } + + delete ffblk; +} +#endif + +void Resolve_File(char* fname) +{ +#ifndef _WIN32 + // step through each sub-directory before going for the win + char* next = fname; + while (next = strchr(next, '/')) { + *next = '\0'; + Resolve_File_Single(fname); + *next++ = '/'; + } + + Resolve_File_Single(fname); +#endif +} + +bool Find_First(const char* fname, unsigned int mode, Find_File_Data** ffblk) +{ + if (ffblk == nullptr) { + return false; + } + *ffblk = nullptr; + + *ffblk = Find_File_Data::CreateFindData(); + if ((*ffblk)->FindFirst(fname)) { + return true; + } + + delete *ffblk; + *ffblk = nullptr; + + return false; +} + +bool Find_Next(Find_File_Data* ffblk) +{ + if (ffblk == nullptr) { + return false; + } + + return ffblk->FindNext(); +} + +void Find_Close(Find_File_Data* ffblk) +{ + if (ffblk != nullptr) { + delete ffblk; + } +} diff --git a/code/file.h b/code/file.h new file mode 100644 index 000000000..fe8420109 --- /dev/null +++ b/code/file.h @@ -0,0 +1,39 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2020-2024 Vanilla Conquer contributors + * Copyright 2026 OpenTS contributors + * + * Contains material derived from Vanilla Conquer (https://github.com/TheAssemblyArmada/Vanilla-Conquer). + * Modified by OpenTS contributors, 2026. + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +void Resolve_File(char* fname); + +class Find_File_Data +{ +public: + static Find_File_Data* CreateFindData(); + + virtual ~Find_File_Data() + { + } + virtual const char* GetName() const = 0; + virtual const char* GetFullName() const + { + return nullptr; + }; + virtual unsigned int GetTime() const = 0; + + virtual bool FindFirst(const char* fname) = 0; + virtual bool FindNext() = 0; + virtual void Close() = 0; +}; + +extern bool Find_First(const char* fname, unsigned int mode, Find_File_Data** ffblk); +extern bool Find_Next(Find_File_Data* ffblk); +extern void Find_Close(Find_File_Data* ffblk); diff --git a/code/file_posix.cpp b/code/file_posix.cpp new file mode 100644 index 000000000..069b78d0f --- /dev/null +++ b/code/file_posix.cpp @@ -0,0 +1,145 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2020-2024 Vanilla Conquer contributors + * Copyright 2026 OpenTS contributors + * + * Contains material derived from Vanilla Conquer (https://github.com/TheAssemblyArmada/Vanilla-Conquer). + * Modified by OpenTS contributors, 2026. + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#ifndef _WIN32 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include "file.h" + +#include +#include +#include +#include +#include +#include +#include + +class Find_File_Data_Posix : public Find_File_Data +{ +public: + Find_File_Data_Posix(); + virtual ~Find_File_Data_Posix(); + + virtual const char* GetName() const; + virtual const char* GetFullName() const + { + return DirEntry != nullptr ? FullName : nullptr; + } + virtual unsigned int GetTime() const; + + virtual bool FindFirst(const char* fname); + virtual bool FindNext(); + virtual void Close(); + +private: + DIR* Directory; + struct dirent* DirEntry; + const char* FileFilter; + char FullName[PATH_MAX]; + char DirName[PATH_MAX]; + + bool FindNextWithFilter(); +}; + +Find_File_Data_Posix::Find_File_Data_Posix() + : Directory(nullptr) + , DirEntry(nullptr) +{ +} + +Find_File_Data_Posix::~Find_File_Data_Posix() +{ + Close(); +} + +const char* Find_File_Data_Posix::GetName() const +{ + if (DirEntry == nullptr) { + return nullptr; + } + return DirEntry->d_name; +} + +unsigned int Find_File_Data_Posix::GetTime() const +{ + if (DirEntry == nullptr) { + return 0; + } + struct stat buf = {0}; + if (stat(FullName, &buf) != 0) { + return false; + } + return buf.st_mtime; +} + +bool Find_File_Data_Posix::FindNextWithFilter() +{ + while (true) { + DirEntry = readdir(Directory); + if (DirEntry == nullptr) { + return false; + } + if (fnmatch(FileFilter, DirEntry->d_name, FNM_PATHNAME | FNM_CASEFOLD) == 0) { + strcpy(FullName, DirName); + strcat(FullName, DirEntry->d_name); + break; + } + } + return true; +} + +bool Find_File_Data_Posix::FindFirst(const char* fname) +{ + Close(); + FullName[0] = '\0'; + DirName[0] = '\0'; + + // split directory and file from the path + char* fdir = strrchr((char*)fname, '/'); + if (fdir != nullptr) { + strncat(DirName, fname, (fdir - fname + 1)); + FileFilter = fdir + 1; + Directory = opendir(DirName); + } else { + FileFilter = fname; + Directory = opendir("."); + } + + if (Directory == nullptr) { + return false; + } + + return FindNextWithFilter(); +} + +bool Find_File_Data_Posix::FindNext() +{ + if (Directory == nullptr) { + return false; + } + return FindNextWithFilter(); +} + +void Find_File_Data_Posix::Close() +{ + if (Directory != nullptr) { + closedir(Directory); + Directory = nullptr; + } +} + +Find_File_Data* Find_File_Data::CreateFindData() +{ + return new Find_File_Data_Posix(); +} +#endif diff --git a/code/file_win.cpp b/code/file_win.cpp new file mode 100644 index 000000000..cdfd16494 --- /dev/null +++ b/code/file_win.cpp @@ -0,0 +1,88 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2020-2024 Vanilla Conquer contributors + * Copyright 2026 OpenTS contributors + * + * Contains material derived from Vanilla Conquer (https://github.com/TheAssemblyArmada/Vanilla-Conquer). + * Modified by OpenTS contributors, 2026. + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#ifdef _WIN32 +#include "file.h" + +#include +#include + +class Find_File_Data_Win : public Find_File_Data +{ +public: + Find_File_Data_Win(); + virtual ~Find_File_Data_Win(); + + virtual const char* GetName() const; + virtual unsigned int GetTime() const; + + virtual bool FindFirst(const char* fname); + virtual bool FindNext(); + virtual void Close(); + +private: + HANDLE FindHandle; + WIN32_FIND_DATAA FindData; +}; + +Find_File_Data_Win::Find_File_Data_Win() + : FindHandle(INVALID_HANDLE_VALUE) + , FindData({0}) +{ +} + +Find_File_Data_Win::~Find_File_Data_Win() +{ + Close(); +} + +const char* Find_File_Data_Win::GetName() const +{ + return FindData.cFileName; +} + +unsigned int Find_File_Data_Win::GetTime() const +{ + ULARGE_INTEGER ull; + ull.LowPart = FindData.ftLastWriteTime.dwLowDateTime; + ull.HighPart = FindData.ftLastWriteTime.dwHighDateTime; + return (unsigned int)(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +bool Find_File_Data_Win::FindFirst(const char* fname) +{ + FindHandle = FindFirstFileA(fname, &FindData); + return (FindHandle != INVALID_HANDLE_VALUE); +} + +bool Find_File_Data_Win::FindNext() +{ + if (FindHandle == INVALID_HANDLE_VALUE) { + return false; + } + + return (FindNextFileA(FindHandle, &FindData) != FALSE); +} + +void Find_File_Data_Win::Close() +{ + if (FindHandle != INVALID_HANDLE_VALUE) { + FindClose(FindHandle); + FindHandle = INVALID_HANDLE_VALUE; + } +} + +Find_File_Data* Find_File_Data::CreateFindData() +{ + return new Find_File_Data_Win(); +} +#endif diff --git a/code/gamedirs.cpp b/code/gamedirs.cpp index d26045e8e..790761aa5 100644 --- a/code/gamedirs.cpp +++ b/code/gamedirs.cpp @@ -14,7 +14,9 @@ #include "cdfile.h" #include "dbgprint.h" +#include #include +#include #include /* @@ -61,7 +63,7 @@ static std::string Terminate_Path(std::string const & path) return(path); default: - return(path + '\\'); + return(path + (char)std::filesystem::path::preferred_separator); } } @@ -279,7 +281,7 @@ std::string Saved_Game_Name(char const * filename) CreateDirectory(folder.c_str(), NULL); - return(folder + '\\' + filename); + return(folder + (char)std::filesystem::path::preferred_separator + filename); } diff --git a/code/ini.cpp b/code/ini.cpp index 4db0ee6f2..7ef526684 100644 --- a/code/ini.cpp +++ b/code/ini.cpp @@ -957,6 +957,8 @@ int INIClass::Get_Int(char const * section, char const * entry, int defvalue) co } +#ifdef _WIN32 + /// /// Fetches a class identifier from the specified section. /// This routine will fetch the printable form of a class identifier from the entry and @@ -1006,6 +1008,7 @@ bool INIClass::Put_CLSID(char const * section, char const * entry, CLSID const & SysFreeString(olestr); return(Put_String(section, entry, buffer)); } +#endif /*********************************************************************************************** diff --git a/code/ini.h b/code/ini.h index a4f33badd..457e0aedc 100644 --- a/code/ini.h +++ b/code/ini.h @@ -34,7 +34,10 @@ #include "crc.h" #include "index.h" +#ifdef _WIN32 #include +#endif + #include #include #include @@ -113,7 +116,9 @@ class INIClass { TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; TPoint2D const Get_Point(char const * section, char const * entry, TPoint2D const & defvalue) const; TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; +#ifdef _WIN32 CLSID const Get_CLSID(char const * section, char const * entry, CLSID defvalue) const; +#endif /* ** Put a data type to the section and entry specified. @@ -130,7 +135,9 @@ class INIClass { bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint2D const & value); +#ifdef _WIN32 bool Put_CLSID(char const * section, char const * entry, CLSID const & value); +#endif // Callers size the buffers they hand to Get_String from this. It does not bound a line // of the file; the reader keeps a line of any length. diff --git a/code/mixfile.cpp b/code/mixfile.cpp index 9a5db0d37..f650c0de9 100644 --- a/code/mixfile.cpp +++ b/code/mixfile.cpp @@ -69,7 +69,7 @@ ** with the mixfile system. */ //template -List MixFileClass::List; +List MixFileClass::MixList; /// template class MixFileClass; @@ -185,7 +185,7 @@ MixFileClass::MixFileClass(char const * filename, PKey const * key) : /* ** Attach to list of mixfiles. */ - List.Add_Tail(this); + MixList.Add_Tail(this); } @@ -303,7 +303,7 @@ void const * MixFileClass::Retrieve(char const * filename) *=============================================================================================*/ MixFileClass * MixFileClass::Finder(char const * filename) { - MixFileClass * ptr = List.First(); + MixFileClass * ptr = MixList.First(); while (ptr->Is_Valid()) { char path[_MAX_PATH]; char name[_MAX_FNAME]; @@ -534,7 +534,10 @@ bool MixFileClass::Offset(char const * filename, void ** realptr, MixFileClass * */ /// Can't call strupr on a const string. - int crc = (CRCEngine()(strupr((char *)filename), strlen(filename))); //Calculate_CRC(strupr((char *)filename), strlen(filename)); + char filename_upper[_MAX_PATH]; + strcpy(filename_upper, filename); + strupr(filename_upper); + int crc = (CRCEngine()(filename_upper, strlen(filename_upper))); //Calculate_CRC(strupr((char *)filename), strlen(filename)); SubBlock key; key.CRC = crc; @@ -542,7 +545,7 @@ bool MixFileClass::Offset(char const * filename, void ** realptr, MixFileClass * /* ** Sweep through all registered mixfiles, trying to find the file in question. */ - ptr = List.First(); + ptr = MixList.First(); while (ptr->Is_Valid()) { SubBlock * block; diff --git a/code/mixfile.h b/code/mixfile.h index 12af50954..b4d0c78eb 100644 --- a/code/mixfile.h +++ b/code/mixfile.h @@ -111,5 +111,5 @@ class MixFileClass : public Node */ void * Data; // Pointer to raw data. - static List List; + static List MixList; }; diff --git a/code/rawfile.cpp b/code/rawfile.cpp index 609822d7b..6269f31ba 100644 --- a/code/rawfile.cpp +++ b/code/rawfile.cpp @@ -2,10 +2,12 @@ * O P E N T S ******************************************************************************* * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2025 Electronic Arts Inc. + * Copyright 2020-2025 Electronic Arts Inc. + * Copyright 2020-2022 Vanilla Conquer contributors * Copyright 2026 OpenTS contributors * - * Contains material derived from Electronic Arts source code. + * Contains material derived from Electronic Arts source code and from Vanilla + * Conquer (https://github.com/TheAssemblyArmada/Vanilla-Conquer). * Modified by OpenTS contributors, 2026. * EA's GPLv3 Section 7 additional terms and supplemental warranty * disclaimers apply; see LICENSE.md. @@ -50,13 +52,23 @@ #include "always.h" #include "rawfile.h" +#include "file.h" #include #include #include #include -#include -#include + +#ifndef _WIN32 +#include +#include +#include +#include +#define _unlink unlink +#else +#include +#include +#endif /*********************************************************************************************** @@ -78,10 +90,9 @@ RawFileClass::~RawFileClass(void) { Close(); - if (Allocated && Filename) { + if (Filename) { free((char *)Filename); ((char *&)Filename) = 0; - Allocated = false; } } @@ -135,12 +146,13 @@ RawFileClass::RawFileClass(char const * filename) : Rights(0), BiasStart(0), BiasLength(-1), - Handle(NULL_HANDLE), - Filename(filename), + Handle(nullptr), + Filename(nullptr), Date(0), Time(0), - Allocated(false) + LastAccessType(0) { + Set_Name(filename); } @@ -166,10 +178,9 @@ RawFileClass::RawFileClass(char const * filename) : *=============================================================================================*/ char const * RawFileClass::Set_Name(char const * filename) { - if (Filename != NULL && Allocated) { + if (Filename != NULL) { free((char *)Filename); - Filename = NULL; - Allocated = false; + Filename = nullptr; } if (filename == NULL) return(NULL); @@ -181,7 +192,18 @@ char const * RawFileClass::Set_Name(char const * filename) Error(ENOMEM, false, filename); return(NULL); } - Allocated = true; + + /* + ** If we ever save this file, make sure we save it in lowercase but + ** if Resolve_File finds an actual file on-disk we use the real name + ** instead. + */ + _strlwr(Filename); + + /* + ** Try to locate an existing file ignoring case, updates Filename + */ + Resolve_File(Filename); return(Filename); } @@ -236,6 +258,7 @@ int RawFileClass::Open(char const * filename, int rights) int RawFileClass::Open(int rights) { Close(); + LastAccessType = 0; /* ** Verify that there is a filename associated with this file object. If not, then this is a @@ -267,23 +290,24 @@ int RawFileClass::Open(int rights) ** an invalid access code. */ default: + errno = EINVAL; break; case READ: - Handle = CreateFile(Filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + Handle = fopen(Filename, "rb"); break; case WRITE: - Handle = CreateFile(Filename, GENERIC_WRITE, 0, - NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + Handle = fopen(Filename, "wb"); break; case READ|WRITE: - // SKB 5/13/99 use OPEN_ALWAYS instead of CREATE_ALWAYS so that files + // SKB 5/13/99 try "r+" first before using "w+" so that files // does not get destroyed. - Handle = CreateFile(Filename, GENERIC_READ | GENERIC_WRITE, 0, - NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + Handle = fopen(Filename, "r+b"); + if (Handle == nullptr) { + Handle = fopen(Filename, "w+b"); + } break; } @@ -299,10 +323,10 @@ int RawFileClass::Open(int rights) ** For the case of the file cannot be found, then allow a retry. All other cases ** are fatal. */ - if (Handle == NULL_HANDLE) { + if (Handle == nullptr) { return(false); -// Error(GetLastError(), false, Filename); +// Error(errno, false, Filename); // continue; } break; @@ -354,22 +378,18 @@ bool RawFileClass::Is_Available(int forced) ** CD-ROM, this routine will return a failure condition. In all but the missing file ** condition, go through the normal error recover channels. */ - for (;;) { - Handle = CreateFile(Filename, GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (Handle == NULL_HANDLE) { - return(false); - } - break; + Handle = fopen(Filename, "r"); + if (Handle == nullptr) { + return(false); } /* ** Since the file could be opened, then close it and return that the file exists. */ - if (!CloseHandle(Handle)) { - Error(GetLastError(), false, Filename); + if (fclose(Handle) != 0) { + Error(errno, false, Filename); } - Handle = NULL_HANDLE; + Handle = nullptr; return(true); } @@ -401,14 +421,20 @@ void RawFileClass::Close(void) ** Try to close the file. If there was an error (who knows what that could be), then ** call the error routine. */ - if (!CloseHandle(Handle)) { - Error(GetLastError(), false, Filename); + if (fclose(Handle) != 0) { + Error(errno, false, Filename); } /* ** At this point the file must have been closed. Mark the file as empty and return. */ - Handle = NULL_HANDLE; + Handle = nullptr; + + /* + ** Clear any positioning information incase class is reused to open another file. + */ + BiasStart = 0; + BiasLength = -1; } } @@ -464,18 +490,24 @@ int RawFileClass::Read(void * buffer, int size) size = size < remainder ? size : remainder; } + if (!opened && LastAccessType != 0 && LastAccessType != READ) { + if (fseek(Handle, ftell(Handle), SEEK_SET) < 0) { + Error(errno, false, Filename); + return(0); + } + } + LastAccessType = READ; + int total = 0; while (size > 0) { - bytesread = 0; - - if (!ReadFile(Handle, buffer, size, &(DWORD &)bytesread, NULL)) { - buffer = (char *)buffer + bytesread; + clearerr(Handle); + bytesread = fread(buffer, 1, size, Handle); + if (ferror(Handle)) { size -= bytesread; total += bytesread; - Error(GetLastError(), true, Filename); + Error(errno, true, Filename); continue; } - buffer = (char *)buffer + bytesread; size -= bytesread; total += bytesread; if (bytesread == 0) break; @@ -526,8 +558,18 @@ int RawFileClass::Write(void const * buffer, int size) opened = true; } - if (!WriteFile(Handle, buffer, size, &(DWORD &)byteswritten, NULL)) { - Error(GetLastError(), false, Filename); + if (!opened && LastAccessType != 0 && LastAccessType != WRITE) { + if (fseek(Handle, ftell(Handle), SEEK_SET) < 0) { + Error(errno, false, Filename); + return(0); + } + } + LastAccessType = WRITE; + + clearerr(Handle); + byteswritten = fwrite(buffer, 1, size, Handle); + if (ferror(Handle)) { + Error(errno, false, Filename); } /* @@ -660,15 +702,30 @@ int RawFileClass::Size(void) */ if (Is_Open()) { - size = GetFileSize(Handle, NULL); - /* - ** If there was in internal error, then call the error function. + ** With stdio we seek to end to obtain the length, then reset the position back. */ - if (size == 0xFFFFFFFF) { - Error(GetLastError(), false, Filename); + clearerr(Handle); + + int position = ftell(Handle); + if (position < 0) { + Error(errno, false, Filename); + return(0); + } + + if (fseek(Handle, 0, SEEK_END) < 0) { + Error(errno, false, Filename); + return(0); } + size = ftell(Handle); + + if (fseek(Handle, position, SEEK_SET) < 0) { + Error(errno, false, Filename); + return(0); + } + LastAccessType = 0; + } else { /* @@ -777,8 +834,8 @@ int RawFileClass::Delete(void) return(false); } - if (!DeleteFile(Filename)) { - Error(GetLastError(), false, Filename); + if (_unlink(Filename) < 0) { + Error(errno, false, Filename); return(false); } break; @@ -809,14 +866,45 @@ int RawFileClass::Delete(void) *=============================================================================================*/ unsigned int RawFileClass::Get_Date_Time(void) { - BY_HANDLE_FILE_INFORMATION info; +#ifdef _WIN32 + if (RawFileClass::Is_Open()) { + BY_HANDLE_FILE_INFORMATION info; + HANDLE osHandle = (HANDLE)_get_osfhandle(_fileno(Handle)); + + if (osHandle != INVALID_HANDLE_VALUE && + GetFileInformationByHandle(osHandle, &info)) { + WORD dosdate; + WORD dostime; + FileTimeToDosDateTime(&info.ftLastWriteTime, &dosdate, &dostime); + return((dosdate << 16) | dostime); + } + } +#else + /* + ** DOS date/time format: + ** https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-dosdatetimetovarianttime + ** + ** POSIX date/time format: + ** https://pubs.opengroup.org/onlinepubs/009696799/basedefs/time.h.html + */ + struct stat statbuf; + + if (stat(Filename, &statbuf) == 0) { + struct tm *parsed_time = localtime(&statbuf.st_mtime); + + if (parsed_time != NULL) { + Date = (((parsed_time->tm_year - 80) & ((1 << 7) - 1)) << 9) | + (((parsed_time->tm_mon + 1) & ((1 << 4) - 1)) << 5) | + (parsed_time->tm_mday & ((1 << 5) - 1)); + + Time = ((parsed_time->tm_hour & ((1 << 5) - 1)) << 11) | + ((parsed_time->tm_min & ((1 << 6) - 1)) << 5) | + ((parsed_time->tm_sec >> 1) & ((1 << 5) - 1)); - if (GetFileInformationByHandle(Handle, &info)) { - WORD dosdate; - WORD dostime; - FileTimeToDosDateTime(&info.ftLastWriteTime, &dosdate, &dostime); - return((dosdate << 16) | dostime); + return(Date << 16 | Time); + } } +#endif return(0); } @@ -838,16 +926,52 @@ unsigned int RawFileClass::Get_Date_Time(void) *=============================================================================================*/ bool RawFileClass::Set_Date_Time(unsigned int datetime) { +#ifdef _WIN32 if (RawFileClass::Is_Open()) { BY_HANDLE_FILE_INFORMATION info; + HANDLE osHandle = (HANDLE)_get_osfhandle(_fileno(Handle)); - if (GetFileInformationByHandle(Handle, &info)) { + if (osHandle != INVALID_HANDLE_VALUE && + GetFileInformationByHandle(osHandle, &info)) { FILETIME filetime; if (DosDateTimeToFileTime((WORD)(datetime >> 16), (WORD)(datetime & 0x0FFFF), &filetime)) { - return(SetFileTime(Handle, &info.ftCreationTime, &filetime, &filetime) != 0); + return(SetFileTime(osHandle, &info.ftCreationTime, &filetime, &filetime) != 0); } } } +#else + /* + ** DOS date/time format: + ** https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-dosdatetimetovarianttime + ** + ** POSIX date/time format: + ** https://pubs.opengroup.org/onlinepubs/009696799/basedefs/time.h.html + */ + struct tm input_time = { 0 }; + time_t unix_time; + + Date = (datetime >> 16) & 0xFFFF; + Time = datetime & 0xFFFF; + + input_time.tm_year = ((Date >> 9) & ((1 << 7) - 1)) + 80; + input_time.tm_mon = ((Date >> 5) & ((1 << 4) - 1)) - 1; + input_time.tm_mday = Date & ((1 << 5) - 1); + + input_time.tm_hour = (Time >> 11) & ((1 << 5) - 1); + input_time.tm_min = (Time >> 5) & ((1 << 6) - 1); + input_time.tm_sec = (Time & ((1 << 5) - 1)) << 1; + + input_time.tm_isdst = -1; + + unix_time = mktime(&input_time); + if (unix_time >= 0) { + struct utimbuf buf = { 0 }; + buf.actime = unix_time; + buf.modtime = unix_time; + + return(utime(Filename, &buf) == 0); + } +#endif return(false); } @@ -923,30 +1047,25 @@ int RawFileClass::Raw_Seek(int pos, int dir) */ if (!Is_Open()) { Error(EBADF, false, Filename); - return(0); - } - - switch (dir) { - case SEEK_SET: - dir = FILE_BEGIN; - break; + } else { - case SEEK_CUR: - dir = FILE_CURRENT; - break; + clearerr(Handle); - case SEEK_END: - dir = FILE_END; - break; - } - pos = SetFilePointer(Handle, pos, NULL, dir); + /* + ** If pos == 0 and dir == SEEK_CUR, fseek should basically do nothing. + ** However, some very bad implementations (like the Nintendo DS's libfat) + ** just goes back to the beginning of the file and iterate it until it + ** finds the current position, which is awful. So instead of doing that, + ** guard this case so that sequential ::Read's do not take too much time. + */ + if (!(pos == 0 && dir == SEEK_CUR)) { + if (fseek(Handle, pos, dir) < 0) { + Error(errno, false, Filename); + } + LastAccessType = 0; + } - /* - ** If there was an error in the seek, then bail with an error condition. - */ - if (pos == 0xFFFFFFFF) { - Error(GetLastError(), false, Filename); - return(0); + pos = ftell(Handle); } /* diff --git a/code/rawfile.h b/code/rawfile.h index a35451f09..52cf7b12a 100644 --- a/code/rawfile.h +++ b/code/rawfile.h @@ -41,10 +41,8 @@ #include #include #include -#include +#include -#define NULL_HANDLE INVALID_HANDLE_VALUE -#define HANDLE_TYPE HANDLE #ifndef WWERROR #define WWERROR -1 #endif @@ -95,7 +93,7 @@ class RawFileClass : public FileClass virtual bool Set_Date_Time(unsigned int datetime); virtual void Error(int error, int canretry = false, char const * filename=NULL) override; void Bias(int start, int length=-1); - HANDLE_TYPE Get_File_Handle(void) { return(Handle); }; + FILE *Get_File_Handle(void) { return(Handle); }; /* ** These bias values enable a sub-portion of a file to appear as if it @@ -118,15 +116,19 @@ class RawFileClass : public FileClass private: /* - ** This is the low level DOS handle. A -1 indicates an empty condition. + ** This is the file handle. A nullptr indicates an empty condition. */ - HANDLE_TYPE Handle; + FILE *Handle; /* - ** This points to the filename as a NULL terminated string. It may point to either a - ** constant or an allocated string as indicated by the "Allocated" flag. + ** This points to a copy of the filename as a NULL terminated string. */ - char const * Filename; + char *Filename; + + /* + ** The type of the last file access operation. Reset by fseek(). + */ + int LastAccessType; // // file date and time are in the following formats: @@ -141,15 +143,6 @@ class RawFileClass : public FileClass // unsigned short Date; unsigned short Time; - - /* - ** Filenames that were assigned as part of the construction process - ** are not allocated. It is assumed that the filename string is a - ** constant in that case and thus making duplication unnecessary. - ** This value will be non-zero if the filename has be allocated - ** (using strdup()). - */ - bool Allocated; }; @@ -195,11 +188,11 @@ inline RawFileClass::RawFileClass(void) : Rights(READ), BiasStart(0), BiasLength(-1), - Handle(INVALID_HANDLE_VALUE), - Filename(0), + Handle(nullptr), + Filename(nullptr), Date(0), Time(0), - Allocated(false) + LastAccessType(0) { } @@ -221,5 +214,5 @@ inline RawFileClass::RawFileClass(void) : *=============================================================================================*/ inline bool RawFileClass::Is_Open(void) const { - return(Handle != INVALID_HANDLE_VALUE); + return(Handle != nullptr); } diff --git a/code/session.cpp b/code/session.cpp index c15304290..7a56972c1 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -76,6 +76,7 @@ #include #include // for station ID computation #include // for station ID computation +#include // for ntohl /***************************** Globals *************************************/ diff --git a/code/sha.cpp b/code/sha.cpp index df04f76e7..95fca0c32 100644 --- a/code/sha.cpp +++ b/code/sha.cpp @@ -78,7 +78,7 @@ void SHAEngine::Process_Partial(void const * & data, int & length) ** Attach as many bytes as possible from the source data into ** the staging buffer. */ - int add_count = std::min((int)length, SRC_BLOCK_SIZE - PartialCount); + int add_count = std::min((int)length, (int)SRC_BLOCK_SIZE - PartialCount); memcpy(&Partial[PartialCount], data, add_count); data = ((char const *&)data) + add_count; PartialCount += add_count; diff --git a/code/sha.h b/code/sha.h index 2a2887cef..e74f2160d 100644 --- a/code/sha.h +++ b/code/sha.h @@ -35,7 +35,7 @@ #include #include #include -#include +#include /* diff --git a/code/vqa.h b/code/vqa.h index d9b53a738..86337755a 100644 --- a/code/vqa.h +++ b/code/vqa.h @@ -19,6 +19,7 @@ #include "ccfile.h" +#include #include //========================================================================== diff --git a/code/wwfile.h b/code/wwfile.h index 9ac186f90..871c71c6d 100644 --- a/code/wwfile.h +++ b/code/wwfile.h @@ -40,13 +40,6 @@ #include #include -#include - -#ifndef SEEK_SET -#define SEEK_SET 0 // Seek from start of file. -#define SEEK_CUR 1 // Seek relative from current location. -#define SEEK_END 2 // Seek from end of file. -#endif class FileClass diff --git a/tests/deploymentconfig/CMakeLists.txt b/tests/deploymentconfig/CMakeLists.txt index f6d40d8b4..c2f3192b8 100644 --- a/tests/deploymentconfig/CMakeLists.txt +++ b/tests/deploymentconfig/CMakeLists.txt @@ -7,6 +7,9 @@ add_executable(DeploymentConfig "${CMAKE_SOURCE_DIR}/code/_deploymentconfig.cpp" "${CMAKE_SOURCE_DIR}/code/bfiofile.cpp" "${CMAKE_SOURCE_DIR}/code/rawfile.cpp" + "${CMAKE_SOURCE_DIR}/code/file.cpp" + "${CMAKE_SOURCE_DIR}/code/file_posix.cpp" + "${CMAKE_SOURCE_DIR}/code/file_win.cpp" "${CMAKE_SOURCE_DIR}/code/dbgprint.cpp" "${CMAKE_SOURCE_DIR}/code/ini.cpp" "${CMAKE_SOURCE_DIR}/code/utf8.cpp" diff --git a/tests/gamedirs/CMakeLists.txt b/tests/gamedirs/CMakeLists.txt index 97346f40a..26ffe5166 100644 --- a/tests/gamedirs/CMakeLists.txt +++ b/tests/gamedirs/CMakeLists.txt @@ -7,6 +7,9 @@ add_executable(GameDirs "${CMAKE_SOURCE_DIR}/code/cdfile.cpp" "${CMAKE_SOURCE_DIR}/code/bfiofile.cpp" "${CMAKE_SOURCE_DIR}/code/rawfile.cpp" + "${CMAKE_SOURCE_DIR}/code/file.cpp" + "${CMAKE_SOURCE_DIR}/code/file_posix.cpp" + "${CMAKE_SOURCE_DIR}/code/file_win.cpp" "${CMAKE_SOURCE_DIR}/code/dbgprint.cpp" "${CMAKE_SOURCE_DIR}/code/ini.cpp" "${CMAKE_SOURCE_DIR}/code/utf8.cpp" From 8c5a34c6883741a8861af47948a41d9f7fcd0b62 Mon Sep 17 00:00:00 2001 From: Marek Benc Date: Thu, 10 Sep 2026 19:38:32 +0200 Subject: [PATCH 2/2] Update filename checks in gamedirscontract test to be case insensitive. --- tests/gamedirs/gamedirscontract.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/gamedirs/gamedirscontract.cpp b/tests/gamedirs/gamedirscontract.cpp index 92c33a51d..97b9b439d 100644 --- a/tests/gamedirs/gamedirscontract.cpp +++ b/tests/gamedirs/gamedirscontract.cpp @@ -45,7 +45,7 @@ void Check_List(std::vector const & actual, std::vector