Files
fs2open.github.com/code/cfile/cfile.cpp
T

1893 lines
47 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#define _CFILE_INTERNAL
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cerrno>
#include <sys/stat.h>
#ifdef _WIN32
#include <io.h>
#include <direct.h>
#include <windows.h>
#include <winbase.h> /* needed for memory mapping of file functions */
#endif
#ifdef SCP_UNIX
#include <glob.h>
#include <sys/mman.h>
#endif
#include "cfile/cfile.h"
#include "cfile/cfilearchive.h"
#include "cfile/cfilesystem.h"
#include "osapi/osapi.h"
#include "parse/encrypt.h"
#include "cfilesystem.h"
#include <limits>
char Cfile_root_dir[CFILE_ROOT_DIRECTORY_LEN] = "";
char Cfile_user_dir[CFILE_ROOT_DIRECTORY_LEN] = "";
// During cfile_init, verify that Pathtypes[n].index == n for each item
// Each path must have a valid parent that can be tracable all the way back to the root
// so that we can create directories when we need to.
//
// Please make sure extensions are all lower-case, or we'll break unix compatibility
//
// clang-format off
cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
// What type this is Path Extensions Parent type
{ CF_TYPE_INVALID, NULL, NULL, CF_TYPE_INVALID },
// Root must be index 1!!
{ CF_TYPE_ROOT, "", ".mve .ogg", CF_TYPE_ROOT },
{ CF_TYPE_DATA, "data", ".cfg .txt", CF_TYPE_ROOT },
{ CF_TYPE_MAPS, "data" DIR_SEPARATOR_STR "maps", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_TEXT, "data" DIR_SEPARATOR_STR "text", ".txt .net", CF_TYPE_DATA },
{ CF_TYPE_MODELS, "data" DIR_SEPARATOR_STR "models", ".pof", CF_TYPE_DATA },
{ CF_TYPE_TABLES, "data" DIR_SEPARATOR_STR "tables", ".tbl .tbm .lua", CF_TYPE_DATA },
{ CF_TYPE_SOUNDS, "data" DIR_SEPARATOR_STR "sounds", ".wav .ogg", CF_TYPE_DATA },
{ CF_TYPE_VOICE, "data" DIR_SEPARATOR_STR "voice", "", CF_TYPE_DATA },
{ CF_TYPE_VOICE_BRIEFINGS, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "briefing", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_VOICE_CMD_BRIEF, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "command_briefings", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_VOICE_DEBRIEFINGS, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "debriefing", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_VOICE_PERSONAS, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "personas", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_VOICE_SPECIAL, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "special", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_VOICE_TRAINING, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "training", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_MUSIC, "data" DIR_SEPARATOR_STR "music", ".wav .ogg", CF_TYPE_DATA },
{ CF_TYPE_MOVIES, "data" DIR_SEPARATOR_STR "movies", ".mve .msb .ogg .mp4 .srt .webm .png",CF_TYPE_DATA },
{ CF_TYPE_INTERFACE, "data" DIR_SEPARATOR_STR "interface", ".pcx .ani .dds .tga .eff .png .jpg .rml .rcss", CF_TYPE_DATA },
{ CF_TYPE_FONT, "data" DIR_SEPARATOR_STR "fonts", ".vf .ttf .otf", CF_TYPE_DATA },
{ CF_TYPE_EFFECTS, "data" DIR_SEPARATOR_STR "effects", ".ani .eff .pcx .neb .tga .jpg .png .dds .sdr", CF_TYPE_DATA },
{ CF_TYPE_HUD, "data" DIR_SEPARATOR_STR "hud", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_PLAYERS, "data" DIR_SEPARATOR_STR "players", ".hcf", /* DON'T add pilot files here!! */ CF_TYPE_DATA },
{ CF_TYPE_PLAYER_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "images", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_SQUAD_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "squads", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_SINGLE_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "single", ".pl2 .cs2 .plr .csg .css .json", CF_TYPE_PLAYERS },
{ CF_TYPE_MULTI_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "multi", ".plr .json", CF_TYPE_PLAYERS },
{ CF_TYPE_PLAYER_BINDS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "presets", ".json", CF_TYPE_PLAYERS },
{ CF_TYPE_CACHE, "data" DIR_SEPARATOR_STR "cache", ".clr .tmp .bx", CF_TYPE_DATA }, //clr=cached color
{ CF_TYPE_MULTI_CACHE, "data" DIR_SEPARATOR_STR "multidata", ".pcx .png .jpg .dds .fs2 .txt", CF_TYPE_DATA },
{ CF_TYPE_MISSIONS, "data" DIR_SEPARATOR_STR "missions", ".fs2 .fc2 .ntl .ssv", CF_TYPE_DATA },
{ CF_TYPE_CONFIG, "data" DIR_SEPARATOR_STR "config", ".cfg .tbl .tbm .xml .csv", CF_TYPE_DATA },
{ CF_TYPE_DEMOS, "data" DIR_SEPARATOR_STR "demos", ".fsd", CF_TYPE_DATA },
{ CF_TYPE_CBANIMS, "data" DIR_SEPARATOR_STR "cbanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_INTEL_ANIMS, "data" DIR_SEPARATOR_STR "intelanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_SCRIPTS, "data" DIR_SEPARATOR_STR "scripts", ".lua .lc .fnl", CF_TYPE_DATA },
{ CF_TYPE_FICTION, "data" DIR_SEPARATOR_STR "fiction", ".txt", CF_TYPE_DATA },
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html", CF_TYPE_DATA }
};
// clang-format on
#define CFILE_STACK_MAX 8
int cfile_inited = 0;
static int Cfile_stack_pos = 0;
static char Cfile_stack[CFILE_STACK_MAX][CFILE_ROOT_DIRECTORY_LEN];
std::array<CFILE, MAX_CFILE_BLOCKS> Cfile_block_list;
static const char *Cfile_cdrom_dir = NULL;
//
// Function prototypes for internally-called functions
//
static int cfget_cfile_block();
static CFILE *cf_open_fill_cfblock(const char* source, int line, const char* original_filename, FILE * fp, int type);
static CFILE *cf_open_packed_cfblock(const char* source, int line, const char* original_filename, FILE *fp, int type, size_t offset, size_t size);
static CFILE *cf_open_memory_fill_cfblock(const char* source, int line, const char* original_filename, const void* data, size_t size, int dir_type);
#if defined _WIN32
static CFILE *cf_open_mapped_fill_cfblock(const char* source, int line, const char* original_filename, HANDLE hFile, int type);
#elif defined SCP_UNIX
static CFILE *cf_open_mapped_fill_cfblock(const char* source, int line, const char* original_filename, FILE *fp, int type);
#endif
static void cf_chksum_long_init();
static void dump_opened_files()
{
for (int i = 0; i < MAX_CFILE_BLOCKS; i++) {
auto cb = &Cfile_block_list[i];
if (cb->type != CFILE_BLOCK_UNUSED) {
mprintf((" %s:%d\n", cb->source_file, cb->line_num));
}
}
}
void cfile_close()
{
mprintf(("Still opened files:\n"));
dump_opened_files();
cf_free_secondary_filelist();
cfile_inited = 0;
}
#ifdef SCP_UNIX
#define MIN_NUM_PATH_COMPONENTS 2 /* Directory + file */
#else
#define MIN_NUM_PATH_COMPONENTS 3 /* Drive + directory + file */
#endif
/**
* @brief Determine if the given path is in the root directory
*
* @param exe_path Path to executable
*
* @return true if root directory, false if not
*/
static bool cfile_in_root_dir(const char *exe_path)
{
int new_token;
int token_count = 0;
const char *p = exe_path;
Assert(exe_path != NULL);
do {
new_token = 0;
while (*p == DIR_SEPARATOR_CHAR) {
p++;
}
while ((*p != '\0') && (*p != DIR_SEPARATOR_CHAR)) {
new_token = 1;
p++;
}
token_count += new_token;
} while (*p != '\0');
return (token_count < MIN_NUM_PATH_COMPONENTS);
}
/**
* @brief Initialize the cfile system. Called once at application start.
*
* @param exe_dir Path to a file (not a directory)
* @param cdrom_dir Path to a CD drive mount point (may be NULL)
*
* @return 0 On success
* @return 1 On error
*/
int cfile_init(const char *exe_dir, const char *cdrom_dir)
{
// initialize encryption
encrypt_init();
if (cfile_inited) {
return 0;
}
char buf[CFILE_ROOT_DIRECTORY_LEN];
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
buf[CFILE_ROOT_DIRECTORY_LEN - 1] = '\0';
// are we in a root directory?
if(cfile_in_root_dir(buf)){
os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, "FreeSpace2/Fred2 cannot be run from a drive root directory!");
return 1;
}
// This needs to be set here because cf_build_secondary_filelist assumes it to be true
cfile_inited = 1;
/*
* Determine the executable's directory. Note that DIR_SEPARATOR_CHAR
* is guaranteed to be found in the string else cfile_in_root_dir()
* would have failed.
*/
char *p;
p = strrchr(buf, DIR_SEPARATOR_CHAR);
*p = '\0';
cfile_chdir(buf);
// set root directory
strcpy_s(Cfile_root_dir, buf);
strcpy_s(Cfile_user_dir, os_get_config_path().c_str());
// Initialize the block list with proper data
Cfile_block_list.fill({});
// 32 bit CRC table init
cf_chksum_long_init();
Cfile_cdrom_dir = cdrom_dir;
cf_build_secondary_filelist(Cfile_cdrom_dir);
return 0;
}
#ifdef _WIN32
// Changes to a drive if valid.. 1=A, 2=B, etc
// If flag, then changes to it.
// Returns 0 if not-valid, 1 if valid.
int cfile_chdrive( int DriveNum, int flag )
{
int Valid = 0;
int n, org;
org = -1;
if (!flag)
org = _getdrive();
_chdrive( DriveNum );
n = _getdrive();
if (n == DriveNum )
Valid = 1;
if ( (!flag) && (n != org) )
_chdrive( org );
return Valid;
}
#endif // _WIN32
/**
* @brief Common code for changing directory
*
* @param new_dir Directory to which to change
* @param cur_dir Current directory (only used on Windows)
*
* @retval 0 Success
* @retval 1 Failed to change to new directory's drive (Windows only)
* @retval 2 Failed to change to new directory
*/
static int _cfile_chdir(const char *new_dir, const char *cur_dir __UNUSED)
{
int status;
const char *path = NULL;
const char no_dir[] = "\\.";
#ifdef _WIN32
const char *colon = strchr(new_dir, ':');
if (colon) {
if (!cfile_chdrive(SCP_tolower(*(colon - 1)) - 'a' + 1, 1))
return 1;
path = colon + 1;
} else
#endif /* _WIN32 */
{
path = new_dir;
}
if (*path == '\0') {
path = no_dir;
}
/* This chdir might get a critical error! */
status = _chdir(path);
if (status != 0) {
#ifdef _WIN32
cfile_chdrive(SCP_tolower(cur_dir[0]) - 'a' + 1, 1);
#endif /* _WIN32 */
return 2;
}
return 0;
}
/**
* @brief Push current directory onto a 'stack' and change to a new directory
*
* The current directory is pushed onto a 'stack' so that it can be easily
* restored at a later time. The new directory is derived from @a type.
*
* @param type path type (CF_TYPE_xxx)
*
* @retval -1 'Stack' is full
* @retval 0 Success
* @retval 1 Failed to change to new directory's drive (Windows only)
* @retval 2 Failed to change to new directory
*/
int cfile_push_chdir(int type)
{
SCP_string dir;
char OriginalDirectory[CFILE_ROOT_DIRECTORY_LEN];
_getcwd(OriginalDirectory, CFILE_ROOT_DIRECTORY_LEN - 1);
Assert(Cfile_stack_pos < CFILE_STACK_MAX);
if (Cfile_stack_pos >= CFILE_STACK_MAX) {
return -1;
}
strcpy_s(Cfile_stack[Cfile_stack_pos++], OriginalDirectory);
cf_create_default_path_string(dir, type, NULL);
return _cfile_chdir(dir.c_str(), OriginalDirectory);
}
/**
* @brief Change to the specified directory
*
* @param dir Directory
*
* @retval 0 Success
* @retval 1 Failed to change to new directory's drive (Windows only)
* @retval 2 Failed to change to new directory
*/
int cfile_chdir(const char *dir)
{
char OriginalDirectory[CFILE_ROOT_DIRECTORY_LEN];
_getcwd(OriginalDirectory, CFILE_ROOT_DIRECTORY_LEN - 1);
return _cfile_chdir(dir, OriginalDirectory);
}
int cfile_pop_dir()
{
Assert(Cfile_stack_pos);
if ( !Cfile_stack_pos )
return -1;
Cfile_stack_pos--;
return cfile_chdir(Cfile_stack[Cfile_stack_pos]);
}
// flush (delete all files in) the passed directory (by type), return the # of files deleted
// NOTE : WILL NOT DELETE READ-ONLY FILES
int cfile_flush_dir(int dir_type)
{
int del_count;
Assert( CF_TYPE_SPECIFIED(dir_type) );
// attempt to change the directory to the passed type
if(cfile_push_chdir(dir_type)){
return 0;
}
// proceed to delete the files
del_count = 0;
#if defined _WIN32
intptr_t find_handle;
_finddata_t find;
find_handle = _findfirst( "*", &find );
if (find_handle != -1) {
do {
if (!(find.attrib & _A_SUBDIR) && !(find.attrib & _A_RDONLY)) {
// delete the file
cf_delete(find.name,dir_type);
// increment the deleted count
del_count++;
}
} while (!_findnext(find_handle, &find));
_findclose( find_handle );
}
#elif defined SCP_UNIX
glob_t globinfo;
memset(&globinfo, 0, sizeof(globinfo));
int status = glob("*", 0, NULL, &globinfo);
if (status == 0) {
for (unsigned int i = 0; i < globinfo.gl_pathc; i++) {
// Determine if this is a regular file
struct stat statbuf;
stat(globinfo.gl_pathv[i], &statbuf);
if (S_ISREG(statbuf.st_mode)) {
// delete the file
cf_delete(globinfo.gl_pathv[i], dir_type);
// increment the deleted count
del_count++;
}
}
globfree(&globinfo);
}
#endif
// pop the directory back
cfile_pop_dir();
// return the # of files deleted
return del_count;
}
// add the given extention to a filename (or filepath) if it doesn't already have this
// extension.
// filename = name of filename or filepath to process
// ext = extension to add. Must start with the period
// Returns: new filename or filepath with extension.
char *cf_add_ext(const char *filename, const char *ext)
{
static char path[MAX_PATH_LEN];
size_t flen = strlen(filename);
size_t elen = strlen(ext);
Assert(flen < MAX_PATH_LEN);
strcpy_s(path, filename);
if ((flen < 4) || stricmp(path + flen - elen, ext) != 0) {
Assert(flen + elen < MAX_PATH_LEN);
strcat_s(path, ext);
}
return path;
}
/**
* @brief Delete the specified file
*
* @param filename Name of file to delete
* @param path_type Path type (CF_TYPE_xxx)
* @param location_flags Where to search for the location of the file to delete
*
* @return 0 on failure, 1 on success
*/
int cf_delete(const char *filename, int path_type, uint32_t location_flags)
{
SCP_string longname;
Assert(CF_TYPE_SPECIFIED(path_type));
cf_create_default_path_string(longname, path_type, filename, location_flags);
return (_unlink(longname.c_str()) != -1);
}
// Same as _access function to read a file's access bits
int cf_access(const char *filename, int dir_type, int mode)
{
SCP_string longname;
Assert( CF_TYPE_SPECIFIED(dir_type) );
cf_create_default_path_string(longname, dir_type, filename);
return access(longname.c_str(), mode);
}
// Returns 1 if the file exists, 0 if not.
// Checks only the file system.
// cf_find_file_location checks the filesystem before VPs
// If offset is 0, it was found in the filesystem, so offset is boolean false
// If offset equates to boolean true, it was found in a VP and the logic will negate the function return
int cf_exists(const char *filename, int dir_type)
{
if ( (filename == NULL) || !strlen(filename) )
return 0;
auto find_res = cf_find_file_location(filename, dir_type);
return find_res.found && !find_res.offset;
}
// Goober5000
// Returns !0 if the file exists, 0 if not.
// Checks both the file system and the VPs.
int cf_exists_full(const char *filename, int dir_type)
{
if ( (filename == NULL) || !strlen(filename) )
return 0;
return cf_find_file_location(filename, dir_type).found;
}
// same as the above, but with extension check
int cf_exists_full_ext(const char *filename, int dir_type, const int num_ext, const char **ext_list)
{
if ( (filename == NULL) || !strlen(filename) )
return 0;
if ( (num_ext <= 0) || (ext_list == NULL) )
return 0;
return cf_find_file_location_ext(filename, num_ext, ext_list, dir_type).found;
}
#ifdef _WIN32
void cf_attrib(const char *filename, int set, int clear, int dir_type)
{
SCP_string longname;
Assert( CF_TYPE_SPECIFIED(dir_type) );
cf_create_default_path_string(longname, dir_type, filename);
FILE *fp = fopen(longname.c_str(), "rb");
if (fp) {
fclose(fp);
DWORD z = GetFileAttributes(longname.c_str());
SetFileAttributes(longname.c_str(), z | (set & ~clear));
}
}
#endif
int cf_rename(const char *old_name, const char *name, int dir_type)
{
Assert( CF_TYPE_SPECIFIED(dir_type) );
int ret_code;
SCP_string old_longname;
SCP_string new_longname;
cf_create_default_path_string(old_longname, dir_type, old_name);
cf_create_default_path_string(new_longname, dir_type, name);
ret_code = rename(old_longname.c_str(), new_longname.c_str());
if(ret_code != 0){
switch(errno){
case EACCES :
return CF_RENAME_FAIL_ACCESS;
case ENOENT :
default:
return CF_RENAME_FAIL_EXIST;
}
}
return CF_RENAME_SUCCESS;
}
// This takes a path (e.g. "C:\Games\FreeSpace2\Lots\More\Directories") and creates it in its entirety.
// Do note that this requires the path to have normalized directory separators as defined by DIR_SEPARATOR_CHAR
static void mkdir_recursive(const char *path) {
size_t pre = 0, pos;
SCP_string tmp(path);
SCP_string dir;
if (tmp[tmp.size() - 1] != DIR_SEPARATOR_CHAR) {
// force trailing / so we can handle everything in loop
tmp += DIR_SEPARATOR_CHAR;
}
while ((pos = tmp.find_first_of(DIR_SEPARATOR_CHAR, pre)) != std::string::npos) {
dir = tmp.substr(0, pos++);
pre = pos;
if (dir.empty()) continue; // if leading / first time is 0 length
_mkdir(dir.c_str());
}
}
// Creates the directory path if it doesn't exist. Even creates all its
// parent paths.
void cf_create_directory(int dir_type, uint32_t location_flags)
{
int num_dirs = 0;
int dir_tree[CF_MAX_PATH_TYPES];
SCP_string longname;
struct stat statbuf;
Assertion( CF_TYPE_SPECIFIED(dir_type), "Invalid dir_type passed to cf_create_directory." );
int current_dir = dir_type;
do {
Assert( num_dirs < CF_MAX_PATH_TYPES ); // Invalid Pathtypes data?
dir_tree[num_dirs++] = current_dir;
current_dir = Pathtypes[current_dir].parent_index;
} while( current_dir != CF_TYPE_ROOT );
int i;
for (i=num_dirs-1; i>=0; i-- ) {
cf_create_default_path_string(longname, dir_tree[i], nullptr, location_flags);
if (stat(longname.c_str(), &statbuf) != 0) {
mprintf(( "CFILE: Creating new directory '%s'\n", longname.c_str() ));
mkdir_recursive(longname.c_str());
}
}
}
// cfopen()
//
// parameters: *filepath ==> name of file to open (may be path+name)
// *mode ==> specifies how file should be opened (eg "rb" for read binary)
// passing NULL to mode triggers an assert and returns NULL
// type ==> one of: CFILE_NORMAL
// CFILE_MEMORY_MAPPED
// dir_type => override extension check, value is one of CF_TYPE* #defines
//
// NOTE: type parameter is an optional parameter. The default value is CFILE_NORMAL
//
//
// returns: success ==> address of CFILE structure
// error ==> NULL
//
CFILE* _cfopen(const char* source, int line, const char* file_path, const char* mode, int type, int dir_type,
bool /* localize */, uint32_t location_flags)
{
/* Bobboau, what is this doing here? 31 is way too short... - Goober5000
if( strlen(file_path) > 31 )
Error(LOCATION, "file name %s too long, \nmust be less than 31 charicters", file_path);*/
if ( !cfile_inited ) {
Int3();
return NULL;
}
//================================================
// Check that all the parameters make sense
Assert(file_path && strlen(file_path));
Assert( mode != NULL );
// Can only open read-only binary files in memory mapped mode.
if ( (type & CFILE_MEMORY_MAPPED) && strcmp(mode,"rb") != 0 ) {
Int3();
return NULL;
}
//===========================================================
// If in write mode, just try to open the file straight off
// the harddisk. No fancy packfile stuff here!
if ( strchr(mode,'w') || strchr(mode,'+') || strchr(mode,'a') ) {
SCP_string longname;
auto last_separator = strrchr(file_path, DIR_SEPARATOR_CHAR);
// For write-only files, require a full path or a path type
if ( last_separator ) {
// Full path given?
longname = file_path;
} else {
// Path type given?
Assert( dir_type != CF_TYPE_ANY );
// Create the directory if necessary
cf_create_directory(dir_type, location_flags);
cf_create_default_path_string(longname, dir_type, file_path, location_flags);
}
Assert( !(type & CFILE_MEMORY_MAPPED) );
// JOHN: TODO, you should create the path if it doesn't exist.
//WMC - For some reason, fread does not return the correct number of bytes read
//in text mode, which messes up FS2_Open's raw_position indicator in fgets. As a consequence, you
//_must_ open files that are gonna be read in binary mode.
char happy_mode[8];
if(strcspn(mode, "ra+") != strlen(mode) && (strchr(mode, 't') || !strchr(mode, 'b')))
{
//*****BEGIN PROCESSING OF MODE*****
//Copies all 'mode' characters over, except for t, and adds b if needed.
unsigned int max = sizeof(happy_mode) - 2; //space for null and 'b'
bool need_b = true;
unsigned int i;
for( i = 0; i < strlen(mode); i++)
{
if(i > max)
break;
if(mode[i] != 't')
happy_mode[i] = mode[i];
if(mode[i] == 'b')
need_b = false;
}
happy_mode[i] = '\0';
if(need_b)
strcat_s(happy_mode, "b");
//*****END PROCESSING OF MODE*****
}
else
{
strcpy_s(happy_mode, mode);
}
FILE *fp = fopen(longname.c_str(), happy_mode);
if (fp) {
return cf_open_fill_cfblock(source, line, last_separator ? (last_separator + 1) : file_path, fp, dir_type);
}
return NULL;
}
//================================================
// Search for file on disk, on cdrom, or in a packfile
auto find_res = cf_find_file_location(file_path, dir_type, location_flags);
if ( find_res.found ) {
// Fount it, now create a cfile out of it
nprintf(("CFileDebug", "Requested file %s found at: %s\n", file_path, find_res.full_name.c_str()));
if ( type & CFILE_MEMORY_MAPPED ) {
// Can't open memory mapped files out of pack or memory files
if ( find_res.offset == 0 && find_res.data_ptr != nullptr ) {
#if defined _WIN32
HANDLE hFile;
hFile = CreateFile(find_res.full_name.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
return cf_open_mapped_fill_cfblock(source, line, file_path, hFile, dir_type);
}
#elif defined SCP_UNIX
FILE* fp = fopen(find_res.full_name.c_str(), "rb");
if (fp) {
return cf_open_mapped_fill_cfblock(source, line, file_path, fp, dir_type);
}
#endif
}
} else {
// since cfopen_special already has all the code to handle the opening we can just use that here
return _cfopen_special(source, line, find_res, mode, dir_type);
}
}
return NULL;
}
// cfopen_ext()
//
// parameters: *filepath ==> name of file to open (may be path+name)
// *mode ==> specifies how file should be opened (eg "rb" for read binary)
// passing NULL to mode triggers an assert and returns NULL
// dir_type => override extension check, value is one of CF_TYPE* #defines
//
// returns: success ==> address of CFILE structure
// error ==> NULL
//
CFILE *_cfopen_special(const char* source, int line, const CFileLocation &res, const char* mode, int dir_type)
{
if ( !cfile_inited) {
Int3();
return NULL;
}
Assert( !res.full_name.empty() );
Assert( mode != NULL );
// cfopen_special() only supports reading files, not creating them
if ( strchr(mode, 'w') ) {
Int3();
return NULL;
}
// In-Memory files are a bit different from normal files so we need to handle them separately
if (res.data_ptr != nullptr) {
return cf_open_memory_fill_cfblock(source, line, res.name_ext.c_str(), res.data_ptr, res.size, dir_type);
}
else {
// "file_path" should already be a fully qualified path, so just try to open it
FILE *fp = fopen(res.full_name.c_str(), "rb");
if (fp) {
if (res.offset) {
// Found it in a pack file
return cf_open_packed_cfblock(source, line, res.name_ext.c_str(), fp, dir_type, res.offset, res.size);
}
else {
// Found it in a normal file
return cf_open_fill_cfblock(source, line, res.name_ext.c_str(), fp, dir_type);
}
}
}
return NULL;
}
// ------------------------------------------------------------------------
// ctmpfile()
//
// Open up a temporary file. A unique name is automatically generated. The
// file will be automatically deleted when file is closed.
//
// return: NULL => tmp file could not be opened
// pointer to CFILE => tmp file successfully opened
//
CFILE *ctmpfile()
{
FILE *fp;
fp = tmpfile();
if ( fp )
return cf_open_fill_cfblock(LOCATION, "<temporary file>", fp, 0);
else
return NULL;
}
// cfget_cfile_block() will try to find an empty Cfile_block structure in the
// Cfile_block_list[] array and return the index.
//
// returns: success ==> index in Cfile_block_list[] array
// failure ==> -1
//
static int cfget_cfile_block()
{
int i;
CFILE* cfile;
for ( i = 0; i < MAX_CFILE_BLOCKS; i++ ) {
cfile = &Cfile_block_list[i];
if (cfile->type == CFILE_BLOCK_UNUSED) {
cfile->data = nullptr;
cfile->fp = nullptr;
cfile->type = CFILE_BLOCK_USED;
cf_clear_compression_info(cfile);
return i;
}
}
// If we've reached this point, a free Cfile_block could not be found
nprintf(("Warning","A free Cfile_block could not be found.\n"));
// Dump a list of all opened files
mprintf(("Out of cfile blocks! Currently opened files:\n"));
dump_opened_files();
UNREACHABLE("There are no more free cfile blocks. This means that there are too many files opened by FSO.\n"
"This is probably caused by a programming or scripting error where a file does not get closed."); // out of free cfile blocks
return -1;
}
// cfclose() closes the file
//
// returns: success ==> 0
// failure ==> EOF
//
int cfclose( CFILE * cfile )
{
int result;
Assert(cfile != NULL);
result = 0;
if ( cfile->data && cfile->mem_mapped ) {
// close memory mapped file
#if defined _WIN32
result = UnmapViewOfFile((void*)cfile->data);
Assert(result);
result = CloseHandle(cfile->hInFile);
Assert(result); // Ensure file handle is closed properly
result = CloseHandle(cfile->hMapFile);
Assert(result); // Ensure file handle is closed properly
result = 0;
#elif defined SCP_UNIX
// FIXME: result is wrong after munmap() but it is successful
//result = munmap(cfile->data, cfile->data_length);
//Assert(result);
// This const_cast is safe since the pointer returned by mmap was also non-const
munmap(const_cast<void*>(cfile->data), cfile->data_length);
if ( cfile->fp != nullptr)
result = fclose(cfile->fp);
#endif
} else if ( cfile->fp != nullptr ) {
Assert(cfile->fp != nullptr);
result = fclose(cfile->fp);
} else {
// VP do nothing
}
cf_clear_compression_info(cfile);
cfile->type = CFILE_BLOCK_UNUSED;
return result;
}
int cf_is_valid(CFILE *cfile)
{
//Was a valid pointer passed?
if(cfile == NULL)
return 0;
//Is it used?
if (cfile->type != CFILE_BLOCK_USED && (cfile->fp != nullptr || cfile->data != nullptr))
return 0;
//It's good, as near as we can tell.
return 1;
}
// cf_open_fill_cfblock() will fill up a Cfile_block element in the Cfile_block_list[] array
// for the case of a file being opened by cf_open();
//
// returns: success ==> ptr to CFILE structure.
// error ==> NULL
//
static CFILE *cf_open_fill_cfblock(const char* source, int line, const char* original_filename, FILE *fp, int type)
{
int cfile_block_index;
cfile_block_index = cfget_cfile_block();
if ( cfile_block_index == -1 ) {
fclose(fp);
return NULL;
} else {
CFILE *cfp = &Cfile_block_list[cfile_block_index];
cfp->data = nullptr;
cfp->mem_mapped = false;
cfp->fp = fp;
cfp->dir_type = type;
cfp->max_read_len = 0;
cfp->original_filename = original_filename;
cfp->source_file = source;
cfp->line_num = line;
int pos = ftell(fp);
if(pos == -1L)
pos = 0;
cf_init_lowlevel_read_code(cfp,0,filelength(fileno(fp)), 0 );
cf_check_compression(cfp);
return cfp;
}
}
// cf_open_packed_cfblock() will fill up a Cfile_block element in the Cfile_block_list[] array
// for the case of a file being opened by cf_open();
//
// returns: success ==> ptr to CFILE structure.
// error ==> NULL
//
static CFILE *cf_open_packed_cfblock(const char* source, int line, const char* original_filename, FILE *fp, int type, size_t offset, size_t size)
{
// Found it in a pack file
int cfile_block_index;
cfile_block_index = cfget_cfile_block();
if ( cfile_block_index == -1 ) {
fclose(fp);
return NULL;
} else {
CFILE *cfp = &Cfile_block_list[cfile_block_index];
cfp->data = nullptr;
cfp->fp = fp;
cfp->mem_mapped = false;
cfp->dir_type = type;
cfp->max_read_len = 0;
cfp->original_filename = original_filename;
cfp->source_file = source;
cfp->line_num = line;
cf_init_lowlevel_read_code(cfp,offset, size, 0 );
cf_check_compression(cfp);
return cfp;
}
}
// cf_open_mapped_fill_cfblock() will fill up a Cfile_block element in the Cfile_block_list[] array
// for the case of a file being opened by cf_open_mapped();
//
// returns: ptr CFILE structure.
//
#if defined _WIN32
static CFILE *cf_open_mapped_fill_cfblock(const char* source, int line, const char* original_filename, HANDLE hFile, int type)
#elif defined SCP_UNIX
static CFILE *cf_open_mapped_fill_cfblock(const char* source, int line, const char* original_filename, FILE *fp, int type)
#endif
{
int cfile_block_index;
cfile_block_index = cfget_cfile_block();
if ( cfile_block_index == -1 ) {
#ifdef SCP_UNIX
fclose(fp);
#endif
return NULL;
}
else {
CFILE *cfp = &Cfile_block_list[cfile_block_index];
cfp->max_read_len = 0;
cfp->fp = nullptr;
cfp->mem_mapped = true;
#if defined _WIN32
cfp->hInFile = hFile;
#endif
cfp->dir_type = type;
cfp->original_filename = original_filename;
cfp->source_file = source;
cfp->line_num = line;
cf_init_lowlevel_read_code(cfp, 0, 0, 0 );
#if defined _WIN32
cfp->hMapFile = CreateFileMapping(cfp->hInFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (cfp->hMapFile == NULL) {
nprintf(("Error", "Could not create file-mapping object.\n"));
return NULL;
}
cfp->data = (ubyte*)MapViewOfFile(cfp->hMapFile, FILE_MAP_READ, 0, 0, 0);
Assert( cfp->data != NULL );
#elif defined SCP_UNIX
cfp->fp = fp;
cfp->data_length = filelength(fileno(fp));
cfp->data = mmap(nullptr, // start
cfp->data_length, // length
PROT_READ, // prot
MAP_SHARED, // flags
fileno(fp), // fd
0); // offset
Assert(cfp->data != nullptr);
#endif
return cfp;
}
}
static CFILE *cf_open_memory_fill_cfblock(const char* source, int line, const char* original_filename, const void* data, size_t size, int dir_type)
{
int cfile_block_index;
cfile_block_index = cfget_cfile_block();
if ( cfile_block_index == -1 ) {
return NULL;
}
else {
CFILE *cfp = &Cfile_block_list[cfile_block_index];
cfp->max_read_len = 0;
cfp->fp = nullptr;
cfp->mem_mapped = false;
cfp->dir_type = dir_type;
cfp->original_filename = original_filename;
cfp->source_file = source;
cfp->line_num = line;
cf_init_lowlevel_read_code(cfp, 0, size, 0 );
cfp->data = data;
return cfp;
}
}
const char *cf_get_filename(const CFILE *cfile)
{
return cfile->original_filename.c_str();
}
int cf_get_dir_type(const CFILE *cfile)
{
return cfile->dir_type;
}
// cf_returndata() returns the data pointer for a memory-mapped file that is associated
// with the CFILE structure passed as a parameter
//
//
const void *cf_returndata(CFILE *cfile)
{
Assert(cfile != NULL);
Assert(cfile->data != nullptr);
return cfile->data;
}
// cutoff point where cfread() will throw an error when it hits this limit
// if 'len' is 0 then this check will be disabled
void cf_set_max_read_len( CFILE * cfile, size_t len )
{
Assert( cfile != NULL );
if (len) {
cfile->max_read_len = cfile->raw_position + len;
} else {
cfile->max_read_len = 0;
}
}
// routines to read basic data types from CFILE's. Put here to
// simplify mac/pc reading from cfiles.
float cfread_float(CFILE* file, float deflt)
{
float f;
if (cfread( &f, sizeof(f), 1, file) != 1)
return deflt;
f = INTEL_FLOAT(&f);
return f;
}
int cfread_int(CFILE* file, int deflt)
{
int i;
if (cfread( &i, sizeof(i), 1, file) != 1)
return deflt;
i = INTEL_INT(i);
return i;
}
uint cfread_uint(CFILE* file, uint deflt)
{
uint i;
if (cfread( &i, sizeof(i), 1, file) != 1)
return deflt;
i = INTEL_INT(i);
return i;
}
short cfread_short(CFILE* file, short deflt)
{
short s;
if (cfread( &s, sizeof(s), 1, file) != 1)
return deflt;
s = INTEL_SHORT(s);
return s;
}
ushort cfread_ushort(CFILE* file, ushort deflt)
{
ushort s;
if (cfread( &s, sizeof(s), 1, file) != 1)
return deflt;
s = INTEL_SHORT(s);
return s;
}
ubyte cfread_ubyte(CFILE* file, ubyte deflt)
{
ubyte b;
if (cfread( &b, sizeof(b), 1, file) != 1)
return deflt;
return b;
}
void cfread_vector(vec3d* vec, CFILE* file, vec3d* deflt) {
vec->xyz.x = cfread_float(file, deflt ? deflt->xyz.x : 0.0f);
vec->xyz.y = cfread_float(file, deflt ? deflt->xyz.y : 0.0f);
vec->xyz.z = cfread_float(file, deflt ? deflt->xyz.z : 0.0f);
}
char cfread_char(CFILE* file, char deflt)
{
char b;
if (cfread( &b, sizeof(b), 1, file) != 1)
return deflt;
return b;
}
void cfread_string(char *buf, int n, CFILE *file)
{
char c;
do {
c = cfread_char(file);
if ( n > 0 ) {
*buf++ = c;
n--;
}
} while (c != 0 );
}
void cfread_string_len(char *buf, int buffer_size, CFILE *file)
{
int len;
len = cfread_int(file);
Assertion( (len <= buffer_size), "String cannot fit in buffer, len: %i, buffer: %i", len, buffer_size);
if (len) {
cfread(buf, len, 1, file);
Assertion(len != buffer_size || buf[len - 1] == 0, "Unterminated string does not fit in buffer");
}
buf[MIN(len, buffer_size - 1)] = 0;
}
SCP_string cfread_string_len(CFILE *file)
{
int len = cfread_int(file);
SCP_string str;
str.resize(len);
if (len)
cfread(&str[0], len, 1, file);
return str;
}
// equivalent write functions of above read functions follow
int cfwrite_float(float f, CFILE *file)
{
f = INTEL_FLOAT(&f);
return cfwrite(&f, sizeof(f), 1, file);
}
int cfwrite_int(int i, CFILE *file)
{
i = INTEL_INT(i);
return cfwrite(&i, sizeof(i), 1, file);
}
int cfwrite_uint(uint i, CFILE *file)
{
i = INTEL_INT(i);
return cfwrite(&i, sizeof(i), 1, file);
}
int cfwrite_short(short s, CFILE *file)
{
s = INTEL_SHORT(s);
return cfwrite(&s, sizeof(s), 1, file);
}
int cfwrite_ushort(ushort s, CFILE *file)
{
s = INTEL_SHORT(s);
return cfwrite(&s, sizeof(s), 1, file);
}
int cfwrite_ubyte(ubyte b, CFILE *file)
{
return cfwrite(&b, sizeof(b), 1, file);
}
int cfwrite_char(char b, CFILE *file)
{
return cfwrite( &b, sizeof(b), 1, file);
}
int cfwrite_string(const char *buf, CFILE *file)
{
if ( (!buf) || (buf && !buf[0]) ) {
return cfwrite_char(0, file);
}
int len = (int)strlen(buf);
if(!cfwrite(buf, len, 1, file)){
return 0;
}
return cfwrite_char(0, file); // write out NULL termination
}
int cfwrite_string_len(const char *buf, CFILE *file)
{
int len = (int)strlen(buf);
if(!cfwrite_int(len, file)){
return 0;
}
if (len){
return cfwrite(buf,len,1,file);
}
return 1;
}
// Get the filelength
int cfilelength(CFILE* cfile) {
Assert(cfile != NULL);
// TODO: return length of memory mapped file
Assert(!cfile->mem_mapped);
// cfile->size gets set at cfopen
// The rest of the code still uses ints, do an overflow check to detect cases where this fails
Assertion(cfile->size <= static_cast<size_t>(std::numeric_limits<int>::max()),
"Integer overflow in cfilelength! A file is too large (but I don't know which...).");
return (int) cfile->size;
}
// cfwrite() writes to the file
//
// returns: number of full elements actually written
//
//
int cfwrite(const void *buf, int elsize, int nelem, CFILE *cfile)
{
if(!cf_is_valid(cfile))
return 0;
if(buf == NULL || elsize == 0 || nelem == 0)
return 0;
if(cfile->lib_offset != 0)
{
Error(LOCATION, "Attempt to write to a VP file (unsupported)");
return 0;
}
if(cfile->data != nullptr)
{
Warning(LOCATION, "Writing is not supported for mem-mapped files");
return EOF;
}
size_t bytes_written = 0;
size_t size = elsize * nelem;
bytes_written = fwrite(buf, 1, size, cfile->fp);
//WMC - update filesize and position
if (bytes_written > 0) {
cfile->size += bytes_written;
cfile->raw_position += bytes_written;
}
#if defined(CHECK_SIZE) && !defined(NDEBUG)
Assert( cfile->size == filelength(fileno(cfile->fp)) );
#endif
return (int)(bytes_written / elsize);
}
// cfputc() writes a character to a file
//
// returns: success ==> returns character written
// error ==> EOF
//
int cfputc(int c, CFILE *cfile)
{
if(!cf_is_valid(cfile))
return EOF;
if(cfile->lib_offset != 0)
{
Error(LOCATION, "Attempt to write character to a VP file (unsupported)");
return EOF;
}
if(cfile->data != nullptr)
{
Warning(LOCATION, "Writing is not supported for mem-mapped files");
return EOF;
}
// writing not supported for memory-mapped files
Assert( !cfile->data );
int result = fputc(c, cfile->fp);
//WMC - update filesize and position
if(result != EOF)
{
cfile->size += 1;
cfile->raw_position += 1;
}
#if defined(CHECK_SIZE) && !defined(NDEBUG)
Assert( cfile->size == filelength(fileno(cfile->fp)) );
#endif
return result;
}
// cfputs() writes a string to a file
//
// returns: success ==> non-negative value
// error ==> EOF
//
int cfputs(const char *str, CFILE *cfile)
{
if(!cf_is_valid(cfile))
return EOF;
if(str == NULL)
return EOF;
if(cfile->lib_offset != 0)
{
Error(LOCATION, "Attempt to write character to a VP file (unsupported)");
return EOF;
}
if(cfile->data != nullptr)
{
Warning(LOCATION, "Writing is not supported for mem-mapped files");
return EOF;
}
int result = fputs(str, cfile->fp);
//WMC - update filesize and position
if(result != EOF)
{
cfile->size += strlen(str);
cfile->raw_position += strlen(str);
}
#if defined(CHECK_SIZE) && !defined(NDEBUG)
Assert( cfile->size == filelength(fileno(cfile->fp)) );
#endif
return result;
}
// cfgetc() reads a character from a file
//
// returns: success ==> returns character read
// error ==> EOF
//
int cfgetc(CFILE *cfile)
{
char tmp;
int result = cfread(&tmp, 1, 1, cfile );
if ( result == 1 ) {
result = char(tmp);
} else {
result = CF_EOF;
}
return result;
}
// cfgets() reads a string from a file
//
// returns: success ==> returns pointer to string
// error ==> NULL
//
char *cfgets(char *buf, int n, CFILE *cfile)
{
Assert(cfile != NULL);
Assert(buf != NULL);
Assert(n > 0 );
char * t = buf;
int i, c;
for (i=0; i<n-1; i++ ) {
do {
char tmp_c;
int ret = cfread( &tmp_c, 1, 1, cfile );
if ( ret != 1 ) {
*buf = 0;
if ( buf > t ) {
return t;
} else {
return NULL;
}
}
c = int(tmp_c);
} while ( c == 13 );
*buf++ = char(c);
if ( c=='\n' ) break;
}
*buf++ = 0;
return t;
}
// 16 and 32 bit checksum stuff ----------------------------------------------------------
// CRC code for mission validation. given to us by Kevin Bentley on 7/20/98. Some sort of
// checksumming code that he wrote a while ago.
#define CRC32_POLYNOMIAL 0xEDB88320
static uint CRCTable[256];
#define CF_CHKSUM_SAMPLE_SIZE 512
// update cur_chksum with the chksum of the new_data of size new_data_size
ushort cf_add_chksum_short(ushort seed, ubyte *buffer, int size)
{
ubyte *ptr = buffer;
uint sum1, sum2;
sum1 = sum2 = (int)(seed);
while(size--) {
sum1 += *ptr++;
if (sum1 >= 255 ) sum1 -= 255;
sum2 += sum1;
}
sum2 %= 255;
return (ushort)((sum1 << 8) + sum2);
}
// update cur_chksum with the chksum of the new_data of size new_data_size
uint cf_add_chksum_long(uint seed, ubyte *buffer, size_t size)
{
uint crc;
ubyte *p;
p = buffer;
crc = seed;
while (size--)
crc = (crc >> 8) ^ CRCTable[(crc ^ *p++) & 0xff];
return crc;
}
static void cf_chksum_long_init()
{
int i, j;
uint crc;
for (i = 0; i < 256; i++) {
crc = i;
for (j = 8; j > 0; j--) {
if (crc & 1)
crc = (crc >> 1) ^ CRC32_POLYNOMIAL;
else
crc >>= 1;
}
CRCTable[i] = crc;
}
}
// single function convenient to use for both short and long checksums
// NOTE : only one of chk_short or chk_long must be non-NULL (indicating which checksum to perform)
static int cf_chksum_do(CFILE *cfile, ushort *chk_short, uint *chk_long, int max_size)
{
ubyte cf_buffer[CF_CHKSUM_SAMPLE_SIZE];
int is_long;
int cf_len = 0;
int cf_total;
int read_size;
// determine whether we're doing a short or long checksum
is_long = 0;
if(chk_short){
Assert(!chk_long);
*chk_short = 0;
} else {
Assert(chk_long);
is_long = 1;
*chk_long = 0;
}
// if max_size is -1, set it to be the size of the file
if(max_size < 0){
cfseek(cfile, 0, SEEK_SET);
max_size = cfilelength(cfile);
}
cf_total = 0;
do {
// determine how much we want to read
if((max_size - cf_total) >= CF_CHKSUM_SAMPLE_SIZE){
read_size = CF_CHKSUM_SAMPLE_SIZE;
} else {
read_size = max_size - cf_total;
}
// read in some buffer
cf_len = cfread(cf_buffer, 1, read_size, cfile);
// total we've read so far
cf_total += cf_len;
// add the checksum
if(cf_len > 0){
// do the proper short or long checksum
if(is_long){
*chk_long = cf_add_chksum_long(*chk_long, cf_buffer, cf_len);
} else {
*chk_short = cf_add_chksum_short(*chk_short, cf_buffer, cf_len);
}
}
} while((cf_len > 0) && (cf_total < max_size));
return 1;
}
// get the chksum of a pack file (VP)
int cf_chksum_pack(const char *filename, uint *chk_long, bool full)
{
const size_t safe_size = 2097152; // 2 Meg
const int header_offset = 32; // skip 32bytes for header (header is currently smaller than this though)
ubyte cf_buffer[CF_CHKSUM_SAMPLE_SIZE];
size_t read_size;
size_t max_size;
if (chk_long == NULL) {
Int3();
return 0;
}
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
*chk_long = 0;
return 0;
}
*chk_long = 0;
// get the max size
fseek(fp, 0, SEEK_END);
max_size = (size_t)ftell(fp);
// maybe do a chksum of the entire file
if (full) {
fseek(fp, 0, SEEK_SET);
}
// othewise it's only a partial check
else {
if (max_size > safe_size) {
max_size = safe_size;
}
Assertion(max_size > header_offset,
"max_size (" SIZE_T_ARG ") > header_offset in packfile %s", max_size, filename);
max_size -= header_offset;
fseek(fp, -((long)max_size), SEEK_END);
}
size_t cf_total = 0;
size_t cf_len = 0;
do {
// determine how much we want to read
if ( (max_size - cf_total) >= CF_CHKSUM_SAMPLE_SIZE )
read_size = CF_CHKSUM_SAMPLE_SIZE;
else
read_size = max_size - cf_total;
// read in some buffer
cf_len = fread(cf_buffer, 1, read_size, fp);
// total we've read so far
cf_total += cf_len;
// add the checksum
if (cf_len > 0)
*chk_long = cf_add_chksum_long((*chk_long), cf_buffer, cf_len);
} while ( (cf_len > 0) && (cf_total < max_size) );
fclose(fp);
return 1;
}
// get the 2 byte checksum of the passed filename - return 0 if operation failed, 1 if succeeded
int cf_chksum_short(const char *filename, ushort *chksum, int max_size, int cf_type)
{
int ret_val;
CFILE *cfile = NULL;
// zero the checksum
*chksum = 0;
// attempt to open the file
cfile = cfopen(filename,"rt",CFILE_NORMAL,cf_type);
if(cfile == NULL){
return 0;
}
// call the overloaded cf_chksum function()
ret_val = cf_chksum_do(cfile, chksum, NULL, max_size);
// close the file down
cfclose(cfile);
cfile = NULL;
// return the result
return ret_val;
}
// get the 2 byte checksum of the passed file - return 0 if operation failed, 1 if succeeded
// NOTE : preserves current file position
int cf_chksum_short(CFILE *file, ushort *chksum, int max_size)
{
int ret_code;
int start_pos;
// Returns current position of file.
start_pos = cftell(file);
if(start_pos == -1){
return 0;
}
// move to the beginning of the file
if(cfseek(file, 0, CF_SEEK_SET)){
return 0;
}
ret_code = cf_chksum_do(file, chksum, NULL, max_size);
// move back to the start position
cfseek(file, start_pos, CF_SEEK_SET);
return ret_code;
}
// get the 32 bit CRC checksum of the passed filename - return 0 if operation failed, 1 if succeeded
int cf_chksum_long(const char *filename, uint *chksum, int max_size, int cf_type)
{
int ret_val;
CFILE *cfile = NULL;
// zero the checksum
*chksum = 0;
// attempt to open the file
cfile = cfopen(filename,"rt",CFILE_NORMAL,cf_type);
if(cfile == NULL){
return 0;
}
// call the overloaded cf_chksum function()
ret_val = cf_chksum_do(cfile, NULL, chksum, max_size);
// close the file down
cfclose(cfile);
cfile = NULL;
// return the result
return ret_val;
}
// get the 32 bit CRC checksum of the passed file - return 0 if operation failed, 1 if succeeded
// NOTE : preserves current file position
int cf_chksum_long(CFILE *file, uint *chksum, int max_size)
{
int ret_code;
int start_pos;
// Returns current position of file.
start_pos = cftell(file);
if(start_pos == -1){
return 0;
}
// move to the beginning of the file
if(cfseek(file, 0, CF_SEEK_SET)){
return 0;
}
ret_code = cf_chksum_do(file, NULL, chksum, max_size);
// move back to the start position
cfseek(file, start_pos, CF_SEEK_SET);
return ret_code;
}
// Flush the open file buffer
//
// exit: 0 - success
// 1 - failure
int cflush(CFILE *cfile)
{
Assert(cfile != NULL);
// not supported for memory mapped files
Assert( !cfile->data );
Assert(cfile->fp != nullptr);
int result = fflush(cfile->fp);
//WMC - update filesize
cfile->size = filelength(fileno(cfile->fp));
return result;
}
int cfile_get_path_type(const SCP_string& dir)
{
SCP_string buf = dir;
// remove leading and trailing slashes
if ( !buf.empty() ) {
auto start = buf.find_first_not_of("\\/");
auto end = buf.find_last_not_of("\\/");
if ( (start > 0) || (end < buf.length()-1) ) {
buf = buf.substr(start, end-start+1);
}
}
if (buf.empty()) {
return CF_TYPE_ROOT;
}
// Use official DIR_SEPARATOR_CHAR
extern void normalize_directory_separators(SCP_string &str);
normalize_directory_separators(buf);
// identify path type
auto best_match = CF_TYPE_INVALID;
for (auto& Pathtype : Pathtypes) {
if ( !Pathtype.path ) {
continue;
}
// skip root, it should have been detected before getting here
if (Pathtype.index == CF_TYPE_ROOT) {
continue;
}
// don't allow unknown subdirs directly under data
if (Pathtype.index == CF_TYPE_DATA) {
if (buf == Pathtype.path) {
best_match = CF_TYPE_DATA;
}
continue;
}
// for everything else just find closest match, allowing for unknown subdirectories
if ( !strnicmp(buf.c_str(), Pathtype.path, strlen(Pathtype.path)) ) {
best_match = Pathtype.index;
}
}
return best_match;
}