diff --git a/code/always.h b/code/always.h index 042ded5c2..095eee753 100644 --- a/code/always.h +++ b/code/always.h @@ -91,3 +91,135 @@ #ifndef _stricmp #define _stricmp stricmp #endif + + +/* +** Define some Windows specific values that are used throghout the games +*/ +#ifndef _WIN32 + +#define _MAX_DRIVE 3 +#define _MAX_DIR 256 +#define _MAX_FNAME 256 +#define _MAX_EXT 256 +#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 void _makepath(char * path, char const * drive, char const * dir, char const * fname, char const * ext) +{ + if (path == nullptr) return; + + size_t length = 0; + + auto append = [&](char const * text, size_t count) { + size_t room = _MAX_PATH - 1 - length; + if (count > room) count = room; + memcpy(path + length, text, count); + length += count; + }; + + if (drive != nullptr && drive[0] != '\0') { + append(drive, 1); + append(":", 1); + } + + if (dir != nullptr && dir[0] != '\0') { + size_t count = strlen(dir); + append(dir, count); + if (dir[count - 1] != '\\' && dir[count - 1] != '/') { + append("/", 1); + } + } + + if (fname != nullptr) { + append(fname, strlen(fname)); + } + + if (ext != nullptr && ext[0] != '\0') { + if (ext[0] != '.') { + append(".", 1); + } + append(ext, strlen(ext)); + } + + path[length] = '\0'; +} + +inline void _splitpath(char const * path, char * drive, char * dir, char * fname, char * ext) +{ + if (drive != nullptr) drive[0] = '\0'; + if (dir != nullptr) dir[0] = '\0'; + if (fname != nullptr) fname[0] = '\0'; + if (ext != nullptr) ext[0] = '\0'; + + if (path == nullptr) return; + + char const * rest = path; + + if (path[0] != '\0' && path[1] == ':') { + if (drive != nullptr) snprintf(drive, _MAX_DRIVE, "%.2s", path); + rest = path + 2; + } + + char const * name = rest; + for (char const * scan = rest; *scan != '\0'; ++scan) { + if (*scan == '\\' || *scan == '/') { + name = scan + 1; + } + } + + if (dir != nullptr) snprintf(dir, _MAX_DIR, "%.*s", (int)(name - rest), rest); + + char const * dot = name + strlen(name); + for (char const * scan = name; *scan != '\0'; ++scan) { + if (*scan == '.') { + dot = scan; + } + } + + if (fname != nullptr) snprintf(fname, _MAX_FNAME, "%.*s", (int)(dot - name), name); + if (ext != nullptr) snprintf(ext, _MAX_EXT, "%s", dot); +} + +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 41d2e7e2b..cb994291f 100644 --- a/code/blowfish.h +++ b/code/blowfish.h @@ -31,7 +31,9 @@ #pragma once +#ifdef _WIN32 #include "win.h" +#endif #include diff --git a/code/cdfile.cpp b/code/cdfile.cpp index 11ef777ac..1593d7aa7 100644 --- a/code/cdfile.cpp +++ b/code/cdfile.cpp @@ -41,6 +41,7 @@ #include "cdfile.h" +#include #include /* @@ -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..309291fb8 --- /dev/null +++ b/code/file.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. + ******************************************************************************/ + +#include "always.h" + +#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..2845d6729 --- /dev/null +++ b/code/file.h @@ -0,0 +1,37 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2020 Electronic Arts Inc. + * Copyright 2020-2024 Vanilla Conquer contributors + * Copyright 2026 OpenTS contributors + * + * 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. + ******************************************************************************/ + +#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..6e9b88608 --- /dev/null +++ b/code/file_posix.cpp @@ -0,0 +1,141 @@ +/******************************************************************************* + * 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 "always.h" + +#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..0b6f31c2d --- /dev/null +++ b/code/file_win.cpp @@ -0,0 +1,87 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2020-2022 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 "always.h" + +#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..a034874e2 100644 --- a/code/gamedirs.cpp +++ b/code/gamedirs.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include /* * The directories the command line named. Empty means the game's own directory, so an @@ -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/mixfile.cpp b/code/mixfile.cpp index 9a5db0d37..04eb9cc49 100644 --- a/code/mixfile.cpp +++ b/code/mixfile.cpp @@ -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; diff --git a/code/mixfile.h b/code/mixfile.h index 20d50ec54..5193713b9 100644 --- a/code/mixfile.h +++ b/code/mixfile.h @@ -113,5 +113,5 @@ class MixFileClass : public Node */ void * Data; // Pointer to raw data. - static List List; + static ::List List; }; diff --git a/code/rawfile.cpp b/code/rawfile.cpp index 609822d7b..2fa88cd8f 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. @@ -51,12 +53,23 @@ #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 +91,9 @@ RawFileClass::~RawFileClass(void) { Close(); - if (Allocated && Filename) { + if (Filename) { free((char *)Filename); ((char *&)Filename) = 0; - Allocated = false; } } @@ -135,12 +147,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 +179,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 +193,20 @@ char const * RawFileClass::Set_Name(char const * filename) Error(ENOMEM, false, filename); return(NULL); } - Allocated = true; + +#ifndef _WIN32 + /* + ** 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); +#endif return(Filename); } @@ -236,6 +261,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 +293,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 +326,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 +381,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 +424,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 +493,26 @@ 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); + buffer = (char *)buffer + bytesread; + if (ferror(Handle)) { size -= bytesread; total += bytesread; - Error(GetLastError(), true, Filename); + Error(errno, true, Filename); + if (bytesread == 0) break; continue; } - buffer = (char *)buffer + bytesread; size -= bytesread; total += bytesread; if (bytesread == 0) break; @@ -526,8 +563,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 +707,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 +839,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 +871,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)); - if (GetFileInformationByHandle(Handle, &info)) { - WORD dosdate; - WORD dostime; - FileTimeToDosDateTime(&info.ftLastWriteTime, &dosdate, &dostime); - return((dosdate << 16) | dostime); + 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)); + + return(Date << 16 | Time); + } } +#endif return(0); } @@ -838,16 +931,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 +1052,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..49bd78cfc 100644 --- a/code/rawfile.h +++ b/code/rawfile.h @@ -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-2024 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. @@ -40,11 +42,9 @@ #include #include #include +#include #include -#include -#define NULL_HANDLE INVALID_HANDLE_VALUE -#define HANDLE_TYPE HANDLE #ifndef WWERROR #define WWERROR -1 #endif @@ -95,7 +95,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 +118,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 +145,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 +190,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 +216,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 321b81b64..51bc7e585 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -78,6 +78,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/stats.cpp b/code/stats.cpp index 789a3c415..d80a0b047 100644 --- a/code/stats.cpp +++ b/code/stats.cpp @@ -423,17 +423,15 @@ void Send_Statistics_Packet(void) FILETIME write_time; //File time is 64 bits GetModuleFileName (ProgramInstance, path_to_exe, sizeof(path_to_exe)); - RawFileClass file; - file.Set_Name(path_to_exe); - file.Open(); - HANDLE handle = file.Get_File_Handle(); - - if (handle != INVALID_HANDLE_VALUE) { - if (GetFileTime (handle, NULL, NULL, &write_time)){ - write_time.dwLowDateTime = htonl (write_time.dwLowDateTime); - write_time.dwHighDateTime = htonl (write_time.dwHighDateTime); - stats.Add_Field (FIELD_GAME_BUILD_DATE, (void*)&write_time, sizeof (write_time)); - } + + // The packet carries each half byte-swapped with the low half still leading. + WIN32_FILE_ATTRIBUTE_DATA attributes; + + if (GetFileAttributesEx (path_to_exe, GetFileExInfoStandard, &attributes)) { + write_time = attributes.ftLastWriteTime; + write_time.dwLowDateTime = htonl (write_time.dwLowDateTime); + write_time.dwHighDateTime = htonl (write_time.dwHighDateTime); + stats.Add_Field (FIELD_GAME_BUILD_DATE, (void*)&write_time, sizeof (write_time)); } /* diff --git a/code/vqa.h b/code/vqa.h index d9b53a738..3337ec551 100644 --- a/code/vqa.h +++ b/code/vqa.h @@ -20,6 +20,7 @@ #include "ccfile.h" #include +#include //========================================================================== // PUBLIC FUNCTIONS 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 7405e28d0..d1621db71 100644 --- a/tests/deploymentconfig/CMakeLists.txt +++ b/tests/deploymentconfig/CMakeLists.txt @@ -10,6 +10,9 @@ opents_add_test(DeploymentConfig _deploymentconfig.cpp bfiofile.cpp rawfile.cpp + file.cpp + file_posix.cpp + file_win.cpp dbgprint.cpp ini.cpp utf8.cpp diff --git a/tests/gamedirs/CMakeLists.txt b/tests/gamedirs/CMakeLists.txt index d34cd8743..bff2374fb 100644 --- a/tests/gamedirs/CMakeLists.txt +++ b/tests/gamedirs/CMakeLists.txt @@ -10,6 +10,9 @@ opents_add_test(GameDirs cdfile.cpp bfiofile.cpp rawfile.cpp + file.cpp + file_posix.cpp + file_win.cpp dbgprint.cpp ini.cpp utf8.cpp