1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 #define _CFILE_INTERNAL
13 
14 #include <cstdlib>
15 #include <cstring>
16 #include <cstdio>
17 
18 #ifdef _WIN32
19 #include <io.h>
20 #include <direct.h>
21 #include <windows.h>
22 #include <winbase.h>		/* needed for memory mapping of file functions */
23 #endif
24 
25 #include "cfile/cfile.h"
26 #include "cfile/cfilearchive.h"
27 #include "cfile/cfilecompression.h"
28 #include "luaconf.h"
29 
30 #include <sstream>
31 #include <limits>
32 
33 
34 #define CHECK_POSITION
35 
36 // Called once to setup the low-level reading code.
cf_init_lowlevel_read_code(CFILE * cfile,size_t lib_offset,size_t size,size_t pos)37 void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos )
38 {
39 	Assert(cfile != nullptr);
40 
41 	cfile->lib_offset = lib_offset;
42 	cfile->raw_position = pos;
43 	cfile->size = size;
44 
45 	if ( cfile->fp )	{
46 		if ( cfile->lib_offset )	{
47 			fseek( cfile->fp, (long)cfile->lib_offset, SEEK_SET );
48 		}
49 
50 		#if defined(CHECK_POSITION) && !defined(NDEBUG)
51 		auto raw_position = ftell(cfile->fp) - cfile->lib_offset;
52 		Assert(raw_position == cfile->raw_position);
53 		#endif
54 	}
55 }
56 
57 //This function checks the file header to see if the file is compressed, if so it fills the correct compression info.
cf_check_compression(CFILE * cfile)58 void cf_check_compression(CFILE* cfile)
59 {
60 	if (cfile->size <= 16)
61 		return;
62 	int header=cfread_int(cfile);
63 	cfseek(cfile, 0, SEEK_SET);
64 	if (comp_check_header(header) == COMP_HEADER_MATCH)
65 		comp_create_ci(cfile, header);
66 }
67 
68 //This function is called to cleanup compression info when the cfile handle is reused.
cf_clear_compression_info(CFILE * cfile)69 void cf_clear_compression_info(CFILE* cfile)
70 {
71 	if (cfile->compression_info.header != 0)
72 	{
73 		free(cfile->compression_info.offsets);
74 		free(cfile->compression_info.decoder_buffer);
75 		cfile->compression_info.offsets = nullptr;
76 		cfile->compression_info.decoder_buffer = nullptr;
77 		cfile->compression_info.header = 0;
78 		cfile->compression_info.block_size = 0;
79 		cfile->compression_info.last_decoded_block_pos = 0;
80 		cfile->compression_info.last_decoded_block_bytes = 0;
81 		cfile->compression_info.num_offsets = 0;
82 	}
83 }
84 
85 
86 // cfeof() Tests for end-of-file on a stream
87 //
88 // returns a nonzero value after the first read operation that attempts to read
89 // past the end of the file. It returns 0 if the current position is not end of file.
90 // There is no error return.
cfeof(CFILE * cfile)91 int cfeof(CFILE *cfile)
92 {
93 	Assert(cfile != NULL);
94 	if (cfile->compression_info.header != 0)
95 		return comp_feof(cfile);
96 	int result = 0;
97 
98 	#if defined(CHECK_POSITION) && !defined(NDEBUG)
99     if (cfile->fp) {
100 		auto raw_position = ftell(cfile->fp) - cfile->lib_offset;
101 		Assert(raw_position == cfile->raw_position);
102 	}
103 	#endif
104 
105 	if (cfile->raw_position >= cfile->size ) {
106 		result = 1;
107 	} else {
108 		result = 0;
109 	}
110 
111 	return result;
112 }
113 
114 // cftell() returns offset into file
115 //
116 // returns:  success ==> offset into the file
117 //           error   ==> -1
118 //
cftell(CFILE * cfile)119 int cftell( CFILE * cfile )
120 {
121 	Assert(cfile != NULL);
122 	if (cfile->compression_info.header != 0)
123 		return (int)comp_ftell(cfile);
124 
125 	#if defined(CHECK_POSITION) && !defined(NDEBUG)
126     if (cfile->fp) {
127 		auto raw_position = ftell(cfile->fp) - cfile->lib_offset;
128 		Assert(raw_position == cfile->raw_position);
129 	}
130 	#endif
131 
132 	// The rest of the code still uses ints, do an overflow check to detect cases where this fails
133 	Assertion(cfile->raw_position <= static_cast<size_t>(std::numeric_limits<int>::max()),
134 		"Integer overflow in cftell, a file is probably too large (but I don't know which one).");
135 	return (int) cfile->raw_position;
136 }
137 
138 
139 // cfseek() moves the file pointer
140 //
141 // returns:   success ==> 0
142 //            error   ==> non-zero
143 //
cfseek(CFILE * cfile,int offset,int where)144 int cfseek( CFILE *cfile, int offset, int where )
145 {
146 
147 	Assert(cfile != NULL);
148 
149 	if (cfile->compression_info.header != 0)
150 		return comp_fseek(cfile, offset, where);
151 
152 	// TODO: seek to offset in memory mapped file
153 	Assert( !cfile->mem_mapped );
154 
155 	size_t goal_position;
156 
157 	switch( where )	{
158 	case CF_SEEK_SET:
159 		goal_position = offset+cfile->lib_offset;
160 		break;
161 	case CF_SEEK_CUR:
162 		{
163 			goal_position = cfile->raw_position+offset+cfile->lib_offset;
164 		}
165 		break;
166 	case CF_SEEK_END:
167 		goal_position = cfile->size+offset+cfile->lib_offset;
168 		break;
169 	default:
170 		Int3();
171 		return 1;
172 	}
173 
174 	// Make sure we don't seek beyond the end of the file
175 	CAP(goal_position, cfile->lib_offset, cfile->lib_offset + cfile->size);
176 
177 	int result = 0;
178 
179 	if (cfile->fp) {
180 		// If we have a file pointer we can also seek in that file
181 		result = fseek(cfile->fp, (long)goal_position, SEEK_SET );
182 		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);
183 	}
184 	// If we only have a data pointer this will do all the work
185 	cfile->raw_position = goal_position - cfile->lib_offset;
186 	Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
187 
188 	#if defined(CHECK_POSITION) && !defined(NDEBUG)
189 	if (cfile->fp) {
190 		auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset;
191 		Assert(tmp_offset == cfile->raw_position);
192 	}
193 	#endif
194 
195 	return result;
196 }
197 
198 
199 // cfread() reads from a file
200 //
201 // returns:   returns the number of full elements read
202 //
203 //
cfread(void * buf,int elsize,int nelem,CFILE * cfile)204 int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
205 {
206 	if(!cf_is_valid(cfile))
207 		return 0;
208 
209 	size_t size = elsize*nelem;
210 
211 	if(buf == NULL || size <= 0)
212 		return 0;
213 
214 	if ( (cfile->raw_position+size) > cfile->size ) {
215 		Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
216 		size = cfile->size - cfile->raw_position;
217 		if ( size < 1 ) {
218 			return 0;
219 		}
220 		//mprintf(( "CFILE: EOF encountered in file\n" ));
221 	}
222 
223 	if (cfile->max_read_len) {
224 		if ( cfile->raw_position+size > cfile->max_read_len ) {
225 			std::ostringstream s_buf;
226 			s_buf << "Attempted to read " << size << "-byte(s) beyond length limit";
227 
228 			throw cfile::max_read_length(s_buf.str());
229 		}
230 	}
231 
232 	size_t bytes_read;
233 	if (cfile->data != nullptr) {
234 		// This is a file from memory
235 		bytes_read = size;
236 		memcpy(buf, reinterpret_cast<const char*>(cfile->data) + cfile->raw_position, size);
237 	} else if (cfile->compression_info.header != 0) {
238 		bytes_read = comp_fread(cfile, reinterpret_cast<char*>(buf),size);
239 		if (bytes_read != size)
240 		{
241 			mprintf(("\nFile: %s decompression error. Result was: %d expected: %d\n", cfile->original_filename.c_str(), (int)bytes_read, (int)size));
242 			Assertion(bytes_read == size, "File decompression error!");
243 		}
244 	} else {
245 		bytes_read = fread(buf, 1, size, cfile->fp);
246 	}
247 
248 	if ( bytes_read > 0 )	{
249 		cfile->raw_position += bytes_read;
250 		Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
251 	}
252 
253 	#if defined(CHECK_POSITION) && !defined(NDEBUG)
254 	//Raw position is not the fp position with compressed files
255     if (cfile->fp && cfile->compression_info.header == 0) {
256 		auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset;
257 		Assert(tmp_offset == cfile->raw_position);
258 	}
259 	#endif
260 
261 	return (int)(bytes_read / elsize);
262 }
263 
cfread_lua_number(double * buf,CFILE * cfile)264 int cfread_lua_number(double *buf, CFILE *cfile)
265 {
266 	if(!cf_is_valid(cfile))
267 		return 0;
268 
269 	if(buf == NULL)
270 		return 0;
271 
272 	// cfread() not supported for memory-mapped files
273 	if(cfile->data != nullptr)
274 	{
275 		Warning(LOCATION, "Writing is not supported for mem-mapped files");
276 		return 0;
277 	}
278 
279 	size_t advance = 0;
280 	int items_read;
281 	if (cfile->fp) {
282 		long orig_pos = ftell(cfile->fp);
283 		items_read = fscanf(cfile->fp, LUA_NUMBER_SCAN, buf);
284 		advance = (size_t) (ftell(cfile->fp)-orig_pos);
285 	} else {
286 		int read;
287 		// %n returns the number of bytes currently read so we append that to the scan format at the end so it will return
288 		// how many bytes we have consumed
289 		items_read = sscanf(reinterpret_cast<const char*>(cfile->data), LUA_NUMBER_SCAN "%n", buf, &read);
290 		if (items_read == 2) {
291 			// We need to correct the items read counter since we read one additional item
292 			items_read = 1;
293 		}
294 		advance = (size_t) read;
295 	}
296 	cfile->raw_position += advance;
297 	Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
298 
299 	#if defined(CHECK_POSITION) && !defined(NDEBUG)
300     if (cfile->fp) {
301 		auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset;
302 		Assert(tmp_offset==cfile->raw_position);
303 	}
304 	#endif
305 
306 	return items_read;
307 
308 }
309