Rev 1 | 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.ToString(), 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.ToString(), (toFile + ".compare").ToString()) )
{
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.addMod(mod.ToString());
}
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.ToString(), (m_sTempDir + "/" + CFileIO(file).GetFilename()).ToString()) )
{
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;
}