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