fallout2-ce/src/xfile.cc

828 lines
21 KiB
C++
Raw Normal View History

2022-05-19 01:51:26 -07:00
#include "xfile.h"
#include "file_find.h"
#include <assert.h>
#ifdef _WIN32
2022-05-19 01:51:26 -07:00
#include <direct.h>
#else
#include <unistd.h>
#endif
2022-05-19 01:51:26 -07:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2022-06-19 09:02:01 -07:00
typedef enum XFileEnumerationEntryType {
XFILE_ENUMERATION_ENTRY_TYPE_FILE,
XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY,
XFILE_ENUMERATION_ENTRY_TYPE_DFILE,
} XFileEnumerationEntryType;
typedef struct XListEnumerationContext {
char name[COMPAT_MAX_PATH];
unsigned char type;
XList* xlist;
} XListEnumerationContext;
typedef bool(XListEnumerationHandler)(XListEnumerationContext* context);
static bool xlistEnumerate(const char* pattern, XListEnumerationHandler* handler, XList* xlist);
static int xbaseMakeDirectory(const char* path);
static void xbaseCloseAll();
static void xbaseExitHandler(void);
static bool xlistEnumerateHandler(XListEnumerationContext* context);
2022-05-19 01:51:26 -07:00
// 0x6B24D0
2022-06-19 09:02:01 -07:00
static XBase* gXbaseHead;
2022-05-19 01:51:26 -07:00
// 0x6B24D4
2022-06-19 09:02:01 -07:00
static bool gXbaseExitHandlerRegistered;
2022-05-19 01:51:26 -07:00
// 0x4DED6C
int xfileClose(XFile* stream)
{
assert(stream); // "stream", "xfile.c", 112
int rc;
switch (stream->type) {
case XFILE_TYPE_DFILE:
rc = dfileClose(stream->dfile);
break;
case XFILE_TYPE_GZFILE:
rc = gzclose(stream->gzfile);
break;
default:
rc = fclose(stream->file);
break;
}
memset(stream, 0, sizeof(*stream));
free(stream);
return rc;
}
// 0x4DEE2C
XFile* xfileOpen(const char* filePath, const char* mode)
{
assert(filePath); // "filename", "xfile.c", 162
assert(mode); // "mode", "xfile.c", 163
2022-05-21 08:22:03 -07:00
XFile* stream = (XFile*)malloc(sizeof(*stream));
2022-05-19 01:51:26 -07:00
if (stream == NULL) {
return NULL;
}
memset(stream, 0, sizeof(*stream));
// NOTE: Compiled code uses different lengths.
char drive[COMPAT_MAX_DRIVE];
char dir[COMPAT_MAX_DIR];
compat_splitpath(filePath, drive, dir, NULL, NULL);
2022-05-19 01:51:26 -07:00
2022-05-28 02:34:49 -07:00
char path[COMPAT_MAX_PATH];
2022-05-19 01:51:26 -07:00
if (drive[0] != '\0' || dir[0] == '\\' || dir[0] == '/' || dir[0] == '.') {
// [filePath] is an absolute path. Attempt to open as plain stream.
stream->file = compat_fopen(filePath, mode);
2022-05-19 01:51:26 -07:00
if (stream->file == NULL) {
free(stream);
return NULL;
}
stream->type = XFILE_TYPE_FILE;
sprintf(path, filePath);
} else {
// [filePath] is a relative path. Loop thru open xbases and attempt to
// open [filePath] from appropriate xbase.
XBase* curr = gXbaseHead;
while (curr != NULL) {
if (curr->isDbase) {
// Attempt to open dfile stream from dbase.
stream->dfile = dfileOpen(curr->dbase, filePath, mode);
if (stream->dfile != NULL) {
stream->type = XFILE_TYPE_DFILE;
sprintf(path, filePath);
break;
}
} else {
// Build path relative to directory-based xbase.
sprintf(path, "%s\\%s", curr->path, filePath);
// Attempt to open plain stream.
stream->file = compat_fopen(path, mode);
2022-05-19 01:51:26 -07:00
if (stream->file != NULL) {
stream->type = XFILE_TYPE_FILE;
break;
}
}
curr = curr->next;
}
if (stream->file == NULL) {
// File was not opened during the loop above. Attempt to open file
// relative to the current working directory.
stream->file = compat_fopen(filePath, mode);
2022-05-19 01:51:26 -07:00
if (stream->file == NULL) {
free(stream);
return NULL;
}
stream->type = XFILE_TYPE_FILE;
sprintf(path, filePath);
}
}
if (stream->type == XFILE_TYPE_FILE) {
// Opened file is a plain stream, which might be gzipped. In this case
// first two bytes will contain magic numbers.
int ch1 = fgetc(stream->file);
int ch2 = fgetc(stream->file);
if (ch1 == 0x1F && ch2 == 0x8B) {
// File is gzipped. Close plain stream and reopen this file as
// gzipped stream.
fclose(stream->file);
stream->type = XFILE_TYPE_GZFILE;
stream->gzfile = compat_gzopen(path, mode);
2022-05-19 01:51:26 -07:00
} else {
// File is not gzipped.
rewind(stream->file);
}
}
return stream;
}
// 0x4DF11C
int xfilePrintFormatted(XFile* stream, const char* format, ...)
{
assert(format); // "format", "xfile.c", 305
va_list args;
va_start(args, format);
int rc = xfilePrintFormattedArgs(stream, format, args);
va_end(args);
return rc;
}
// [vfprintf].
//
// 0x4DF1AC
int xfilePrintFormattedArgs(XFile* stream, const char* format, va_list args)
{
assert(stream); // "stream", "xfile.c", 332
assert(format); // "format", "xfile.c", 333
int rc;
switch (stream->type) {
case XFILE_TYPE_DFILE:
rc = dfilePrintFormattedArgs(stream->dfile, format, args);
break;
case XFILE_TYPE_GZFILE:
rc = gzvprintf(stream->gzfile, format, args);
break;
default:
rc = vfprintf(stream->file, format, args);
break;
}
return rc;
}
// 0x4DF22C
int xfileReadChar(XFile* stream)
{
assert(stream); // "stream", "xfile.c", 354
int ch;
switch (stream->type) {
case XFILE_TYPE_DFILE:
ch = dfileReadChar(stream->dfile);
break;
case XFILE_TYPE_GZFILE:
ch = gzgetc(stream->gzfile);
break;
default:
ch = fgetc(stream->file);
break;
}
return ch;
}
// 0x4DF280
char* xfileReadString(char* string, int size, XFile* stream)
{
assert(string); // "s", "xfile.c", 375
assert(size); // "n", "xfile.c", 376
assert(stream); // "stream", "xfile.c", 377
char* result;
switch (stream->type) {
case XFILE_TYPE_DFILE:
result = dfileReadString(string, size, stream->dfile);
break;
case XFILE_TYPE_GZFILE:
result = gzgets(stream->gzfile, string, size);
break;
default:
result = fgets(string, size, stream->file);
break;
}
return result;
}
// 0x4DF320
int xfileWriteChar(int ch, XFile* stream)
{
assert(stream); // "stream", "xfile.c", 399
int rc;
switch (stream->type) {
case XFILE_TYPE_DFILE:
rc = dfileWriteChar(ch, stream->dfile);
break;
case XFILE_TYPE_GZFILE:
rc = gzputc(stream->gzfile, ch);
break;
default:
rc = fputc(ch, stream->file);
break;
}
return rc;
}
// 0x4DF380
int xfileWriteString(const char* string, XFile* stream)
{
assert(string); // "s", "xfile.c", 421
assert(stream); // "stream", "xfile.c", 422
int rc;
switch (stream->type) {
case XFILE_TYPE_DFILE:
rc = dfileWriteString(string, stream->dfile);
break;
case XFILE_TYPE_GZFILE:
rc = gzputs(stream->gzfile, string);
break;
default:
rc = fputs(string, stream->file);
break;
}
return rc;
}
// 0x4DF44C
size_t xfileRead(void* ptr, size_t size, size_t count, XFile* stream)
{
assert(ptr); // "ptr", "xfile.c", 421
assert(stream); // "stream", "xfile.c", 422
size_t elementsRead;
switch (stream->type) {
case XFILE_TYPE_DFILE:
elementsRead = dfileRead(ptr, size, count, stream->dfile);
break;
case XFILE_TYPE_GZFILE:
// FIXME: There is a bug in the return value. Both [dfileRead] and
// [fread] returns number of elements read, but [gzwrite] have no such
// concept, it works with bytes, and returns number of bytes read.
// Depending on the [size] and [count] parameters this function can
// return wrong result.
elementsRead = gzread(stream->gzfile, ptr, size * count);
break;
default:
elementsRead = fread(ptr, size, count, stream->file);
break;
}
return elementsRead;
}
// 0x4DF4E8
size_t xfileWrite(const void* ptr, size_t size, size_t count, XFile* stream)
{
assert(ptr); // "ptr", "xfile.c", 504
assert(stream); // "stream", "xfile.c", 505
size_t elementsWritten;
switch (stream->type) {
case XFILE_TYPE_DFILE:
elementsWritten = dfileWrite(ptr, size, count, stream->dfile);
break;
case XFILE_TYPE_GZFILE:
// FIXME: There is a bug in the return value. [fwrite] returns number
// of elements written (while [dfileWrite] does not support writing at
// all), but [gzwrite] have no such concept, it works with bytes, and
// returns number of bytes written. Depending on the [size] and [count]
// parameters this function can return wrong result.
elementsWritten = gzwrite(stream->gzfile, ptr, size * count);
break;
default:
elementsWritten = fwrite(ptr, size, count, stream->file);
break;
}
return elementsWritten;
}
// 0x4DF5D8
int xfileSeek(XFile* stream, long offset, int origin)
{
assert(stream); // "stream", "xfile.c", 547
int result;
switch (stream->type) {
case XFILE_TYPE_DFILE:
result = dfileSeek(stream->dfile, offset, origin);
break;
case XFILE_TYPE_GZFILE:
result = gzseek(stream->gzfile, offset, origin);
break;
default:
result = fseek(stream->file, offset, origin);
break;
}
return result;
}
// 0x4DF690
long xfileTell(XFile* stream)
{
assert(stream); // "stream", "xfile.c", 588
long pos;
switch (stream->type) {
case XFILE_TYPE_DFILE:
pos = dfileTell(stream->dfile);
break;
case XFILE_TYPE_GZFILE:
pos = gztell(stream->gzfile);
break;
default:
pos = ftell(stream->file);
break;
}
return pos;
}
// 0x4DF6E4
void xfileRewind(XFile* stream)
{
assert(stream); // "stream", "xfile.c", 608
switch (stream->type) {
case XFILE_TYPE_DFILE:
dfileRewind(stream->dfile);
break;
case XFILE_TYPE_GZFILE:
gzrewind(stream->gzfile);
break;
default:
rewind(stream->file);
break;
}
}
// 0x4DF780
int xfileEof(XFile* stream)
{
assert(stream); // "stream", "xfile.c", 648
int rc;
switch (stream->type) {
case XFILE_TYPE_DFILE:
rc = dfileEof(stream->dfile);
break;
case XFILE_TYPE_GZFILE:
rc = gzeof(stream->gzfile);
break;
default:
rc = feof(stream->file);
break;
}
return rc;
}
// 0x4DF828
long xfileGetSize(XFile* stream)
{
assert(stream); // "stream", "xfile.c", 690
long fileSize;
switch (stream->type) {
case XFILE_TYPE_DFILE:
fileSize = dfileGetSize(stream->dfile);
break;
case XFILE_TYPE_GZFILE:
fileSize = 0;
break;
default:
fileSize = compat_filelength(fileno(stream->file));
2022-05-19 01:51:26 -07:00
break;
}
return fileSize;
}
// Closes all open xbases and opens a set of xbases specified by [paths].
//
// [paths] is a set of paths separated by semicolon. Can be NULL, in this case
// all open xbases are simply closed.
//
// 0x4DF878
bool xbaseReopenAll(char* paths)
{
// NOTE: Uninline.
xbaseCloseAll();
if (paths != NULL) {
char* tok = strtok(paths, ";");
while (tok != NULL) {
if (!xbaseOpen(tok)) {
return false;
}
tok = strtok(NULL, ";");
}
}
return true;
}
// 0x4DF938
bool xbaseOpen(const char* path)
{
assert(path); // "path", "xfile.c", 747
// Register atexit handler so that underlying dbase (if any) can be
// gracefully closed.
if (!gXbaseExitHandlerRegistered) {
atexit(xbaseExitHandler);
gXbaseExitHandlerRegistered = true;
}
XBase* curr = gXbaseHead;
XBase* prev = NULL;
while (curr != NULL) {
if (compat_stricmp(path, curr->path) == 0) {
2022-05-19 01:51:26 -07:00
break;
}
prev = curr;
curr = curr->next;
}
if (curr != NULL) {
if (prev != NULL) {
// Move found xbase to the top.
prev->next = curr->next;
curr->next = gXbaseHead;
gXbaseHead = curr;
}
return true;
}
2022-05-21 08:22:03 -07:00
XBase* xbase = (XBase*)malloc(sizeof(*xbase));
2022-05-19 01:51:26 -07:00
if (xbase == NULL) {
return false;
}
memset(xbase, 0, sizeof(*xbase));
xbase->path = strdup(path);
if (xbase->path == NULL) {
free(xbase);
return false;
}
DBase* dbase = dbaseOpen(path);
if (dbase != NULL) {
xbase->isDbase = true;
xbase->dbase = dbase;
xbase->next = gXbaseHead;
gXbaseHead = xbase;
return true;
}
2022-05-28 02:34:49 -07:00
char workingDirectory[COMPAT_MAX_PATH];
if (getcwd(workingDirectory, COMPAT_MAX_PATH) == NULL) {
2022-05-19 01:51:26 -07:00
// FIXME: Leaking xbase and path.
return false;
}
if (chdir(path) == 0) {
chdir(workingDirectory);
xbase->next = gXbaseHead;
gXbaseHead = xbase;
return true;
}
if (xbaseMakeDirectory(path) != 0) {
// FIXME: Leaking xbase and path.
return false;
}
chdir(workingDirectory);
xbase->next = gXbaseHead;
gXbaseHead = xbase;
return true;
}
// 0x4DFB3C
2022-06-19 09:02:01 -07:00
static bool xlistEnumerate(const char* pattern, XListEnumerationHandler* handler, XList* xlist)
2022-05-19 01:51:26 -07:00
{
assert(pattern); // "filespec", "xfile.c", 845
assert(handler); // "enumfunc", "xfile.c", 846
DirectoryFileFindData directoryFileFindData;
XListEnumerationContext context;
context.xlist = xlist;
char nativePattern[COMPAT_MAX_PATH];
strcpy(nativePattern, pattern);
compat_windows_path_to_native(nativePattern);
char drive[COMPAT_MAX_DRIVE];
char dir[COMPAT_MAX_DIR];
char fileName[COMPAT_MAX_FNAME];
char extension[COMPAT_MAX_EXT];
compat_splitpath(nativePattern, drive, dir, fileName, extension);
2022-05-19 01:51:26 -07:00
if (drive[0] != '\0' || dir[0] == '\\' || dir[0] == '/' || dir[0] == '.') {
if (fileFindFirst(nativePattern, &directoryFileFindData)) {
2022-05-19 01:51:26 -07:00
do {
bool isDirectory = fileFindIsDirectory(&directoryFileFindData);
char* entryName = fileFindGetName(&directoryFileFindData);
2022-05-19 01:51:26 -07:00
if (isDirectory) {
if (strcmp(entryName, "..") == 0 || strcmp(entryName, ".") == 0) {
continue;
}
context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY;
} else {
context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE;
}
compat_makepath(context.name, drive, dir, entryName, NULL);
2022-05-19 01:51:26 -07:00
if (!handler(&context)) {
break;
}
} while (fileFindNext(&directoryFileFindData));
}
return findFindClose(&directoryFileFindData);
}
XBase* xbase = gXbaseHead;
while (xbase != NULL) {
if (xbase->isDbase) {
DFileFindData dbaseFindData;
if (dbaseFindFirstEntry(xbase->dbase, &dbaseFindData, pattern)) {
context.type = XFILE_ENUMERATION_ENTRY_TYPE_DFILE;
do {
strcpy(context.name, dbaseFindData.fileName);
if (!handler(&context)) {
return dbaseFindClose(xbase->dbase, &dbaseFindData);
}
} while (dbaseFindNextEntry(xbase->dbase, &dbaseFindData));
dbaseFindClose(xbase->dbase, &dbaseFindData);
}
} else {
2022-05-28 02:34:49 -07:00
char path[COMPAT_MAX_PATH];
2022-05-19 01:51:26 -07:00
sprintf(path, "%s\\%s", xbase->path, pattern);
compat_windows_path_to_native(path);
2022-05-19 01:51:26 -07:00
if (fileFindFirst(path, &directoryFileFindData)) {
do {
bool isDirectory = fileFindIsDirectory(&directoryFileFindData);
char* entryName = fileFindGetName(&directoryFileFindData);
2022-05-19 01:51:26 -07:00
if (isDirectory) {
if (strcmp(entryName, "..") == 0 || strcmp(entryName, ".") == 0) {
continue;
}
context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY;
} else {
context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE;
}
compat_makepath(context.name, drive, dir, entryName, NULL);
2022-05-19 01:51:26 -07:00
if (!handler(&context)) {
break;
}
} while (fileFindNext(&directoryFileFindData));
}
2022-06-07 13:18:59 -07:00
findFindClose(&directoryFileFindData);
2022-05-19 01:51:26 -07:00
}
xbase = xbase->next;
}
compat_splitpath(nativePattern, drive, dir, fileName, extension);
if (fileFindFirst(nativePattern, &directoryFileFindData)) {
2022-05-19 01:51:26 -07:00
do {
bool isDirectory = fileFindIsDirectory(&directoryFileFindData);
char* entryName = fileFindGetName(&directoryFileFindData);
2022-05-19 01:51:26 -07:00
if (isDirectory) {
if (strcmp(entryName, "..") == 0 || strcmp(entryName, ".") == 0) {
continue;
}
context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY;
} else {
context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE;
}
compat_makepath(context.name, drive, dir, entryName, NULL);
2022-05-19 01:51:26 -07:00
if (!handler(&context)) {
break;
}
} while (fileFindNext(&directoryFileFindData));
}
return findFindClose(&directoryFileFindData);
}
// 0x4DFF28
bool xlistInit(const char* pattern, XList* xlist)
{
xlistEnumerate(pattern, xlistEnumerateHandler, xlist);
return xlist->fileNamesLength != -1;
}
// 0x4DFF48
void xlistFree(XList* xlist)
{
assert(xlist); // "list", "xfile.c", 949
for (int index = 0; index < xlist->fileNamesLength; index++) {
if (xlist->fileNames[index] != NULL) {
free(xlist->fileNames[index]);
}
}
free(xlist->fileNames);
memset(xlist, 0, sizeof(*xlist));
}
// Recursively creates specified file path.
//
// 0x4DFFAC
2022-06-19 09:02:01 -07:00
static int xbaseMakeDirectory(const char* filePath)
2022-05-19 01:51:26 -07:00
{
2022-05-28 02:34:49 -07:00
char workingDirectory[COMPAT_MAX_PATH];
if (getcwd(workingDirectory, COMPAT_MAX_PATH) == NULL) {
2022-05-19 01:51:26 -07:00
return -1;
}
char drive[COMPAT_MAX_DRIVE];
char dir[COMPAT_MAX_DIR];
compat_splitpath(filePath, drive, dir, NULL, NULL);
2022-05-19 01:51:26 -07:00
2022-05-28 02:34:49 -07:00
char path[COMPAT_MAX_PATH];
2022-05-19 01:51:26 -07:00
if (drive[0] != '\0' || dir[0] == '\\' || dir[0] == '/' || dir[0] == '.') {
// [filePath] is an absolute path.
strcpy(path, filePath);
} else {
// Find first directory-based xbase.
XBase* curr = gXbaseHead;
while (curr != NULL) {
if (!curr->isDbase) {
sprintf(path, "%s\\%s", curr->path, filePath);
break;
}
curr = curr->next;
}
if (curr == NULL) {
// Either there are no directory-based xbase, or there are no open
// xbases at all - resolve path against current working directory.
sprintf(path, "%s\\%s", workingDirectory, filePath);
}
}
char* pch = path;
if (*pch == '\\' || *pch == '/') {
pch++;
}
while (*pch != '\0') {
if (*pch == '\\' || *pch == '/') {
char temp = *pch;
*pch = '\0';
if (chdir(path) != 0) {
if (compat_mkdir(path) != 0) {
2022-05-19 01:51:26 -07:00
chdir(workingDirectory);
return -1;
}
} else {
chdir(workingDirectory);
}
*pch = temp;
}
pch++;
}
// Last path component.
compat_mkdir(path);
2022-05-19 01:51:26 -07:00
chdir(workingDirectory);
return 0;
}
// Closes all xbases.
//
// NOTE: Inlined.
//
// 0x4E01F8
2022-06-19 09:02:01 -07:00
static void xbaseCloseAll()
2022-05-19 01:51:26 -07:00
{
XBase* curr = gXbaseHead;
gXbaseHead = NULL;
while (curr != NULL) {
XBase* next = curr->next;
if (curr->isDbase) {
dbaseClose(curr->dbase);
}
free(curr->path);
free(curr);
curr = next;
}
}
// xbase atexit
2022-06-19 09:02:01 -07:00
static void xbaseExitHandler(void)
2022-05-19 01:51:26 -07:00
{
// NOTE: Uninline.
xbaseCloseAll();
}
// 0x4E0278
2022-06-19 09:02:01 -07:00
static bool xlistEnumerateHandler(XListEnumerationContext* context)
2022-05-19 01:51:26 -07:00
{
if (context->type == XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY) {
return true;
}
XList* xlist = context->xlist;
char** fileNames = (char**)realloc(xlist->fileNames, sizeof(*fileNames) * (xlist->fileNamesLength + 1));
if (fileNames == NULL) {
xlistFree(xlist);
xlist->fileNamesLength = -1;
return false;
}
xlist->fileNames = fileNames;
fileNames[xlist->fileNamesLength] = strdup(context->name);
if (fileNames[xlist->fileNamesLength] == NULL) {
xlistFree(xlist);
xlist->fileNamesLength = -1;
return false;
}
xlist->fileNamesLength++;
return true;
}