Subversion Repositories spk

Rev

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


#include "ModDiff.h"
#include "File_IO.h"
#include "CatFile.h"

CModDiff::CModDiff(const Utils::WString &dir, const Utils::WString &sAddon, int maxPatch) : m_pCatFile(NULL), m_sAddon(sAddon), m_sTempDir(L"."), m_iMaxPatch(maxPatch)
{
        m_bLoaded = this->LoadDirectory(dir);
}

CModDiff::~CModDiff(void)
{
        delete m_pCatFile;
}

bool CModDiff::LoadDirectory(const Utils::WString &dir)
{
        m_sCurrentDir = dir;
        m_fileSystem.setAddon(m_sAddon);
        return m_fileSystem.LoadFilesystem(dir, m_iMaxPatch);
}

bool CModDiff::startDiff(const Utils::WString &sModFile)
{
        ClearError();
        if ( !CFileIO::Exists(sModFile) ) { m_iError = MDERR_FILENOTFOUND; return false; }

        delete m_pCatFile;
        m_pCatFile = new CCatFile;

        if ( m_pCatFile->open(sModFile, m_sAddon, CATREAD_CATDECRYPT, false) != CATERR_NONE ) { 
                delete m_pCatFile;
                m_pCatFile = NULL;
                m_iError = MDERR_CANTOPENMOD; 
                return false; 
        }

        return true;
}

Utils::WString CModDiff::_extractFile(const Utils::WString &sFile, const Utils::WString &sTo)
{
        SInCatFile *c = m_pCatFile->findData(sFile);
        if ( !c ) c = m_pCatFile->findData(m_sAddon + L"/" + sFile);
        if ( !c ) return m_fileSystem.extractGameFile(sFile, sTo);
        if (m_pCatFile->extractFile(c, sTo)) return sTo;
        return L"";
}

bool CModDiff::doDiff(const Utils::WString &sModFile)
{
        // find the file in the loaded cat
        SInCatFile *c = m_pCatFile->findData(sModFile);
        if ( !c ) c = m_pCatFile->findData(m_sAddon + L"/" + sModFile);
        if ( !c ) return false;

        Utils::WString temp = m_sTempDir;

        // extract the matching file
        Utils::WString sToFile = CFileIO(temp + L"/" + CFileIO(c->sFile).filename()).fullFilename();
        if ( !m_pCatFile->extractFile(sModFile, sToFile) ) return false;

        // create a diff
        Utils::WString to = m_fileSystem.extractGameFile(c->sFile, sToFile + L".compare");
        if ( !to.empty() ) {
                SDiffFile *diff = diffFile(to, sToFile, c->sFile);
                if ( diff ) { 
                        this->_adjustFile(sModFile, diff, false);
                }
                CFileIO::Remove(to);
        }

        CFileIO::Remove(sToFile);

        return true;
}


bool CModDiff::_adjustTShips(SDiffFile *pDiff, bool bReverse)
{
        // need to read TCockpits
        Utils::WString sTo = this->_extractFile(L"types/TCockpits.pck", L"TCockpits.xml");
        if ( sTo.empty() ) return false;
        CFileIO TCockpits(L"TCockpits.xml");
        if ( !TCockpits.exists() ) return false;
        std::vector<Utils::WString> lines;
        if (!TCockpits.readLines(lines))
                return false;

        // remove all the comments
        auto itr = lines.begin();
        while(itr != lines.end())
        {
                if ((*itr)[0] == L'/')
                        itr = lines.erase(itr);
                else
                        itr++;
        }
        lines.erase(lines.begin());

        std::map<Utils::WString, int> fileSet;
        int iPos = 0;
        for(itr = lines.begin(); itr != lines.end(); itr++)
                fileSet[itr->token(L";", -2)] = iPos++;

        // now cycle through ships and adjust the cockpits
        int iCount = -1;
        for ( SDiffEntry *pEntry = pDiff->m_lEntries.First(); pEntry; pEntry = pDiff->m_lEntries.Next() ) {
                switch (pEntry->iType) {
                        case DIFFTYPE_ADDITION:
                                {
                                        SDiffEntryAddition *pAddition = static_cast<SDiffEntryAddition *>(pEntry);
                                        if ( pAddition ) {
                                                for ( int t = 0; t < 6; t++ ) {
                                                        Utils::WString sE = pAddition->sEntry.token(L";", 32 + (t * 2));
                                                        if ( !bReverse && sE.isNumber() )
                                                        {
                                                                int iTurret = sE;
                                                                if ( iTurret && static_cast<size_t>(iTurret) < lines.size()) 
                                                                {
                                                                        Utils::WString sCockpit = lines[iTurret];
                                                                        pAddition->sEntry = pAddition->sEntry.replaceToken(L";", 32 + (t * 2), sCockpit.token(L";", -2) + ":" + static_cast<long>(iTurret));
                                                                }
                                                        }
                                                        else if ( bReverse && !sE.isNumber() ) {
                                                                int iUnfound = 0;
                                                                if ( sE.contains(L":") ) {
                                                                        iUnfound = sE.token(L":", 2);
                                                                        sE = sE.token(L":", 1);
                                                                }
                                                                int iEntry = (fileSet.find(sE) == fileSet.end()) ? iUnfound : fileSet[sE];
                                                                pAddition->sEntry = pAddition->sEntry.replaceToken(L";", 32 + (t * 2), static_cast<long>(iEntry));
                                                        }
                                                }
                                        }
                                }
                                break;
                        case DIFFTYPE_CHANGE:
                                {
                                        SDiffEntryChange *pChange = static_cast<SDiffEntryChange *>(pEntry);
                                        if ( pChange->iPos >= 32 && pChange->iPos <= 42 && (pChange->iPos % 2) && pChange->sEntry.isNumber() ) {
                                                Utils::WString sCockpit = lines[pChange->sEntry.toInt()];
                                                pChange->sEntry = sCockpit.token(L";", -2) + L":" + pChange->sEntry;
                                        }
                                }
                                break;
                }
        }

        return true;
}

int CModDiff::_specialType(const Utils::WString &sFile) const
{
        if ( sFile.Compare(L"TShips") ) return MERGETYPE_TSHIPS;
        return MERGETYPE_NONE;
}

void CModDiff::_adjustFile(const Utils::WString &sFile, SDiffFile *pDiff, bool bReverse)
{
        // check if we need to adjust the file
        CFileIO File(sFile);
        int iType = _specialType(File.baseName());
        if ( iType == MERGETYPE_NONE ) return;

        // read in the file data
        switch(iType) {
                case MERGETYPE_TSHIPS:
                        this->_adjustTShips(pDiff, bReverse);
                        break;
        }
}

bool CModDiff::CreateDiff(const Utils::WString &modfile)
{
        ClearError();

        //check for valid parameters
        if ( !CFileIO::Exists(modfile) ) { m_iError = MDERR_FILENOTFOUND; return false; }

        Utils::WString addonDir = L"";
        int addonSize = addonDir.length() + 1;

        // try and open the mod file
        CCatFile cat;
        if ( cat.open(modfile, addonDir, CATREAD_DAT, false) != CATERR_NONE ) { m_iError = MDERR_CANTOPENMOD; return false; }

        Utils::WString temp = m_sTempDir;

        // we'll need to read in all the types/text files
        for (unsigned int i = 0; i < cat.GetNumFiles(); i++)
        {
                SInCatFile *f = cat.GetFile(i);
                Utils::WString checkFile = f->sFile.findReplace(L"\\", L"/");
                if ( (checkFile.left(6).Compare(L"types/") || checkFile.left(2).Compare(L"t/") || checkFile.left(6 + addonSize).Compare(addonDir + L"/types/") || checkFile.left(2 + addonSize).Compare(addonDir + L"/t/")) && _validFile(checkFile) )
                {
                        // extract the file to the temp dir
                        Utils::WString toFile = CFileIO(temp + L"/" + CFileIO(f->sFile).filename()).fullFilename();
                        if (cat.extractFile(f, toFile ))
                        {
                                // now extract the matching file from the game dir
                                if (!m_fileSystem.extractGameFile(f->sFile, toFile + L".compare").empty() )
                                {
                                        diffFile(toFile + L".compare", toFile, f->sFile);
                                        CFileIO::Remove(toFile + L".compare");
                                }
                                // make sure we clear up afterwards
                                CFileIO::Remove(toFile);
                        }
                }
        }

        return true;
}

SDiffFile *CModDiff::diffFile(const Utils::WString &baseFile, const Utils::WString &modFile, const Utils::WString &fileType)
{
        int type = 0;

        SDiffFile *diffFile = new SDiffFile;
        diffFile->sFile = fileType;

        // read both files and compare them
        CFileIO Base(baseFile);
        std::vector<Utils::WString> baseLines;
        if(Base.readLines(baseLines))
        {
                CFileIO Mod(modFile);
                std::vector<Utils::WString> lines;
                if(Mod.readLines(lines))
                {
                        int id = -1;
                        std::vector<Utils::WString>::iterator node[2];
                        node[0] = baseLines.begin();
                        node[1] = lines.begin();
                        std::vector<Utils::WString>::iterator endNode[2];
                        endNode[0] = baseLines.end();
                        endNode[1] = lines.end();

                        Utils::WString prev[2];

                        while (node[0] != endNode[0] || node[1] != endNode[1])
                        {
                                Utils::WString str[2];

                                for ( int i = 0; i < 2; i++ )
                                {
                                        while (node[i] != endNode[i])
                                        {
                                                Utils::WString l = *node[i];
                                                node[i]++;

                                                l.removeFirstSpace();
                                                l.removeChar('\r');
                                                if ( !l.empty() && l.left(1) != L"/") {
                                                        str[i] += l;
                                                        if ( _isLineComplete(str[i], fileType, (id == -1) ? true : false) ) 
                                                                break;
                                                }
                                        }
                                }

                                if ( id == -1 )
                                        id = 0;
                                else
                                {
                                        // first check for mismatch amount, one of the nodes will be empty
                                        if ( str[0].empty() && !str[1].empty() ) // mod file has more entries (these must be additions)
                                        {
                                                SDiffEntryAddition *entry = new SDiffEntryAddition;
                                                entry->iID = id;
                                                entry->sEntry = str[1];
                                                diffFile->m_lEntries.push_back(entry);
                                        }
                                        else if ( str[1].empty() && !str[0].empty() ) // mod file has less entries (must have removed some)
                                        {
                                                SDiffEntry *entry = new SDiffEntryRemoval;
                                                entry->iID = id;
                                                diffFile->m_lEntries.push_back(entry);
                                        }
                                        else // we have a line for both, we need to compare them
                                        {
                                                // we migth have multiple entries to add when changed
                                                if ( str[0].empty() || str[1].empty() ) continue;
                                                _compareLine(str[0], str[1], type, id, diffFile);                                               
                                        }

                                        ++id;
                                }
                        }
                }
        }

        if ( diffFile->m_lEntries.empty() ) {
                delete diffFile;
                return NULL;
        }
        else {
                m_lFiles.push_back(diffFile);
                return diffFile;
        }
}

int CModDiff::_amountPosition(const Utils::WString &fileType)
{
        return 2;
}

bool CModDiff::_isLineComplete(const Utils::WString &line, const Utils::WString &fileType, bool first)
{
        if ( first )
        {
                if ( line.countToken(L";") > _amountPosition(fileType) ) return true;
                return false;
        }

        return true;
}

void CModDiff::_compareLine(const Utils::WString &line1, const Utils::WString &line2, int type, int id, SDiffFile *diffFile)
{
        std::vector<Utils::WString> str1;
        std::vector<Utils::WString> str2;
        if (!line1.tokenise(L";", str1))
                return;
        if (!line2.tokenise(L";", str2))
                return;

        size_t max = ((str1.size() > str2.size()) ? str2.size() : str1.size());
        for (size_t i = 0; i < max; i++ )
        {
                if ( str1[i] == str2[i] ) continue;
                if ( str1[i].Compare(str2[i]) ) continue;
                if ( str1[i].empty() && str2[i].empty() ) continue;
                if ( str1[i].length() && str1[i][0] == '\0' && str2[i].length() && str2[i][0] == '\0' ) continue;
                SDiffEntryChange *diff = new SDiffEntryChange;
                diff->iID = id;
                diff->iPos = i;
                diff->sEntry = str2[i];
                diff->sFrom = str1[i];
                diffFile->m_lEntries.push_back(diff);
        }
}

void CModDiff::Clean()
{
        for ( CListNode<SDiffFile> *node = m_lFiles.Front(); node; node = node->next() )
        {
                node->Data()->m_lEntries.clear();
                node->DeleteData();
        }
        m_lFiles.clear();
}

bool CModDiff::WriteDiff(const Utils::WString &file)
{
        if ( m_lFiles.empty() ) return false;

        Utils::WStringList lines;
        
        for ( CListNode<SDiffFile> *node = m_lFiles.Front(); node; node = node->next() )
        {
                lines.pushBack(L"$$" + node->Data()->sFile);
                for ( CListNode<SDiffEntry> *node2 = node->Data()->m_lEntries.Front(); node2; node2 = node2->next() )
                {
                        switch(node2->Data()->iType)
                        {
                                case DIFFTYPE_ADDITION:
                                        lines.pushBack(L"+++:" + Utils::WString::Number((long)node2->Data()->iID) + L":" + ((SDiffEntryAddition *)node2->Data())->sEntry);
                                        break;
                                case DIFFTYPE_REMOVAL:
                                        lines.pushBack(L"---:" + Utils::WString::Number((long)node2->Data()->iID));
                                        break;
                                case DIFFTYPE_CHANGE:
                                        lines.pushBack(L"///:" + Utils::WString::Number((long)node2->Data()->iID) + L":" + Utils::WString::Number((long)((SDiffEntryChange *)node2->Data())->iPos) + L":" + ((SDiffEntryChange *)node2->Data())->sEntry);
                                        break;
                        }
                }
        }
        

        CFileIO File(file);
        return File.writeFile(&lines);
}

bool CModDiff::ReadDiff(const Utils::WString &file)
{
        Clean();

        CFileIO File(file);
        if ( !File.exists() ) return false;

        std::vector<Utils::WString> lines;
        if(File.readLines(lines))
        {
                SDiffFile *diffFile = NULL;
                for(auto itr = lines.begin(); itr != lines.end(); itr++)
                {
                        if (itr->left(2).Compare(L"$$") )
                        {
                                diffFile = new SDiffFile;
                                m_lFiles.push_back(diffFile);
                                diffFile->sFile = itr->right(-2);
                        }
                        else if ( diffFile )
                        {
                                if ( itr->left(4).Compare(L"+++:") )
                                {
                                        SDiffEntryAddition *addition = new SDiffEntryAddition;
                                        addition->iID = itr->token(L":", 2).toInt();
                                        addition->sEntry = itr->tokens(L":", 3);
                                        diffFile->m_lEntries.push_back(addition);
                                }
                                else if ( itr->left(4).Compare(L"---:") )
                                {
                                        SDiffEntryRemoval *entry = new SDiffEntryRemoval;
                                        entry->iID = itr->token(L":", 2).toInt();
                                        diffFile->m_lEntries.push_back(entry);
                                }
                                else if (itr->left(4).Compare(L"///:") )
                                {
                                        SDiffEntryChange *entry = new SDiffEntryChange;
                                        entry->iID = itr->token(L":", 2).toInt();
                                        entry->iPos = itr->token(L":", 3).toInt();
                                        entry->sEntry = itr->tokens(L":", 4);
                                        diffFile->m_lEntries.push_back(entry);
                                }
                        }
                }

                return true;
        }

        return false;
}

bool CModDiff::ApplyMod(const Utils::WString &mod)
{
        return m_fileSystem.addMod(mod);
}

bool CModDiff::ApplyDiff(const Utils::WString &mod)
{
        if ( m_lFiles.empty() ) return false;

        this->ApplyMod(mod);

        bool ret = false;

        Utils::WString temp = m_sTempDir;
        Utils::WString addonDir = L"";

        CCatFile cat;
        if ( !CCatFile::Opened(cat.open(mod, addonDir, CATREAD_CATDECRYPT, true)) )
                return false;

        for ( CListNode<SDiffFile> *node = m_lFiles.Front(); node; node = node->next() )
        {
                // extract the file from the game
                SDiffFile *f = node->Data();

                Utils::WStringList writeLines;
                int id;
                if ( _readGameFile(f->sFile, writeLines, &id))
                {
                        // now apply the diff
                        this->_adjustFile(f->sFile, f, true);
                        for ( CListNode<SDiffEntry> *eNode = f->m_lEntries.Front(); eNode; eNode = eNode->next() )
                        {
                                switch ( eNode->Data()->iType )
                                {
                                        case DIFFTYPE_ADDITION:
                                                writeLines.pushBack(((SDiffEntryAddition *)eNode->Data())->sEntry);
                                                break;
                                        case DIFFTYPE_REMOVAL:
                                                writeLines.removeAt(eNode->Data()->iID);
                                                break;
                                        case DIFFTYPE_CHANGE:
                                                {
                                                        auto strAt = writeLines.get(eNode->Data()->iID);
                                                        if ( strAt )
                                                                strAt->str = strAt->str.replaceToken(L";", ((SDiffEntryChange *)eNode->Data())->iPos, ((SDiffEntryChange *)eNode->Data())->sEntry);
                                                }
                                                break;
                                }
                        }

                        // add our comments and info
                        writeLines.pushFront(Utils::WString((long)id) + L";" + (long)writeLines.size() + L";");
                        writeLines.pushFront(L"// Generated by ModDiff (SPK Version: " + Utils::WString::FromFloat(GetLibraryVersion(), 2) + L")");

                        // now write the file
                        CFileIO WriteFile(temp + L"/" + CFileIO(f->sFile).filename());
                        if ( WriteFile.writeFile(&writeLines) )
                        {
                                if ( cat.appendFile(m_sTempDir + L"/" + CFileIO(f->sFile).filename(), f->sFile) )
                                        ret = true;
                                WriteFile.remove();
                        }
                }
        }

        if ( ret )
                cat.WriteCatFile();

        return ret;
}

bool CModDiff::_readGameFile(const Utils::WString &file, Utils::WStringList &writeLines, int *id)
{
        bool ret = false;

        Utils::WString sTo = m_fileSystem.extractGameFile(file, m_sTempDir + L"/" + CFileIO(file).filename());
        if ( !sTo.empty() ) {
                CFileIO File(sTo);

                std::vector<Utils::WString> lines;
                if(File.readLines(lines))
                {
                        int entries = -1;
                        for(auto itr = lines.begin(); itr != lines.end(); itr++)
                        {
                                Utils::WString l = *itr;
                                l.removeFirstSpace();
                                if ( l.empty() ) continue;
                                if ( l.left(1) == L"/" ) continue;

                                if ( entries == -1 )
                                {
                                        entries = l.token(L":", 2).toInt();
                                        if ( id )
                                                (*id) = l.token(L":", 1).toInt();
                                }
                                else
                                        writeLines.pushBack(l);
                        }
                        ret = true;
                }
                File.close();
                File.remove();
        }

        return ret;
}


bool CModDiff::_validFile(const Utils::WString &file)
{
        return CModDiff::CanBeDiffed(file);
}

bool CModDiff::CanBeDiffed(const Utils::WString &file)
{
        Utils::WString checkFile = file;
        checkFile = CFileIO(file).dir() + L"/" + CFileIO(file).baseName();

        // all t files are the same format
        if ( checkFile.left(7).Compare(L"types/T") )
                return true;

        return false;
}