File: cfilecompression.cpp

package info (click to toggle)
freespace2 24.0.2%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: trixie
  • size: 43,188 kB
  • sloc: cpp: 583,107; ansic: 21,729; python: 1,174; sh: 464; makefile: 248; xml: 181
file content (206 lines) | stat: -rw-r--r-- 7,518 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#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, ( ( 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;
}