Subversion Repositories spk

Rev

Rev 213 | Rev 308 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "CatFile.h"
#include <time.h>
#include "File.h"
#include "DirIO.h"
#include "logging/Log.h"

CCatFile::CCatFile () : m_bCatChanged(false)
{
        m_iDataType = CATFILE_NONE;
        m_sData = NULL;
        m_lSize = 0;
        m_bCreate = false;
        _lFiles = new std::vector<SInCatFile *>;
}

CCatFile::~CCatFile ()
{
        m_fDatFile.close();
        m_fCatFile.close();

        if ( m_bCatChanged ) {
                WriteCatFile ();
                m_fCatFile.close();
        }

        _clearFiles();
        delete _lFiles;

        if ( m_sData ) delete []m_sData;
}

void CCatFile::_clearFiles()
{
        for(auto itr = _lFiles->begin(); itr != _lFiles->end(); itr++)
        {
                if ((*itr)->sData )
                        delete [](*itr)->sData;
                delete (*itr);
        }

        _lFiles->clear();
}

bool CCatFile::IsAddonDir(const Utils::WString &dir)
{
        Utils::WString d = dir.findReplace(L"\\", L"/");
        d = d.token(L"/", 1);

        if (d.Compare(L"types"))
                return true;
        if (d.Compare(L"scripts"))
                return true;
        if (d.Compare(L"t"))
                return true;
        if (d.Compare(L"mov"))
                return true;
        if (d.Compare(L"loadscr"))
                return true;
        if (d.Compare(L"l"))
                return true;
        if ( d.Compare(L"maps") )
                return true;
        if ( d.Compare(L"director") )
                return true;
        if ( d.Compare(L"cutscenes") )
                return true;
        return false;
}

bool CCatFile::Opened(int error, bool allowCreate) 
{
        if ( error == CATERR_NONE ) return true;
        if ( allowCreate && error == CATERR_CREATED ) return true;
        return false;
}
int CCatFile::open(const Utils::WString &sCatfile, const Utils::WString &addon, int readtype, bool create)
{
        Utils::WString catfile = sCatfile;

        m_bCreate = false;
        Utils::WString datfile;
        if ( !catfile.right(4).Compare(L".cat") ) {
                datfile = catfile + L".dat";
                catfile += L".cat";
        }
        else datfile = CFileIO(catfile).changeFileExtension(L"dat");

        if ( readtype == CATREAD_JUSTCONTENTS ) create = false;

        // first check if the dat file exists and opens
        bool created = false;
        if ( readtype != CATREAD_JUSTCONTENTS ) {
                if ( !CFileIO::Exists(datfile) ) {
                        if ( create ) created = true;
                        else              return CATERR_NODATFILE;
                }
        }

        // now open the cat file to read
        CFileIO File(catfile);
        if ( !File.startRead() ) {
                if ( create ) created = true;
                else              return CATERR_NOCATFILE;
        }

        if ( created ) {
                m_fCatFile.open(catfile);
                m_fDatFile.open(datfile);
                m_bCreate = true;
                return CATERR_CREATED;
        }

        // find the file size
        if ( !File.fileSize() ) {
                File.close();
                return CATERR_FILEEMPTY;
        }

        // size must be multiples of 5
        size_t size = File.fileSize() + ((File.fileSize() % 5) ? 5 - (File.fileSize() % 5) : 0);

        // read cat to buffer
        try {
                if ( m_sData ) delete [] m_sData;
                m_sData = new unsigned char[size + 1];
        }
        catch (std::exception &e) {
                CLog::logf(CLog::Log_IO, 2, L"Memory Exception error, unable to alloc enough memory to store file data, %d (%hs)", size + 1, e.what());
                return CATERR_MALLOC;
        }

        m_lSize = size;
        try {
                File.read(m_sData, m_lSize);
        } catch (std::exception &e) {
                CLog::logf(CLog::Log_IO, 3, L"CCatFile::Open() unable to read from cat file, %hs", e.what());
                RemoveData ();
                File.close();
                return CATERR_READCAT;
        }

        m_sData[size] = 0;
        m_iDataType = CATFILE_READ;
        File.close();

        if ( readtype != CATREAD_CAT ) {
                if ( !DecryptData () ) return CATERR_DECRYPT;
                m_sData[File.fileSize()] = 0;
                readFiles ();
        }

        _sAddonDir = addon;

        m_fCatFile.open ( catfile );

        if ( readtype != CATREAD_JUSTCONTENTS ) {
                m_fDatFile.open ( datfile );

                // check the file size matches
                long compare = 0;
                if ( m_fDatFile.startRead() ) {
                        compare = m_fDatFile.fileSize();
                        m_fDatFile.close();
                }
                
                if (!_lFiles->empty())
                {
                        SInCatFile* c = _lFiles->back();
                        if (c && (c->lSize + c->lOffset) != compare) {
                                //                      return CATERR_MISMATCH;
                        }
                }
        }

        if ( readtype >= CATREAD_DAT ) LoadDatFile ();

        return CATERR_NONE;
}

bool CCatFile::removeFile(const Utils::WString &file)
{
        if ( !_sAddonDir.empty() ) {
                SInCatFile *f = findData(_sAddonDir + L"\\" + file);
                if ( f )
                        removeFile(f);
        }
        {
                SInCatFile *f = findData(L"addon\\" + file);
                if ( f )
                        removeFile(f);
        }
        SInCatFile *f = findData(file);
        if ( !f )
        {
                m_iError = CATERR_NOFILE;
                return false;
        }

        return removeFile(f);
}

void CCatFile::LoadDatFile ()
{
        if ( m_fDatFile.NoFile() )
                return;

        if ( m_fDatFile.startRead() ) 
        {
                for(auto itr = _lFiles->cbegin(); itr != _lFiles->cend(); ++itr)
                {
                        SInCatFile *c = *itr;
                        if ( c->sData ) delete c->sData;

                        try {
                                c->sData = new unsigned char[c->lSize + 1];
                        }
                        catch (std::exception &e) {
                                CLog::logf(CLog::Log_IO, 2, L"CCatFile::LoadDatFile() unable to malloc data storage: %s, %d (%hs)", c->sFile.c_str(), c->lSize, e.what());
                                continue;
                        }

                        try {
                                m_fDatFile.read(c->sData, c->lSize);
                        }
                        catch(std::exception &e) {
                                CLog::logf(CLog::Log_IO, 2, L"CCatFile::LoadDatFile() unable to read file data: %s, %d (%hs)", c->sFile.c_str(), c->lSize, e.what());
                                continue;
                        }
                        c->bDecrypted = false;
                        this->DecryptDAT(c);
                }
                m_fDatFile.close();
        }
}

bool CCatFile::readFiles ()
{
        size_t offset = 0;

        std::vector<Utils::WString> lines;
        Utils::WString::FromString(m_sData).tokenise(L"\n", lines);

        _clearFiles();

        if (!lines.empty()) {
                size_t start = 1;
                _sReadFilename = lines[0];
                if ( _sReadFilename.token(L" ", -1).isNumber() ) {
                        _sReadFilename.clear();
                        start = 0;
                }

                for(size_t i = start; i < lines.size(); i++) {
                        Utils::WString l = lines[i];

                        if (!l.empty())
                        {
                                SInCatFile* catfile = new SInCatFile;

                                catfile->lSize = static_cast<size_t>(l.token(L" ", -1).toLong());
                                catfile->sFile = l.tokens(L" ", 1, -2);
                                catfile->lOffset = offset;
                                catfile->sData = 0;

                                offset += catfile->lSize;

                                _lFiles->push_back(catfile);
                        }
                }
        }

/*
        unsigned char *data = m_sData;
        int spacePos = -1;
        while ( m_sData[num] != '\0' ) {
                if ( m_sData[num] == '\n' ) {
                        m_sData[num] = 0;
                        if ( spacePos != -1 )
                        {
                                Utils::String file;
                                int size = 0;

                                m_sData[spacePos] = '\0';
                                file = (char *)data;
                                data = (m_sData + (spacePos + 1));
                                if ( !Utils::String((char *)data).isNumber() )
                                        size = 0;
                                else
                                        size = atoi((const char *)data);

                                if ( size )
                                {
                                        SInCatFile *catfile = new SInCatFile;

                                        catfile->lSize = size;
                                        catfile->sFile = file;
                                        catfile->lOffset = offset;
                                        catfile->sData = 0;

                                        offset += catfile->lSize;

                                        m_lFiles.push_back ( catfile );
                                }
                        }
                        data = (m_sData + (num + 1));
                        spacePos = -1;
                }
                else if ( m_sData[num] == ' ' )
                        spacePos = num;
                ++num;
        }
*/
        return true;
}

bool CCatFile::DecryptData ( unsigned char *data, size_t size )
{
        unsigned char cl=0xDB, dh=0xDC, dl=0xDD, ah=0xDE, ch=0xDF, al;
        unsigned char *ptr = data, *end = data + size;
        for ( ptr = data; ptr < end; ptr += 5)
        {
                al=ch;
                ch+=5;
                *(ptr + 4) ^= al;
                al=ah;
                ah+=5;
                *(ptr + 3) ^= al;
                al=dl;
                dl+=5;
                *(ptr + 2) ^= al;
                al=dh;
                dh+=5;
                *(ptr + 1) ^= al;
                al=cl;
                cl+=5;
                *(ptr + 0) ^= al;
        }

        return true;
}

bool CCatFile::DecryptData ()
{
        if ( !DecryptData ( m_sData, m_lSize ) ) return false;

        m_iDataType = CATERR_DECRYPT;

        return true;
}

void CCatFile::RemoveData ()
{
        delete m_sData;
        m_sData = NULL;
        m_iDataType = CATFILE_NONE;
        m_lSize = 0;
}

bool CCatFile::readFileToData(const Utils::WString &filename)
{
        SInCatFile *c = findData(filename);
        return readFileToData(c);
}

bool CCatFile::readFileToData(SInCatFile *c)
{
        if ( !c ) return false;
        size_t size = 0;
        if ( !c->sData ) c->sData = readData ( c, &size );
        if ( c->sData ) return true;
        return false;
}

unsigned char *CCatFile::readData(SInCatFile *c, size_t *size)
{
        *size = c->lSize;

        if ( !c->sData ) {
                if ( m_fDatFile.startRead() ) {
                        m_fDatFile.seek(c->lOffset);
                        c->sData = m_fDatFile.read(c->lSize);
                        c->bDecrypted = false;
                        m_fDatFile.close();

                        if ( !c->sData ) (*size) = 0;
                        else {
                                c->sData[c->lSize] = '\0';
                                DecryptDAT(c);
                        }
                }
        }

        return c->sData;
}

void CCatFile::DecryptDAT(SInCatFile *pFile) 
{
        if ( pFile->bDecrypted ) return;
        pFile->bDecrypted = true;
        this->DecryptDAT(pFile->sData, pFile->lSize);
}
void CCatFile::DecryptDAT(unsigned char *buffer, size_t size)
{
        for(unsigned char *pos=buffer, *end=buffer + size; pos < end; pos++){
                *pos^=0x33;
        }
}
unsigned char *CCatFile::readData(const Utils::WString &filename, size_t *size)
{
        *size = 0;

        if ( !m_fDatFile.NoFile() ) {
                SInCatFile *c = findData(filename);
                if ( c ) return readData ( c, size );
        }

        return NULL;
}

unsigned char *CCatFile::UnpackFile ( SInCatFile *c, size_t *size)
{
        *size = 0;
        if ( !c )
                return NULL;

        this->DecryptDAT(c);

        int iFiletype = this->_checkFiletype(c->sData, 3);

        // plain file
        if ( iFiletype == FILETYPE_PLAIN ) {
                *size = c->lSize;
                return c->sData;
        }

        //otherwise unpack it

//      if ( IsDataPCK ( (const unsigned char *)c->sData, c->lSize ) || forcepck )
        /*int iOffset = 0;
        if ( iFiletype == FILETYPE_PCK ) {
                iOffset = 1;
                for(size_t i=0; i < c->lSize; i++){
                        c->sData[i] ^= magic;
                }
        }
        

        return UnPCKData ( c->sData + iOffset, c->lSize - iOffset, size );*/
        unsigned char *data = UnPCKData(c->sData, c->lSize, size, false);
        if ( !data ) data = UnPCKData(c->sData, c->lSize, size, true);
        return data;
}

bool CCatFile::removeFile(SInCatFile *f)
{
        if ( (m_bCreate) || (_lFiles->empty()) )
                return false;

        if ( !f )
                return false;

        int err = m_fDatFile.TruncateFile ( f->lOffset, f->lSize );
        if ( err == FILEERR_TOSMALL )
        {
                if ( (int)m_fDatFile.GetFilesize() > this->GetEndOffset() )
                        m_fDatFile.TruncateFile( this->GetEndOffset(), m_fDatFile.GetFilesize() - this->GetEndOffset() );
        }
        else if ( err != FILEERR_NONE )
                return false;

        // adjust the file positions
        int iOffset = -1;
        for (auto itr = _lFiles->cbegin(); itr != _lFiles->cend(); ++itr)
        {
                SInCatFile *c = *itr;
                if (c == f) {
                        iOffset = c->lOffset;
                }
                else if ( iOffset >= 0 ) {
                        c->lOffset = iOffset;
                        iOffset += c->lSize;
                }
        }

        // now just write the new cat file
        auto findItr = std::find(_lFiles->begin(), _lFiles->end(), f);
        if (findItr != _lFiles->end())
                _lFiles->erase(findItr);
        m_bCatChanged = true;

        return true;
}

bool CCatFile::WriteCatFile ()
{
        if ( (m_bCreate) && (_lFiles->empty()) ) return false;

        Utils::WString cat = m_fDatFile.filename() + L"\n";
        for (auto itr = _lFiles->cbegin(); itr != _lFiles->cend(); ++itr)
        {
                SInCatFile *c = *itr;
                if ( !c ) continue;
                if ( c->sFile.empty() ) continue;
                Utils::WString str = c->sFile;
                cat += str.findReplace(L"/", L"\\").findReplace(L"\\\\", L"\\") + L" " + (long)c->lSize + L"\n";
        }

        if ( !cat.length() ) return false;

        // make sure the len is in multiples of 5, otherwise decryptData could cause heap problems
        size_t len = cat.length() + ((cat.length() % 5) ? 5 - (cat.length() % 5) : 0);

        bool ret = false;
        try {
                unsigned char *data = new unsigned char[len + 1];
                memcpy(data, cat.c_str(), cat.length() * sizeof(unsigned char));
                for ( size_t i = len; i >= cat.length(); i-- ) data[i] = '\0';
                this->DecryptData(data, len);

                bool ret = m_fCatFile.write(data, cat.length());
                delete []data;
        }
        catch(std::exception &e) {
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::WriteCatFile() unable to malloc, %d (%hs)", len + 1, e.what());
                return false;
        }

        m_fCatFile.close();
        m_bCatChanged = false;
        return ret;
}

std::vector<SInCatFile *> *CCatFile::GetFiles() const
{
        return _lFiles;
}

bool CCatFile::checkExtensionPck(const Utils::WString &filename) const
{       
        Utils::WString ext = CFileIO(filename).extension().lower();
        if ( ext == L"xml" )
                return true;
        else if ( ext == L"txt" )
                return true;
        else if ( ext == L"bob" )
                return true;
        else if ( ext == L"bod" )
                return true;

        return false;
}

bool CCatFile::CheckPackedExtension(const Utils::WString &sFilename)
{
        Utils::WString ext = CFileIO(sFilename).extension().lower();

        if ( ext == L"pck" )
                return true;
        else if ( ext == L"pbb" )
                return true;
        else if ( ext == L"pbd" )
                return true;

        return false;
}

Utils::WString CCatFile::PckChangeExtension(const Utils::WString &f)
{
        CFileIO fo ( f );
        Utils::WString ext = fo.extension ().lower();
        if (ext == L"txt")
                return fo.changeFileExtension(L"pck");
        else if (ext == L"xml")
                return fo.changeFileExtension(L"pck");
        else if ( ext == L"bob" )
                return fo.changeFileExtension(L"pbb");
        else if ( ext == L"bod" )
                return fo.changeFileExtension(L"pbd");

        return f;
}

bool CCatFile::appendFile(const Utils::WString &filename, const Utils::WString &sTo, bool pck, bool bXor, Utils::WString *sChangeTo)
{
        CLog::logf(CLog::Log_IO, 1, L"CCatFile::AppendFile() Adding file, %s, into cat file, %s::%s [PCK:%s XOR:%s]", filename.c_str(), this->m_fCatFile.filename().c_str(), sTo.c_str(), (pck) ? L"Yes" : L"No", (bXor) ? L"Yes" : L"No");

        if ( filename.isin (L"::" ) ) return writeFromCat(filename.token(L"::", 1), filename.token(L"::", 2));
        if ( (!m_bCreate) && (!m_fDatFile.exists ()) ) {
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() Cat File: %s, doesn't exist, quitting...", m_fCatFile.fullFilename().c_str());
                return false;
        }
        Utils::WString to = sTo;
        if ( !_sAddonDir.empty() && CCatFile::IsAddonDir(to) ) {
                CLog::logf(CLog::Log_IO, 3, L"CCatFile::AppendFile() changing destination to included addon fir, %s => %s", to, _sAddonDir + L"/" + to);
                to = _sAddonDir + L"\\" + to;
        }

        // change the file extension and remove the file again
        if ( pck && checkExtensionPck(filename) ) {
                CLog::logf(CLog::Log_IO, 3, L"CCatFile::AppendFile() changing file extension for packed file, %s => %s", to.c_str(), PckChangeExtension(to).c_str());
                to = PckChangeExtension(to);
        }
        if ( sChangeTo ) *sChangeTo = to;

        if ( !_lFiles->empty() ) {
                SInCatFile *checkf = findData(to);
                if ( checkf ) {
                        CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() removing existing filechanging file extension for packed file, %s => %s", to.c_str(), PckChangeExtension(to).c_str());
                        if ( !removeFile(checkf) ) {
                                CLog::logf(CLog::Log_IO, 1, L"CCatFile::AppendFile() unable to remove existing file, quitting...");
                                return false;
                        }
                }
        }

        bool append = false;

        SInCatFile *f = new SInCatFile;
        f->sData = 0;
        f->lOffset = this->GetEndOffset();
        
        bool dofile = true;

        if ( _lFiles->empty() ) {
                CLog::logf(CLog::Log_IO, 3, L"CCatFile::AppendFile() no existing files found, wipeing the existing dat file");
                m_fDatFile.WipeFile();
        }

        CFileIO File(filename);
        if ( !File.startRead() ) {
                CLog::logf(CLog::Log_IO, 3, L"CCatFile::AppendFile() unable to open file, %s, for reading", filename.c_str());
                return false;
        }
        
        CLog::logf(CLog::Log_IO, 3, L"CCatFile::AppendFile() reading file data, %s:%d", filename.c_str(), File.fileSize());
        unsigned char *data = File.readAll();
        File.close();

        // check if we need to pack the file
        int iFileType = this->_checkFiletype(data, 3);
        CLog::logf(CLog::Log_IO, 3, L"CCatFile::AppendFile() preparing to append file data, Type=%d", iFileType);
        if ( iFileType == FILETYPE_PLAIN && CheckPackedExtension(to) ) {
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() plain text file needs to packed");
                size_t newsize = 0;
                unsigned char *newdata = PCKData(data, File.fileSize(), &newsize, bXor);
                CLog::logf(CLog::Log_IO, 1, L"CCatFile::AppendFile() file has been packed, %d => %d", File.fileSize(), newsize);

                f->lSize = newsize;
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() Decrypting data");
                this->DecryptDAT(newdata, f->lSize);
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() appending data to Dat file, Offset:%d", f->lOffset);
                append = m_fDatFile.AppendDataToPos ( (const char *)newdata, newsize, f->lOffset );

                delete [] newdata;
        }
        else  {
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() Decrypting data");
                this->DecryptDAT(data, File.fileSize());
                f->lSize = File.fileSize();
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() appending data to Dat file, Offset:%d", f->lOffset);
                append = m_fDatFile.AppendDataToPos ( (const char *)data, f->lSize, f->lOffset );
        }

        CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() cleaning up memory");
        delete [] data;

        m_fDatFile.close();

        if ( append ) {
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() append complete, adding file into cat list");
                m_bCreate = false;
                f->sFile = to;
                _lFiles->push_back(f);
                CLog::logf(CLog::Log_IO, 3, L"CCatFile::AppendFile() writing the new cat file");
                m_bCatChanged = true;
        }
        else {
                CLog::logf(CLog::Log_IO, 2, L"CCatFile::AppendFile() appending failed, cleaning up file data");
                delete f;
        }

        CLog::logf(CLog::Log_IO, 4, L"CCatFile::AppendFile() function complete");

        return append;
}

bool CCatFile::addData(const Utils::WString &catfile, unsigned char *data, size_t size, const Utils::WString &to, bool pck, bool create)
{
        int err = open(catfile, L"", CATREAD_CATDECRYPT, create);
        if ( (err != CATERR_NONE) && (err != CATERR_CREATED) )
                return false;

        return appendData(data, size, to, pck);
}

int CCatFile::GetEndOffset()
{
        if ( _lFiles->empty() )
                return 0;
        else {
                return _lFiles->back()->lOffset + _lFiles->back()->lSize;
        }
}

size_t CCatFile::GetNumFiles() const 
{ 
        return _lFiles->size(); 
}

SInCatFile *CCatFile::GetFile(unsigned int num) const 
{ 
        return (num >= 0 && num < _lFiles->size()) ? _lFiles->at(num) : NULL; 
}

bool CCatFile::appendData(unsigned char *data, size_t size, const Utils::WString &aTo, bool pck, bool bXor)
{
        if ( (!m_bCreate) && (!m_fCatFile.exists ()) )
                return false;

        if ( (size <= 0) || (!data) )
                return false;

        Utils::WString to = aTo;

        // then check if the file already exists
        if ( !_lFiles->empty() )
        {
                SInCatFile *f = findData(to);
                if ( f )
                {
                        if (!removeFile ( f ))
                                return false;
                }
        }

        bool append = false;

        if (_lFiles->empty())
                m_fDatFile.WipeFile();

        SInCatFile *f = new SInCatFile;
        f->sData = 0;
        if (_lFiles->empty())
                f->lOffset = 0;
        else
                f->lOffset = m_fDatFile.GetFilesize();

        // if file extension is packed but not the file, then pack it, "pck" forces it to be packed unless it already is

        f->lSize = size;
        if ( (((pck) && (checkExtensionPck (to))) || ((CheckPackedExtension (to)))) && (!IsDataPCK ( data, size )) )
        {
                to = PckChangeExtension ( to );

                if (!_lFiles->empty())
                {
                        SInCatFile *f = findData(to);
                        if ( f )
                        {
                                if (!removeFile(f))
                                        return false;
                        }
                }
                size_t newsize = 0;
                unsigned char *d = PCKData ( data, size, &newsize, bXor );

                f->lSize = newsize;
                this->DecryptDAT(d, newsize);
                append = m_fDatFile.AppendDataToPos ( (const char *)d, newsize, f->lOffset );

                delete [] d;
        }
        else {
                this->DecryptDAT(data, size);
                append = m_fDatFile.AppendDataToPos ( (const char *)data, size, f->lOffset );
        }

        m_fDatFile.close();
        if ( append ) {
                m_bCreate = false;
                f->sFile = to;
                _lFiles->push_back(f);
                m_bCatChanged = true;
        }
        else
                delete f;

        return true;
}

Utils::WString CCatFile::RenameFileExtension(SInCatFile *f)
{
        CFileIO fo(f->sFile);
        Utils::WString ext = fo.extension().lower();
        if ( ext == L"pck" )
        {
                Utils::WString firstDir = f->sFile.findReplace(L"/", L"\\").token(L"\\", 1).lower();

                if ( firstDir == L"t" )
                        return fo.changeFileExtension(L"xml");
                else if ( firstDir == L"director" )
                        return fo.changeFileExtension(L"xml");
                return fo.changeFileExtension(L"txt");
        }
        else if ( ext == L"pbb" )
                return fo.changeFileExtension(L"bob");
        else if ( ext == L"pbd" )
                return fo.changeFileExtension(L"bod");
        return f->sFile;
}

void CCatFile::findFiles(Utils::WStringList &files, const Utils::WString &filemask) const
{
        if (_lFiles->empty())
                return;

        for (auto itr = _lFiles->cbegin(); itr != _lFiles->cend(); ++itr)
        {
                if (filemask.match((*itr)->sFile))
                        files.pushBack((*itr)->sFile);
        }
}

bool CCatFile::extractAll(const Utils::WString &dir)
{
        if (_lFiles->empty())
                return false;

        for (auto itr = _lFiles->cbegin(); itr != _lFiles->cend(); ++itr)
        {
                if ( !extractFile(*itr, dir, true) )
                        return false;
        }

        return true;
}
bool CCatFile::extractFile(const Utils::WString &filename, const Utils::WString &to, bool preserve)
{
        if (_lFiles->empty())
                return false;

        // check if file exists
        if ( !_sAddonDir.empty() ) {
                SInCatFile *f = findData(_sAddonDir + L"\\" + filename);
                if ( f )
                        return extractFile(f, to, preserve);
        }
        {
                SInCatFile *f = findData(L"addon\\" + filename);
                if ( f )
                        return extractFile(f, to, preserve);
        }

        SInCatFile *f = findData(filename);
        if ( !f )
        {
                m_iError = CATERR_NOFILE;
                return false;
        }

        return extractFile(f, to, preserve);
}

int CCatFile::_checkFiletype(const unsigned char *pBuffer, int iSize)
{
        if(iSize >= 3) {
                unsigned char magic = pBuffer[0] ^ 0xC8;
                if( (pBuffer[1] ^ magic) == 0x1F && (pBuffer[2] ^ magic) == 0x8B) {
                        return FILETYPE_PCK;
                }
        }

        if ( iSize >= 2 && (pBuffer[0] == 0x1F && pBuffer[1] == 0x8B) ) {
                return FILETYPE_DEFLATE;
        }       

        return FILETYPE_PLAIN;
}

bool CCatFile::extractFile(SInCatFile* f, const Utils::WString &to, bool preserve)
{
        unsigned char *data = 0;
        size_t size = 0;

        // load the data from file
        readFileToData(f);

        data = this->UnpackFile(f, &size);
        if ( !data ) {
                m_iError = CATERR_CANTREAD;
                return false;
        }

        CFileIO fo(CCatFile::RenameFileExtension(f));
        // check for a file name
        Utils::WString checkFile = to;
        Utils::WString todir, tofile = to;
        if ( checkFile.containsAny(L"\\/") )
        {
                checkFile = checkFile.findReplace (L"\\", L"/" );
                checkFile = checkFile.findReplace (L"//", L"/" );
                tofile = checkFile.token(L"/", checkFile.countToken(L"/"));

                if ( !checkFile.contains(L".") || preserve )
                {
                        tofile = fo.filename();
                        todir = checkFile;
                }
                else
                {
                        todir = CFileIO(checkFile).dir();
                        tofile = CFileIO(checkFile).filename();
                }
        }

        if ( tofile.empty() )
        {
                if ( !tofile.empty() )
                        tofile += L"/";
                tofile += fo.filename();
                todir = CFileIO(tofile).dir();
                tofile = CFileIO(tofile).filename();
        }
        
        if ( preserve )
        {
                if ( !fo.dir().Compare(todir.right(- (int)fo.dir().length())) )
                {
                        if ( !todir.empty() && todir.right(1) != L"/" )
                                todir += L"/";
                        todir += fo.dir();
                }
        }

        if ( tofile.empty() )
                tofile = fo.filename();

        bool bWritten = false;

        // create the directory to extract to
        if ( !todir.empty() && !CDirIO(todir).create() )
                m_iError = CATERR_CANTCREATEDIR;
        else
        {
                Utils::WString file = todir;
                if ( !file.empty() )
                        file += L"/";
                file += tofile;
                file = file.findReplace(L"/", L"\\");
                file = file.findReplace(L"\\\\", L"\\");

                CFileIO File(file);
                if ( !File.startWrite() ) m_iError = CATERR_INVALIDDEST;
                else bWritten = File.write(data, size);
        }

        delete data;
        f->sData = 0;

        if ( bWritten ) this->clearError();

        return bWritten;
}

const Utils::WString &CCatFile::internalDatFilename() const
{
        return _sReadFilename;
}

Utils::WString CCatFile::getErrorString () const
{
        switch ( m_iError )
        {
                case CATERR_NONE:
                        return Utils::WString::Null();
                case CATERR_NODATFILE:
                        return L"Unable to open Dat file";
                case CATERR_NOCATFILE:
                        return L"Unable to open Cat file";
                case CATERR_FILEEMPTY:
                        return L"Cat file is empty";
                case CATERR_READCAT:
                        return L"Unable to read from cat file";
                case CATERR_DECRYPT:
                        return L"Unable to decrypt cat file";
                case CATERR_MISMATCH:
                        return L"File size mismatch with Dat file";
                case CATERR_NOFILE:
                        return L"Unable to find file in archive";
                case CATERR_CANTREAD:
                        return L"Unable to read file in archive";
                case CATERR_CANTCREATEDIR:
                        return L"Unable to create destiantion directory";
                case CATERR_INVALIDDEST:
                        return L"Unable to write to destiantion file";
        }

        return L"Invalid";
}


unsigned char *CompressPCKData ( unsigned char *buffer, size_t size, size_t *retsize, time_t mtime )
{
        size_t newsize = (size * 2) + 20;
        unsigned char *data = (unsigned char *)malloc ( sizeof(unsigned char) * newsize );

        z_stream zs;
        char flags=0;

//      error(0);

        unsigned char *d = data;
//      if(m_pszComment && strlen(m_pszComment) > 0) flags|=GZ_F_COMMENT;
//      if(m_pszFileName && strlen(m_pszFileName) > 0) flags|=GZ_F_FILENAME;

        int pos = PCKHEADERSIZE;
        *d = 0x1F;              d++;
        *d = 0x8B;              d++;
        *d = 8;                 d++;
        *d = flags;     d++;
        memcpy(d, &mtime, sizeof(mtime));
        d += 4;
        *d = 0;                 d++;
        *d = 11;                d++;
//      if(flags & GZ_F_FILENAME) put((const char *)m_pszFileName);
//      if(flags & GZ_F_COMMENT) put((const char *)m_pszComment);

        memset(&zs, 0, sizeof(zs));
        zs.next_in=buffer;
        zs.avail_in=(long)size;

        int ret;
        unsigned long ubound;
        ret=deflateInit2(&zs, 9, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY);
        if(ret!=Z_OK)
                return false;

        ubound=deflateBound(&zs, (unsigned long)size);
        if ( newsize < ubound)
        {
                newsize += ubound;
                data = (unsigned char *)realloc ( data, sizeof(unsigned char) * newsize );
        }


        zs.next_out=d;
        zs.avail_out=(unsigned int)newsize - pos;

        while((ret=deflate(&zs, Z_FINISH))==Z_OK)
        {
                newsize += 1024;
                data = (unsigned char *)realloc ( data, sizeof(unsigned char) * newsize );
                zs.next_out=data + zs.total_out;
                zs.avail_out=(unsigned int)newsize - zs.total_out;
        }
        pos += zs.total_out;

        deflateEnd(&zs);

        unsigned long crc=crc32(0, NULL, 0);
        crc=crc32(crc, buffer, (unsigned int)size);

        int s = sizeof(crc) + sizeof(size);
        if ( newsize < (size_t)(s + pos) )
        {
                newsize += (s + pos) - newsize;
                data = (unsigned char *)realloc ( data, sizeof(unsigned char) * newsize );
        }

        memcpy(&data[pos], &crc, sizeof(crc));
        pos += sizeof(crc);
        memcpy(&data[pos], &size, sizeof(size));
        pos += sizeof(size);

        newsize = pos;

        unsigned char *retdata = NULL;
        if ( ret == Z_STREAM_END )
        {
                *retsize = newsize;
                retdata = new unsigned char[newsize];
                memcpy ( retdata, data, newsize );
        }
        free ( data );

        return retdata;
}

unsigned char *PCKData ( unsigned char *data, size_t oldsize, size_t *newsize, bool bXor )
{
        unsigned char *newdata = CompressPCKData ( data, oldsize, newsize, time(NULL) );
        if ( !bXor )
                return newdata;

        if ( newdata )
        {
                char magic = (char)clock(), m;
                m=magic ^ 0xC8;

                unsigned char *ptr = newdata, *end = newdata + *newsize;
                // XOR encryption
                if ( bXor )
                {
                        for ( ; ptr < end; ptr++ )
                                (*ptr)^=magic;
                }

                unsigned char *finalData = new unsigned char[*newsize + 1];
                finalData[0] = m;
                memcpy ( finalData + 1, newdata, *newsize );
                delete [] newdata;
                (*newsize)++;
                return finalData;
        }

        return NULL;
}

bool CCatFile::writeFromCat(CCatFile *fcat, const Utils::WString &file)
{
        // now find the file in the cat file
        SInCatFile *getfile = fcat->findData(file);
        if ( !getfile )
                return false;

        // read the dat from the cat file
        size_t size = 0;
        unsigned char *data = fcat->readData ( getfile, &size );
        if ( !data )
                return false;

        // now check if it exists in this file
        removeFile(file);

        int offset = (_lFiles->empty()) ? 0 : (_lFiles->back()->lOffset + _lFiles->back()->lSize);
        // now write to the new file
        if ( getfile->bDecrypted ) this->DecryptDAT(getfile->sData, getfile->lSize);
        getfile->bDecrypted = false;
        if ( !m_fDatFile.AppendDataToPos((const char *)getfile->sData, getfile->lSize, offset) ) return false;
    m_fDatFile.close();

        // finally add to the list
        SInCatFile *f = new SInCatFile;
        f->sData = 0;
        if (_lFiles->empty())
                f->lOffset = 0;
        else
                f->lOffset = _lFiles->back()->lOffset + _lFiles->back()->lSize;
        f->sFile = file;
        f->lSize = size;
        _lFiles->push_back(f);
        m_bCatChanged = true;

        return true;
}

bool CCatFile::writeFromCat(const Utils::WString &catfile, const Utils::WString &file)
{
        CCatFile fcat;
        if ( fcat.open(catfile, L"", CATREAD_CATDECRYPT, false) )
                return false;

        return this->writeFromCat(&fcat, file);
}
SInCatFile *CCatFile::findData(const Utils::WString &filename) const
{
        if (_lFiles->empty())
                return NULL;

        Utils::WString check = filename;
        check = check.findReplace(L"\\", L"/");
        for (auto itr = _lFiles->cbegin(); itr != _lFiles->cend(); ++itr)
        {
                Utils::WString f = (*itr)->sFile;
                f = f.findReplace(L"\\", L"/");
                if (f.Compare(check))
                        return *itr;
        }
        return NULL;
}

bool CCatFile::markRemoveFile(const Utils::WString &file)
{
        SInCatFile *f = findData(file);
        if ( !f )
        {
                m_iError = CATERR_NOFILE;
                return false;
        }

        return markRemoveFile(f);
}
bool CCatFile::markRemoveFile ( SInCatFile *f )
{
        f->bDelete = true;
        return true;
}