1876 lines
48 KiB
C++
1876 lines
48 KiB
C++
/*****************************************
|
|
*
|
|
* GOB File System
|
|
*
|
|
* Here's what Merriam-Webster says about "gob": --Chuck
|
|
* Entry: gob
|
|
* Function: noun
|
|
* Etymology: Middle English gobbe, from Middle French gobe large piece of food,
|
|
* back-formation from gobet
|
|
* Date: 14th century
|
|
* 1 : LUMP
|
|
* 2 : a large amount -- usually used in plural <gobs of money>
|
|
*
|
|
* Purpose: Provide fast, efficient disk access on a variety of platforms.
|
|
*
|
|
* Implementation:
|
|
* The GOB system maintains two files -- GOB and GFC. The GOB file is actually
|
|
* an archive of many files split into variable size, compressed blocks. The GFC,
|
|
* GOB File Control, contains 3 tables -- a block table, basic file table, and
|
|
* extended file table. The block table is analogous to a DOS FAT. The basic
|
|
* file table contains a minimal set of file information to handle basic reading
|
|
* tasks. The extended file table is optionally loaded and contains additional
|
|
* file information. File names are case insensitive.
|
|
*
|
|
* Files can be read in a normal manner. Open, read, seek and close
|
|
* operations are all provided. Files can only be written in a single
|
|
* contiguous chunk of blocks at the end of an archive. Reads are processed
|
|
* through a configurable number of read ahead buffers to in an effort to
|
|
* minimize both reads and seeks. Other operations including delete, verify,
|
|
* access, and get size are also supported on files inside an archive.
|
|
*
|
|
* The system supports read profiling. By supplying a file read callback
|
|
* function, the library will output the block number of each read. This can
|
|
* be used rearrange block in the archive to minimize seek times. The
|
|
* GOBRearrange sorts files in an archive.
|
|
*
|
|
* Supports block based caching. Primarily aimed at caching files off a DVD/CD
|
|
* to a faster hard disk.
|
|
*
|
|
* Future Work:
|
|
*
|
|
* Dependencies: vvInt, snprintf, zlib
|
|
* Owner: Chris McEvoy
|
|
* History:
|
|
* 09/23/2001 Original version
|
|
* 10/28/2002 Merged into vvtech
|
|
*
|
|
* Copyright (C) 2002, Vicarious Visions, Inc. All Rights Reserved.
|
|
*
|
|
* UNPUBLISHED -- Rights reserved under the copyright laws of the
|
|
* United States. Use of a copyright notice is precautionary only and
|
|
* does not imply publication or disclosure.
|
|
*
|
|
* THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION
|
|
* OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION,
|
|
* DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR
|
|
* EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC.
|
|
*
|
|
*****************************************/
|
|
|
|
/*
|
|
This is an unofficial branch of GOB, for Jedi Academy
|
|
Maintainer: Brian Osman
|
|
*/
|
|
|
|
#include "goblib.h"
|
|
#include "../zlib/zlib.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
|
|
#if (VV_PLATFORM == VV_PLATFORM_WIN) || (VV_PLATFORM == VV_PLATFORM_XBOX)
|
|
# define CDECL __cdecl
|
|
#else
|
|
# define CDECL
|
|
#endif
|
|
|
|
// Profiling data
|
|
static GOBProfileReadFunc ProfileReadCallback = NULL;
|
|
static GOBBool ProfileEnabled = GOB_FALSE;
|
|
|
|
// Indicates whether or not the library has been initialized
|
|
static GOBBool LibraryInit = GOB_FALSE;
|
|
|
|
// Callbacks for handling low-level compression/decompression
|
|
static struct GOBCodecFuncSet CodecFuncs;
|
|
|
|
// Callbacks for handling low-level memory alloc and free
|
|
static struct GOBMemoryFuncSet MemFuncs;
|
|
|
|
// Callbacks for handling low-level file access
|
|
static struct GOBFileSysFuncSet FSFuncs;
|
|
|
|
// Callbacks for handling block caching (ie Xbox temp space)
|
|
static struct GOBCacheFileFuncSet CacheFileFuncs;
|
|
static GOBBool CacheFileActive = GOB_FALSE;
|
|
|
|
// Name of the GFC file
|
|
static GOBChar ControlFileName[GOB_MAX_FILE_NAME_LEN];
|
|
|
|
// Handle to the GOB archive
|
|
static GOBFSHandle ArchiveHandle = (GOBFSHandle*)0xFFFFFFFF;
|
|
|
|
// Size of the active GOB archive
|
|
static GOBUInt32 ArchiveSize = 0;
|
|
static GOBUInt32 ArchiveNumBlocks = 0;
|
|
static GOBUInt32 ArchiveNumFiles = 0;
|
|
|
|
// Cached blocks
|
|
struct GOBBlockCache
|
|
{
|
|
GOBChar* data;
|
|
GOBUInt32 block;
|
|
GOBUInt32 time;
|
|
GOBUInt32 size;
|
|
};
|
|
static struct GOBBlockCache* CacheBlocks = NULL;
|
|
static GOBUInt32 NumCacheBlocks = 0;
|
|
static GOBUInt32 CacheBlockCounter = 0;
|
|
|
|
// Read ahead buffer
|
|
struct GOBReadBuffer
|
|
{
|
|
GOBChar* data;
|
|
GOBChar* dataStart;
|
|
GOBUInt32 pos;
|
|
GOBUInt32 size;
|
|
};
|
|
static struct GOBReadBuffer ReadBuffer;
|
|
|
|
// Decompression buffer
|
|
static GOBChar* DecompBuffer = NULL;
|
|
|
|
// Stats gathering
|
|
static struct GOBReadStats ReadStats;
|
|
static GOBUInt32 CurrentArchivePos = 0;
|
|
|
|
// File tables (from the GFC)
|
|
static struct GOBFileTableBasicEntry* FileTableBasic = NULL;
|
|
static struct GOBFileTableExtEntry* FileTableExt = NULL;
|
|
|
|
// Block tables (from the GFC)
|
|
static struct GOBBlockTableEntry* BlockTable = NULL;
|
|
static GOBUInt32* BlockCRC = NULL;
|
|
static GOBUInt32* CacheFileTable = NULL;
|
|
|
|
// Do the tables need to be written?
|
|
static GOBBool FileTableDirty = GOB_FALSE;
|
|
|
|
// Information about open files
|
|
struct OpenFileInfo
|
|
{
|
|
GOBBool valid;
|
|
GOBUInt32 startBlock;
|
|
GOBUInt32 block;
|
|
GOBUInt32 offset;
|
|
|
|
GOBUInt32 pos;
|
|
GOBUInt32 size;
|
|
};
|
|
|
|
// Open file table -- indices in this array are passed
|
|
// back to the caller as pseudo file handles.
|
|
static struct OpenFileInfo OpenFiles[GOB_MAX_OPEN_FILES];
|
|
|
|
// Converting text to lower case -- this isn't very
|
|
// clean. A common buffer is used to store lower case
|
|
// text. So its not thread safe... among other things. ;)
|
|
static GOBChar LowerCaseBuffer[GOB_MAX_FILE_NAME_LEN];
|
|
static GOBChar* LowerCase(const GOBChar* name)
|
|
{
|
|
GOBInt32 i;
|
|
for (i = 0; name[i]; ++i) {
|
|
LowerCaseBuffer[i] = (GOBChar)tolower(name[i]);
|
|
}
|
|
LowerCaseBuffer[i] = 0;
|
|
|
|
return LowerCaseBuffer;
|
|
}
|
|
|
|
// Checks if a file handle is invalid
|
|
static GOBBool InvalidHandle(GOBFSHandle h)
|
|
{
|
|
return (GOBUInt32)h == 0xFFFFFFFF ? GOB_TRUE : GOB_FALSE;
|
|
}
|
|
|
|
// Endian conversion
|
|
#if VV_ENDIAN == VV_ENDIAN_LITTLE
|
|
static GOBUInt32 SwapBytes(GOBUInt32 x)
|
|
{
|
|
return
|
|
(x >> 24) |
|
|
((x >> 8) & 0xFF00) |
|
|
((x << 8) & 0xFF0000) |
|
|
(x << 24);
|
|
|
|
}
|
|
#else
|
|
static GOBUInt32 SwapBytes(GOBUInt32 x)
|
|
{
|
|
return x;
|
|
}
|
|
#endif
|
|
|
|
|
|
// Given a file name, get its index in the FileTable
|
|
static GOBInt32 GetFileTableEntry(const GOBChar* file)
|
|
{
|
|
GOBUInt32 entry;
|
|
GOBUInt32 hash;
|
|
|
|
// hash the file name
|
|
hash = crc32(0L, Z_NULL, 0);
|
|
hash = crc32(hash, (const unsigned char*)file, strlen(file));
|
|
|
|
// linear search for matching a matching hash
|
|
for (entry = 0; entry < ArchiveNumFiles; ++entry) {
|
|
if (FileTableBasic[entry].block != GOB_INVALID_BLOCK &&
|
|
FileTableBasic[entry].hash == hash)
|
|
{
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Mark the contents of cache and read buffer invalid
|
|
static GOBVoid InvalidateCache(GOBVoid)
|
|
{
|
|
GOBUInt32 i;
|
|
for (i = 0; i < NumCacheBlocks; ++i) {
|
|
CacheBlocks[i].block = 0xFFFFFFFF;
|
|
}
|
|
ReadBuffer.pos = 0xFFFFFFFF;
|
|
}
|
|
|
|
// Deallocate memory used by cache and read buffer
|
|
static GOBVoid FreeCache(GOBVoid)
|
|
{
|
|
GOBUInt32 i;
|
|
|
|
if (CacheBlocks) {
|
|
for (i = 0; i < NumCacheBlocks; ++i) {
|
|
if (CacheBlocks[i].data) MemFuncs.free(CacheBlocks[i].data);
|
|
CacheBlocks[i].data = NULL;
|
|
}
|
|
|
|
MemFuncs.free(CacheBlocks);
|
|
NumCacheBlocks = 0;
|
|
CacheBlocks = NULL;
|
|
}
|
|
}
|
|
|
|
// Write the file table to disk if the form of a GFC
|
|
static GOBError CommitFileTable(GOBVoid)
|
|
{
|
|
GOBUInt32 num;
|
|
struct GOBFileTableBasicEntry basic;
|
|
struct GOBFileTableExtEntry ext;
|
|
struct GOBBlockTableEntry block;
|
|
|
|
// open the GFC
|
|
GOBFSHandle handle = FSFuncs.open(ControlFileName, GOBACCESS_WRITE);
|
|
if (InvalidHandle(handle)) return GOBERR_FILE_WRITE;
|
|
|
|
// write the magic identifier
|
|
num = SwapBytes(GOB_MAGIC_IDENTIFIER);
|
|
if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE;
|
|
|
|
// write the size of the GOB
|
|
num = SwapBytes(ArchiveSize);
|
|
if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE;
|
|
|
|
// write number of blocks in archive
|
|
num = SwapBytes(ArchiveNumBlocks);
|
|
if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE;
|
|
|
|
// write number of file in archive
|
|
num = SwapBytes(ArchiveNumFiles);
|
|
if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE;
|
|
|
|
// write block table -- with endian conversion
|
|
for (num = 0; num < ArchiveNumBlocks; ++num) {
|
|
block.next = SwapBytes(BlockTable[num].next);
|
|
block.offset = SwapBytes(BlockTable[num].offset);
|
|
block.size = SwapBytes(BlockTable[num].size);
|
|
|
|
if (!FSFuncs.write(handle, &block, sizeof(block))) return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
// write block CRCs -- with endian conversion
|
|
for (num = 0; num < ArchiveNumBlocks; ++num) {
|
|
BlockCRC[num] = SwapBytes(BlockCRC[num]);
|
|
if (!FSFuncs.write(handle, &BlockCRC[num], sizeof(BlockCRC[num]))) {
|
|
return GOBERR_FILE_WRITE;
|
|
}
|
|
}
|
|
|
|
// write each basic table entry -- with endian conversion
|
|
for (num = 0; num < ArchiveNumFiles; ++num) {
|
|
basic.hash = SwapBytes(FileTableBasic[num].hash);
|
|
basic.block = SwapBytes(FileTableBasic[num].block);
|
|
basic.size = SwapBytes(FileTableBasic[num].size);
|
|
|
|
if (!FSFuncs.write(handle, &basic, sizeof(basic))) return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
// write each extended table entry -- with endian conversion
|
|
for (num = 0; num < ArchiveNumFiles; ++num) {
|
|
strcpy(ext.name, FileTableExt[num].name);
|
|
ext.crc = SwapBytes(FileTableExt[num].crc);
|
|
ext.time = SwapBytes(FileTableExt[num].time);
|
|
|
|
if (!FSFuncs.write(handle, &ext, sizeof(ext))) return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
// all done
|
|
FSFuncs.close(&handle);
|
|
FileTableDirty = GOB_FALSE;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
|
|
static GOBVoid DeallocTables(GOBVoid)
|
|
{
|
|
if (BlockTable) {
|
|
// free the block table
|
|
MemFuncs.free(BlockTable);
|
|
BlockTable = NULL;
|
|
}
|
|
|
|
if (BlockCRC) {
|
|
// free the block crc table
|
|
MemFuncs.free(BlockCRC);
|
|
BlockCRC = NULL;
|
|
}
|
|
|
|
if (CacheFileTable)
|
|
{
|
|
// free the block cache table
|
|
MemFuncs.free(CacheFileTable);
|
|
CacheFileTable = NULL;
|
|
}
|
|
|
|
if (FileTableBasic) {
|
|
// free the basic file table
|
|
MemFuncs.free(FileTableBasic);
|
|
FileTableBasic = NULL;
|
|
}
|
|
|
|
if (FileTableExt) {
|
|
// free the extended file table
|
|
MemFuncs.free(FileTableExt);
|
|
FileTableExt = NULL;
|
|
}
|
|
}
|
|
|
|
static GOBError AllocTables(GOBUInt32 num_blocks, GOBUInt32 num_files,
|
|
GOBBool extended, GOBBool safe)
|
|
{
|
|
GOBUInt32 num;
|
|
|
|
// dump any old tables
|
|
DeallocTables();
|
|
|
|
// allocate the block table
|
|
BlockTable = (struct GOBBlockTableEntry*)
|
|
MemFuncs.alloc(num_blocks * sizeof(struct GOBBlockTableEntry));
|
|
if (!BlockTable) return GOBERR_NO_MEMORY;
|
|
|
|
if (safe) {
|
|
// allocate the block crc table for verifying data validity
|
|
BlockCRC = (GOBUInt32*)MemFuncs.alloc(num_blocks * sizeof(GOBUInt32));
|
|
if (!BlockCRC) return GOBERR_NO_MEMORY;
|
|
}
|
|
else {
|
|
BlockCRC = NULL;
|
|
}
|
|
|
|
if (CacheFileActive)
|
|
{
|
|
// allocate the block cache bitfield
|
|
CacheFileTable = (GOBUInt32*)
|
|
MemFuncs.alloc((num_blocks / 32 + 1) * 4);
|
|
if (!CacheFileTable) return GOBERR_NO_MEMORY;
|
|
}
|
|
|
|
// allocate the basic file table
|
|
FileTableBasic = (struct GOBFileTableBasicEntry*)
|
|
MemFuncs.alloc(num_files * sizeof(struct GOBFileTableBasicEntry));
|
|
if (!FileTableBasic) return GOBERR_NO_MEMORY;
|
|
|
|
if (extended) {
|
|
// allocate the extended file table
|
|
FileTableExt = (struct GOBFileTableExtEntry*)
|
|
MemFuncs.alloc(num_files * sizeof(struct GOBFileTableExtEntry));
|
|
if (!FileTableExt) return GOBERR_NO_MEMORY;
|
|
}
|
|
else {
|
|
FileTableExt = NULL;
|
|
}
|
|
|
|
// clear the tables
|
|
for (num = 0; num < num_files; ++num) {
|
|
FileTableBasic[num].block = GOB_INVALID_BLOCK;
|
|
if (FileTableExt) FileTableExt[num].name[0] = 0;
|
|
}
|
|
|
|
for (num = 0; num < num_blocks; ++num) {
|
|
BlockTable[num].next = GOB_INVALID_BLOCK;
|
|
BlockTable[num].size = GOB_INVALID_SIZE;
|
|
}
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
|
|
// GOBInit
|
|
// Public function. Initialize the library.
|
|
GOBError GOBInit(struct GOBMemoryFuncSet* mem,
|
|
struct GOBFileSysFuncSet* file,
|
|
struct GOBCodecFuncSet* codec,
|
|
struct GOBCacheFileFuncSet* cache)
|
|
{
|
|
GOBInt32 i;
|
|
GOBError err;
|
|
|
|
if (LibraryInit) return GOBERR_ALREADY_INIT;
|
|
|
|
// setup the callbacks
|
|
MemFuncs = *mem;
|
|
FSFuncs = *file;
|
|
CodecFuncs = *codec;
|
|
if (cache) {
|
|
CacheFileFuncs = *cache;
|
|
CacheFileActive = GOB_TRUE;
|
|
} else {
|
|
CacheFileActive = GOB_FALSE;
|
|
}
|
|
|
|
// allocate decompression buffer
|
|
DecompBuffer = (GOBChar*)MemFuncs.alloc(GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD);
|
|
if (!DecompBuffer) return GOBERR_NO_MEMORY;
|
|
|
|
// clear open table
|
|
for (i = 0; i < GOB_MAX_OPEN_FILES; ++i) {
|
|
OpenFiles[i].valid = GOB_FALSE;
|
|
}
|
|
|
|
LibraryInit = GOB_TRUE;
|
|
|
|
err = GOBSetCacheSize(1);
|
|
if (err != GOBERR_OK) {
|
|
LibraryInit = GOB_FALSE;
|
|
return err;
|
|
}
|
|
|
|
ReadBuffer.data = NULL;
|
|
err = GOBSetReadBufferSize(128*1024);
|
|
if (err != GOBERR_OK) {
|
|
LibraryInit = GOB_FALSE;
|
|
return err;
|
|
}
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBShutdown
|
|
// Public function. Close the library.
|
|
GOBError GOBShutdown(GOBVoid)
|
|
{
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
|
|
// if we have an open archive, close it
|
|
if (!InvalidHandle(ArchiveHandle)) GOBArchiveClose();
|
|
|
|
FreeCache();
|
|
|
|
// free read ahead buffer
|
|
if (ReadBuffer.data) {
|
|
MemFuncs.free(ReadBuffer.data);
|
|
ReadBuffer.data = NULL;
|
|
}
|
|
|
|
// free decompression buffer
|
|
MemFuncs.free(DecompBuffer);
|
|
|
|
// free the file and block tables
|
|
DeallocTables();
|
|
|
|
LibraryInit = GOB_FALSE;
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
|
|
// GOBArchiveCreate
|
|
// Public function. Create an empty GFC and GOB.
|
|
GOBError GOBArchiveCreate(const GOBChar* file)
|
|
{
|
|
GOBChar fname[GOB_MAX_FILE_NAME_LEN];
|
|
GOBFSHandle handle;
|
|
GOBError error;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (!InvalidHandle(ArchiveHandle)) return GOBERR_ALREADY_OPEN;
|
|
|
|
// Allocate the max space for tables
|
|
error = AllocTables(GOB_MAX_BLOCKS, GOB_MAX_FILES, GOB_TRUE, GOB_TRUE);
|
|
if (GOBERR_OK != error) {
|
|
return error;
|
|
}
|
|
|
|
// create an empty GFC
|
|
_snprintf(ControlFileName, GOB_MAX_FILE_NAME_LEN, "%s.gfc", file);
|
|
|
|
ArchiveSize = 0;
|
|
ArchiveNumBlocks = 0;
|
|
ArchiveNumFiles = 0;
|
|
CacheFileActive = GOB_FALSE;
|
|
|
|
CommitFileTable();
|
|
|
|
// create an empty GOB
|
|
_snprintf(fname, GOB_MAX_FILE_NAME_LEN, "%s.gob", file);
|
|
handle = FSFuncs.open(fname, GOBACCESS_WRITE);
|
|
if (InvalidHandle(handle)) return GOBERR_CANNOT_CREATE;
|
|
|
|
FSFuncs.close(&handle);
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBArchiveOpen
|
|
// Public function. Open a GOB file and cache file tables.
|
|
GOBError GOBArchiveOpen(const GOBChar* file, GOBAccessType atype,
|
|
GOBBool extended, GOBBool safe)
|
|
{
|
|
GOBChar fname[GOB_MAX_FILE_NAME_LEN];
|
|
GOBFSHandle handle;
|
|
GOBUInt32 magic;
|
|
GOBUInt32 i;
|
|
GOBError error;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (!InvalidHandle(ArchiveHandle)) return GOBERR_ALREADY_OPEN;
|
|
|
|
// open the GFC
|
|
_snprintf(ControlFileName, GOB_MAX_FILE_NAME_LEN, "%s.gfc", file);
|
|
handle = FSFuncs.open(ControlFileName, atype);
|
|
if (InvalidHandle(handle)) return GOBERR_FILE_NOT_FOUND;
|
|
|
|
// read and check the magic
|
|
if (!FSFuncs.read(handle, &magic, sizeof(magic))) return GOBERR_FILE_READ;
|
|
if (SwapBytes(magic) != GOB_MAGIC_IDENTIFIER) return GOBERR_NOT_GOB_FILE;
|
|
|
|
// read the GOB archive size
|
|
if (!FSFuncs.read(handle, &ArchiveSize, sizeof(ArchiveSize))) return GOBERR_FILE_READ;
|
|
ArchiveSize = SwapBytes(ArchiveSize);
|
|
|
|
// read the number of blocks
|
|
if (!FSFuncs.read(handle, &ArchiveNumBlocks, sizeof(ArchiveNumBlocks))) return GOBERR_FILE_READ;
|
|
ArchiveNumBlocks = SwapBytes(ArchiveNumBlocks);
|
|
|
|
// read the number of files
|
|
if (!FSFuncs.read(handle, &ArchiveNumFiles, sizeof(ArchiveNumFiles))) return GOBERR_FILE_READ;
|
|
ArchiveNumFiles = SwapBytes(ArchiveNumFiles);
|
|
|
|
// Allocate the space for tables
|
|
if (atype == GOBACCESS_READ) {
|
|
error = AllocTables(ArchiveNumBlocks, ArchiveNumFiles, extended, safe);
|
|
}
|
|
else {
|
|
error = AllocTables(GOB_MAX_BLOCKS, GOB_MAX_FILES, extended, safe);
|
|
}
|
|
if (GOBERR_OK != error) {
|
|
return error;
|
|
}
|
|
|
|
// read the block table
|
|
if (ArchiveNumBlocks &&
|
|
!FSFuncs.read(handle, BlockTable,
|
|
sizeof(struct GOBBlockTableEntry) * ArchiveNumBlocks))
|
|
{
|
|
return GOBERR_FILE_READ;
|
|
}
|
|
|
|
if (BlockCRC) {
|
|
// read the block CRCs
|
|
if (ArchiveNumBlocks &&
|
|
!FSFuncs.read(handle, BlockCRC,
|
|
sizeof(GOBUInt32) * ArchiveNumBlocks))
|
|
{
|
|
return GOBERR_FILE_READ;
|
|
}
|
|
}
|
|
else {
|
|
// skip block CRCs
|
|
FSFuncs.seek(handle, sizeof(GOBUInt32) * ArchiveNumBlocks,
|
|
GOBSEEK_CURRENT);
|
|
}
|
|
|
|
if (CacheFileActive)
|
|
{
|
|
// clear the block cache table
|
|
for (i = 0; i < ArchiveNumBlocks / 32; ++i) {
|
|
CacheFileTable[i] = 0;
|
|
}
|
|
}
|
|
|
|
// open the cache file
|
|
if (CacheFileActive && !CacheFileFuncs.open(ArchiveSize)) {
|
|
CacheFileActive = GOB_FALSE;
|
|
}
|
|
|
|
// endian convert the table
|
|
for (i = 0; i < ArchiveNumBlocks; ++i) {
|
|
BlockTable[i].next = SwapBytes(BlockTable[i].next);
|
|
BlockTable[i].offset = SwapBytes(BlockTable[i].offset);
|
|
BlockTable[i].size = SwapBytes(BlockTable[i].size);
|
|
|
|
if (BlockCRC) {
|
|
BlockCRC[i] = SwapBytes(BlockCRC[i]);
|
|
}
|
|
}
|
|
|
|
// read the basic file table
|
|
if (ArchiveNumFiles &&
|
|
!FSFuncs.read(handle, FileTableBasic,
|
|
sizeof(struct GOBFileTableBasicEntry) * ArchiveNumFiles))
|
|
{
|
|
return GOBERR_FILE_READ;
|
|
}
|
|
|
|
// endian convert the table
|
|
for (i = 0; i < ArchiveNumFiles; ++i) {
|
|
FileTableBasic[i].hash = SwapBytes(FileTableBasic[i].hash);
|
|
FileTableBasic[i].block = SwapBytes(FileTableBasic[i].block);
|
|
FileTableBasic[i].size = SwapBytes(FileTableBasic[i].size);
|
|
}
|
|
|
|
// if we have memory for the extended file table
|
|
if (FileTableExt) {
|
|
// read the table
|
|
if (ArchiveNumFiles &&
|
|
!FSFuncs.read(handle, FileTableExt,
|
|
sizeof(struct GOBFileTableExtEntry) * ArchiveNumFiles))
|
|
{
|
|
return GOBERR_FILE_READ;
|
|
}
|
|
|
|
// endian convert the table
|
|
for (i = 0; i < ArchiveNumFiles; ++i) {
|
|
FileTableExt[i].crc = SwapBytes(FileTableExt[i].crc);
|
|
FileTableExt[i].time = SwapBytes(FileTableExt[i].time);
|
|
}
|
|
}
|
|
|
|
FSFuncs.close(&handle);
|
|
|
|
// open the GOB
|
|
_snprintf(fname, GOB_MAX_FILE_NAME_LEN, "%s.gob", file);
|
|
ArchiveHandle = FSFuncs.open(fname, atype);
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_FILE_NOT_FOUND;
|
|
|
|
// initialize stats gathering
|
|
CurrentArchivePos = 0;
|
|
ReadStats.bufferUsed = 0;
|
|
ReadStats.bytesRead = 0;
|
|
ReadStats.cacheBytesRead = 0;
|
|
ReadStats.cacheBytesWrite = 0;
|
|
ReadStats.totalSeeks = 0;
|
|
ReadStats.farSeeks = 0;
|
|
ReadStats.filesOpened = 0;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBArchiveClose
|
|
// Public function. Close an open GOB archive.
|
|
GOBError GOBArchiveClose(GOBVoid)
|
|
{
|
|
GOBInt32 i;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
|
|
// close any open files
|
|
for (i = 0; i < GOB_MAX_OPEN_FILES; ++i) {
|
|
GOBClose(i);
|
|
}
|
|
|
|
// close the GOB
|
|
FSFuncs.close(&ArchiveHandle);
|
|
ArchiveHandle = (GOBFSHandle*)0xFFFFFFFF;
|
|
|
|
// commit the file table if we're updated it
|
|
if (FileTableDirty) {
|
|
CommitFileTable();
|
|
}
|
|
|
|
// close the cache file
|
|
if (CacheFileActive) {
|
|
CacheFileFuncs.close();
|
|
CacheFileActive = GOB_FALSE;
|
|
}
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
static int CDECL SortBlockDescsCallback(const void* elem1, const void* elem2)
|
|
{
|
|
return (int)((struct GOBBlockTableEntry *)elem1)->offset -
|
|
(int)((struct GOBBlockTableEntry *)elem2)->offset;
|
|
}
|
|
|
|
// GOBArchiveCheckMarkers
|
|
// Public function. Check start/end markers to check approximate validity of GOB file
|
|
GOBError GOBArchiveCheckMarkers(GOBVoid)
|
|
{
|
|
GOBUInt32 i;
|
|
GOBUInt32 valid_blocks;
|
|
struct GOBBlockTableEntry *blocks;
|
|
GOBUInt32 block;
|
|
GOBUInt32 start_marker;
|
|
GOBUInt32 end_marker;
|
|
GOBBool ok;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
|
|
// count valid blocks
|
|
valid_blocks = 0;
|
|
for (i = 0; i < ArchiveNumBlocks; i++)
|
|
{
|
|
if (BlockTable[i].size != GOB_INVALID_SIZE &&
|
|
BlockTable[i].next != GOB_INVALID_BLOCK)
|
|
{
|
|
valid_blocks++;
|
|
}
|
|
}
|
|
|
|
// arcvive is empty
|
|
if (valid_blocks == 0)
|
|
{
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// alloc mem for valid block list
|
|
blocks = (GOBBlockTableEntry *) MemFuncs.alloc(sizeof(*blocks) * valid_blocks);
|
|
if (blocks == NULL)
|
|
{
|
|
return GOBERR_NO_MEMORY;
|
|
}
|
|
|
|
// copy valid blocks descriptions
|
|
block = 0;
|
|
for (i = 0; i < ArchiveNumBlocks; ++i)
|
|
{
|
|
if (BlockTable[i].size != GOB_INVALID_SIZE &&
|
|
BlockTable[i].next != GOB_INVALID_BLOCK)
|
|
{
|
|
blocks[block++] = BlockTable[i];
|
|
}
|
|
}
|
|
assert(block == valid_blocks);
|
|
|
|
// and sort 'em
|
|
qsort(blocks, valid_blocks, sizeof(*blocks), SortBlockDescsCallback);
|
|
|
|
// suppress some warnings
|
|
start_marker = 0;
|
|
end_marker = 0;
|
|
|
|
// now scan entire archive for start-of-block and end-of-block markers
|
|
for (i = 0; i < valid_blocks; i++)
|
|
{
|
|
ok = GOB_TRUE;
|
|
ok = ok && !FSFuncs.seek(ArchiveHandle, blocks[i].offset, GOBSEEK_START);
|
|
ok = ok && FSFuncs.read(ArchiveHandle, &start_marker, sizeof(GOBUInt32)) == sizeof(GOBUInt32);
|
|
ok = ok && !FSFuncs.seek(ArchiveHandle, blocks[i].offset + blocks[i].size - sizeof(GOBUInt32), GOBSEEK_START);
|
|
ok = ok && FSFuncs.read(ArchiveHandle, &end_marker, sizeof(GOBUInt32)) == sizeof(GOBUInt32);
|
|
if (!ok ||
|
|
SwapBytes(start_marker) != GOBMARKER_STARTBLOCK ||
|
|
SwapBytes(end_marker) != GOBMARKER_ENDBLOCK)
|
|
{
|
|
MemFuncs.free(blocks);
|
|
|
|
return GOBERR_NOT_GOB_FILE;
|
|
}
|
|
}
|
|
|
|
MemFuncs.free(blocks);
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBArchiveCreate
|
|
// Public function. Create an empty GFC and GOB.
|
|
GOBUInt32 GOBGetSlack(GOBUInt32 x)
|
|
{
|
|
GOBUInt32 align = x % GOB_BLOCK_ALIGNMENT;
|
|
if (align) return GOB_BLOCK_ALIGNMENT - align;
|
|
return 0;
|
|
}
|
|
|
|
// GOBOpen
|
|
// Public function. Open a file inside a GOB.
|
|
GOBError GOBOpen(GOBChar* file, GOBHandle* handle)
|
|
{
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
|
|
// find a free handle
|
|
for (*handle = 0; *handle < GOB_MAX_OPEN_FILES; (*handle) += 1) {
|
|
if (!OpenFiles[*handle].valid) break;
|
|
}
|
|
|
|
if (*handle >= GOB_MAX_OPEN_FILES) return GOBERR_TOO_MANY_OPEN;
|
|
|
|
// find the file in the table
|
|
lfile = LowerCase(file);
|
|
|
|
entry = GetFileTableEntry(lfile);
|
|
|
|
if (entry == -1) return GOBERR_FILE_NOT_FOUND;
|
|
|
|
// setup the open file
|
|
OpenFiles[*handle].startBlock = OpenFiles[*handle].block =
|
|
FileTableBasic[entry].block;
|
|
OpenFiles[*handle].size = FileTableBasic[entry].size;
|
|
OpenFiles[*handle].offset = 0;
|
|
OpenFiles[*handle].pos = 0;
|
|
|
|
OpenFiles[*handle].valid = GOB_TRUE;
|
|
|
|
++ReadStats.filesOpened;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBOpenCode
|
|
// Public function. Open file with a code inside a GOB.
|
|
GOBError GOBOpenCode(GOBInt32 code, GOBHandle* handle)
|
|
{
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
|
|
// find a free handle
|
|
for (*handle = 0; *handle < GOB_MAX_OPEN_FILES; (*handle) += 1) {
|
|
if (!OpenFiles[*handle].valid) break;
|
|
}
|
|
|
|
if (*handle >= GOB_MAX_OPEN_FILES) return GOBERR_TOO_MANY_OPEN;
|
|
|
|
// setup the open file
|
|
OpenFiles[*handle].startBlock = OpenFiles[*handle].block =
|
|
FileTableBasic[code].block;
|
|
OpenFiles[*handle].size = FileTableBasic[code].size;
|
|
OpenFiles[*handle].offset = 0;
|
|
OpenFiles[*handle].pos = 0;
|
|
|
|
OpenFiles[*handle].valid = GOB_TRUE;
|
|
|
|
++ReadStats.filesOpened;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBClose
|
|
// Public function. Close a file.
|
|
GOBError GOBClose(GOBHandle handle)
|
|
{
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
if (!OpenFiles[handle].valid) return GOBERR_NOT_OPEN;
|
|
|
|
// close the file by simply invalidating the open
|
|
// file table entry
|
|
OpenFiles[handle].valid = GOB_FALSE;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
static GOBUInt32 RawRead(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 pos)
|
|
{
|
|
GOBUInt32 bytes;
|
|
|
|
// Reads _must_ be aligned otherwise things get very slow
|
|
if (pos % GOB_BLOCK_ALIGNMENT) {
|
|
return 0;
|
|
}
|
|
if ((GOBUInt32)buffer % GOB_MEM_ALIGNMENT) {
|
|
return 0;
|
|
}
|
|
|
|
// seek
|
|
if (FSFuncs.seek(ArchiveHandle, pos, GOBSEEK_START)) return 0;
|
|
|
|
if (CurrentArchivePos != pos) ++ReadStats.totalSeeks;
|
|
if (pos > CurrentArchivePos + GOB_BLOCK_ALIGNMENT ||
|
|
CurrentArchivePos > pos + GOB_BLOCK_ALIGNMENT)
|
|
{
|
|
++ReadStats.farSeeks;
|
|
}
|
|
|
|
// read
|
|
bytes = FSFuncs.read(ArchiveHandle, buffer, size);
|
|
|
|
ReadStats.bytesRead += bytes;
|
|
CurrentArchivePos = pos + bytes;
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static GOBUInt32 CacheRawRead(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 pos)
|
|
{
|
|
GOBUInt32 bytes;
|
|
|
|
// Reads _must_ be aligned otherwise things get very slow
|
|
if (pos % GOB_BLOCK_ALIGNMENT) {
|
|
return 0;
|
|
}
|
|
if ((GOBUInt32)buffer % GOB_MEM_ALIGNMENT) {
|
|
return 0;
|
|
}
|
|
|
|
// seek
|
|
if (CacheFileFuncs.seek(pos)) return 0;
|
|
|
|
// read
|
|
bytes = CacheFileFuncs.read(buffer, size);
|
|
ReadStats.cacheBytesRead += bytes;
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static GOBUInt32 CacheRawWrite(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 pos)
|
|
{
|
|
GOBUInt32 bytes;
|
|
|
|
// Writes _must_ be aligned otherwise things get very slow
|
|
if (pos % GOB_BLOCK_ALIGNMENT) {
|
|
return 0;
|
|
}
|
|
if ((GOBUInt32)buffer % GOB_MEM_ALIGNMENT) {
|
|
return 0;
|
|
}
|
|
|
|
// seek
|
|
if (CacheFileFuncs.seek(pos)) return 0;
|
|
|
|
// write
|
|
bytes = CacheFileFuncs.write(buffer, size);
|
|
ReadStats.cacheBytesWrite += bytes;
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static GOBInt32 BlockReadLow(GOBUInt32 block)
|
|
{
|
|
GOBUInt32 pos;
|
|
GOBUInt32 bytes;
|
|
GOBBool cache_read;
|
|
GOBBool cache_write;
|
|
GOBBool cache_fail;
|
|
|
|
pos = 0;
|
|
cache_read = GOB_FALSE;
|
|
cache_write = GOB_FALSE;
|
|
cache_fail = GOB_FALSE;
|
|
|
|
for (;;) {
|
|
// is the block in the read ahead buffer?
|
|
if (ReadBuffer.pos <= BlockTable[block].offset + pos &&
|
|
ReadBuffer.pos + ReadBuffer.size > BlockTable[block].offset + pos)
|
|
{
|
|
GOBUInt32 buffer_offset;
|
|
GOBUInt32 buffer_size;
|
|
|
|
// use data in the read buffer
|
|
buffer_offset = BlockTable[block].offset + pos - ReadBuffer.pos;
|
|
buffer_size = ReadBuffer.size - buffer_offset;
|
|
|
|
// clamp size
|
|
if (buffer_size > BlockTable[block].size - pos) {
|
|
buffer_size = BlockTable[block].size - pos;
|
|
}
|
|
|
|
memcpy(&DecompBuffer[pos], &ReadBuffer.dataStart[buffer_offset], buffer_size);
|
|
|
|
pos += buffer_size;
|
|
}
|
|
|
|
// got enough data
|
|
if (pos == BlockTable[block].size) break;
|
|
|
|
// refill read buffer
|
|
ReadBuffer.pos = BlockTable[block].offset + pos;
|
|
ReadBuffer.pos -= ReadBuffer.pos % GOB_BLOCK_ALIGNMENT;
|
|
|
|
// check if block is in the external cache system
|
|
if (CacheFileActive &&
|
|
CacheFileTable[block / 32] & (1 << (block % 32)))
|
|
{
|
|
if (CacheRawRead(ReadBuffer.dataStart,
|
|
ReadBuffer.size, ReadBuffer.pos))
|
|
{
|
|
cache_read = GOB_TRUE;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// read block from archive
|
|
bytes = RawRead(ReadBuffer.dataStart, ReadBuffer.size, ReadBuffer.pos);
|
|
if (bytes != ReadBuffer.size &&
|
|
bytes != ArchiveSize - ReadBuffer.pos)
|
|
{
|
|
return -1; // Main read fail error code
|
|
}
|
|
|
|
// write block to cache file
|
|
if (CacheFileActive)
|
|
{
|
|
if (CacheRawWrite(ReadBuffer.dataStart, bytes,
|
|
ReadBuffer.pos) == bytes)
|
|
{
|
|
cache_write = GOB_TRUE;
|
|
}
|
|
else
|
|
{
|
|
cache_fail = GOB_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cache_write) {
|
|
if (!cache_fail) return 2;
|
|
return 0;
|
|
}
|
|
|
|
if (cache_read) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static GOBBool BlockReadWithCache(GOBUInt32 block)
|
|
{
|
|
GOBInt32 i;
|
|
|
|
for (i = 0; i < GOB_READ_RETRYS; ++i) {
|
|
GOBInt32 result;
|
|
|
|
// read the data
|
|
result = BlockReadLow(block);
|
|
if (result >= 0)
|
|
{
|
|
if (BlockCRC) {
|
|
// crc check
|
|
GOBUInt32 crc;
|
|
|
|
crc = adler32(0L, Z_NULL, 0);
|
|
crc = adler32(crc, (const unsigned char*)DecompBuffer,
|
|
BlockTable[block].size);
|
|
|
|
if (BlockCRC[block] != crc) {
|
|
// crc mismatch, we must have got bad data --
|
|
// try invalidating the cache and retrying...
|
|
if (CacheFileActive) {
|
|
CacheFileTable[block / 32] &= ~(1 << (block % 32));
|
|
}
|
|
ReadBuffer.pos = 0xFFFFFFFF;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// if cache write occurred -- mark block as cached
|
|
if (result == 2) {
|
|
CacheFileTable[block / 32] |= (1 << (block % 32));
|
|
}
|
|
|
|
// read success, crc success (or no check performed)
|
|
return GOB_TRUE;
|
|
}
|
|
}
|
|
|
|
// multiple read/crc failures
|
|
return GOB_FALSE;
|
|
}
|
|
|
|
static GOBUInt32 BlockRead(GOBVoid* buffer, GOBUInt32 block)
|
|
{
|
|
GOBUInt32 size;
|
|
GOBInt32 codec_index;
|
|
GOBChar *compressed_data;
|
|
|
|
// read block from cache or archive
|
|
if (!BlockReadWithCache(block))
|
|
{
|
|
return GOB_INVALID_SIZE;
|
|
}
|
|
|
|
// decompress
|
|
codec_index = 0;
|
|
size = 0; // Initialize to satisfy compiler
|
|
compressed_data = DecompBuffer + sizeof(GOBUInt32); // skip start-of-block marker
|
|
while (codec_index < CodecFuncs.codecs) {
|
|
// Check if codec matches
|
|
if (*compressed_data == CodecFuncs.codec[codec_index].tag) {
|
|
size = GOB_BLOCK_SIZE;
|
|
if (CodecFuncs.codec[codec_index].decompress(compressed_data + 1,
|
|
BlockTable[block].size - 1 - sizeof(GOBUInt32) * 2, buffer, &size)) {
|
|
return GOB_INVALID_SIZE;
|
|
}
|
|
break;
|
|
}
|
|
codec_index++;
|
|
}
|
|
|
|
// If no suitable codecs were found, we're screwed
|
|
if (codec_index == CodecFuncs.codecs) {
|
|
return GOB_INVALID_SIZE;
|
|
}
|
|
|
|
if (ProfileReadCallback && ProfileEnabled) {
|
|
// register current read command
|
|
ProfileReadCallback(block);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static GOBVoid FillCacheBlock(GOBUInt32 block, GOBUInt32 index)
|
|
{
|
|
CacheBlocks[index].time = CacheBlockCounter++;
|
|
CacheBlocks[index].block = block;
|
|
CacheBlocks[index].size = BlockRead(CacheBlocks[index].data, block);
|
|
}
|
|
|
|
static GOBInt32 FindBestCacheBlock(GOBUInt32 block)
|
|
{
|
|
GOBInt32 i;
|
|
GOBUInt32 oldest_time;
|
|
GOBInt32 oldest_index;
|
|
|
|
oldest_time = 0xFFFFFFFF;
|
|
oldest_index = -1;
|
|
|
|
for (i = 0; i < (signed)NumCacheBlocks; ++i) {
|
|
if (CacheBlocks[i].block == block) {
|
|
// if block is in this read buffer, use it
|
|
return i;
|
|
}
|
|
|
|
// find the buffer that hasn't been accessed
|
|
// for the longest time
|
|
if (CacheBlocks[i].time < oldest_time) {
|
|
oldest_time = CacheBlocks[i].time;
|
|
oldest_index = i;
|
|
}
|
|
}
|
|
|
|
// use the buffer that hasn't been accessed
|
|
// in the longest time
|
|
return oldest_index;
|
|
}
|
|
|
|
// GOBRead
|
|
// Public function. Read from an open file using
|
|
// a funky read-ahead buffer system.
|
|
GOBUInt32 GOBRead(GOBVoid* buffer, GOBUInt32 size, GOBHandle handle)
|
|
{
|
|
GOBUInt32 pos;
|
|
GOBInt32 cache_id;
|
|
|
|
if (!LibraryInit) return 0;
|
|
if (InvalidHandle(ArchiveHandle)) return 0;
|
|
if (!OpenFiles[handle].valid) return 0;
|
|
|
|
// make sure we're reading within the file
|
|
if (OpenFiles[handle].pos + size > OpenFiles[handle].size) {
|
|
size = OpenFiles[handle].size - OpenFiles[handle].pos;
|
|
if (!size) return 0;
|
|
}
|
|
|
|
cache_id = FindBestCacheBlock(OpenFiles[handle].block);
|
|
if (cache_id < 0) return GOB_INVALID_SIZE;
|
|
|
|
pos = OpenFiles[handle].pos;
|
|
|
|
for (;;) {
|
|
// are looking for data inside the read buffer?
|
|
if (CacheBlocks[cache_id].block == OpenFiles[handle].block) {
|
|
// move any relevant data from the read buffer to the target buffer
|
|
GOBUInt32 buffer_size;
|
|
|
|
// calc size of data we want from current buffer
|
|
buffer_size = CacheBlocks[cache_id].size - OpenFiles[handle].offset;
|
|
if (buffer_size > size) buffer_size = size;
|
|
|
|
// move from read buffer into output buffer
|
|
memcpy(&((char*)buffer)[OpenFiles[handle].pos - pos],
|
|
&CacheBlocks[cache_id].data[OpenFiles[handle].offset],
|
|
buffer_size);
|
|
|
|
// update file position
|
|
OpenFiles[handle].pos += buffer_size;
|
|
OpenFiles[handle].offset += buffer_size;
|
|
|
|
// if we've completed this block -- move to next
|
|
if (OpenFiles[handle].offset == CacheBlocks[cache_id].size) {
|
|
OpenFiles[handle].block = BlockTable[OpenFiles[handle].block].next;
|
|
OpenFiles[handle].offset = 0;
|
|
}
|
|
|
|
CacheBlocks[cache_id].time = CacheBlockCounter++;
|
|
|
|
ReadStats.bufferUsed += buffer_size;
|
|
size -= buffer_size;
|
|
if (size == 0) break;
|
|
}
|
|
|
|
// refill the buffer
|
|
FillCacheBlock(OpenFiles[handle].block, cache_id);
|
|
if (CacheBlocks[cache_id].size == GOB_INVALID_SIZE) {
|
|
CacheBlocks[cache_id].block = GOB_INVALID_BLOCK;
|
|
return GOB_INVALID_SIZE;
|
|
}
|
|
|
|
// reading off the end of the archive
|
|
if (CacheBlocks[cache_id].block != OpenFiles[handle].block) break;
|
|
}
|
|
|
|
return OpenFiles[handle].pos - pos;
|
|
}
|
|
|
|
// GOBSeek
|
|
// Public function. Seek to a position in an open file.
|
|
GOBError GOBSeek(GOBHandle handle, GOBUInt32 offset, GOBSeekType type, GOBUInt32* pos)
|
|
{
|
|
GOBUInt32 blocks;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
if (!OpenFiles[handle].valid) return GOBERR_NOT_OPEN;
|
|
|
|
// find a new position based on the seek type
|
|
switch (type) {
|
|
case GOBSEEK_START:
|
|
*pos = offset;
|
|
break;
|
|
|
|
case GOBSEEK_CURRENT:
|
|
*pos = OpenFiles[handle].pos + offset;
|
|
break;
|
|
|
|
case GOBSEEK_END:
|
|
*pos = OpenFiles[handle].size + offset;
|
|
break;
|
|
|
|
default:
|
|
return GOBERR_INVALID_SEEK;
|
|
}
|
|
|
|
// check to make sure we're still in the file
|
|
if (*pos > OpenFiles[handle].size) {
|
|
return GOBERR_INVALID_SEEK;
|
|
}
|
|
|
|
// update the file position
|
|
OpenFiles[handle].pos = *pos;
|
|
|
|
// update block
|
|
blocks = *pos / GOB_BLOCK_SIZE;
|
|
OpenFiles[handle].block = OpenFiles[handle].startBlock;
|
|
while (blocks--) {
|
|
OpenFiles[handle].block = BlockTable[OpenFiles[handle].block].next;
|
|
}
|
|
|
|
// update position inside block
|
|
OpenFiles[handle].offset = *pos % GOB_BLOCK_SIZE;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
|
|
static GOBUInt32 FindFreeBlock(GOBVoid)
|
|
{
|
|
GOBInt32 i;
|
|
for (i = 0; i < GOB_MAX_BLOCKS; ++i) {
|
|
if (BlockTable[i].next == GOB_INVALID_BLOCK) return i;
|
|
}
|
|
return GOB_MAX_BLOCKS;
|
|
}
|
|
|
|
// GOBWrite
|
|
// Public function. Write an entire file. The file should not be open!
|
|
GOBError GOBWrite(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 mtime, const GOBChar* file, GOBUInt32 codec_mask)
|
|
{
|
|
GOBHandle handle;
|
|
GOBInt32 slack;
|
|
GOBChar* lfile;
|
|
GOBUInt32 hash;
|
|
GOBUInt32 crc;
|
|
GOBInt32 i;
|
|
GOBChar* out;
|
|
GOBUInt32 pos;
|
|
GOBUInt32 last_block;
|
|
GOBInt32 codec_index;
|
|
GOBInt32 compression_ratio;
|
|
GOBChar* out_data;
|
|
GOBUInt32 compressed_size;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
if (!FileTableExt) return GOBERR_NO_EXTENDED;
|
|
if (!BlockCRC) return GOBERR_NO_EXTENDED;
|
|
|
|
InvalidateCache();
|
|
|
|
// delete the file if it exists
|
|
GOBDelete(file);
|
|
|
|
// find a free entry in the file table
|
|
for (handle = 0; handle < GOB_MAX_FILES; ++handle) {
|
|
if (FileTableBasic[handle].block == GOB_INVALID_BLOCK) break;
|
|
}
|
|
|
|
if (handle >= GOB_MAX_FILES) return GOBERR_TOO_MANY_FILES;
|
|
if (handle >= (GOBInt32)ArchiveNumFiles) ArchiveNumFiles = handle + 1;
|
|
|
|
// move to the end of the GOB
|
|
if (FSFuncs.seek(ArchiveHandle, 0, GOBSEEK_END)) {
|
|
return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
// alloc compression buffer
|
|
out = (GOBChar*)MemFuncs.alloc(GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD);
|
|
|
|
last_block = GOB_MAX_BLOCKS - 1;
|
|
|
|
for (pos = 0; pos < size; pos += GOB_BLOCK_SIZE) {
|
|
GOBUInt32 block;
|
|
GOBUInt32 in_size;
|
|
|
|
// get a free block
|
|
block = FindFreeBlock();
|
|
if (block >= GOB_MAX_BLOCKS) return GOBERR_TOO_MANY_BLOCKS;
|
|
if (block >= ArchiveNumBlocks) ArchiveNumBlocks = block + 1;
|
|
|
|
// if this is not the first block, mark next block for the last block
|
|
// else assign the first block in file table
|
|
if (pos != 0) BlockTable[last_block].next = block;
|
|
else FileTableBasic[handle].block = block;
|
|
|
|
// invalidate the next block
|
|
BlockTable[block].next = GOB_MAX_BLOCKS;
|
|
|
|
// compute the decompressed block size
|
|
in_size = size - pos;
|
|
if (in_size > GOB_BLOCK_SIZE) in_size = GOB_BLOCK_SIZE;
|
|
|
|
// compress block
|
|
|
|
for (
|
|
codec_index = 0;
|
|
codec_index < CodecFuncs.codecs;
|
|
codec_index++)
|
|
{
|
|
if ( ! (GOB_CODEC_MASK(codec_index) & codec_mask) )
|
|
{
|
|
// skip if this codec is not listed as one of the allowed ones
|
|
continue;
|
|
}
|
|
BlockTable[block].size = GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD;
|
|
out_data = out;
|
|
*(GOBUInt32*)out_data = SwapBytes(GOBMARKER_STARTBLOCK);
|
|
out_data += sizeof(GOBUInt32);
|
|
*out_data = CodecFuncs.codec[codec_index].tag;
|
|
out_data++;
|
|
if (CodecFuncs.codec[codec_index].compress(&((GOBChar*)buffer)[pos],
|
|
in_size, out_data, &BlockTable[block].size))
|
|
{
|
|
return GOBERR_COMPRESS_FAIL;
|
|
}
|
|
out_data += BlockTable[block].size;
|
|
*(GOBUInt32*)out_data = SwapBytes(GOBMARKER_ENDBLOCK);
|
|
out_data += sizeof(GOBUInt32);
|
|
|
|
// Adjust for the prefixed start-of-block marker and codec tag and trailing end-of-block marker
|
|
compressed_size = BlockTable[block].size;
|
|
BlockTable[block].size += 1 + sizeof(GOBUInt32) * 2;
|
|
|
|
// Check compression result
|
|
compression_ratio = compressed_size * 100 / in_size;
|
|
if (compression_ratio <= CodecFuncs.codec[codec_index].max_ratio)
|
|
{
|
|
// Compressed result is under par. Let's go with it
|
|
break;
|
|
}
|
|
|
|
// Otherwise, try the next compressor
|
|
}
|
|
|
|
// If no suitable codecs were found, take our ball and go home
|
|
if (codec_index == CodecFuncs.codecs) return GOBERR_NO_SUITABLE_CODEC;
|
|
|
|
// compute and store the CRC
|
|
BlockCRC[block] = adler32(0L, Z_NULL, 0);
|
|
BlockCRC[block] = adler32(BlockCRC[block], (const unsigned char*)out,
|
|
BlockTable[block].size);
|
|
|
|
// write block
|
|
if (FSFuncs.write(ArchiveHandle, out, BlockTable[block].size) !=
|
|
(signed)BlockTable[block].size)
|
|
{
|
|
return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
// compute the slack (to keep alignment)
|
|
slack = GOBGetSlack(BlockTable[block].size);
|
|
|
|
// write the slack space
|
|
memset(out, 0, slack);
|
|
if (FSFuncs.write(ArchiveHandle, out, slack) != slack) {
|
|
return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
BlockTable[block].offset = ArchiveSize;
|
|
ArchiveSize += BlockTable[block].size + slack;
|
|
|
|
last_block = block;
|
|
}
|
|
|
|
MemFuncs.free(out);
|
|
|
|
lfile = LowerCase(file);
|
|
|
|
// calculate file name hash
|
|
hash = crc32(0L, Z_NULL, 0);
|
|
hash = crc32(hash, (const unsigned char*)lfile, strlen(lfile));
|
|
|
|
// make sure hash is unique
|
|
for (i = 0; i < GOB_MAX_FILES; ++i) {
|
|
if (i != handle &&
|
|
FileTableBasic[i].block != GOB_INVALID_BLOCK &&
|
|
FileTableBasic[i].hash == hash)
|
|
{
|
|
return GOBERR_DUP_HASH;
|
|
}
|
|
}
|
|
|
|
// update the file tables
|
|
FileTableBasic[handle].hash = hash;
|
|
FileTableBasic[handle].size = size;
|
|
|
|
strcpy(FileTableExt[handle].name, lfile);
|
|
|
|
crc = crc32(0L, Z_NULL, 0);
|
|
crc = crc32(crc, (const unsigned char*)buffer, size);
|
|
FileTableExt[handle].crc = crc;
|
|
|
|
FileTableExt[handle].time = mtime;
|
|
|
|
FileTableDirty = GOB_TRUE;
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBDelete
|
|
// Public function. Delete a file from a GOB. The file should not be open!
|
|
GOBError GOBDelete(const GOBChar* file)
|
|
{
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
GOBUInt32 block;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
if (!FileTableExt) return GOBERR_NO_EXTENDED;
|
|
if (!BlockCRC) return GOBERR_NO_EXTENDED;
|
|
|
|
// find the file in the table
|
|
lfile = LowerCase(file);
|
|
|
|
entry = GetFileTableEntry(lfile);
|
|
if (entry == -1) return GOBERR_FILE_NOT_FOUND;
|
|
|
|
// invalidate blocks
|
|
block = FileTableBasic[entry].block;
|
|
do {
|
|
GOBUInt32 next;
|
|
next = BlockTable[block].next;
|
|
BlockTable[block].next = GOB_INVALID_BLOCK;
|
|
block = next;
|
|
} while(block != GOB_MAX_BLOCKS);
|
|
|
|
// invalidate the file
|
|
FileTableBasic[entry].block = GOB_INVALID_BLOCK;
|
|
|
|
FileTableDirty = GOB_TRUE;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBRearrange
|
|
// Public function. Sorts the blocks in an archive.
|
|
GOBError GOBRearrange(const GOBChar* file, const GOBUInt32* xlat, GOBFileSysRenameFunc _rename)
|
|
{
|
|
GOBError err;
|
|
GOBVoid* buffer;
|
|
GOBInt32 slack;
|
|
GOBVoid* slack_buf;
|
|
GOBUInt32 i;
|
|
GOBUInt32 size;
|
|
GOBFSHandle temp_handle;
|
|
GOBChar full_name[GOB_MAX_FILE_NAME_LEN];
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (!InvalidHandle(ArchiveHandle)) return GOBERR_ALREADY_OPEN;
|
|
if (!FileTableExt) return GOBERR_NO_EXTENDED;
|
|
if (!BlockCRC) return GOBERR_NO_EXTENDED;
|
|
|
|
// start things up
|
|
err = GOBArchiveOpen(file, GOBACCESS_READ, GOB_TRUE, GOB_TRUE);
|
|
if (err != GOBERR_OK) return err;
|
|
|
|
// create temporary file
|
|
temp_handle = FSFuncs.open("~temp.tmp", GOBACCESS_WRITE);
|
|
if (InvalidHandle(temp_handle)) return GOBERR_FILE_WRITE;
|
|
|
|
size = 0;
|
|
|
|
// create an empty buffer for slack
|
|
slack_buf = MemFuncs.alloc(GOB_BLOCK_ALIGNMENT);
|
|
if (!slack_buf) return GOBERR_NO_MEMORY;
|
|
memset(slack_buf, 0, GOB_BLOCK_ALIGNMENT);
|
|
|
|
// get memory for block
|
|
buffer = MemFuncs.alloc(GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD);
|
|
if (!buffer) return GOBERR_NO_MEMORY;
|
|
|
|
// copy files in new order to end of archive
|
|
for (i = 0; i < ArchiveNumBlocks; ++i) {
|
|
if (BlockTable[xlat[i]].next != GOB_INVALID_BLOCK) {
|
|
// seek to the block
|
|
if (FSFuncs.seek(ArchiveHandle,
|
|
BlockTable[xlat[i]].offset, GOBSEEK_START))
|
|
{
|
|
return GOBERR_FILE_READ;
|
|
}
|
|
|
|
// read the block
|
|
if (FSFuncs.read(ArchiveHandle, buffer, BlockTable[xlat[i]].size) !=
|
|
(signed)BlockTable[xlat[i]].size)
|
|
{
|
|
return GOBERR_FILE_READ;
|
|
}
|
|
|
|
// write block
|
|
if (FSFuncs.write(temp_handle, buffer, BlockTable[xlat[i]].size) !=
|
|
(signed)BlockTable[xlat[i]].size)
|
|
{
|
|
return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
// write the slack
|
|
slack = GOBGetSlack(BlockTable[xlat[i]].size);
|
|
if (FSFuncs.write(temp_handle, slack_buf, slack) != slack) {
|
|
return GOBERR_FILE_WRITE;
|
|
}
|
|
|
|
// update block pos
|
|
BlockTable[xlat[i]].offset = size;
|
|
size += BlockTable[xlat[i]].size + slack;
|
|
}
|
|
}
|
|
|
|
MemFuncs.free(buffer);
|
|
MemFuncs.free(slack_buf);
|
|
|
|
// close the archive
|
|
err = GOBArchiveClose();
|
|
if (err != GOBERR_OK) return err;
|
|
|
|
// close temp file
|
|
FSFuncs.close(&temp_handle);
|
|
|
|
// overrwrite archive with temp file
|
|
_snprintf(full_name, GOB_MAX_FILE_NAME_LEN, "%s.gob", file);
|
|
if (_rename("~temp.tmp", full_name)) return GOBERR_FILE_RENAME;
|
|
|
|
ArchiveSize = size;
|
|
|
|
CommitFileTable();
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
|
|
// GOBVerify
|
|
// Public function. Verifies the integrity of a file.
|
|
GOBError GOBVerify(const GOBChar* file, GOBBool* status)
|
|
{
|
|
GOBHandle handle;
|
|
GOBError err;
|
|
GOBVoid* buffer;
|
|
GOBUInt32 size, junk;
|
|
GOBUInt32 crc;
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
if (!FileTableExt) return GOBERR_NO_EXTENDED;
|
|
if (!BlockCRC) return GOBERR_NO_EXTENDED;
|
|
|
|
// get the file size
|
|
size = 0; // assign to avoid compiler warning
|
|
err = GOBGetSize(file, &size, &junk, &junk);
|
|
if (err != GOBERR_OK) return err;
|
|
|
|
// open the file
|
|
err = GOBOpen((GOBChar*)file, &handle);
|
|
if (err != GOBERR_OK) return err;
|
|
|
|
lfile = LowerCase(file);
|
|
entry = GetFileTableEntry(lfile);
|
|
|
|
// alloc space for the file
|
|
buffer = MemFuncs.alloc(size);
|
|
if (!buffer) return GOBERR_NO_MEMORY;
|
|
|
|
// read it into the buffer
|
|
crc = GOBRead(buffer, size, handle);
|
|
if (crc != size) return GOBERR_FILE_READ;
|
|
|
|
// calc the crc
|
|
crc = crc32(0L, Z_NULL, 0);
|
|
crc = crc32(crc, (const unsigned char*)buffer, size);
|
|
|
|
MemFuncs.free(buffer);
|
|
|
|
// verify the crc matches
|
|
if (crc != FileTableExt[entry].crc) *status = GOB_FALSE;
|
|
else *status = GOB_TRUE;
|
|
|
|
err = GOBClose(handle);
|
|
if (err != GOBERR_OK) return err;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBGetSize
|
|
// Public function. Get a file compressed, decompressed, slack sizes.
|
|
GOBError GOBGetSize(const GOBChar* file,
|
|
GOBUInt32* decomp, GOBUInt32* comp, GOBUInt32* slack)
|
|
{
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
GOBUInt32 block;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
|
|
// get file table entry
|
|
lfile = LowerCase(file);
|
|
entry = GetFileTableEntry(lfile);
|
|
if (entry == -1) return GOBERR_FILE_NOT_FOUND;
|
|
|
|
// decompressed size from file table
|
|
*decomp = FileTableBasic[entry].size;
|
|
|
|
// compressed size is sum of block sizes
|
|
*comp = 0;
|
|
*slack = 0;
|
|
block = FileTableBasic[entry].block;
|
|
while (block != GOB_MAX_BLOCKS) {
|
|
*comp += BlockTable[block].size;
|
|
*slack += GOBGetSlack(BlockTable[block].size);
|
|
block = BlockTable[block].next;
|
|
}
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBGetTime
|
|
// Public function. Get a file modification time.
|
|
GOBError GOBGetTime(const GOBChar* file, GOBUInt32* time)
|
|
{
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
if (!FileTableExt) return GOBERR_NO_EXTENDED;
|
|
if (!BlockCRC) return GOBERR_NO_EXTENDED;
|
|
|
|
lfile = LowerCase(file);
|
|
entry = GetFileTableEntry(lfile);
|
|
if (entry == -1) return GOBERR_FILE_NOT_FOUND;
|
|
|
|
*time = FileTableExt[entry].time;
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBGetCRC
|
|
// Public function. Get a file CRC.
|
|
GOBError GOBGetCRC(const GOBChar* file, GOBUInt32* crc)
|
|
{
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
if (!FileTableExt) return GOBERR_NO_EXTENDED;
|
|
if (!BlockCRC) return GOBERR_NO_EXTENDED;
|
|
|
|
lfile = LowerCase(file);
|
|
entry = GetFileTableEntry(lfile);
|
|
if (entry == -1) return GOBERR_FILE_NOT_FOUND;
|
|
|
|
*crc = FileTableExt[entry].crc;
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBAccess
|
|
// Public function. Determine if a file exists in the archive.
|
|
GOBError GOBAccess(const GOBChar* file, GOBBool* status)
|
|
{
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
|
|
lfile = LowerCase(file);
|
|
entry = GetFileTableEntry(lfile);
|
|
if (entry == -1) *status = GOB_FALSE;
|
|
else *status = GOB_TRUE;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBGetFileCode
|
|
// Public function. Find the index into the file table of a file.
|
|
GOBInt32 GOBGetFileCode(const GOBChar* file)
|
|
{
|
|
GOBInt32 entry;
|
|
GOBChar* lfile;
|
|
|
|
if (!LibraryInit) return -1;
|
|
if (InvalidHandle(ArchiveHandle)) return -1;
|
|
|
|
lfile = LowerCase(file);
|
|
entry = GetFileTableEntry(lfile);
|
|
|
|
return entry;
|
|
}
|
|
|
|
// GOBGetFileTables
|
|
// Public function. Return the active file tables.
|
|
GOBError GOBGetFileTables(struct GOBFileTableBasicEntry** basic,
|
|
struct GOBFileTableExtEntry** ext)
|
|
{
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
*basic = FileTableBasic;
|
|
*ext = FileTableExt;
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBGetBlockTable
|
|
// Public function. Return the active block table.
|
|
GOBError GOBGetBlockTable(struct GOBBlockTableEntry** table, GOBUInt32* num)
|
|
{
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN;
|
|
*table = BlockTable;
|
|
*num = ArchiveNumBlocks;
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBSetCacheSize
|
|
// Public function. Allocates buffers to cache blocks.
|
|
GOBError GOBSetCacheSize(GOBUInt32 num)
|
|
{
|
|
GOBUInt32 i;
|
|
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
|
|
// only continue if we actually need to resize
|
|
if (num == NumCacheBlocks) return GOBERR_OK;
|
|
|
|
// free old cache buffers
|
|
FreeCache();
|
|
|
|
NumCacheBlocks = 0;
|
|
|
|
CacheBlocks = (struct GOBBlockCache*)MemFuncs.alloc(
|
|
sizeof(struct GOBBlockCache) * num);
|
|
if (!CacheBlocks) return GOBERR_NO_MEMORY;
|
|
|
|
// allocate cache blocks and initialize
|
|
for (i = 0; i < num; ++i) {
|
|
CacheBlocks[i].data = (GOBChar*)MemFuncs.alloc(GOB_BLOCK_SIZE);
|
|
if (!CacheBlocks[i].data) return GOBERR_NO_MEMORY;
|
|
|
|
CacheBlocks[i].size = 0;
|
|
CacheBlocks[i].time = 0;
|
|
CacheBlocks[i].block = 0xFFFFFFFF;
|
|
|
|
++NumCacheBlocks;
|
|
}
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBSetReadBufferSize
|
|
// Public function. Allocate a read ahead buffer.
|
|
GOBError GOBSetReadBufferSize(GOBUInt32 size)
|
|
{
|
|
if (!LibraryInit) return GOBERR_NOT_INIT;
|
|
|
|
// only continue if we actually need to resize
|
|
if (size == ReadBuffer.size) return GOBERR_OK;
|
|
|
|
// remove old buffer
|
|
if (ReadBuffer.data) MemFuncs.free(ReadBuffer.data);
|
|
|
|
// allocate new buffer
|
|
ReadBuffer.data = (GOBChar*)MemFuncs.alloc(size + GOB_MEM_ALIGNMENT);
|
|
if (!ReadBuffer.data) return GOB_INVALID_SIZE;
|
|
|
|
// set aligned pointer
|
|
ReadBuffer.dataStart =
|
|
&ReadBuffer.data[GOB_MEM_ALIGNMENT -
|
|
((GOBUInt32)(ReadBuffer.data) % GOB_MEM_ALIGNMENT)];
|
|
|
|
ReadBuffer.pos = 0xFFFFFFFF;
|
|
ReadBuffer.size = size;
|
|
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
// GOBGetReadStats
|
|
// Public function. Get file read statistics (seeks, sizes).
|
|
struct GOBReadStats GOBGetReadStats(GOBVoid)
|
|
{
|
|
return ReadStats;
|
|
}
|
|
|
|
|
|
GOBVoid GOBSetProfileFuncs(struct GOBProfileFuncSet* fset)
|
|
{
|
|
ProfileReadCallback = fset->read;
|
|
}
|
|
|
|
GOBError GOBStartProfile(GOBVoid)
|
|
{
|
|
if (ProfileEnabled) return GOBERR_PROFILE_ON;
|
|
ProfileEnabled = GOB_TRUE;
|
|
return GOBERR_OK;
|
|
}
|
|
|
|
GOBError GOBStopProfile(GOBVoid)
|
|
{
|
|
if (!ProfileEnabled) return GOBERR_PROFILE_OFF;
|
|
ProfileEnabled = GOB_FALSE;
|
|
return GOBERR_OK;
|
|
}
|