mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
207 lines
7.4 KiB
C++
207 lines
7.4 KiB
C++
#define _CFILE_INTERNAL
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <direct.h>
|
|
#include <windows.h>
|
|
#include <winbase.h>
|
|
#endif
|
|
|
|
#include "lz4.h"
|
|
#include "cfilecompression.h"
|
|
#include "cfilearchive.h"
|
|
|
|
/*INTERNAL FUNCTIONS*/
|
|
/*LZ41*/
|
|
void lz41_load_offsets(CFILE* cf);
|
|
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length);
|
|
void lz41_create_ci(CFILE* cf, int header);
|
|
/*MISC*/
|
|
int fso_fseek(CFILE* cfile, int offset, int where);
|
|
/*END OF INTERNAL FUNCTIONS*/
|
|
|
|
int comp_check_header(int header)
|
|
{
|
|
if (LZ41_FILE_HEADER == header)
|
|
return COMP_HEADER_MATCH;
|
|
|
|
return COMP_HEADER_MISMATCH;
|
|
}
|
|
|
|
void comp_create_ci(CFILE* cf, int header)
|
|
{
|
|
//mprintf(("(CI)Compressed File Opened: %s \n", cf->original_filename.c_str()));
|
|
|
|
if (LZ41_FILE_HEADER == header)
|
|
lz41_create_ci(cf, header);
|
|
|
|
//mprintf(("(CI)Uncompressed FileSize: %d \n", cf->size));
|
|
//mprintf(("(CI)Compressed FileSize: %d \n", cf->compression_info.compressed_size));
|
|
//mprintf(("(CI)Block Size: %d \n", cf->compression_info.block_size));
|
|
}
|
|
|
|
size_t comp_fread(CFILE* cf, char* buffer, size_t length)
|
|
{
|
|
/* Check the request to be at least 1 byte */
|
|
Assertion(length > 0, "Invalid length requested.");
|
|
|
|
/* Check that we are not requesting to read beyond end of file */
|
|
Assertion(cf->raw_position + length <= cf->size, "Invalid length requested.");
|
|
|
|
if (LZ41_FILE_HEADER == cf->compression_info.header)
|
|
return lz41_stream_random_access(cf, buffer, cf->raw_position, length);
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t comp_ftell(CFILE* cf)
|
|
{
|
|
return cf->raw_position;
|
|
}
|
|
|
|
int comp_feof(CFILE* cf)
|
|
{
|
|
if (cf->raw_position >= cf->size)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int comp_fseek(CFILE* cf, int offset, int where)
|
|
{
|
|
size_t goal_position;
|
|
switch (where) {
|
|
case SEEK_SET: goal_position = offset; break;
|
|
case SEEK_CUR: goal_position = cf->raw_position + offset; break;
|
|
case SEEK_END: goal_position = cf->size + offset; break;
|
|
default: return 1;
|
|
}
|
|
|
|
// Make sure we don't seek beyond the end of the file
|
|
CAP(goal_position,(size_t)0, cf->size);
|
|
|
|
cf->raw_position = goal_position;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//Special fseek for compressed files handled by FSO, only SEEK_SET and SEEK_END is supported.
|
|
int fso_fseek(CFILE* cfile, int offset, int where)
|
|
{
|
|
size_t goal_position;
|
|
|
|
switch (where) {
|
|
case SEEK_SET: goal_position = offset + cfile->lib_offset; break;
|
|
case SEEK_END: goal_position = cfile->compression_info.compressed_size + offset + cfile->lib_offset; break;
|
|
default: return 0;
|
|
}
|
|
|
|
// Make sure we don't seek beyond the end of the file
|
|
CAP(goal_position, cfile->lib_offset, cfile->lib_offset + cfile->compression_info.compressed_size);
|
|
|
|
int result = 0;
|
|
if (cfile->fp)
|
|
{
|
|
result = fseek(cfile->fp, (long)goal_position, SEEK_SET);
|
|
Assertion(goal_position >= cfile->lib_offset, "Invalid offset values detected while seeking! Goal was " SIZE_T_ARG ", lib_offset is " SIZE_T_ARG ".", goal_position, cfile->lib_offset);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void lz41_create_ci(CFILE* cf, int header)
|
|
{
|
|
cf->compression_info.header = header;
|
|
cf->compression_info.compressed_size = cf->size;
|
|
fso_fseek(cf, (int)sizeof(int)*-3, SEEK_END);
|
|
auto fNumoffsets = fread(&cf->compression_info.num_offsets, sizeof(int), 1, cf->fp);
|
|
auto fSize = fread(&cf->size, sizeof(int), 1, cf->fp);
|
|
auto fBsize = fread(&cf->compression_info.block_size, sizeof(int), 1, cf->fp);
|
|
|
|
Assertion(cf->compression_info.num_offsets > 0, "Invalid number of offsets, compressed file is possibly in the wrong format or corrupted.");
|
|
#if !defined(NDEBUG)
|
|
Assertion(cf->size > 4, "Invalid filesize, compressed file is possibly in the wrong format or corrupted.");
|
|
Assertion(cf->compression_info.block_size > 16, "Invalid block size, compressed file is possibly in the wrong format or corrupted.");
|
|
Assertion(fNumoffsets == 1, "Error while reading the number of offsets, compressed file is possibly in the wrong format or corrupted.");
|
|
Assertion(fSize == 1, "Error while reading original filesize, compressed file is possibly in the wrong format or corrupted.");
|
|
Assertion(fBsize == 1, "Error while reading block size, compressed file is possibly in the wrong format or corrupted.");
|
|
#endif
|
|
|
|
cf->compression_info.decoder_buffer = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
|
|
cf->compression_info.last_decoded_block_pos = 0;
|
|
cf->compression_info.last_decoded_block_bytes = 0;
|
|
lz41_load_offsets(cf);
|
|
}
|
|
|
|
void lz41_load_offsets(CFILE* cf)
|
|
{
|
|
cf->compression_info.offsets = (int*)malloc(cf->compression_info.num_offsets * sizeof(int));
|
|
int block;
|
|
int* offsets_ptr = cf->compression_info.offsets;
|
|
|
|
/* Seek to the first offset position, remember to consider the trailing ints */
|
|
fso_fseek(cf, static_cast<int>( ( sizeof(int) * cf->compression_info.num_offsets ) * -1 ) - (sizeof(int)*3 ), SEEK_END);
|
|
for (block = 0; block < cf->compression_info.num_offsets; ++block)
|
|
{
|
|
auto bytes_read = fread(offsets_ptr++, sizeof(int), 1, cf->fp);
|
|
Assertion(bytes_read == 1, "Error reading offset list.");
|
|
}
|
|
}
|
|
|
|
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length)
|
|
{
|
|
LZ4_streamDecode_t lz4_stream_decode_body;
|
|
LZ4_streamDecode_t* lz4_stream_decode = &lz4_stream_decode_body;
|
|
/* The blocks (current_block to end_block) contain the data we want */
|
|
size_t current_block = offset / cf->compression_info.block_size;
|
|
size_t end_block = ((offset + length - 1) / cf->compression_info.block_size) + 1;
|
|
size_t written_bytes = 0;
|
|
|
|
if (cf->compression_info.num_offsets <= (int)end_block)
|
|
return (size_t)LZ41_OFFSETS_MISMATCH;
|
|
|
|
/* Seek to the first block to read, if it matches the cached block, search the next one instead */
|
|
if (cf->compression_info.last_decoded_block_pos == cf->compression_info.offsets[current_block] && current_block + 1 <= end_block)
|
|
fso_fseek(cf, cf->compression_info.offsets[current_block+1], SEEK_SET);
|
|
else
|
|
fso_fseek(cf, cf->compression_info.offsets[current_block], SEEK_SET);
|
|
|
|
offset = offset % cf->compression_info.block_size;
|
|
char* cmp_buf = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
|
|
|
|
/* Start decoding */
|
|
for (; current_block < end_block; ++current_block)
|
|
{
|
|
/* Only read and decode if the requested block is not the cached one */
|
|
if (cf->compression_info.last_decoded_block_pos != cf->compression_info.offsets[current_block])
|
|
{
|
|
cf->compression_info.last_decoded_block_pos = cf->compression_info.offsets[current_block];
|
|
|
|
/* The difference in offsets is the size of the block */
|
|
int cmp_bytes = cf->compression_info.offsets[current_block + 1] - cf->compression_info.offsets[current_block];
|
|
auto bytes_read = fread(cmp_buf, cmp_bytes, 1, cf->fp);
|
|
Assertion(bytes_read == 1, "Error reading from compressed file.");
|
|
|
|
cf->compression_info.last_decoded_block_bytes = LZ4_decompress_safe_continue(lz4_stream_decode, cmp_buf, cf->compression_info.decoder_buffer, cmp_bytes, cf->compression_info.block_size);
|
|
if (cf->compression_info.last_decoded_block_bytes <= 0)
|
|
{
|
|
free(cmp_buf);
|
|
return (size_t)LZ41_DECOMPRESSION_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Write out the part of the data we care about from buffer */
|
|
size_t block_length = (length < (cf->compression_info.last_decoded_block_bytes - offset) ? length : (cf->compression_info.last_decoded_block_bytes - offset));
|
|
memcpy(bytes_out + written_bytes, cf->compression_info.decoder_buffer + offset, (size_t)block_length);
|
|
written_bytes += block_length;
|
|
offset = 0;
|
|
length -= block_length;
|
|
}
|
|
|
|
free(cmp_buf);
|
|
return written_bytes;
|
|
}
|