/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #define _CFILE_INTERNAL #include #include #include #ifdef _WIN32 #include #include #include #include /* needed for memory mapping of file functions */ #endif #include "cfile/cfile.h" #include "cfile/cfilearchive.h" #include "cfile/cfilecompression.h" #include "luaconf.h" #include #include #define CHECK_POSITION // Called once to setup the low-level reading code. void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos ) { Assert(cfile != nullptr); cfile->lib_offset = lib_offset; cfile->raw_position = pos; cfile->size = size; if ( cfile->fp ) { if ( cfile->lib_offset ) { fseek( cfile->fp, (long)cfile->lib_offset, SEEK_SET ); } #if defined(CHECK_POSITION) && !defined(NDEBUG) auto raw_position = ftell(cfile->fp) - cfile->lib_offset; Assert(raw_position == cfile->raw_position); #endif } } //This function checks the file header to see if the file is compressed, if so it fills the correct compression info. void cf_check_compression(CFILE* cfile) { if (cfile->size <= 16) return; int header=cfread_int(cfile); cfseek(cfile, 0, SEEK_SET); if (comp_check_header(header) == COMP_HEADER_MATCH) comp_create_ci(cfile, header); } //This function is called to cleanup compression info when the cfile handle is reused. void cf_clear_compression_info(CFILE* cfile) { if (cfile->compression_info.header != 0) { free(cfile->compression_info.offsets); free(cfile->compression_info.decoder_buffer); cfile->compression_info.offsets = nullptr; cfile->compression_info.decoder_buffer = nullptr; cfile->compression_info.header = 0; cfile->compression_info.block_size = 0; cfile->compression_info.last_decoded_block_pos = 0; cfile->compression_info.last_decoded_block_bytes = 0; cfile->compression_info.num_offsets = 0; } } // cfeof() Tests for end-of-file on a stream // // returns a nonzero value after the first read operation that attempts to read // past the end of the file. It returns 0 if the current position is not end of file. // There is no error return. int cfeof(CFILE *cfile) { Assert(cfile != NULL); if (cfile->compression_info.header != 0) return comp_feof(cfile); int result = 0; #if defined(CHECK_POSITION) && !defined(NDEBUG) if (cfile->fp) { auto raw_position = ftell(cfile->fp) - cfile->lib_offset; Assert(raw_position == cfile->raw_position); } #endif if (cfile->raw_position >= cfile->size ) { result = 1; } else { result = 0; } return result; } // cftell() returns offset into file // // returns: success ==> offset into the file // error ==> -1 // int cftell( CFILE * cfile ) { Assert(cfile != NULL); if (cfile->compression_info.header != 0) return (int)comp_ftell(cfile); #if defined(CHECK_POSITION) && !defined(NDEBUG) if (cfile->fp) { auto raw_position = ftell(cfile->fp) - cfile->lib_offset; Assert(raw_position == cfile->raw_position); } #endif // The rest of the code still uses ints, do an overflow check to detect cases where this fails Assertion(cfile->raw_position <= static_cast(std::numeric_limits::max()), "Integer overflow in cftell, a file is probably too large (but I don't know which one)."); return (int) cfile->raw_position; } // cfseek() moves the file pointer // // returns: success ==> 0 // error ==> non-zero // int cfseek( CFILE *cfile, int offset, int where ) { Assert(cfile != NULL); if (cfile->compression_info.header != 0) return comp_fseek(cfile, offset, where); size_t goal_position; switch( where ) { case CF_SEEK_SET: goal_position = offset+cfile->lib_offset; break; case CF_SEEK_CUR: { goal_position = cfile->raw_position+offset+cfile->lib_offset; } break; case CF_SEEK_END: goal_position = cfile->size+offset+cfile->lib_offset; break; default: Int3(); return 1; } // Make sure we don't seek beyond the end of the file CAP(goal_position, cfile->lib_offset, cfile->lib_offset + cfile->size); int result = 0; if (cfile->fp) { // If we have a file pointer we can also seek in that file 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); } // If we only have a data pointer this will do all the work cfile->raw_position = goal_position - cfile->lib_offset; Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!"); #if defined(CHECK_POSITION) && !defined(NDEBUG) if (cfile->fp) { auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset; Assert(tmp_offset == cfile->raw_position); } #endif return result; } size_t cf_check_read_size(CFILE* cfile, size_t size) { if ( (cfile->raw_position+size) > cfile->size ) { Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!"); size = cfile->size - cfile->raw_position; if ( size < 1 ) { return 0; } //mprintf(( "CFILE: EOF encountered in file\n" )); } if (cfile->max_read_len) { if ( cfile->raw_position+size > cfile->max_read_len ) { std::ostringstream s_buf; s_buf << "Attempted to read " << size << "-byte(s) beyond length limit"; throw cfile::max_read_length(s_buf.str()); } } return size; } // cfread() reads from a file // // returns: returns the number of full elements read // // int cfread(void *buf, int elsize, int nelem, CFILE *cfile) { if(!cf_is_valid(cfile)) return 0; size_t size = elsize*nelem; if(buf == nullptr || size <= 0) return 0; size = cf_check_read_size(cfile, size); if (size < 1) { return 0; } size_t bytes_read; if (cfile->data != nullptr) { // This is a file from memory bytes_read = size; memcpy(buf, reinterpret_cast(cfile->data) + cfile->raw_position, size); } else if (cfile->compression_info.header != 0) { bytes_read = comp_fread(cfile, reinterpret_cast(buf),size); if (bytes_read != size) { mprintf(("\nFile: %s decompression error. Result was: %d expected: %d\n", cfile->original_filename.c_str(), (int)bytes_read, (int)size)); Assertion(bytes_read == size, "File decompression error!"); } } else { bytes_read = fread(buf, 1, size, cfile->fp); } if ( bytes_read > 0 ) { cfile->raw_position += bytes_read; Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!"); } #if defined(CHECK_POSITION) && !defined(NDEBUG) //Raw position is not the fp position with compressed files if (cfile->fp && cfile->compression_info.header == 0) { auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset; Assert(tmp_offset == cfile->raw_position); } #endif return (int)(bytes_read / elsize); } int cfread_lua_number(double *buf, CFILE *cfile) { if(!cf_is_valid(cfile)) return 0; if(buf == NULL) return 0; // cfread() not supported for memory-mapped files if(cfile->data != nullptr) { Warning(LOCATION, "Writing is not supported for mem-mapped files"); return 0; } size_t advance = 0; int items_read; if (cfile->fp) { long orig_pos = ftell(cfile->fp); items_read = fscanf(cfile->fp, LUA_NUMBER_SCAN, buf); advance = (size_t) (ftell(cfile->fp)-orig_pos); } else { int read; // %n returns the number of bytes currently read so we append that to the scan format at the end so it will return // how many bytes we have consumed items_read = sscanf(reinterpret_cast(cfile->data), LUA_NUMBER_SCAN "%n", buf, &read); if (items_read == 2) { // We need to correct the items read counter since we read one additional item items_read = 1; } advance = (size_t) read; } cfile->raw_position += advance; Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!"); #if defined(CHECK_POSITION) && !defined(NDEBUG) if (cfile->fp) { auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset; Assert(tmp_offset==cfile->raw_position); } #endif return items_read; }