fallout2-ce/src/db.cc

718 lines
17 KiB
C++

#include "db.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "platform_compat.h"
#include "xfile.h"
namespace fallout {
typedef struct FileList {
XList xlist;
struct FileList* next;
} FileList;
static int _db_list_compare(const void* p1, const void* p2);
// Generic file progress report handler.
//
// 0x51DEEC
static FileReadProgressHandler* gFileReadProgressHandler = nullptr;
// Bytes read so far while tracking progress.
//
// Once this value reaches [gFileReadProgressChunkSize] the handler is called
// and this value resets to zero.
//
// 0x51DEF0
static int gFileReadProgressBytesRead = 0;
// The number of bytes to read between calls to progress handler.
//
// 0x673040
static int gFileReadProgressChunkSize;
// 0x673044
static FileList* gFileListHead;
// Opens file database.
//
// Returns -1 if [filePath1] was specified, but could not be opened by the
// underlying xbase implementation. Result of opening [filePath2] is ignored.
// Returns 0 on success.
//
// NOTE: There are two unknown parameters passed via edx and ecx. The [a2] is
// always 0 at the calling sites, and [a4] is always 1. Both parameters are not
// used, so it's impossible to figure out their meaning.
//
// 0x4C5D30
int dbOpen(const char* filePath1, int a2, const char* filePath2, int a4)
{
if (filePath1 != nullptr) {
if (!xbaseOpen(filePath1)) {
return -1;
}
}
if (filePath2 != nullptr) {
xbaseOpen(filePath2);
}
return 0;
}
// 0x4C5D58
int db_total()
{
return 1;
}
// 0x4C5D60
void dbExit()
{
xbaseReopenAll(nullptr);
}
// TODO: sizePtr should be long*.
//
// 0x4C5D68
int dbGetFileSize(const char* filePath, int* sizePtr)
{
assert(filePath); // "filename", "db.c", 108
assert(sizePtr); // "de", "db.c", 109
File* stream = xfileOpen(filePath, "rb");
if (stream == nullptr) {
return -1;
}
*sizePtr = xfileGetSize(stream);
xfileClose(stream);
return 0;
}
// 0x4C5DD4
int dbGetFileContents(const char* filePath, void* ptr)
{
assert(filePath); // "filename", "db.c", 141
assert(ptr); // "buf", "db.c", 142
File* stream = xfileOpen(filePath, "rb");
if (stream == nullptr) {
return -1;
}
long size = xfileGetSize(stream);
if (gFileReadProgressHandler != nullptr) {
unsigned char* byteBuffer = (unsigned char*)ptr;
long remainingSize = size;
long chunkSize = gFileReadProgressChunkSize - gFileReadProgressBytesRead;
while (remainingSize >= chunkSize) {
size_t bytesRead = xfileRead(byteBuffer, sizeof(*byteBuffer), chunkSize, stream);
byteBuffer += bytesRead;
remainingSize -= bytesRead;
gFileReadProgressBytesRead = 0;
gFileReadProgressHandler();
chunkSize = gFileReadProgressChunkSize;
}
if (remainingSize != 0) {
gFileReadProgressBytesRead += xfileRead(byteBuffer, sizeof(*byteBuffer), remainingSize, stream);
}
} else {
xfileRead(ptr, 1, size, stream);
}
xfileClose(stream);
return 0;
}
// 0x4C5EB4
int fileClose(File* stream)
{
return xfileClose(stream);
}
// 0x4C5EC8
File* fileOpen(const char* filename, const char* mode)
{
return xfileOpen(filename, mode);
}
// 0x4C5ED0
int filePrintFormatted(File* stream, const char* format, ...)
{
assert(format); // "format", "db.c", 224
va_list args;
va_start(args, format);
int rc = xfilePrintFormattedArgs(stream, format, args);
va_end(args);
return rc;
}
// 0x4C5F24
int fileReadChar(File* stream)
{
if (gFileReadProgressHandler != nullptr) {
int ch = xfileReadChar(stream);
gFileReadProgressBytesRead++;
if (gFileReadProgressBytesRead >= gFileReadProgressChunkSize) {
gFileReadProgressHandler();
gFileReadProgressBytesRead = 0;
}
return ch;
}
return xfileReadChar(stream);
}
// 0x4C5F70
char* fileReadString(char* string, size_t size, File* stream)
{
if (gFileReadProgressHandler != nullptr) {
if (xfileReadString(string, size, stream) == nullptr) {
return nullptr;
}
gFileReadProgressBytesRead += strlen(string);
while (gFileReadProgressBytesRead >= gFileReadProgressChunkSize) {
gFileReadProgressHandler();
gFileReadProgressBytesRead -= gFileReadProgressChunkSize;
}
return string;
}
return xfileReadString(string, size, stream);
}
// 0x4C5FEC
int fileWriteString(const char* string, File* stream)
{
return xfileWriteString(string, stream);
}
// 0x4C5FFC
size_t fileRead(void* ptr, size_t size, size_t count, File* stream)
{
if (gFileReadProgressHandler != nullptr) {
unsigned char* byteBuffer = (unsigned char*)ptr;
size_t totalBytesRead = 0;
long remainingSize = size * count;
long chunkSize = gFileReadProgressChunkSize - gFileReadProgressBytesRead;
while (remainingSize >= chunkSize) {
size_t bytesRead = xfileRead(byteBuffer, sizeof(*byteBuffer), chunkSize, stream);
byteBuffer += bytesRead;
totalBytesRead += bytesRead;
remainingSize -= bytesRead;
gFileReadProgressBytesRead = 0;
gFileReadProgressHandler();
chunkSize = gFileReadProgressChunkSize;
}
if (remainingSize != 0) {
size_t bytesRead = xfileRead(byteBuffer, sizeof(*byteBuffer), remainingSize, stream);
gFileReadProgressBytesRead += bytesRead;
totalBytesRead += bytesRead;
}
return totalBytesRead / size;
}
return xfileRead(ptr, size, count, stream);
}
// 0x4C60B8
size_t fileWrite(const void* buf, size_t size, size_t count, File* stream)
{
return xfileWrite(buf, size, count, stream);
}
// 0x4C60C0
int fileSeek(File* stream, long offset, int origin)
{
return xfileSeek(stream, offset, origin);
}
// 0x4C60C8
long fileTell(File* stream)
{
return xfileTell(stream);
}
// 0x4C60D0
void fileRewind(File* stream)
{
xfileRewind(stream);
}
// 0x4C60D8
int fileEof(File* stream)
{
return xfileEof(stream);
}
// NOTE: Not sure about signness.
//
// 0x4C60E0
int fileReadUInt8(File* stream, unsigned char* valuePtr)
{
int value = fileReadChar(stream);
if (value == -1) {
return -1;
}
*valuePtr = value & 0xFF;
return 0;
}
// NOTE: Not sure about signness.
//
// 0x4C60F4
int fileReadInt16(File* stream, short* valuePtr)
{
unsigned char high;
// NOTE: Uninline.
if (fileReadUInt8(stream, &high) == -1) {
return -1;
}
unsigned char low;
// NOTE: Uninline.
if (fileReadUInt8(stream, &low) == -1) {
return -1;
}
*valuePtr = (high << 8) | low;
return 0;
}
// NOTE: Probably uncollapsed 0x4C60F4. There are only couple of places where
// the game reads/writes 16-bit integers. I'm not sure there are unsigned
// shorts used, but there are definitely signed (art offsets can be both
// positive and negative). Provided just in case.
int fileReadUInt16(File* stream, unsigned short* valuePtr)
{
return fileReadInt16(stream, (short*)valuePtr);
}
// 0x4C614C
int fileReadInt32(File* stream, int* valuePtr)
{
int value;
if (xfileRead(&value, 4, 1, stream) == -1) {
return -1;
}
*valuePtr = ((value & 0xFF000000) >> 24) | ((value & 0xFF0000) >> 8) | ((value & 0xFF00) << 8) | ((value & 0xFF) << 24);
return 0;
}
// NOTE: Uncollapsed 0x4C614C. The opposite of [_db_fwriteLong]. It can be either
// signed vs. unsigned variant, as well as int vs. long. It's provided here to
// identify places where data was written with [_db_fwriteLong].
int _db_freadInt(File* stream, int* valuePtr)
{
return fileReadInt32(stream, valuePtr);
}
// NOTE: Probably uncollapsed 0x4C614C.
int fileReadUInt32(File* stream, unsigned int* valuePtr)
{
return _db_freadInt(stream, (int*)valuePtr);
}
// NOTE: Uncollapsed 0x4C614C. The opposite of [fileWriteFloat].
int fileReadFloat(File* stream, float* valuePtr)
{
return fileReadInt32(stream, (int*)valuePtr);
}
int fileReadBool(File* stream, bool* valuePtr)
{
int value;
if (fileReadInt32(stream, &value) == -1) {
return -1;
}
*valuePtr = (value != 0);
return 0;
}
// NOTE: Not sure about signness.
//
// 0x4C61AC
int fileWriteUInt8(File* stream, unsigned char value)
{
return xfileWriteChar(value, stream);
};
// 0x4C61C8
int fileWriteInt16(File* stream, short value)
{
// NOTE: Uninline.
if (fileWriteUInt8(stream, (value >> 8) & 0xFF) == -1) {
return -1;
}
// NOTE: Uninline.
if (fileWriteUInt8(stream, value & 0xFF) == -1) {
return -1;
}
return 0;
}
// NOTE: Probably uncollapsed 0x4C61C8.
int fileWriteUInt16(File* stream, unsigned short value)
{
return fileWriteInt16(stream, (short)value);
}
// NOTE: Not sure about signness and int vs. long.
//
// 0x4C6214
int fileWriteInt32(File* stream, int value)
{
// NOTE: Uninline.
return _db_fwriteLong(stream, value);
}
// NOTE: Can either be signed vs. unsigned variant of [fileWriteInt32],
// or int vs. long.
//
// 0x4C6244
int _db_fwriteLong(File* stream, int value)
{
if (fileWriteInt16(stream, (value >> 16) & 0xFFFF) == -1) {
return -1;
}
if (fileWriteInt16(stream, value & 0xFFFF) == -1) {
return -1;
}
return 0;
}
// NOTE: Probably uncollapsed 0x4C6214 or 0x4C6244.
int fileWriteUInt32(File* stream, unsigned int value)
{
return _db_fwriteLong(stream, (int)value);
}
// 0x4C62C4
int fileWriteFloat(File* stream, float value)
{
// NOTE: Uninline.
return _db_fwriteLong(stream, *(int*)&value);
}
int fileWriteBool(File* stream, bool value)
{
return _db_fwriteLong(stream, value ? 1 : 0);
}
// 0x4C62FC
int fileReadUInt8List(File* stream, unsigned char* arr, int count)
{
for (int index = 0; index < count; index++) {
unsigned char ch;
// NOTE: Uninline.
if (fileReadUInt8(stream, &ch) == -1) {
return -1;
}
arr[index] = ch;
}
return 0;
}
// NOTE: Probably uncollapsed 0x4C62FC. There are couple of places where
// [fileReadUInt8List] is used to read strings of fixed length. I'm not
// pretty sure this function existed in the original code, but at least
// it increases visibility of these places.
int fileReadFixedLengthString(File* stream, char* string, int length)
{
return fileReadUInt8List(stream, (unsigned char*)string, length);
}
// 0x4C6330
int fileReadInt16List(File* stream, short* arr, int count)
{
for (int index = 0; index < count; index++) {
short value;
// NOTE: Uninline.
if (fileReadInt16(stream, &value) == -1) {
return -1;
}
arr[index] = value;
}
return 0;
}
// NOTE: Probably uncollapsed 0x4C6330.
int fileReadUInt16List(File* stream, unsigned short* arr, int count)
{
return fileReadInt16List(stream, (short*)arr, count);
}
// NOTE: Not sure about signed/unsigned int/long.
//
// 0x4C63BC
int fileReadInt32List(File* stream, int* arr, int count)
{
if (count == 0) {
return 0;
}
if (fileRead(arr, sizeof(*arr) * count, 1, stream) < 1) {
return -1;
}
for (int index = 0; index < count; index++) {
int value = arr[index];
arr[index] = ((value & 0xFF000000) >> 24) | ((value & 0xFF0000) >> 8) | ((value & 0xFF00) << 8) | ((value & 0xFF) << 24);
}
return 0;
}
// NOTE: Uncollapsed 0x4C63BC. The opposite of [_db_fwriteLongCount].
int _db_freadIntCount(File* stream, int* arr, int count)
{
return fileReadInt32List(stream, arr, count);
}
// NOTE: Probably uncollapsed 0x4C63BC.
int fileReadUInt32List(File* stream, unsigned int* arr, int count)
{
return fileReadInt32List(stream, (int*)arr, count);
}
// 0x4C6464
int fileWriteUInt8List(File* stream, unsigned char* arr, int count)
{
for (int index = 0; index < count; index++) {
// NOTE: Uninline.
if (fileWriteUInt8(stream, arr[index]) == -1) {
return -1;
}
}
return 0;
}
// NOTE: Probably uncollapsed 0x4C6464. See [fileReadFixedLengthString].
int fileWriteFixedLengthString(File* stream, char* string, int length)
{
return fileWriteUInt8List(stream, (unsigned char*)string, length);
}
// 0x4C6490
int fileWriteInt16List(File* stream, short* arr, int count)
{
for (int index = 0; index < count; index++) {
// NOTE: Uninline.
if (fileWriteInt16(stream, arr[index]) == -1) {
return -1;
}
}
return 0;
}
// NOTE: Probably uncollapsed 0x4C6490.
int fileWriteUInt16List(File* stream, unsigned short* arr, int count)
{
return fileWriteInt16List(stream, (short*)arr, count);
}
// NOTE: Can be either signed/unsigned + int/long variant.
//
// 0x4C64F8
int fileWriteInt32List(File* stream, int* arr, int count)
{
for (int index = 0; index < count; index++) {
// NOTE: Uninline.
if (_db_fwriteLong(stream, arr[index]) == -1) {
return -1;
}
}
return 0;
}
// NOTE: Not sure about signed/unsigned int/long.
//
// 0x4C6550
int _db_fwriteLongCount(File* stream, int* arr, int count)
{
for (int index = 0; index < count; index++) {
int value = arr[index];
// NOTE: Uninline.
if (fileWriteInt16(stream, (value >> 16) & 0xFFFF) == -1) {
return -1;
}
// NOTE: Uninline.
if (fileWriteInt16(stream, value & 0xFFFF) == -1) {
return -1;
}
}
return 0;
}
// NOTE: Probably uncollapsed 0x4C64F8 or 0x4C6550.
int fileWriteUInt32List(File* stream, unsigned int* arr, int count)
{
return fileWriteInt32List(stream, (int*)arr, count);
}
// 0x4C6628
int fileNameListInit(const char* pattern, char*** fileNameListPtr, int a3, int a4)
{
FileList* fileList = (FileList*)malloc(sizeof(*fileList));
if (fileList == nullptr) {
return 0;
}
memset(fileList, 0, sizeof(*fileList));
XList* xlist = &(fileList->xlist);
if (!xlistInit(pattern, xlist)) {
free(fileList);
return 0;
}
int length = 0;
if (xlist->fileNamesLength != 0) {
qsort(xlist->fileNames, xlist->fileNamesLength, sizeof(*xlist->fileNames), _db_list_compare);
int fileNamesLength = xlist->fileNamesLength;
for (int index = 0; index < fileNamesLength - 1; index++) {
if (compat_stricmp(xlist->fileNames[index], xlist->fileNames[index + 1]) == 0) {
char* temp = xlist->fileNames[index + 1];
memmove(&(xlist->fileNames[index + 1]), &(xlist->fileNames[index + 2]), sizeof(*xlist->fileNames) * (xlist->fileNamesLength - index - 1));
xlist->fileNames[xlist->fileNamesLength - 1] = temp;
fileNamesLength--;
index--;
}
}
bool isWildcard = *pattern == '*';
for (int index = 0; index < fileNamesLength; index += 1) {
char* name = xlist->fileNames[index];
char dir[COMPAT_MAX_DIR];
char fileName[COMPAT_MAX_FNAME];
char extension[COMPAT_MAX_EXT];
compat_windows_path_to_native(name);
compat_splitpath(name, nullptr, dir, fileName, extension);
if (!isWildcard || *dir == '\0' || (strchr(dir, '\\') == nullptr && strchr(dir, '/') == nullptr)) {
// NOTE: Quick and dirty fix to buffer overflow. See RE to
// understand the problem.
char path[COMPAT_MAX_PATH];
snprintf(path, sizeof(path), "%s%s", fileName, extension);
free(xlist->fileNames[length]);
xlist->fileNames[length] = compat_strdup(path);
length++;
}
}
}
fileList->next = gFileListHead;
gFileListHead = fileList;
*fileNameListPtr = xlist->fileNames;
return length;
}
// 0x4C6868
void fileNameListFree(char*** fileNameListPtr, int a2)
{
if (gFileListHead == nullptr) {
return;
}
FileList* currentFileList = gFileListHead;
FileList* previousFileList = gFileListHead;
while (*fileNameListPtr != currentFileList->xlist.fileNames) {
previousFileList = currentFileList;
currentFileList = currentFileList->next;
if (currentFileList == nullptr) {
return;
}
}
if (previousFileList == gFileListHead) {
gFileListHead = currentFileList->next;
} else {
previousFileList->next = currentFileList->next;
}
xlistFree(&(currentFileList->xlist));
free(currentFileList);
}
// TODO: Return type should be long.
//
// 0x4C68BC
int fileGetSize(File* stream)
{
return xfileGetSize(stream);
}
// 0x4C68C4
void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size)
{
if (handler != nullptr && size != 0) {
gFileReadProgressHandler = handler;
gFileReadProgressChunkSize = size;
} else {
gFileReadProgressHandler = nullptr;
gFileReadProgressChunkSize = 0;
}
}
// 0x4C68E8
int _db_list_compare(const void* p1, const void* p2)
{
return compat_stricmp(*(const char**)p1, *(const char**)p2);
}
} // namespace fallout