/* * Descent 3 * Copyright (C) 2024 Parallax Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #if !defined(POSIX) // Non-Linux Build Includes #include #else // Linux Build Includes #include "linux_fix.h" #endif #include "byteswap.h" #include "pserror.h" #include "ddio.h" #include "psglob.h" #include "cfile.h" #include "hogfile.h" //info about library file #include "mem.h" // Library structures struct library_entry { char name[PSFILENAME_LEN + 1]; // just the filename part uint32_t offset; // offset into library file uint32_t length; // length of this file uint32_t timestamp; // time and date of file uint32_t flags; // misc flags }; struct library { std::filesystem::path name; // includes path + filename uint32_t nfiles = 0; std::vector> entries; std::shared_ptr next; int handle = 0; // identifier for this lib FILE *file = nullptr; // pointer to file for this lib, if no one using it }; // Map of paths. If value of entry is true, path is only for specific extensions std::map paths; // Map of extensions <=> relevant paths std::map extensions; std::shared_ptr Libraries; int lib_handle = 0; // Structure thrown on disk error cfile_error cfe; // The message for unexpected end of file const char *eof_error = "Unexpected end of file"; // Generates a cfile error void ThrowCFileError(int type, CFILE *file, const char *msg) { cfe.read_write = type; cfe.msg = msg; cfe.file = file; throw &cfe; } static void cf_Close(); // searches through the open HOG files, and opens a file if it finds it in any of the libs static CFILE *open_file_in_lib(const char *filename); // Opens a HOG file. Future calls to cfopen(), etc. will look in this HOG. // Parameters: libname - the path & filename of the HOG file // NOTE: libname must be valid for the entire execution of the program. Therefore, it should either // be a fully-specified path name, or the current directory must not change. // Returns: 0 if error, else library handle that can be used to close the library int cf_OpenLibrary(const std::filesystem::path &libname) { FILE *fp; int i; uint32_t offset; static int first_time = 1; tHogHeader header{}; tHogFileEntry entry{}; // allocation library structure std::shared_ptr lib = std::make_shared(); // resolve library name std::filesystem::path resolve_dir = libname.parent_path(); std::filesystem::path resolve_name = libname; if (!resolve_dir.empty()) { resolve_name = libname.filename(); } std::filesystem::path t_out = cf_FindRealFileNameCaseInsensitive(resolve_name, resolve_dir); if (t_out.empty()) { return 0; // CF_NO_FILE } // re-assemble if (!resolve_dir.empty()) lib->name = resolve_dir / t_out; else lib->name = t_out; fp = fopen(lib->name.u8string().c_str(), "rb"); if (fp == nullptr) { return 0; // CF_NO_FILE; } // check if this if first library opened if (first_time) { atexit(cf_Close); first_time = 0; } // read HOG header if (!ReadHogHeader(fp, &header)) { fclose(fp); return 0; // CF_BAD_LIB; } lib->nfiles = header.nfiles; // allocate CFILE hog info. lib->entries.reserve(lib->nfiles); lib->next = Libraries; Libraries = lib; // set data offset of first file offset = header.file_data_offset; // Go to index start fseek(fp, HOG_HDR_SIZE, SEEK_SET); // read in index table for (i = 0; i < lib->nfiles; i++) { if (!ReadHogEntry(fp, &entry)) { fclose(fp); return 0; } // Make sure files are in order ASSERT((i == 0) || (stricmp(entry.name, lib->entries[i - 1]->name) >= 0)); // Copy into table std::unique_ptr lib_entry = std::make_unique(); strcpy(lib_entry->name, entry.name); lib_entry->flags = entry.flags; lib_entry->length = entry.len; lib_entry->offset = offset; lib_entry->timestamp = entry.timestamp; lib->entries.push_back(std::move(lib_entry)); offset += entry.len; } // assign a handle lib->handle = ++lib_handle; // Save the file pointer lib->file = fp; // Success. Return the handle return lib->handle; } /** * Closes a library file. * @param handle the handle returned by cf_OpenLibrary() */ void cf_CloseLibrary(int handle) { std::shared_ptr lib, prev; for (lib = Libraries; lib; prev = lib, lib = lib->next) { if (lib->handle == handle) { if (prev) prev->next = lib->next; else Libraries = lib->next; if (lib->file) fclose(lib->file); return; // successful close } } } // Closes down the CFILE system, freeing up all data, etc. void cf_Close() { std::shared_ptr next; while (Libraries) { next = Libraries->next; Libraries = next; } } bool cf_SetSearchPath(const std::filesystem::path &path, const std::vector &ext_list) { // Don't add non-existing path into search paths if (!std::filesystem::is_directory(path)) return false; // Get & store full path paths.insert_or_assign(std::filesystem::absolute(path), !ext_list.empty()); // Set extensions for this path if (!ext_list.empty()) { for (auto const &ext : ext_list) { if (!ext.empty()) { extensions.insert_or_assign(ext, path); } } } return true; } /** * Removes all search paths that have been added by cf_SetSearchPath */ void cf_ClearAllSearchPaths() { paths.clear(); extensions.clear(); } /** * Opens a file for reading in a library, given the library id * @param filename * @param libhandle * @return */ CFILE *cf_OpenFileInLibrary(const std::filesystem::path &filename, int libhandle) { if (libhandle <= 0) return nullptr; std::shared_ptr lib = Libraries; // find the library that we want to use while (lib) { if (lib->handle == libhandle) break; lib = lib->next; } if (nullptr == lib) { // couldn't find the library handle return nullptr; } // now do a binary search for the file entry int i, first = 0, last = lib->nfiles - 1, c; bool found = false; do { i = (first + last) / 2; c = stricmp(filename.u8string().c_str(), lib->entries[i]->name); // compare to current if (c == 0) { found = true; break; } if (first >= last) // exhausted search break; if (c > 0) // search key after check key first = i + 1; else // search key before check key last = i - 1; } while (true); if (!found) return nullptr; // file not in library // open the file for reading FILE *fp; int r; // See if there's an available FILE if (lib->file) { fp = lib->file; lib->file = nullptr; } else { fp = fopen(lib->name.u8string().c_str(), "rb"); if (!fp) { mprintf(1, "Error opening library <%s> when opening file <%s>; errno=%d.", lib->name.u8string().c_str(), filename.u8string().c_str(), errno); Int3(); return nullptr; } } CFILE *cfile = (CFILE *)mem_malloc(sizeof(*cfile)); if (!cfile) Error("Out of memory in cf_OpenFileInLibrary()"); cfile->name = lib->entries[i]->name; cfile->file = fp; cfile->lib_handle = lib->handle; cfile->size = lib->entries[i]->length; cfile->lib_offset = lib->entries[i]->offset; cfile->position = 0; cfile->flags = 0; r = fseek(fp, cfile->lib_offset, SEEK_SET); ASSERT(r == 0); return cfile; } // searches through the open HOG files, and opens a file if it finds it in any of the libs CFILE *open_file_in_lib(const char *filename) { CFILE *cfile; std::shared_ptr lib = Libraries; while (lib) { int i; // Do binary search for the file int first = 0, last = lib->nfiles - 1, c, found = 0; do { i = (first + last) / 2; c = stricmp(filename, lib->entries[i]->name); // compare to current if (c == 0) { // found it found = 1; break; } if (first >= last) // exhausted search break; if (c > 0) // search key after check key first = i + 1; else // search key before check key last = i - 1; } while (true); if (found) { FILE *fp; int r; // See if there's an available FILE if (lib->file) { fp = lib->file; lib->file = nullptr; } else { fp = fopen(lib->name.u8string().c_str(), "rb"); if (!fp) { mprintf(1, "Error opening library <%s> when opening file <%s>; errno=%d.", lib->name.u8string().c_str(), filename, errno); Int3(); return nullptr; } } cfile = (CFILE *)mem_malloc(sizeof(*cfile)); if (!cfile) Error("Out of memory in open_file_in_lib()"); cfile->name = lib->entries[i]->name; cfile->file = fp; cfile->lib_handle = lib->handle; cfile->size = lib->entries[i]->length; cfile->lib_offset = lib->entries[i]->offset; cfile->position = 0; cfile->flags = 0; r = fseek(fp, cfile->lib_offset, SEEK_SET); ASSERT(r == 0); return cfile; } lib = lib->next; } return nullptr; } std::filesystem::path cf_FindRealFileNameCaseInsensitive(const std::filesystem::path &fname, const std::filesystem::path &directory) { // Dumb check, maybe there already all ok? if (exists((directory / fname))) { return fname.filename(); } std::filesystem::path result, search_path, search_file; search_path = directory / fname.parent_path(); search_file = fname.filename(); // If directory does not exist, nothing to search. if (!std::filesystem::is_directory(search_path) || search_file.empty()) { return {}; } // Search component in search_path auto const &it = std::filesystem::directory_iterator(search_path); auto found = std::find_if(it, end(it), [&search_file, &search_path, &result](const auto& dir_entry) { return stricmp(dir_entry.path().filename().u8string().c_str(), search_file.u8string().c_str()) == 0; }); if (found != end(it)) { // Match, append to result result = found->path(); search_path = result; } else { // Component not found, mission failed return {}; } return result.filename(); } // look for the file in the specified directory static CFILE *open_file_in_directory(const std::filesystem::path &filename, const char *mode, const std::filesystem::path &directory); // look for the file in the specified directory CFILE *open_file_in_directory(const std::filesystem::path &filename, const char *mode, const std::filesystem::path &directory) { FILE *fp; CFILE *cfile; std::filesystem::path using_filename; char tmode[3] = "rb"; if (std::filesystem::is_directory(directory)) { // Make a full path using_filename = directory / filename; } else { // no directory specified, so just use filename passed using_filename = filename; } // set read or write mode tmode[0] = mode[0]; // if mode is "w", then open in text or binary as requested. If "r", always open in "rb" tmode[1] = (mode[0] == 'w') ? mode[1] : 'b'; // try to open file fp = fopen(using_filename.u8string().c_str(), tmode); if (!fp) { #if defined(POSIX) // If we tried to open file for reading, assume there maybe case-sensitive files if (tmode[0] == 'r') { // Try different cases of the filename using_filename = cf_FindRealFileNameCaseInsensitive(filename, directory); if (using_filename.empty()) { // just give up return nullptr; } if (std::filesystem::is_directory(directory)) { // Make a full path using_filename = directory / using_filename; } fp = fopen(using_filename.u8string().c_str(), tmode); if (!fp) { // no dice return nullptr; } } else { // Error on writing file return nullptr; } #else // We on incase-sensitive filesystem, no file means no file. return nullptr; #endif } else { using_filename = filename; } // found the file, open it cfile = (CFILE *)mem_malloc(sizeof(*cfile)); if (!cfile) Error("Out of memory in open_file_in_directory()"); cfile->name = (char *)mem_malloc(sizeof(char) * (strlen(using_filename.u8string().c_str()) + 1)); if (!cfile->name) Error("Out of memory in open_file_in_directory()"); strcpy(cfile->name, using_filename.u8string().c_str()); cfile->file = fp; cfile->lib_handle = -1; cfile->size = ddio_GetFileLength(fp); cfile->lib_offset = 0; // 0 means on disk, not in HOG cfile->position = 0; cfile->flags = 0; return cfile; } // Opens a file for reading or writing // If a path is specified, will try to open the file only in that path. // If no path is specified, will look through search directories and library files. // Parameters: filename - the name if the file, with or without a path // mode - the standard C mode string // Returns: the CFile handle, or NULL if file not opened CFILE *cfopen(const std::filesystem::path &filename, const char *mode) { CFILE *cfile; // Check for valid mode ASSERT((mode[0] == 'r') || (mode[0] == 'w')); ASSERT((mode[1] == 'b') || (mode[1] == 't')); // get the parts of the pathname std::filesystem::path path = filename.parent_path(); std::filesystem::path fname = filename.stem(); std::filesystem::path ext = filename.extension(); // if there is a path specified, use it instead of the libraries, search dirs, etc. // if the file is writable, just open it, instead of looking in libs, etc. if (!path.empty() || (mode[0] == 'w')) { // use path specified with file cfile = open_file_in_directory(filename, mode, std::filesystem::path()); goto got_file; // don't look in libs, etc. } // First look in the directories for this file's extension for (auto const &entry : extensions) { if (!strnicmp(entry.first.u8string().c_str(), ext.u8string().c_str(), _MAX_EXT)) { // found ext cfile = open_file_in_directory(filename, mode, entry.second); if (cfile) { goto got_file; } } } // Next look in the general directories for (auto const &entry : paths) { if (!entry.second) { cfile = open_file_in_directory(filename, mode, entry.first); if (cfile) goto got_file; } } // Lastly, try the hog files cfile = open_file_in_lib(filename.u8string().c_str()); got_file:; if (cfile) { if (mode[0] == 'w') cfile->flags |= CFF_WRITING; if (mode[1] == 't') cfile->flags |= CFF_TEXT; } return cfile; } // Returns the length of the specified file // Parameters: cfp - the file pointer returned by cfopen() uint32_t cfilelength(CFILE *cfp) { return cfp->size; } // Closes an open CFILE. // Parameters: cfile - the file pointer returned by cfopen() void cfclose(CFILE *cfp) { // Either give the file back to the library, or close it if (cfp->lib_handle != -1) { std::shared_ptr lib; for (lib = Libraries; lib; lib = lib->next) { if (lib->handle == cfp->lib_handle) { // found the library // if library doesn't already have a file, give it this one if (lib->file == nullptr) { lib->file = cfp->file; cfp->file = nullptr; } break; } } } // If the file handle wasn't given back to library, close the file if (cfp->file) fclose(cfp->file); // free the name, if allocated if (!cfp->lib_offset) mem_free(cfp->name); // free the cfile struct mem_free(cfp); } // Just like stdio fgetc(), except works on a CFILE // Returns a char or EOF int cfgetc(CFILE *cfp) { int c; static uint8_t ch[3] = "\0\0"; if (cfp->position >= cfp->size) return EOF; fread(ch, sizeof(char), 1, cfp->file); c = ch[0]; // c = getc( cfp->file ); if (cfeof(cfp)) c = EOF; if (c != EOF) { cfp->position++; // do special newline handling for text files: // if CR or LF by itself, return as newline // if CR/LF pair, return as newline if (cfp->flags & CFF_TEXT) { if (c == 10) // return LF as newline c = '\n'; else if (c == 13) { // check for CR/LF pair fread(ch, sizeof(char), 1, cfp->file); int cc = ch[0]; // getc(cfp->file); // if (cc != EOF) { if (!cfeof(cfp)) { if (cc == 10) // line feed? cfp->position++; //..yes, so swallow it else { // ungetc(cc,cfp->file); //..no, so put it back fseek(cfp->file, -1, SEEK_CUR); } } c = '\n'; // return CR or CR/LF pair as newline } } } return c; } // Just like stdio fseek(), except works on a CFILE int cfseek(CFILE *cfp, long offset, int where) { int c; long goal_position; switch (where) { case SEEK_SET: goal_position = offset; break; case SEEK_CUR: goal_position = cfp->position + offset; break; case SEEK_END: goal_position = cfp->size + offset; break; default: return 1; } c = fseek(cfp->file, cfp->lib_offset + goal_position, SEEK_SET); cfp->position = ftell(cfp->file) - cfp->lib_offset; return c; } // Just like stdio ftell(), except works on a CFILE long cftell(CFILE *cfp) { return cfp->position; } // Returns true if at EOF int cfeof(CFILE *cfp) { return (cfp->position >= cfp->size); } // Tells if the file exists // Returns non-zero if file exists. Also tells if the file is on disk // or in a hog - See return values in cfile.h int cfexist(const std::filesystem::path &filename) { CFILE *cfp; int ret; cfp = cfopen(filename, "rb"); if (!cfp) { // Didn't get file. Why? if (errno == EACCES) // File exists, but couldn't open it return CFES_ON_DISK; // so say it exists on the disk return CFES_NOT_FOUND; // Say we didn't find the file } ret = cfp->lib_offset ? CFES_IN_LIBRARY : CFES_ON_DISK; cfclose(cfp); return ret; } // Reads the specified number of bytes from a file into the buffer // DO NOT USE THIS TO READ STRUCTURES. This function is for byte // data, such as a string or a bitmap of 8-bit pixels. // Returns the number of bytes read. // Throws an exception of type (cfile_error *) if the OS returns an error on read int cf_ReadBytes(uint8_t *buf, int count, CFILE *cfp) { int i; const char *error_msg = eof_error; // default error ASSERT(!(cfp->flags & CFF_TEXT)); if (cfp->position + count <= cfp->size) { i = fread(buf, 1, count, cfp->file); if (i == count) { cfp->position += i; return i; } // if not EOF, then get the error message if (!feof(cfp->file)) error_msg = strerror(errno); } mprintf(1, "Error reading %d bytes from position %d of file <%s>; errno=%d.", count, cfp->position, cfp->name, errno); return 0; } // The following functions read numeric vales from a CFILE. All values are // stored in the file in Intel (little-endian) format. These functions // will convert to big-endian if required. // These funtions will exit the program with an error if the value // cannot be read, so do not call these if you don't require the data // to be present. // Read and return an integer (32 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on read int32_t cf_ReadInt(CFILE *cfp, bool little_endian) { int32_t i; cf_ReadBytes((uint8_t *)&i, sizeof(i), cfp); return little_endian ? D3::convert_le(i) : D3::convert_be(i); } // Read and return a int16_t (16 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on read int16_t cf_ReadShort(CFILE *cfp, bool little_endian) { int16_t i; cf_ReadBytes((uint8_t *)&i, sizeof(i), cfp); return little_endian ? D3::convert_le(i) : D3::convert_be(i); } // Read and return a byte (8 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on read int8_t cf_ReadByte(CFILE *cfp) { int8_t i; cf_ReadBytes((uint8_t *)&i, sizeof(i), cfp); return i; } // Read and return a float (32 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on read float cf_ReadFloat(CFILE *cfp) { float f; cf_ReadBytes((uint8_t *)&f, sizeof(f), cfp); return INTEL_FLOAT(f); } // Read and return a double (64 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on read double cf_ReadDouble(CFILE *cfp) { double f; cf_ReadBytes((uint8_t *)&f, sizeof(f), cfp); return D3::convert_le(f); } // Reads a string from a CFILE. If the file is type binary, this // function reads until a NULL or EOF is found. If the file is text, // the function reads until a newline or EOF is found. The string is always // written to the destination buffer null-terminated, without the newline. // Parameters: buf - where the string is written // n - the maximum string length, including the terminating 0 // cfp - the CFILE pointer // Returns the number of bytes in the string, before the terminator // Does not generate an exception on EOF int cf_ReadString(char *buf, size_t n, CFILE *cfp) { int c; int count; char *bp; if (n == 0) return -1; bp = buf; for (count = 0;; count++) { c = cfgetc(cfp); if (c == EOF) { if (!cfeof(cfp)) // not actually at EOF, so must be error ThrowCFileError(CFE_READING, cfp, strerror(errno)); break; } if ((!(cfp->flags & CFF_TEXT) && (c == 0)) || ((cfp->flags & CFF_TEXT) && (c == '\n'))) break; // end-of-string if (count < n - 1) // store char if room in buffer *bp++ = c; } *bp = 0; // write terminator return count; } // Writes the specified number of bytes from a file into the buffer // DO NOT USE THIS TO WRITE STRUCTURES. This function is for byte // data, such as a string or a bitmap of 8-bit pixels. // Returns the number of bytes written. // Throws an exception of type (cfile_error *) if the OS returns an error on write int cf_WriteBytes(const uint8_t *buf, int count, CFILE *cfp) { int i; if (!(cfp->flags & CFF_WRITING)) return 0; ASSERT(count > 0); i = fwrite(buf, 1, count, cfp->file); cfp->position += i; if (i != count) ThrowCFileError(CFE_WRITING, cfp, strerror(errno)); return i; } // Writes a null-terminated string to a file. If the file is type binary, // the string is terminated in the file with a null. If the file is type // text, the string is terminated with a newline. // Parameters: buf - pointer to the string // cfp = the CFILE pointer // Returns the number of bytes written // Throws an exception of type (cfile_error *) if the OS returns an error on write int cf_WriteString(CFILE *cfp, const char *buf) { int len; len = strlen(buf); if (len != 0) // write string cf_WriteBytes((uint8_t *)buf, len, cfp); // Terminate with newline (text file) or NULL (binary file) cf_WriteByte(cfp, (cfp->flags & CFF_TEXT) ? '\n' : 0); return len + 1; } // Just like stdio fprintf(), except works on a CFILE int cfprintf(CFILE *cfp, const char *format, ...) { va_list args; int count; va_start(args, format); count = vfprintf(cfp->file, format, args); va_end(args); cfp->position += count + 1; // count doesn't include terminator return count; } // The following functions write numeric vales to a CFILE. All values are // stored to the file in Intel (little-endian) format. // All these throw an exception if there's an error on write. // Write an integer (32 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on write void cf_WriteInt(CFILE *cfp, int32_t i) { int t = INTEL_INT(i); cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp); } // Write a int16_t (16 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on write void cf_WriteShort(CFILE *cfp, int16_t s) { int16_t t = INTEL_SHORT(s); cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp); } // Write a byte (8 bits). // Throws an exception of type (cfile_error *) if the OS returns an error on write void cf_WriteByte(CFILE *cfp, int8_t b) { if (fputc(b, cfp->file) == EOF) ThrowCFileError(CFE_WRITING, cfp, strerror(errno)); cfp->position++; // If text file & writing newline, increment again for LF if ((cfp->flags & CFF_TEXT) && (b == '\n')) // check for text mode newline cfp->position++; } // Write a float (32 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on write void cf_WriteFloat(CFILE *cfp, float f) { float t = INTEL_FLOAT(f); cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp); } // Write a double (64 bits) // Throws an exception of type (cfile_error *) if the OS returns an error on write void cf_WriteDouble(CFILE *cfp, double d) { auto t = D3::convert_le(d); cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp); } // Copies a file. Returns TRUE if copied ok. Returns FALSE if error opening either file. // Throws an exception of type (cfile_error *) if the OS returns an error on read or write bool cf_CopyFile(const std::filesystem::path &dest, const std::filesystem::path &src, int copytime) { CFILE *infile, *outfile; if (!stricmp(dest.u8string().c_str(), src.u8string().c_str())) return true; // don't copy files if they are the same infile = (CFILE *)cfopen(src, "rb"); if (!infile) return false; outfile = (CFILE *)cfopen(dest, "wb"); if (!outfile) { cfclose(infile); return false; } int progress = 0; int readcount = 0; #define COPY_CHUNK_SIZE 5000 uint8_t copybuf[COPY_CHUNK_SIZE]; while (!cfeof(infile)) { // uint8_t c; if (progress + COPY_CHUNK_SIZE <= infile->size) { readcount = COPY_CHUNK_SIZE; } else { readcount = infile->size - progress; } cf_ReadBytes(copybuf, readcount, infile); cf_WriteBytes(copybuf, readcount, outfile); progress += readcount; // c=cf_ReadByte (infile); // cf_WriteByte (outfile,c); } cfclose(infile); cfclose(outfile); if (!infile->lib_offset && copytime) { cf_CopyFileTime(dest, src); } return true; } // Checks to see if two files are different. // Returns TRUE if the files are different, or FALSE if they are the same. bool cf_Diff(const std::filesystem::path &a, const std::filesystem::path &b) { return (ddio_FileDiff(a, b)); } // Copies the file time from one file to another void cf_CopyFileTime(const std::filesystem::path &dest, const std::filesystem::path &src) { ddio_CopyFileTime(dest, src); } // Changes a files attributes (ie read/write only) void cf_ChangeFileAttributes(const char *name, int attr) { if (_chmod(name, attr) == -1) Int3(); // Get Jason or Matt, file not found! } // rewinds cfile position void cf_Rewind(CFILE *fp) { if (fp->lib_offset) { int r = fseek(fp->file, fp->lib_offset, SEEK_SET); ASSERT(r == 0); } else { rewind(fp->file); } fp->position = 0; } // Calculates a 32-bit CRC for the specified file. a return code of -1 means file note found #define CRC32_POLYNOMIAL 0xEDB88320L #define CRC_BUFFER_SIZE 5000 uint32_t cf_CalculateFileCRC(CFILE *infile) { int i, j; uint8_t crcbuf[CRC_BUFFER_SIZE]; static bool Cfile_crc_calculated = false; static uint32_t CRCTable[256]; uint32_t crc; uint32_t temp1; uint32_t temp2; uint32_t readlen; // Only make the lookup table once if (!Cfile_crc_calculated) { Cfile_crc_calculated = true; for (i = 0; i <= 255; i++) { crc = i; for (j = 8; j > 0; j--) { if (crc & 1) crc = (crc >> 1) ^ CRC32_POLYNOMIAL; else crc >>= 1; } CRCTable[i] = crc; } } crc = 0xffffffffl; while (!cfeof(infile)) { if ((infile->size - infile->position) < CRC_BUFFER_SIZE) readlen = infile->size - infile->position; else readlen = CRC_BUFFER_SIZE; if (!cf_ReadBytes(crcbuf, readlen, infile)) { // Doh, error time! Int3(); return 0xFFFFFFFF; } for (uint32_t a = 0; a < readlen; a++) { temp1 = (crc >> 8) & 0x00FFFFFFL; temp2 = CRCTable[((int)crc ^ crcbuf[a]) & 0xff]; crc = temp1 ^ temp2; } } return crc ^ 0xffffffffl; } uint32_t cf_GetfileCRC(const std::filesystem::path &src) { CFILE *infile = cfopen(src, "rb"); if (!infile) return 0xFFFFFFFF; uint32_t crc = cf_CalculateFileCRC(infile); cfclose(infile); return crc; } char cfile_search_wildcard[256]; std::shared_ptr cfile_search_library; int cfile_search_curr_index = 0; bool cfile_search_ispattern = false; // the following cf_LibraryFind function are similar to the ddio_Find functions as they look // for files that match the wildcard passed in, however, this is to be used for hog files. bool cf_LibraryFindFirst(int handle, const char *wildcard, char *buffer) { ASSERT(wildcard); ASSERT(buffer); if (!wildcard || !buffer) return false; *buffer = '\0'; if (cfile_search_library) cf_LibraryFindClose(); // find the library cfile_search_library = Libraries; while (cfile_search_library && cfile_search_library->handle != handle) { cfile_search_library = cfile_search_library->next; } if (!cfile_search_library) return false; // now find the first matching file strncpy(cfile_search_wildcard, wildcard, 255); cfile_search_wildcard[255] = '\0'; cfile_search_ispattern = (bool)(PSGlobHasPattern(cfile_search_wildcard) != 0); cfile_search_curr_index = 0; while (cfile_search_curr_index < cfile_search_library->nfiles) { if (cfile_search_ispattern) { if (PSGlobMatch(cfile_search_wildcard, cfile_search_library->entries[cfile_search_curr_index]->name, 0, 0)) { // it's a match strcpy(buffer, cfile_search_library->entries[cfile_search_curr_index]->name); cfile_search_curr_index++; return true; } } else { if (!stricmp(cfile_search_library->entries[cfile_search_curr_index]->name, cfile_search_wildcard)) { strcpy(buffer, cfile_search_library->entries[cfile_search_curr_index]->name); cfile_search_curr_index++; return true; } } cfile_search_curr_index++; } // we didn't find a match return false; } bool cf_LibraryFindNext(char *buffer) { while (cfile_search_curr_index < cfile_search_library->nfiles) { if (cfile_search_ispattern) { if (PSGlobMatch(cfile_search_wildcard, cfile_search_library->entries[cfile_search_curr_index]->name, 0, 0)) { // it's a match strcpy(buffer, cfile_search_library->entries[cfile_search_curr_index]->name); cfile_search_curr_index++; return true; } } else { if (!stricmp(cfile_search_library->entries[cfile_search_curr_index]->name, cfile_search_wildcard)) { strcpy(buffer, cfile_search_library->entries[cfile_search_curr_index]->name); cfile_search_curr_index++; return true; } } cfile_search_curr_index++; } return false; } void cf_LibraryFindClose() { cfile_search_library = nullptr; cfile_search_curr_index = 0; cfile_search_ispattern = false; } bool cf_IsFileInHog(const std::filesystem::path &filename, const std::filesystem::path &hogname) { std::shared_ptr lib = Libraries; while (lib) { if (stricmp(lib->name.u8string().c_str(), hogname.u8string().c_str()) == 0) { // Now look for filename CFILE *cf; cf = cf_OpenFileInLibrary(filename, lib->handle); if (cf) { cfclose(cf); return true; } } lib = lib->next; } return false; }