Descent3/cfile/cfile.cpp

1042 lines
32 KiB
C++
Raw Normal View History

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
2024-05-16 15:21:30 +00:00
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <cerrno>
#include <filesystem>
#include <map>
#include <memory>
#include <vector>
#include "byteswap.h"
#include "crossplat.h"
#include "cfile.h"
#include "ddio.h"
#include "hogfile.h" //info about library file
#include "log.h"
2024-04-16 03:43:29 +00:00
#include "mem.h"
#include "pserror.h"
2024-04-16 18:56:40 +00:00
// Library structures
struct library_entry {
2024-04-16 18:56:40 +00:00
char name[PSFILENAME_LEN + 1]; // just the filename part
2024-05-16 15:21:30 +00:00
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<std::unique_ptr<library_entry>> entries;
std::shared_ptr<library> next;
int handle = 0; // identifier for this lib
FILE *file = nullptr; // pointer to file for this lib, if no one using it
};
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
/* The "root" directories of the D3 file tree
*
* Directories that come later in the list override directories that come
* earlier in the list. For example, if Base_directories[0] / "d3.hog" exists
* and Base_directories[1] / "d3.hog" also exists, then the one in
* Base_directories[1] will get used. The one in Base_directories[0] will be
* ignored.
*/
std::vector<std::filesystem::path> Base_directories = {};
Move Base_directory into the cfile module The main motivation behind this commit is to make it easier to create a future commit. That future commit will will take multiple different functions from throughout the codebase and replace them with a new function named cf_LocatePath(). One of the functions that will get replaced is cf_FindRealFileNameCaseInsensitive(). There are tests for cf_FindRealFileNameCaseInsensitive() in cfile/tests/cfile_tests.cpp. When I make that future commit, I will have to change the tests in cfile/test/cfile_tests.cpp so that they test the cf_LocatePath() function instead of the cf_FindRealFileNameCaseInsensitive() function. There is an important difference between cf_LocatePath() and cf_FindRealFileNameCaseInsensitive(). cf_LocatePath() depends on the Base_directory variable. cf_FindRealFileNameCaseInsensitive() does not. In order to update the tests so that they use cf_LocatePath(), I need to make sure that the tests have access to the Base_directory variable. Before this change, the Base_directory variable was declared in Descent3/init.cpp. That meant that if a program wanted to access Base_directory, then it would have to link to Descent3/init.cpp. In other words, we would have to link to Descent3/init.cpp when compiling the program that currently tests cf_FindRealFileNameCaseInsensitive() but will test cf_LocatePath() in the future. I tried making that program link to Descent3/init.cpp, but I gave up after a wile. Descent3/init.cpp depends on a lot of other things in the codebase. In order to make it easier to create that future commit, this commit moves the Base_directory variable into the cfile module. When I create that future commit, I won’t have to mess with anything linking related because the cfile tests already link to the cfile module. Additionally, this change will make compiling that test program more efficient. There’s not need for the compiler to look at the entirety of Descent3/init.cpp just because we need a single variable from it.
2024-07-27 12:56:49 +00:00
// Map of paths. If value of entry is true, path is only for specific extensions
std::map<std::filesystem::path, bool> paths;
// Map of extensions <=> relevant paths
std::map<std::filesystem::path, std::filesystem::path> extensions;
std::shared_ptr<library> Libraries;
2024-04-16 18:56:40 +00:00
int lib_handle = 0;
2024-04-16 18:56:40 +00:00
// Structure thrown on disk error
2024-04-16 03:43:29 +00:00
cfile_error cfe;
2024-04-16 18:56:40 +00:00
// The message for unexpected end of file
const char *eof_error = "Unexpected end of file";
Move Base_directory into the cfile module The main motivation behind this commit is to make it easier to create a future commit. That future commit will will take multiple different functions from throughout the codebase and replace them with a new function named cf_LocatePath(). One of the functions that will get replaced is cf_FindRealFileNameCaseInsensitive(). There are tests for cf_FindRealFileNameCaseInsensitive() in cfile/tests/cfile_tests.cpp. When I make that future commit, I will have to change the tests in cfile/test/cfile_tests.cpp so that they test the cf_LocatePath() function instead of the cf_FindRealFileNameCaseInsensitive() function. There is an important difference between cf_LocatePath() and cf_FindRealFileNameCaseInsensitive(). cf_LocatePath() depends on the Base_directory variable. cf_FindRealFileNameCaseInsensitive() does not. In order to update the tests so that they use cf_LocatePath(), I need to make sure that the tests have access to the Base_directory variable. Before this change, the Base_directory variable was declared in Descent3/init.cpp. That meant that if a program wanted to access Base_directory, then it would have to link to Descent3/init.cpp. In other words, we would have to link to Descent3/init.cpp when compiling the program that currently tests cf_FindRealFileNameCaseInsensitive() but will test cf_LocatePath() in the future. I tried making that program link to Descent3/init.cpp, but I gave up after a wile. Descent3/init.cpp depends on a lot of other things in the codebase. In order to make it easier to create that future commit, this commit moves the Base_directory variable into the cfile module. When I create that future commit, I won’t have to mess with anything linking related because the cfile tests already link to the cfile module. Additionally, this change will make compiling that test program more efficient. There’s not need for the compiler to look at the entirety of Descent3/init.cpp just because we need a single variable from it.
2024-07-27 12:56:49 +00:00
/* This function should be called at least once before you use anything else
* from this module.
*/
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
void cf_AddBaseDirectory(const std::filesystem::path &base_directory) {
if (std::filesystem::exists(base_directory)) {
Base_directories.push_back(base_directory);
} else {
LOG_WARNING << "Ignoring nonexistent base directory: " << base_directory;
}
}
/* After you call this function, you must call cf_AddBaseDirectory() at least
* once before you use anything else from this module.
*/
void cf_ClearBaseDirectories() {
Base_directories.clear();
Move Base_directory into the cfile module The main motivation behind this commit is to make it easier to create a future commit. That future commit will will take multiple different functions from throughout the codebase and replace them with a new function named cf_LocatePath(). One of the functions that will get replaced is cf_FindRealFileNameCaseInsensitive(). There are tests for cf_FindRealFileNameCaseInsensitive() in cfile/tests/cfile_tests.cpp. When I make that future commit, I will have to change the tests in cfile/test/cfile_tests.cpp so that they test the cf_LocatePath() function instead of the cf_FindRealFileNameCaseInsensitive() function. There is an important difference between cf_LocatePath() and cf_FindRealFileNameCaseInsensitive(). cf_LocatePath() depends on the Base_directory variable. cf_FindRealFileNameCaseInsensitive() does not. In order to update the tests so that they use cf_LocatePath(), I need to make sure that the tests have access to the Base_directory variable. Before this change, the Base_directory variable was declared in Descent3/init.cpp. That meant that if a program wanted to access Base_directory, then it would have to link to Descent3/init.cpp. In other words, we would have to link to Descent3/init.cpp when compiling the program that currently tests cf_FindRealFileNameCaseInsensitive() but will test cf_LocatePath() in the future. I tried making that program link to Descent3/init.cpp, but I gave up after a wile. Descent3/init.cpp depends on a lot of other things in the codebase. In order to make it easier to create that future commit, this commit moves the Base_directory variable into the cfile module. When I create that future commit, I won’t have to mess with anything linking related because the cfile tests already link to the cfile module. Additionally, this change will make compiling that test program more efficient. There’s not need for the compiler to look at the entirety of Descent3/init.cpp just because we need a single variable from it.
2024-07-27 12:56:49 +00:00
}
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
std::filesystem::path cf_LocatePathCaseInsensitiveHelper(const std::filesystem::path &relative_path,
const std::filesystem::path &starting_dir) {
#ifdef WIN32
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
std::filesystem::path result = starting_dir / relative_path;
if (std::filesystem::exists(result)) {
return result;
} else {
return {};
}
#else
// Dumb check, maybe there already all ok?
if (exists((starting_dir / relative_path))) {
return starting_dir / relative_path;
}
std::filesystem::path result, search_path, search_file;
search_path = starting_dir / relative_path.parent_path();
search_file = relative_path.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;
#endif
}
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
std::vector<std::filesystem::path> cf_LocatePathMultiplePathsHelper(const std::filesystem::path &relative_path,
bool stop_after_first_result) {
ASSERT(("realative_path should be a relative path.", relative_path.is_relative()));
std::vector<std::filesystem::path> return_value = { };
for (auto base_directories_iterator = Base_directories.rbegin();
base_directories_iterator != Base_directories.rend();
++base_directories_iterator) {
ASSERT(("base_directory should be an absolute path.", base_directories_iterator->is_absolute()));
auto to_append = cf_LocatePathCaseInsensitiveHelper(relative_path, *base_directories_iterator);
ASSERT(("to_append should be either empty or an absolute path.", to_append.empty() || to_append.is_absolute()));
if (std::filesystem::exists(to_append)) {
return_value.insert(return_value.begin(), to_append);
if (stop_after_first_result) {
break;
}
}
}
return return_value;
}
/**
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
* Tries to find a relative path inside of one of the Base_directories.
*
* @param relative_path A relative path that well hopefully find in
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
* one of the Base_directories. You dont have to get the
* capitalization of relative_path correct, even on macOS
* and Linux.
*
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
* @return Either an absolute path thats inside a base directory or an empty
* path if nothing is found.
*/
std::filesystem::path cf_LocatePath(const std::filesystem::path &relative_path) {
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
auto return_value_list = cf_LocatePathMultiplePathsHelper(relative_path, true);
if (return_value_list.empty()) {
return "";
} else {
return return_value_list.front();
}
}
/**
* Tries to find multiple relative paths inside of the Base_directories.
*
* @param relative_path A relative path that well hopefully find in
* one or more of the Base_directories. You dont have to
* get the capitalization of relative_path correct, even on
* macOS and Linux.
*
* @return A list of absolute paths. Each path will be inside one of the
* Base_directories.
*/
std::vector<std::filesystem::path> cf_LocateMultiplePaths(const std::filesystem::path &relative_path) {
return cf_LocatePathMultiplePathsHelper(relative_path, false);
}
/* Not all Base_directories are necessarily writable, but this function will
* return one that should be writable.
*/
std::filesystem::path cf_GetWritableBaseDirectory() {
return Base_directories.front();
}
2024-04-16 18:56:40 +00:00
// Generates a cfile error
void ThrowCFileError(int type, CFILE *file, const char *msg) {
2024-04-16 18:56:40 +00:00
cfe.read_write = type;
cfe.msg = msg;
cfe.file = file;
throw &cfe;
2024-04-16 03:43:29 +00:00
}
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);
2024-04-16 18:56:40 +00:00
// Opens a HOG file. Future calls to cfopen(), etc. will look in this HOG.
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
// Parameters: libname - path to the HOG file, relative to one of the Base_directories.
// NOTE: libname must be valid for the entire execution of the program. Therefore, Base_directories
// must not change.
2024-04-16 18:56:40 +00:00
// Returns: 0 if error, else library handle that can be used to close the library
int cf_OpenLibrary(const std::filesystem::path &libname) {
2024-04-16 18:56:40 +00:00
FILE *fp;
2024-05-16 15:21:30 +00:00
int i;
uint32_t offset;
2024-04-16 18:56:40 +00:00
static int first_time = 1;
tHogHeader header{};
tHogFileEntry entry{};
2024-04-16 18:56:40 +00:00
// allocation library structure
std::shared_ptr<library> lib = std::make_shared<library>();
lib->name = cf_LocatePath(libname);
fp = fopen(lib->name.u8string().c_str(), "rb");
if (fp == nullptr) {
2024-04-16 18:56:40 +00:00
return 0; // CF_NO_FILE;
}
2024-04-16 18:56:40 +00:00
// 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);
2024-04-16 18:56:40 +00:00
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);
2024-04-16 18:56:40 +00:00
// 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));
2024-04-16 18:56:40 +00:00
// Copy into table
std::unique_ptr<library_entry> lib_entry = std::make_unique<library_entry>();
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;
2024-04-16 18:56:40 +00:00
}
// assign a handle
lib->handle = ++lib_handle;
// Save the file pointer
lib->file = fp;
// Success. Return the handle
2024-04-16 18:56:40 +00:00
return lib->handle;
2024-04-16 03:43:29 +00:00
}
/**
* Closes a library file.
* @param handle the handle returned by cf_OpenLibrary()
*/
2024-04-16 18:56:40 +00:00
void cf_CloseLibrary(int handle) {
std::shared_ptr<library> lib, prev;
2024-04-16 18:56:40 +00:00
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
2024-04-16 18:56:40 +00:00
}
}
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// Closes down the CFILE system, freeing up all data, etc.
void cf_Close() {
std::shared_ptr<library> next;
2024-04-16 18:56:40 +00:00
while (Libraries) {
next = Libraries->next;
Libraries = next;
}
2024-04-16 03:43:29 +00:00
}
bool cf_SetSearchPath(const std::filesystem::path &path, const std::vector<std::filesystem::path> &ext_list) {
// Don't add non-existing path into search paths
if (!std::filesystem::is_directory(path))
return false;
2024-04-16 18:56:40 +00:00
// 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);
}
2024-04-16 18:56:40 +00:00
}
}
return true;
2024-04-16 03:43:29 +00:00
}
/**
* Removes all search paths that have been added by cf_SetSearchPath
*/
void cf_ClearAllSearchPaths() {
paths.clear();
extensions.clear();
2024-04-16 03:43:29 +00:00
}
/**
* 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) {
2024-04-16 18:56:40 +00:00
if (libhandle <= 0)
return nullptr;
2024-04-16 18:56:40 +00:00
std::shared_ptr<library> lib = Libraries;
2024-04-16 18:56:40 +00:00
// find the library that we want to use
while (lib) {
if (lib->handle == libhandle)
break;
lib = lib->next;
}
if (nullptr == lib) {
2024-04-16 18:56:40 +00:00
// couldn't find the library handle
return nullptr;
2024-04-16 18:56:40 +00:00
}
// now do a binary search for the file entry
int i, first = 0, last = lib->nfiles - 1, c;
bool found = false;
2024-04-16 18:56:40 +00:00
do {
i = (first + last) / 2;
c = stricmp(filename.u8string().c_str(), lib->entries[i]->name); // compare to current
if (c == 0) {
found = true;
2024-04-16 18:56:40 +00:00
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);
2024-04-16 18:56:40 +00:00
if (!found)
return nullptr; // file not in library
2024-04-16 18:56:40 +00:00
// 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;
2024-04-16 18:56:40 +00:00
} else {
fp = fopen(lib->name.u8string().c_str(), "rb");
2024-04-16 18:56:40 +00:00
if (!fp) {
LOG_ERROR.printf("Error opening library <%s> when opening file <%s>; errno=%d.", lib->name.u8string().c_str(),
filename.u8string().c_str(), errno);
2024-04-16 18:56:40 +00:00
Int3();
return nullptr;
2024-04-16 18:56:40 +00:00
}
}
auto cfile = mem_rmalloc<CFILE>();
2024-04-16 18:56:40 +00:00
if (!cfile)
Error("Out of memory in cf_OpenFileInLibrary()");
cfile->name = lib->entries[i]->name;
2024-04-16 18:56:40 +00:00
cfile->file = fp;
cfile->lib_handle = lib->handle;
cfile->size = lib->entries[i]->length;
cfile->lib_offset = lib->entries[i]->offset;
2024-04-16 18:56:40 +00:00
cfile->position = 0;
cfile->flags = 0;
r = fseek(fp, cfile->lib_offset, SEEK_SET);
ASSERT(r == 0);
return cfile;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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<library> lib = Libraries;
2024-04-16 18:56:40 +00:00
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
2024-04-16 18:56:40 +00:00
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);
2024-04-16 18:56:40 +00:00
if (found) {
FILE *fp;
int r;
// See if there's an available FILE
if (lib->file) {
fp = lib->file;
lib->file = nullptr;
2024-04-16 18:56:40 +00:00
} else {
fp = fopen(lib->name.u8string().c_str(), "rb");
2024-04-16 18:56:40 +00:00
if (!fp) {
LOG_ERROR.printf("Error opening library <%s> when opening file <%s>; errno=%d.", lib->name.u8string().c_str(),
filename, errno);
2024-04-16 18:56:40 +00:00
Int3();
return nullptr;
2024-04-16 18:56:40 +00:00
}
}
cfile = mem_rmalloc<CFILE>();
2024-04-16 18:56:40 +00:00
if (!cfile)
Error("Out of memory in open_file_in_lib()");
cfile->name = lib->entries[i]->name;
2024-04-16 18:56:40 +00:00
cfile->file = fp;
cfile->lib_handle = lib->handle;
cfile->size = lib->entries[i]->length;
cfile->lib_offset = lib->entries[i]->offset;
2024-04-16 18:56:40 +00:00
cfile->position = 0;
cfile->flags = 0;
r = fseek(fp, cfile->lib_offset, SEEK_SET);
ASSERT(r == 0);
return cfile;
}
lib = lib->next;
}
return nullptr;
2024-04-16 03:43:29 +00:00
}
// 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);
2024-04-16 18:56:40 +00:00
// 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) {
2024-04-16 18:56:40 +00:00
FILE *fp;
CFILE *cfile;
std::filesystem::path using_filename;
2024-04-16 18:56:40 +00:00
char tmode[3] = "rb";
if (std::filesystem::is_directory(directory)) {
2024-04-16 18:56:40 +00:00
// Make a full path
using_filename = directory / filename;
} else if (filename.is_absolute()) {
// no directory specified, and filename is an absolute path
using_filename = filename;
} else {
// no directory specified, and filename is a relative path
using_filename = cf_LocatePath(filename);
}
2024-04-16 18:56:40 +00:00
// 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"
2024-04-16 18:56:40 +00:00
tmode[1] = (mode[0] == 'w') ? mode[1] : 'b';
// try to open file
fp = fopen(using_filename.u8string().c_str(), tmode);
2024-04-16 03:43:29 +00:00
if (!fp) {
// File not found
return nullptr;
} else {
using_filename = filename;
}
// found the file, open it
cfile = mem_rmalloc<CFILE>();
if (!cfile)
Error("Out of memory in open_file_in_directory()");
cfile->name = mem_rmalloc<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;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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
2024-04-16 03:43:29 +00:00
// mode - the standard C mode string
2024-04-16 18:56:40 +00:00
// Returns: the CFile handle, or NULL if file not opened
CFILE *cfopen(const std::filesystem::path &filename, const char *mode) {
2024-04-16 18:56:40 +00:00
CFILE *cfile;
2024-04-16 18:56:40 +00:00
// 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();
2024-04-16 18:56:40 +00:00
// 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.
2024-04-16 18:56:40 +00:00
}
2024-04-16 18:56:40 +00:00
// 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) {
2024-04-16 18:56:40 +00:00
goto got_file;
}
2024-04-16 18:56:40 +00:00
}
}
2024-04-16 18:56:40 +00:00
// 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)
2024-04-16 18:56:40 +00:00
goto got_file;
}
}
// Lastly, try the hog files
cfile = open_file_in_lib(filename.u8string().c_str());
2024-04-16 03:43:29 +00:00
got_file:;
2024-04-16 18:56:40 +00:00
if (cfile) {
if (mode[0] == 'w')
cfile->flags |= CFF_WRITING;
2024-04-16 18:56:40 +00:00
if (mode[1] == 't')
cfile->flags |= CFF_TEXT;
2024-04-16 18:56:40 +00:00
}
return cfile;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// Returns the length of the specified file
// Parameters: cfp - the file pointer returned by cfopen()
2024-05-16 15:21:30 +00:00
uint32_t cfilelength(CFILE *cfp) { return cfp->size; }
2024-04-16 18:56:40 +00:00
// 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<library> lib;
2024-04-16 18:56:40 +00:00
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) {
2024-04-16 18:56:40 +00:00
lib->file = cfp->file;
cfp->file = nullptr;
2024-04-16 18:56:40 +00:00
}
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);
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// Just like stdio fgetc(), except works on a CFILE
// Returns a char or EOF
int cfgetc(CFILE *cfp) {
int c;
2024-05-24 02:58:46 +00:00
static uint8_t ch[3] = "\0\0";
2024-04-16 18:56:40 +00:00
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) {
2024-04-16 18:56:40 +00:00
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;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// Just like stdio fseek(), except works on a CFILE
2024-05-16 15:21:30 +00:00
int cfseek(CFILE *cfp, long offset, int where) {
int c;
long goal_position;
2024-04-16 18:56:40 +00:00
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;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// Just like stdio ftell(), except works on a CFILE
2024-05-16 15:21:30 +00:00
long cftell(CFILE *cfp) { return cfp->position; }
2024-04-16 18:56:40 +00:00
// Returns true if at EOF
int cfeof(CFILE *cfp) { return (cfp->position >= cfp->size); }
2024-04-16 03:43:29 +00:00
// 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) {
2024-04-16 18:56:40 +00:00
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
2024-04-16 18:56:40 +00:00
}
ret = cfp->lib_offset ? CFES_IN_LIBRARY : CFES_ON_DISK;
2024-04-16 18:56:40 +00:00
cfclose(cfp);
return ret;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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
2024-05-24 03:07:26 +00:00
int cf_ReadBytes(uint8_t *buf, int count, CFILE *cfp) {
2024-04-16 18:56:40 +00:00
int i;
const char *error_msg = eof_error; // default error
ASSERT(!(cfp->flags & CFF_TEXT));
2024-04-16 18:56:40 +00:00
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);
}
LOG_ERROR.printf("Error reading %d bytes from position %d of file <%s>; errno=%d.", count, cfp->position, cfp->name,
errno);
2024-04-16 18:56:40 +00:00
return 0;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// The following functions read numeric vales from a CFILE. All values are
2024-04-16 03:43:29 +00:00
// 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
2024-04-16 18:56:40 +00:00
// 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;
2024-05-24 03:07:26 +00:00
cf_ReadBytes((uint8_t *)&i, sizeof(i), cfp);
return little_endian ? D3::convert_le(i) : D3::convert_be(i);
2024-04-16 03:43:29 +00:00
}
// Read and return a int16_t (16 bits)
2024-04-16 18:56:40 +00:00
// 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;
2024-05-24 03:07:26 +00:00
cf_ReadBytes((uint8_t *)&i, sizeof(i), cfp);
return little_endian ? D3::convert_le(i) : D3::convert_be(i);
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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) {
2024-05-16 15:21:30 +00:00
int8_t i;
2024-05-24 03:07:26 +00:00
cf_ReadBytes((uint8_t *)&i, sizeof(i), cfp);
2024-05-16 15:21:30 +00:00
return i;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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;
2024-05-24 03:07:26 +00:00
cf_ReadBytes((uint8_t *)&f, sizeof(f), cfp);
2024-04-16 18:56:40 +00:00
return INTEL_FLOAT(f);
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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;
2024-05-24 03:07:26 +00:00
cf_ReadBytes((uint8_t *)&f, sizeof(f), cfp);
2024-05-16 15:21:30 +00:00
return D3::convert_le<double>(f);
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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
2024-04-16 03:43:29 +00:00
// n - the maximum string length, including the terminating 0
// cfp - the CFILE pointer
2024-04-16 18:56:40 +00:00
// 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;
2024-05-16 15:21:30 +00:00
int count;
2024-04-16 18:56:40 +00:00
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')))
2024-04-16 18:56:40 +00:00
break; // end-of-string
if (count < n - 1) // store char if room in buffer
*bp++ = c;
}
*bp = 0; // write terminator
return count;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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
2024-05-24 03:07:26 +00:00
int cf_WriteBytes(const uint8_t *buf, int count, CFILE *cfp) {
2024-04-16 18:56:40 +00:00
int i;
if (!(cfp->flags & CFF_WRITING))
2024-04-16 18:56:40 +00:00
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;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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
2024-04-16 03:43:29 +00:00
// cfp = the CFILE pointer
2024-04-16 18:56:40 +00:00
// 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
2024-05-24 03:07:26 +00:00
cf_WriteBytes((uint8_t *)buf, len, cfp);
2024-04-16 18:56:40 +00:00
// Terminate with newline (text file) or NULL (binary file)
cf_WriteByte(cfp, (cfp->flags & CFF_TEXT) ? '\n' : 0);
2024-04-16 18:56:40 +00:00
return len + 1;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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);
2024-04-18 01:16:39 +00:00
va_end(args);
2024-04-16 18:56:40 +00:00
cfp->position += count + 1; // count doesn't include terminator
return count;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// The following functions write numeric vales to a CFILE. All values are
// stored to the file in Intel (little-endian) format.
2024-04-16 03:43:29 +00:00
// All these throw an exception if there's an error on write.
2024-04-16 18:56:40 +00:00
// 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) {
2024-04-16 18:56:40 +00:00
int t = INTEL_INT(i);
2024-05-24 03:07:26 +00:00
cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp);
2024-04-16 03:43:29 +00:00
}
// Write a int16_t (16 bits)
2024-04-16 18:56:40 +00:00
// 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);
2024-05-24 03:07:26 +00:00
cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp);
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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) {
2024-04-16 18:56:40 +00:00
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
2024-04-16 18:56:40 +00:00
cfp->position++;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// Write a float (32 bits)
// Throws an exception of type (cfile_error *) if the OS returns an error on write
2024-05-16 15:21:30 +00:00
void cf_WriteFloat(CFILE *cfp, float f) {
2024-04-16 18:56:40 +00:00
float t = INTEL_FLOAT(f);
2024-05-24 03:07:26 +00:00
cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp);
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// Write a double (64 bits)
// Throws an exception of type (cfile_error *) if the OS returns an error on write
2024-05-16 15:21:30 +00:00
void cf_WriteDouble(CFILE *cfp, double d) {
auto t = D3::convert_le<double>(d);
2024-05-24 03:07:26 +00:00
cf_WriteBytes((uint8_t *)&t, sizeof(t), cfp);
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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) {
2024-04-16 18:56:40 +00:00
CFILE *infile, *outfile;
if (!stricmp(dest.u8string().c_str(), src.u8string().c_str()))
return true; // don't copy files if they are the same
2024-04-16 18:56:40 +00:00
infile = (CFILE *)cfopen(src, "rb");
if (!infile)
return false;
2024-04-16 18:56:40 +00:00
outfile = (CFILE *)cfopen(dest, "wb");
if (!outfile) {
cfclose(infile);
return false;
2024-04-16 18:56:40 +00:00
}
int progress = 0;
int readcount = 0;
#define COPY_CHUNK_SIZE 5000
2024-05-24 03:07:26 +00:00
uint8_t copybuf[COPY_CHUNK_SIZE];
2024-04-16 18:56:40 +00:00
while (!cfeof(infile)) {
2024-05-24 03:07:26 +00:00
// uint8_t c;
2024-04-16 18:56:40 +00:00
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);
}
bool nlo = !infile->lib_offset;
2024-04-16 18:56:40 +00:00
cfclose(infile);
cfclose(outfile);
if (nlo && copytime) {
2024-04-16 18:56:40 +00:00
cf_CopyFileTime(dest, src);
}
return true;
2024-04-16 03:43:29 +00:00
}
2024-04-16 18:56:40 +00:00
// 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)); }
2024-04-16 18:56:40 +00:00
// 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);
}
2024-04-16 03:43:29 +00:00
// rewinds cfile position
2024-04-16 18:56:40 +00:00
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;
2024-04-16 03:43:29 +00:00
}
// Calculates a 32-bit CRC for the specified file. a return code of -1 means file note found
2024-04-16 18:56:40 +00:00
#define CRC32_POLYNOMIAL 0xEDB88320L
#define CRC_BUFFER_SIZE 5000
2024-05-24 02:51:16 +00:00
uint32_t cf_CalculateFileCRC(CFILE *infile) {
2024-04-16 18:56:40 +00:00
int i, j;
2024-05-24 03:07:26 +00:00
uint8_t crcbuf[CRC_BUFFER_SIZE];
2024-04-16 18:56:40 +00:00
static bool Cfile_crc_calculated = false;
2024-05-24 02:51:16 +00:00
static uint32_t CRCTable[256];
uint32_t crc;
uint32_t temp1;
uint32_t temp2;
uint32_t readlen;
2024-04-16 18:56:40 +00:00
// 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;
}
2024-05-24 02:51:16 +00:00
for (uint32_t a = 0; a < readlen; a++) {
2024-04-16 18:56:40 +00:00
temp1 = (crc >> 8) & 0x00FFFFFFL;
temp2 = CRCTable[((int)crc ^ crcbuf[a]) & 0xff];
crc = temp1 ^ temp2;
}
}
return crc ^ 0xffffffffl;
2024-04-16 03:43:29 +00:00
}
uint32_t cf_GetfileCRC(const std::filesystem::path &src) {
CFILE *infile = cfopen(src, "rb");
2024-04-16 18:56:40 +00:00
if (!infile)
return 0xFFFFFFFF;
2024-04-16 03:43:29 +00:00
2024-05-24 02:51:16 +00:00
uint32_t crc = cf_CalculateFileCRC(infile);
2024-04-16 18:56:40 +00:00
cfclose(infile);
return crc;
2024-04-16 03:43:29 +00:00
}
int cf_DoForeachFileInLibrary(int handle, const std::filesystem::path &ext,
const std::function<void(std::filesystem::path)> &func) {
auto search_library = Libraries;
while (search_library && search_library->handle != handle) {
search_library = search_library->next;
}
if (!search_library)
return 0;
// Iterate entries on found library
int result = 0;
for (const auto &item : search_library->entries) {
if (stricmp(std::filesystem::path(item->name).extension().u8string().c_str(), ext.u8string().c_str()) == 0) {
func(item->name);
result++;
}
}
return result;
}
bool cf_IsFileInHog(const std::filesystem::path &filename, const std::filesystem::path &hogname) {
std::shared_ptr<library> lib = Libraries;
2024-04-16 18:56:40 +00:00
while (lib) {
if (stricmp(lib->name.u8string().c_str(), hogname.u8string().c_str()) == 0) {
2024-04-16 18:56:40 +00:00
// Now look for filename
CFILE *cf;
cf = cf_OpenFileInLibrary(filename, lib->handle);
if (cf) {
cfclose(cf);
return true;
}
}
lib = lib->next;
}
return false;
2024-04-18 01:16:39 +00:00
}