#include "db.h" #include #include #include #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