
// This source is from OpenEQ by Daeken et al. Modified a bit by Derision, some bug fixes etc.


#include <string.h>

#include <zlib.h>

#include "pfs.hpp"

#pragma pack(1)
//#define DEBUGPFS

struct struct_header {
  uint32 offset;
  char magicCookie[4];
  uint32 unknown;
} typedef struct_header;

struct struct_directory_header {
  uint32 count;
} typedef struct_directory_header;

struct struct_directory {
  uint32 crc, offset, size;
} typedef struct_directory;

struct struct_data_block {
  uint32 deflen, inflen;
} typedef struct_data_block;

struct struct_fn_header {
  uint32 fncount;
} typedef struct_fn_header;

struct struct_fn_entry {
  uint32 fnlen;
} typedef struct_fn_entry;

#pragma pack()

inline void decompress(char *p, char *p2, int len, int uLen) {
  int status;
  z_stream d_stream;

  d_stream.zalloc = (alloc_func)0;
  d_stream.zfree = (free_func)0;
  d_stream.opaque = (voidpf)0;

  d_stream.next_in  = (Bytef *) p;
  d_stream.avail_in = len;
  d_stream.next_out = (Bytef *) p2;
  d_stream.avail_out = uLen;

  inflateInit(&d_stream);
  status = inflate(&d_stream, Z_NO_FLUSH);
  inflateEnd(&d_stream);
}

inline void Lower(char *str) {
  while(*str) {
    if(*str >= 'A' && *str <= 'Z')
      *str += 'a' - 'A';
    ++str;
  }
}

PFSLoader::PFSLoader() {
  // Set the status of the loader that nothing is loaded.
  this->buffer = nullptr;
  this->buf_len = -1;
  this->status = 0;
}

PFSLoader::~PFSLoader() {
  this->Close();
}

int PFSLoader::Open(FILE *fp)
{
	struct_header s3d_header;
	struct_directory_header s3d_dir_header;
	struct_directory s3d_dir;
	struct_data_block s3d_data;
	struct_fn_header *s3d_fn_header;
	struct_fn_entry *s3d_fn_entry;

	uint32 *offsets;

	char *temp, *temp2;
	int i, j, pos, inf, tmp, running = 0;

	if(!fp)
		return 0;

	fread(&s3d_header, sizeof(struct_header), 1, fp);

	if(s3d_header.magicCookie[0] != 'P' || s3d_header.magicCookie[1] != 'F'
		|| s3d_header.magicCookie[2] != 'S' || s3d_header.magicCookie[3] != ' ')
		return 0;

	this->fp = fp;

#ifdef DEBUGPFS
	printf("Offset: %d\n", s3d_header.offset);
#endif

	fseek(fp, s3d_header.offset, SEEK_SET);
	fread(&s3d_dir_header, sizeof(struct_directory_header), 1, fp);

	this->count = s3d_dir_header.count - 1;
	this->filenames = new char *[s3d_dir_header.count];
	this->files = new uint32[s3d_dir_header.count - 1];
	offsets = new uint32[s3d_dir_header.count - 1];

	for(i = 0; i < (int)s3d_dir_header.count; ++i)
	{
		fread(&s3d_dir, sizeof(struct_directory), 1, fp);

		if(s3d_dir.crc == ntohl(0xC90A5861))
		{
			pos = ftell(fp);
			fseek(fp, s3d_dir.offset, SEEK_SET);
			temp = new char[s3d_dir.size];
			memset(temp, 0, s3d_dir.size);
			inf = 0;

			while(inf < (int)s3d_dir.size)
			{
				fread(&s3d_data, sizeof(struct_data_block), 1, fp);
				temp2 = new char[s3d_data.deflen];
				fread(temp2, s3d_data.deflen, 1, fp);
				decompress(temp2, temp + inf, s3d_data.deflen, s3d_data.inflen);
				delete[] temp2;
				inf += s3d_data.inflen;
			}
			fseek(fp, pos, SEEK_SET);
			s3d_fn_header = (struct_fn_header *) temp;
			pos = sizeof(struct_fn_header);
			for(j = 0; j < (int)s3d_fn_header->fncount; ++j)
			{
				s3d_fn_entry = (struct_fn_entry *) &temp[pos];
				this->filenames[j] = new char[s3d_fn_entry->fnlen + 1];
				this->filenames[j][s3d_fn_entry->fnlen] = 0;
				memcpy(this->filenames[j], &temp[pos + sizeof(struct_fn_entry)], s3d_fn_entry->fnlen);
				pos += sizeof(struct_fn_entry) + s3d_fn_entry->fnlen;
			}
			delete [] temp;
		}

		else
		{
			this->files[running] = ftell(fp) - 12;
			offsets[running] = s3d_dir.offset;
			++running;
		}

	}

	for(i = s3d_dir_header.count - 2; i > 0; i--)
	{
		for(j = 0; j < i; j++)
		{
			if(offsets[j] > offsets[j+1])
			{
				tmp = offsets[j];
				offsets[j] = offsets[j + 1];
				offsets[j + 1] = tmp;
				tmp = this->files[j];
				this->files[j] = this->files[j + 1];
				this->files[j + 1] = tmp;
			}
		}
	}

	delete [] offsets;

	this->status = 1;

	return 1;
}

int PFSLoader::Close()
{
	if(this->status)
	{
		while(this->count > 0)
		{
			delete [] this->filenames[this->count - 1];
			--this->count;
		}
		delete[] this->filenames;
		delete[] this->files;
	}
	else
		return 0;

	this->buffer = nullptr;
	this->buf_len = -1;
	this->status = 0;

	return 1;
}

const char *PFSLoader::FindExtension(const char *ext) {
  int i;

  int elen = strlen(ext);

  for(i = 0; i < this->count; ++i) {
    int flen = strlen(this->filenames[i]);
	if(flen <= elen)
		continue;
    if(!strcmp(this->filenames[i]+(flen-elen), ext))
		return(this->filenames[i]);
  }
  return(nullptr);
}

int PFSLoader::GetFile(char *name, uchar **buf, int *len) {
  struct_directory s3d_dir;
  struct_data_block s3d_data;
  char *temp2;
  long inf;
  int i;
  Lower(name);

  for(i = 0; i < this->count; ++i) {

    // Derision: Added basic wildcard ability as .ZON filenames don't always match the EQG name
    if(!strcmp(this->filenames[i], name) ||
       ((name[0]=='*') && !strcmp(this->filenames[i]+strlen(filenames[i])-strlen(name)+2, name+2))) {
      fseek(this->fp, this->files[i], SEEK_SET);
      fread(&s3d_dir, sizeof(struct_directory), 1, this->fp);
      fseek(this->fp, s3d_dir.offset, SEEK_SET);
      *buf = new uchar[s3d_dir.size];

      inf = 0;
      while(inf < (int)s3d_dir.size) {
        fread(&s3d_data, sizeof(struct_data_block), 1, this->fp);
        temp2 = new char[s3d_data.deflen];
        fread(temp2, s3d_data.deflen, 1, this->fp);
        decompress(temp2, (char *) *buf + inf, s3d_data.deflen, s3d_data.inflen);
        delete [] temp2;
        inf += s3d_data.inflen;
      }

      *len = inf;
      return 1;
    }
  }
  return 0;
}
