Subversion Repositories spk

Rev

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(CyString &dir, int maxPatch)
{
        m_bLoaded = LoadDirectory(dir);
        m_sTempDir = ".";
        m_iMaxPatch = maxPatch;
}

CModDiff::~CModDiff(void)
{
}

bool CModDiff::LoadDirectory(CyString &dir)
{
        m_sCurrentDir = dir;
        return m_fileSystem.LoadFilesystem(dir, NullString, m_iMaxPatch);
}

bool CModDiff::CreateDiff(CyString &modfile)
{
        ClearError();

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

        CyString addonDir = "";
        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; }

        // we'll need to read in all the types/text files
        for ( int i = 0; i < cat.GetNumFiles(); i++ )
        {
                SInCatFile *f = cat.GetFile(i);
                CyString checkFile = f->sFile.FindReplace("\\", "/");
                if ( (checkFile.Left(6).Compare("types/") || checkFile.Left(2).Compare("t/") || checkFile.Left(6 + addonSize).Compare(addonDir + "/types/") || checkFile.Left(2 + addonSize).Compare(addonDir + "/t/")) && ValidFile(checkFile) )
                {
                        // extract the file to the temp dir
                        CyString toFile = CFileIO(m_sTempDir + "/" + CFileIO(f->sFile).GetFilename()).GetFullFilename();
                        if ( cat.ExtractFile(f, toFile ) )
                        {
                                // now extract the matching file from the game dir
                                if ( m_fileSystem.ExtractGameFile(f->sFile, toFile + ".compare") )
                                {
                                        DiffFile(toFile + ".compare", toFile, f->sFile);
                                        CFileIO(toFile + ".compare").Remove();
                                }
                                // make sure we clear up afterwards
                                CFileIO(toFile).Remove();
                        }
                }
        }

        return true;
}

bool CModDiff::DiffFile(CyString &baseFile, CyString &modFile, CyString &fileType)
{
        int type = 0;

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

        // read both files and compare them
        CFileIO Base(baseFile);
        CyStringList *baseLines = Base.ReadLinesStr();
        if ( baseLines )
        {
                CFileIO Mod(modFile);
                CyStringList *lines = Mod.ReadLinesStr();
                if ( lines )
                {
                        int id = -1;
                        SStringList *node[2];
                        node[0] = baseLines->Head();
                        node[1] = lines->Head();

                        CyString prev[2];

                        while ( node[0] || node[1] )
                        {
                                CyString str[2];

                                for ( int i = 0; i < 2; i++ )
                                {
                                        while (node[i])
                                        {
                                                CyString l = node[i]->str;
                                                node[i] = node[i]->next;

                                                l.RemoveFirstSpace();
                                                l.RemoveChar('\r');
                                                if ( !l.Empty() && l.Left(1) != "/") 
                                                {
                                                        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;
                                }
                        }

                        delete lines;
                }
                delete baseLines;
        }

        if ( diffFile->m_lEntries.empty() )
                delete diffFile;
        else
                m_lFiles.push_back(diffFile);

        return true;
}

int CModDiff::GetAmountPosition(const CyString &fileType)
{
        return 2;
}

bool CModDiff::IsLineComplete(CyString &line, CyString &fileType, bool first)
{
        if ( first )
        {
                if ( line.NumToken(";") > GetAmountPosition(fileType) ) return true;
                return false;
        }

        return true;
}

void CModDiff::CompareLine(CyString &line1, CyString &line2, int type, int id, SDiffFile *diffFile)
{
        int max1, max2;
        CyString *str1 = line1.SplitToken(";", &max1);
        CyString *str2 = line2.SplitToken(";", &max2);

        if ( !str1 || !str2 ) return;

        int max = ((max1 > max2) ? max2 : max1);
        for ( int 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(CyString &file)
{
        if ( m_lFiles.empty() ) return false;

        CyStringList lines;
        
        for ( CListNode<SDiffFile> *node = m_lFiles.Front(); node; node = node->next() )
        {
                lines.PushBack(CyString("$$") + 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(CyString("+++:") + (long)node2->Data()->iID + ":" + ((SDiffEntryAddition *)node2->Data())->sEntry);
                                        break;
                                case DIFFTYPE_REMOVAL:
                                        lines.PushBack(CyString("---:") + (long)node2->Data()->iID);
                                        break;
                                case DIFFTYPE_CHANGE:
                                        lines.PushBack(CyString("///:") + (long)node2->Data()->iID + ":" + (long)((SDiffEntryChange *)node2->Data())->iPos + ":" + ((SDiffEntryChange *)node2->Data())->sEntry);
                                        break;
                        }
                }
        }
        

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

bool CModDiff::ReadDiff(CyString &file)
{
        Clean();

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

        CyStringList *lines = File.ReadLinesStr();

        if ( lines )
        {
                SDiffFile *diffFile = NULL;
                for ( SStringList *str = lines->Head(); str; str = str->next )
                {
                        if ( str->str.Left(2).Compare("$$") )
                        {
                                diffFile = new SDiffFile;
                                m_lFiles.push_back(diffFile);
                                diffFile->sFile = str->str.Right(-2);
                        }
                        else if ( diffFile )
                        {
                                if ( str->str.Left(4).Compare("+++:") )
                                {
                                        SDiffEntryAddition *addition = new SDiffEntryAddition;
                                        addition->iID = str->str.GetToken(":", 2, 2).ToInt();
                                        addition->sEntry = str->str.GetToken(":", 3);
                                        diffFile->m_lEntries.push_back(addition);
                                }
                                else if ( str->str.Left(4).Compare("---:") )
                                {
                                        SDiffEntryRemoval *entry = new SDiffEntryRemoval;
                                        entry->iID = str->str.GetToken(":", 2, 2).ToInt();
                                        diffFile->m_lEntries.push_back(entry);
                                }
                                else if ( str->str.Left(4).Compare("///:") )
                                {
                                        SDiffEntryChange *entry = new SDiffEntryChange;
                                        entry->iID = str->str.GetToken(":", 2, 2).ToInt();
                                        entry->iPos = str->str.GetToken(":", 3, 3).ToInt();
                                        entry->sEntry = str->str.GetToken(":", 4);
                                        diffFile->m_lEntries.push_back(entry);
                                }
                        }
                }

                delete lines;

                return true;
        }

        return false;
}

bool CModDiff::ApplyMod(CyString &mod)
{
        return m_fileSystem.LoadMod(mod);
}

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

        bool ret = false;

        CyString addonDir = "";

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

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

                CyStringList writeLines;
                int id;
                if ( ReadGameFile(f->sFile, &writeLines, &id) )
                {
                        // now apply the diff
                        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.GetAt(eNode->Data()->iID)->remove = true;
                                                break;
                                        case DIFFTYPE_CHANGE:
                                                {
                                                        SStringList *strAt = writeLines.GetAt(eNode->Data()->iID);
                                                        if ( strAt )
                                                                strAt->str.RepToken(";", ((SDiffEntryChange *)eNode->Data())->iPos, ((SDiffEntryChange *)eNode->Data())->sEntry);
                                                }
                                                break;
                                }
                        }

                        // add our comments and info
                        writeLines.PushFront(CyString((long)id) + ";" + (long)writeLines.Count() + ";");
                        writeLines.PushFront(CyString("// Generated by ModDiff (SPK Version: ") + CyString::CreateFromFloat(GetLibraryVersion(), 2) + ")");

                        // now write the file
                        CFileIO WriteFile(m_sTempDir + "/" + CFileIO(f->sFile).GetFilename());
                        if ( WriteFile.WriteFile(&writeLines) )
                        {
                                if ( cat.AppendFile(m_sTempDir + "/" + CFileIO(f->sFile).GetFilename(), f->sFile) )
                                {
                                        ret = true;
                                }
                        }
                }
        }

        if ( ret )
                cat.WriteCatFile();

        return ret;
}

bool CModDiff::ReadGameFile(CyString &file, CyStringList *writeLines, int *id)
{
        bool ret = false;

        if ( m_fileSystem.ExtractGameFile(file, m_sTempDir + "/" + CFileIO(file).GetFilename()) )
        {
                CFileIO File(m_sTempDir + "/" + CFileIO(file).GetFilename());

                CyStringList *lines = File.ReadLinesStr();
                if ( lines )
                {
                        int entries = -1;
                        for ( SStringList *str = lines->Head(); str; str = str->next )
                        {
                                CyString l = str->str;
                                l.RemoveFirstSpace();
                                if ( l.Empty() ) continue;
                                if ( l.Left(1) == "/" ) continue;

                                if ( entries == -1 )
                                {
                                        entries = l.GetToken(":", 2, 2).ToInt();
                                        if ( id )
                                                (*id) = l.GetToken(":", 1, 1).ToInt();
                                }
                                else
                                        writeLines->PushBack(l);
                        }
                        delete lines;
                        ret = true;
                }
                File.Remove();
        }

        return ret;
}


bool CModDiff::ValidFile(CyString &file)
{
        return CModDiff::CanBeDiffed(file);
}

bool CModDiff::CanBeDiffed(const CyString &file)
{
        CyString checkFile = file;
        checkFile = CFileIO(file).GetDir() + "/" + CFileIO(file).GetBaseName();

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

        return false;
}