+====================
+FS_OpenVirtualFile
+
+Open a file. The syntax is the same as fopen
+====================
+*/
+qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
+{
+ if (FS_CheckNastyPath(filepath, false))
+ {
+ Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
+ return NULL;
+ }
+
+ return FS_OpenReadFile (filepath, quiet, false, 16);
+}
+
+
+/*
+====================
+FS_FileFromData
+
+Open a file. The syntax is the same as fopen
+====================
+*/
+qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
+{
+ qfile_t* file;
+ file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
+ memset (file, 0, sizeof (*file));
+ file->flags = QFILE_FLAG_DATA;
+ file->ungetc = EOF;
+ file->real_length = size;
+ file->data = data;
+ return file;
+}
+
+/*
+====================
+FS_Close
+
+Close a file
+====================
+*/
+int FS_Close (qfile_t* file)
+{
+ if(file->flags & QFILE_FLAG_DATA)
+ {
+ Mem_Free(file);
+ return 0;
+ }
+
+ if (close (file->handle))
+ return EOF;
+
+ if (file->filename)
+ {
+ if (file->flags & QFILE_FLAG_REMOVE)
+ remove(file->filename);
+
+ Mem_Free((void *) file->filename);
+ }
+
+ if (file->ztk)
+ {
+ qz_inflateEnd (&file->ztk->zstream);
+ Mem_Free (file->ztk);
+ }
+
+ Mem_Free (file);
+ return 0;
+}
+
+void FS_RemoveOnClose(qfile_t* file)
+{
+ file->flags |= QFILE_FLAG_REMOVE;
+}
+
+/*
+====================
+FS_Write
+
+Write "datasize" bytes into a file
+====================
+*/
+fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
+{
+ fs_offset_t result;
+
+ // If necessary, seek to the exact file position we're supposed to be
+ if (file->buff_ind != file->buff_len)
+ lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
+
+ // Purge cached data
+ FS_Purge (file);
+
+ // Write the buffer and update the position
+ result = write (file->handle, data, (fs_offset_t)datasize);
+ file->position = lseek (file->handle, 0, SEEK_CUR);
+ if (file->real_length < file->position)
+ file->real_length = file->position;
+
+ if (result < 0)
+ return 0;
+
+ return result;
+}
+
+
+/*
+====================
+FS_Read
+
+Read up to "buffersize" bytes from a file
+====================
+*/
+fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
+{
+ fs_offset_t count, done;
+
+ if (buffersize == 0)
+ return 0;
+
+ // Get rid of the ungetc character
+ if (file->ungetc != EOF)
+ {
+ ((char*)buffer)[0] = file->ungetc;
+ buffersize--;
+ file->ungetc = EOF;
+ done = 1;
+ }
+ else
+ done = 0;
+
+ if(file->flags & QFILE_FLAG_DATA)
+ {
+ size_t left = file->real_length - file->position;
+ if(buffersize > left)
+ buffersize = left;
+ memcpy(buffer, file->data + file->position, buffersize);
+ file->position += buffersize;
+ return buffersize;
+ }
+
+ // First, we copy as many bytes as we can from "buff"
+ if (file->buff_ind < file->buff_len)
+ {
+ count = file->buff_len - file->buff_ind;
+ count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
+ done += count;
+ memcpy (buffer, &file->buff[file->buff_ind], count);
+ file->buff_ind += count;
+
+ buffersize -= count;
+ if (buffersize == 0)
+ return done;
+ }
+
+ // NOTE: at this point, the read buffer is always empty
+
+ // If the file isn't compressed
+ if (! (file->flags & QFILE_FLAG_DEFLATED))
+ {
+ fs_offset_t nb;
+
+ // We must take care to not read after the end of the file
+ count = file->real_length - file->position;
+
+ // If we have a lot of data to get, put them directly into "buffer"
+ if (buffersize > sizeof (file->buff) / 2)
+ {
+ if (count > (fs_offset_t)buffersize)
+ count = (fs_offset_t)buffersize;
+ lseek (file->handle, file->offset + file->position, SEEK_SET);
+ nb = read (file->handle, &((unsigned char*)buffer)[done], count);
+ if (nb > 0)
+ {
+ done += nb;
+ file->position += nb;
+
+ // Purge cached data
+ FS_Purge (file);
+ }
+ }
+ else
+ {
+ if (count > (fs_offset_t)sizeof (file->buff))
+ count = (fs_offset_t)sizeof (file->buff);
+ lseek (file->handle, file->offset + file->position, SEEK_SET);
+ nb = read (file->handle, file->buff, count);
+ if (nb > 0)
+ {
+ file->buff_len = nb;
+ file->position += nb;
+
+ // Copy the requested data in "buffer" (as much as we can)
+ count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
+ memcpy (&((unsigned char*)buffer)[done], file->buff, count);
+ file->buff_ind = count;
+ done += count;
+ }
+ }
+
+ return done;
+ }
+
+ // If the file is compressed, it's more complicated...
+ // We cycle through a few operations until we have read enough data
+ while (buffersize > 0)
+ {
+ ztoolkit_t *ztk = file->ztk;
+ int error;
+
+ // NOTE: at this point, the read buffer is always empty
+
+ // If "input" is also empty, we need to refill it
+ if (ztk->in_ind == ztk->in_len)
+ {
+ // If we are at the end of the file
+ if (file->position == file->real_length)
+ return done;
+
+ count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
+ if (count > (fs_offset_t)sizeof (ztk->input))
+ count = (fs_offset_t)sizeof (ztk->input);
+ lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
+ if (read (file->handle, ztk->input, count) != count)
+ {
+ Con_Printf ("FS_Read: unexpected end of file\n");
+ break;
+ }
+
+ ztk->in_ind = 0;
+ ztk->in_len = count;
+ ztk->in_position += count;
+ }
+
+ ztk->zstream.next_in = &ztk->input[ztk->in_ind];
+ ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
+
+ // Now that we are sure we have compressed data available, we need to determine
+ // if it's better to inflate it in "file->buff" or directly in "buffer"
+
+ // Inflate the data in "file->buff"
+ if (buffersize < sizeof (file->buff) / 2)
+ {
+ ztk->zstream.next_out = file->buff;
+ ztk->zstream.avail_out = sizeof (file->buff);
+ error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
+ if (error != Z_OK && error != Z_STREAM_END)
+ {
+ Con_Printf ("FS_Read: Can't inflate file\n");
+ break;
+ }
+ ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
+
+ file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
+ file->position += file->buff_len;
+
+ // Copy the requested data in "buffer" (as much as we can)
+ count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
+ memcpy (&((unsigned char*)buffer)[done], file->buff, count);
+ file->buff_ind = count;
+ }
+
+ // Else, we inflate directly in "buffer"
+ else
+ {
+ ztk->zstream.next_out = &((unsigned char*)buffer)[done];
+ ztk->zstream.avail_out = (unsigned int)buffersize;
+ error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
+ if (error != Z_OK && error != Z_STREAM_END)
+ {
+ Con_Printf ("FS_Read: Can't inflate file\n");
+ break;
+ }
+ ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
+
+ // How much data did it inflate?
+ count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
+ file->position += count;
+
+ // Purge cached data
+ FS_Purge (file);
+ }
+
+ done += count;
+ buffersize -= count;
+ }
+
+ return done;
+}
+
+
+/*
+====================
+FS_Print
+
+Print a string into a file
+====================
+*/
+int FS_Print (qfile_t* file, const char *msg)
+{
+ return (int)FS_Write (file, msg, strlen (msg));
+}
+
+/*
+====================
+FS_Printf
+
+Print a string into a file
+====================
+*/
+int FS_Printf(qfile_t* file, const char* format, ...)
+{
+ int result;
+ va_list args;
+
+ va_start (args, format);
+ result = FS_VPrintf (file, format, args);
+ va_end (args);
+
+ return result;
+}
+
+
+/*
+====================
+FS_VPrintf
+
+Print a string into a file
+====================
+*/
+int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
+{
+ int len;
+ fs_offset_t buff_size = MAX_INPUTLINE;
+ char *tempbuff;
+
+ for (;;)
+ {
+ tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
+ len = dpvsnprintf (tempbuff, buff_size, format, ap);
+ if (len >= 0 && len < buff_size)
+ break;
+ Mem_Free (tempbuff);
+ buff_size *= 2;
+ }
+
+ len = write (file->handle, tempbuff, len);
+ Mem_Free (tempbuff);
+
+ return len;
+}
+
+
+/*
+====================
+FS_Getc
+
+Get the next character of a file
+====================
+*/
+int FS_Getc (qfile_t* file)
+{
+ unsigned char c;
+
+ if (FS_Read (file, &c, 1) != 1)
+ return EOF;
+
+ return c;
+}
+
+
+/*
+====================
+FS_UnGetc
+
+Put a character back into the read buffer (only supports one character!)
+====================
+*/
+int FS_UnGetc (qfile_t* file, unsigned char c)
+{
+ // If there's already a character waiting to be read
+ if (file->ungetc != EOF)
+ return EOF;
+
+ file->ungetc = c;
+ return c;
+}
+
+
+/*
+====================
+FS_Seek
+
+Move the position index in a file
+====================
+*/
+int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
+{
+ ztoolkit_t *ztk;
+ unsigned char* buffer;
+ fs_offset_t buffersize;
+
+ // Compute the file offset
+ switch (whence)
+ {
+ case SEEK_CUR:
+ offset += file->position - file->buff_len + file->buff_ind;
+ break;
+
+ case SEEK_SET:
+ break;
+
+ case SEEK_END:
+ offset += file->real_length;
+ break;
+
+ default:
+ return -1;
+ }
+ if (offset < 0 || offset > file->real_length)
+ return -1;
+
+ if(file->flags & QFILE_FLAG_DATA)
+ {
+ file->position = offset;
+ return 0;
+ }
+
+ // If we have the data in our read buffer, we don't need to actually seek
+ if (file->position - file->buff_len <= offset && offset <= file->position)
+ {
+ file->buff_ind = offset + file->buff_len - file->position;
+ return 0;
+ }
+
+ // Purge cached data
+ FS_Purge (file);
+
+ // Unpacked or uncompressed files can seek directly
+ if (! (file->flags & QFILE_FLAG_DEFLATED))
+ {
+ if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
+ return -1;
+ file->position = offset;
+ return 0;
+ }
+
+ // Seeking in compressed files is more a hack than anything else,
+ // but we need to support it, so here we go.
+ ztk = file->ztk;
+
+ // If we have to go back in the file, we need to restart from the beginning
+ if (offset <= file->position)
+ {
+ ztk->in_ind = 0;
+ ztk->in_len = 0;
+ ztk->in_position = 0;
+ file->position = 0;
+ lseek (file->handle, file->offset, SEEK_SET);
+
+ // Reset the Zlib stream
+ ztk->zstream.next_in = ztk->input;
+ ztk->zstream.avail_in = 0;
+ qz_inflateReset (&ztk->zstream);
+ }
+
+ // We need a big buffer to force inflating into it directly
+ buffersize = 2 * sizeof (file->buff);
+ buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
+
+ // Skip all data until we reach the requested offset
+ while (offset > file->position)
+ {
+ fs_offset_t diff = offset - file->position;
+ fs_offset_t count, len;
+
+ count = (diff > buffersize) ? buffersize : diff;
+ len = FS_Read (file, buffer, count);
+ if (len != count)
+ {
+ Mem_Free (buffer);
+ return -1;
+ }
+ }
+
+ Mem_Free (buffer);
+ return 0;
+}
+
+
+/*
+====================
+FS_Tell
+
+Give the current position in a file
+====================
+*/
+fs_offset_t FS_Tell (qfile_t* file)
+{
+ return file->position - file->buff_len + file->buff_ind;
+}
+
+
+/*
+====================
+FS_FileSize
+
+Give the total size of a file
+====================
+*/
+fs_offset_t FS_FileSize (qfile_t* file)
+{
+ return file->real_length;
+}
+
+
+/*
+====================
+FS_Purge
+
+Erases any buffered input or output data
+====================
+*/
+void FS_Purge (qfile_t* file)
+{
+ file->buff_len = 0;
+ file->buff_ind = 0;
+ file->ungetc = EOF;
+}
+
+
+/*
+============