Subversion Repositories spk

Rev

Rev 160 | Rev 181 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 cycrow 1
 
2
#include "ModDiff.h"
3
#include "File_IO.h"
4
#include "CatFile.h"
5
 
58 cycrow 6
CModDiff::CModDiff(const Utils::String &dir, const Utils::String &sAddon, int maxPatch) : m_pCatFile(NULL), m_sAddon(sAddon), m_sTempDir("."), m_iMaxPatch(maxPatch)
1 cycrow 7
{
58 cycrow 8
	m_bLoaded = this->LoadDirectory(dir);
1 cycrow 9
}
10
 
11
CModDiff::~CModDiff(void)
12
{
58 cycrow 13
	delete m_pCatFile;
1 cycrow 14
}
15
 
58 cycrow 16
bool CModDiff::LoadDirectory(const Utils::String &dir)
1 cycrow 17
{
18
	m_sCurrentDir = dir;
101 cycrow 19
	m_fileSystem.setAddon(m_sAddon);
58 cycrow 20
	return m_fileSystem.LoadFilesystem(dir, m_iMaxPatch);
1 cycrow 21
}
22
 
58 cycrow 23
bool CModDiff::startDiff(const Utils::String &sModFile)
1 cycrow 24
{
25
	ClearError();
58 cycrow 26
	if ( !CFileIO::Exists(sModFile) ) { m_iError = MDERR_FILENOTFOUND; return false; }
1 cycrow 27
 
58 cycrow 28
	delete m_pCatFile;
29
	m_pCatFile = new CCatFile;
30
 
125 cycrow 31
	if ( m_pCatFile->open(sModFile, m_sAddon, CATREAD_CATDECRYPT, false) != CATERR_NONE ) { 
58 cycrow 32
		delete m_pCatFile;
33
		m_pCatFile = NULL;
34
		m_iError = MDERR_CANTOPENMOD; 
35
		return false; 
36
	}
37
 
38
	return true;
39
}
40
 
41
Utils::String CModDiff::_extractFile(const Utils::String &sFile, const Utils::String &sTo)
42
{
43
	SInCatFile *c = m_pCatFile->FindData(sFile);
44
	if ( !c ) c = m_pCatFile->FindData(m_sAddon + "/" + sFile);
45
	if ( !c ) return m_fileSystem.ExtractGameFile(sFile, sTo);
46
	if ( m_pCatFile->ExtractFile(c, sTo) ) return sTo;
47
	return "";
48
}
49
 
50
bool CModDiff::doDiff(const Utils::String &sModFile)
51
{
52
	// find the file in the loaded cat
53
	SInCatFile *c = m_pCatFile->FindData(sModFile);
54
	if ( !c ) c = m_pCatFile->FindData(m_sAddon + "/" + sModFile);
55
	if ( !c ) return false;
56
 
57
	// extract the matching file
58
	Utils::String sToFile = CFileIO(m_sTempDir + "/" + CFileIO(c->sFile).filename()).fullFilename();
59
	if ( !m_pCatFile->ExtractFile(sModFile, sToFile) ) return false;
60
 
61
	// create a diff
124 cycrow 62
	Utils::String to = m_fileSystem.ExtractGameFile(c->sFile, sToFile + ".compare");
58 cycrow 63
	if ( !to.empty() ) {
124 cycrow 64
		SDiffFile *diff = diffFile(to, sToFile, c->sFile);
58 cycrow 65
		if ( diff ) { 
66
			this->_adjustFile(sModFile, diff, false);
67
		}
68
		CFileIO::Remove(to);
69
	}
70
 
71
	CFileIO::Remove(sToFile);
72
 
73
	return true;
74
}
75
 
76
 
77
bool CModDiff::_adjustTShips(SDiffFile *pDiff, bool bReverse)
78
{
79
	// need to read TCockpits
80
	Utils::String sTo = this->_extractFile("types/TCockpits.pck", "TCockpits.xml");
81
	if ( sTo.empty() ) return false;
82
	CFileIO TCockpits("TCockpits.xml");
83
	if ( !TCockpits.exists() ) return false;
173 cycrow 84
	std::vector<Utils::String> lines;
85
	if (!TCockpits.readLines(lines))
86
		return false;
58 cycrow 87
 
88
	// remove all the comments
173 cycrow 89
	auto itr = lines.begin();
90
	while(itr != lines.end())
91
	{
92
		if ((*itr)[0] == '/')
93
			itr = lines.erase(itr);
94
		else
95
			itr++;
58 cycrow 96
	}
173 cycrow 97
	lines.erase(lines.begin());
58 cycrow 98
 
84 cycrow 99
	std::map<Utils::String, int> fileSet;
58 cycrow 100
	int iPos = 0;
173 cycrow 101
	for(itr = lines.begin(); itr != lines.end(); itr++)
102
		fileSet[itr->token(";", -2)] = iPos++;
58 cycrow 103
 
104
	// now cycle through ships and adjust the cockpits
105
	int iCount = -1;
106
	for ( SDiffEntry *pEntry = pDiff->m_lEntries.First(); pEntry; pEntry = pDiff->m_lEntries.Next() ) {
107
		switch (pEntry->iType) {
108
			case DIFFTYPE_ADDITION:
109
				{
110
					SDiffEntryAddition *pAddition = static_cast<SDiffEntryAddition *>(pEntry);
111
					if ( pAddition ) {
112
						for ( int t = 0; t < 6; t++ ) {
113
							Utils::String sE = pAddition->sEntry.token(";", 32 + (t * 2));
173 cycrow 114
							if ( !bReverse && sE.isNumber() )
115
							{
58 cycrow 116
								int iTurret = sE;
173 cycrow 117
								if ( iTurret && static_cast<size_t>(iTurret) < lines.size()) 
118
								{
119
									Utils::String sCockpit = lines[iTurret];
58 cycrow 120
									pAddition->sEntry = pAddition->sEntry.replaceToken(";", 32 + (t * 2), sCockpit.token(";", -2) + ":" + static_cast<long>(iTurret));
121
								}
122
							}
123
							else if ( bReverse && !sE.isNumber() ) {
124
								int iUnfound = 0;
125
								if ( sE.isin(":") ) {
126
									iUnfound = sE.token(":", 2);
127
									sE = sE.token(":", 1);
128
								}
129
								int iEntry = (fileSet.find(sE) == fileSet.end()) ? iUnfound : fileSet[sE];
130
								pAddition->sEntry = pAddition->sEntry.replaceToken(";", 32 + (t * 2), static_cast<long>(iEntry));
131
							}
132
						}
133
					}
134
				}
135
				break;
136
			case DIFFTYPE_CHANGE:
137
				{
138
					SDiffEntryChange *pChange = static_cast<SDiffEntryChange *>(pEntry);
139
					if ( pChange->iPos >= 32 && pChange->iPos <= 42 && (pChange->iPos % 2) && pChange->sEntry.isNumber() ) {
173 cycrow 140
						Utils::String sCockpit = lines[pChange->sEntry.toInt()];
58 cycrow 141
						pChange->sEntry = sCockpit.token(";", -2) + ":" + pChange->sEntry;
142
					}
143
				}
144
				break;
145
		}
146
	}
147
 
148
	return true;
149
}
150
 
151
int CModDiff::_specialType(const Utils::String &sFile)
152
{
153
	if ( sFile.Compare("TShips") ) return MERGETYPE_TSHIPS;
154
	return MERGETYPE_NONE;
155
}
156
 
157
void CModDiff::_adjustFile(const Utils::String &sFile, SDiffFile *pDiff, bool bReverse)
158
{
159
	// check if we need to adjust the file
160
	CFileIO File(sFile);
161
	int iType = _specialType(File.baseName());
162
	if ( iType == MERGETYPE_NONE ) return;
163
 
164
	// read in the file data
165
	switch(iType) {
166
		case MERGETYPE_TSHIPS:
167
			this->_adjustTShips(pDiff, bReverse);
168
			break;
169
	}
170
}
171
 
172
bool CModDiff::CreateDiff(const Utils::String &modfile)
173
{
174
	ClearError();
175
 
1 cycrow 176
	//check for valid parameters
52 cycrow 177
	if ( !CFileIO(modfile).ExistsOld() ) { m_iError = MDERR_FILENOTFOUND; return false; }
1 cycrow 178
 
58 cycrow 179
	Utils::String addonDir = "";
180
	int addonSize = addonDir.length() + 1;
1 cycrow 181
 
182
	// try and open the mod file
183
	CCatFile cat;
125 cycrow 184
	if ( cat.open(modfile, addonDir, CATREAD_DAT, false) != CATERR_NONE ) { m_iError = MDERR_CANTOPENMOD; return false; }
1 cycrow 185
 
186
	// we'll need to read in all the types/text files
125 cycrow 187
	for (unsigned int i = 0; i < cat.GetNumFiles(); i++)
1 cycrow 188
	{
189
		SInCatFile *f = cat.GetFile(i);
124 cycrow 190
		Utils::String checkFile = f->sFile.findReplace("\\", "/");
58 cycrow 191
		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) )
1 cycrow 192
		{
193
			// extract the file to the temp dir
58 cycrow 194
			Utils::String toFile = CFileIO(m_sTempDir + "/" + CFileIO(f->sFile).filename()).fullFilename();
1 cycrow 195
			if ( cat.ExtractFile(f, toFile ) )
196
			{
197
				// now extract the matching file from the game dir
124 cycrow 198
				if ( m_fileSystem.ExtractGameFile(f->sFile, toFile + ".compare") )
1 cycrow 199
				{
124 cycrow 200
					diffFile(toFile + ".compare", toFile, f->sFile);
58 cycrow 201
					CFileIO::Remove(toFile + ".compare");
1 cycrow 202
				}
203
				// make sure we clear up afterwards
58 cycrow 204
				CFileIO::Remove(toFile);
1 cycrow 205
			}
206
		}
207
	}
208
 
209
	return true;
210
}
211
 
58 cycrow 212
SDiffFile *CModDiff::diffFile(const Utils::String &baseFile, const Utils::String &modFile, const Utils::String &fileType)
1 cycrow 213
{
214
	int type = 0;
215
 
216
	SDiffFile *diffFile = new SDiffFile;
217
	diffFile->sFile = fileType;
218
 
219
	// read both files and compare them
220
	CFileIO Base(baseFile);
173 cycrow 221
	std::vector<Utils::String> baseLines;
222
	if(Base.readLines(baseLines))
1 cycrow 223
	{
224
		CFileIO Mod(modFile);
173 cycrow 225
		std::vector<Utils::String> lines;
226
		if(Mod.readLines(lines))
1 cycrow 227
		{
228
			int id = -1;
173 cycrow 229
			std::vector<Utils::String>::iterator node[2];
230
			node[0] = baseLines.begin();
231
			node[1] = lines.begin();
232
			std::vector<Utils::String>::iterator endNode[2];
233
			endNode[0] = baseLines.end();
234
			endNode[1] = lines.end();
1 cycrow 235
 
58 cycrow 236
			Utils::String prev[2];
1 cycrow 237
 
173 cycrow 238
			while (node[0] != endNode[0] || node[1] != endNode[1])
1 cycrow 239
			{
58 cycrow 240
				Utils::String str[2];
1 cycrow 241
 
242
				for ( int i = 0; i < 2; i++ )
243
				{
173 cycrow 244
					while (node[i] != endNode[i])
1 cycrow 245
					{
173 cycrow 246
						Utils::String l = *node[i];
247
						node[i]++;
1 cycrow 248
 
58 cycrow 249
						l.removeFirstSpace();
250
						l.removeChar('\r');
251
						if ( !l.empty() && l.left(1) != "/") {
1 cycrow 252
							str[i] += l;
58 cycrow 253
							if ( _isLineComplete(str[i], fileType, (id == -1) ? true : false) ) 
1 cycrow 254
								break;
255
						}
256
					}
257
				}
258
 
259
				if ( id == -1 )
260
					id = 0;
261
				else
262
				{
263
					// first check for mismatch amount, one of the nodes will be empty
58 cycrow 264
					if ( str[0].empty() && !str[1].empty() ) // mod file has more entries (these must be additions)
1 cycrow 265
					{
266
						SDiffEntryAddition *entry = new SDiffEntryAddition;
267
						entry->iID = id;
268
						entry->sEntry = str[1];
269
						diffFile->m_lEntries.push_back(entry);
270
					}
58 cycrow 271
					else if ( str[1].empty() && !str[0].empty() ) // mod file has less entries (must have removed some)
1 cycrow 272
					{
273
						SDiffEntry *entry = new SDiffEntryRemoval;
274
						entry->iID = id;
275
						diffFile->m_lEntries.push_back(entry);
276
					}
277
					else // we have a line for both, we need to compare them
278
					{
279
						// we migth have multiple entries to add when changed
58 cycrow 280
						if ( str[0].empty() || str[1].empty() ) continue;
281
						_compareLine(str[0], str[1], type, id, diffFile);						
1 cycrow 282
					}
283
 
284
					++id;
285
				}
286
			}
287
		}
288
	}
289
 
58 cycrow 290
	if ( diffFile->m_lEntries.empty() ) {
1 cycrow 291
		delete diffFile;
58 cycrow 292
		return NULL;
293
	}
294
	else {
1 cycrow 295
		m_lFiles.push_back(diffFile);
58 cycrow 296
		return diffFile;
297
	}
1 cycrow 298
}
299
 
58 cycrow 300
int CModDiff::_amountPosition(const Utils::String &fileType)
1 cycrow 301
{
302
	return 2;
303
}
304
 
58 cycrow 305
bool CModDiff::_isLineComplete(const Utils::String &line, const Utils::String &fileType, bool first)
1 cycrow 306
{
307
	if ( first )
308
	{
58 cycrow 309
		if ( line.countToken(";") > _amountPosition(fileType) ) return true;
1 cycrow 310
		return false;
311
	}
312
 
313
	return true;
314
}
315
 
58 cycrow 316
void CModDiff::_compareLine(const Utils::String &line1, const Utils::String &line2, int type, int id, SDiffFile *diffFile)
1 cycrow 317
{
318
	int max1, max2;
58 cycrow 319
	Utils::String *str1 = line1.tokenise(";", &max1);
320
	Utils::String *str2 = line2.tokenise(";", &max2);
1 cycrow 321
 
322
	if ( !str1 || !str2 ) return;
323
 
324
	int max = ((max1 > max2) ? max2 : max1);
325
	for ( int i = 0; i < max; i++ )
326
	{
327
		if ( str1[i] == str2[i] ) continue;
328
		if ( str1[i].Compare(str2[i]) ) continue;
58 cycrow 329
		if ( str1[i].empty() && str2[i].empty() ) continue;
330
		if ( str1[i].length() && str1[i][0] == '\0' && str2[i].length() && str2[i][0] == '\0' ) continue;
1 cycrow 331
		SDiffEntryChange *diff = new SDiffEntryChange;
332
		diff->iID = id;
333
		diff->iPos = i;
334
		diff->sEntry = str2[i];
335
		diff->sFrom = str1[i];
336
		diffFile->m_lEntries.push_back(diff);
337
	}
338
}
339
 
340
void CModDiff::Clean()
341
{
342
	for ( CListNode<SDiffFile> *node = m_lFiles.Front(); node; node = node->next() )
343
	{
344
		node->Data()->m_lEntries.clear();
345
		node->DeleteData();
346
	}
347
	m_lFiles.clear();
348
}
349
 
58 cycrow 350
bool CModDiff::WriteDiff(const Utils::String &file)
1 cycrow 351
{
352
	if ( m_lFiles.empty() ) return false;
353
 
160 cycrow 354
	Utils::CStringList lines;
1 cycrow 355
 
356
	for ( CListNode<SDiffFile> *node = m_lFiles.Front(); node; node = node->next() )
357
	{
160 cycrow 358
		lines.pushBack("$$" + node->Data()->sFile);
1 cycrow 359
		for ( CListNode<SDiffEntry> *node2 = node->Data()->m_lEntries.Front(); node2; node2 = node2->next() )
360
		{
361
			switch(node2->Data()->iType)
362
			{
363
				case DIFFTYPE_ADDITION:
160 cycrow 364
					lines.pushBack("+++:" + Utils::String::Number((long)node2->Data()->iID) + ":" + ((SDiffEntryAddition *)node2->Data())->sEntry);
1 cycrow 365
					break;
366
				case DIFFTYPE_REMOVAL:
160 cycrow 367
					lines.pushBack("---:" + Utils::String::Number((long)node2->Data()->iID));
1 cycrow 368
					break;
369
				case DIFFTYPE_CHANGE:
160 cycrow 370
					lines.pushBack("///:" + Utils::String::Number((long)node2->Data()->iID) + ":" + Utils::String::Number((long)((SDiffEntryChange *)node2->Data())->iPos) + ":" + ((SDiffEntryChange *)node2->Data())->sEntry);
1 cycrow 371
					break;
372
			}
373
		}
374
	}
375
 
376
 
377
	CFileIO File(file);
160 cycrow 378
	return File.writeFile(&lines);
1 cycrow 379
}
380
 
58 cycrow 381
bool CModDiff::ReadDiff(const Utils::String &file)
1 cycrow 382
{
383
	Clean();
384
 
385
	CFileIO File(file);
52 cycrow 386
	if ( !File.exists() ) return false;
1 cycrow 387
 
173 cycrow 388
	std::vector<Utils::String> lines;
389
	if(File.readLines(lines))
1 cycrow 390
	{
391
		SDiffFile *diffFile = NULL;
173 cycrow 392
		for(auto itr = lines.begin(); itr != lines.end(); itr++)
1 cycrow 393
		{
173 cycrow 394
			if (itr->left(2).Compare("$$") )
1 cycrow 395
			{
396
				diffFile = new SDiffFile;
397
				m_lFiles.push_back(diffFile);
173 cycrow 398
				diffFile->sFile = itr->right(-2);
1 cycrow 399
			}
400
			else if ( diffFile )
401
			{
173 cycrow 402
				if ( itr->left(4).Compare("+++:") )
1 cycrow 403
				{
404
					SDiffEntryAddition *addition = new SDiffEntryAddition;
173 cycrow 405
					addition->iID = itr->token(":", 2).toInt();
406
					addition->sEntry = itr->tokens(":", 3);
1 cycrow 407
					diffFile->m_lEntries.push_back(addition);
408
				}
173 cycrow 409
				else if ( itr->left(4).Compare("---:") )
1 cycrow 410
				{
411
					SDiffEntryRemoval *entry = new SDiffEntryRemoval;
173 cycrow 412
					entry->iID = itr->token(":", 2).toInt();
1 cycrow 413
					diffFile->m_lEntries.push_back(entry);
414
				}
173 cycrow 415
				else if (itr->left(4).Compare("///:") )
1 cycrow 416
				{
417
					SDiffEntryChange *entry = new SDiffEntryChange;
173 cycrow 418
					entry->iID = itr->token(":", 2).toInt();
419
					entry->iPos = itr->token(":", 3).toInt();
420
					entry->sEntry = itr->tokens(":", 4);
1 cycrow 421
					diffFile->m_lEntries.push_back(entry);
422
				}
423
			}
424
		}
425
 
426
		return true;
427
	}
428
 
429
	return false;
430
}
431
 
58 cycrow 432
bool CModDiff::ApplyMod(const Utils::String &mod)
1 cycrow 433
{
58 cycrow 434
	return m_fileSystem.addMod(mod);
1 cycrow 435
}
436
 
58 cycrow 437
bool CModDiff::ApplyDiff(const Utils::String &mod)
1 cycrow 438
{
439
	if ( m_lFiles.empty() ) return false;
440
 
58 cycrow 441
	this->ApplyMod(mod);
442
 
1 cycrow 443
	bool ret = false;
444
 
125 cycrow 445
	Utils::String addonDir = "";
1 cycrow 446
 
447
	CCatFile cat;
125 cycrow 448
	if ( !CCatFile::Opened(cat.open(mod, addonDir, CATREAD_CATDECRYPT, true)) )
1 cycrow 449
		return false;
450
 
451
	for ( CListNode<SDiffFile> *node = m_lFiles.Front(); node; node = node->next() )
452
	{
453
		// extract the file from the game
454
		SDiffFile *f = node->Data();
455
 
160 cycrow 456
		Utils::CStringList writeLines;
1 cycrow 457
		int id;
160 cycrow 458
		if ( _readGameFile(f->sFile, writeLines, &id) )
1 cycrow 459
		{
460
			// now apply the diff
58 cycrow 461
			this->_adjustFile(f->sFile, f, true);
1 cycrow 462
			for ( CListNode<SDiffEntry> *eNode = f->m_lEntries.Front(); eNode; eNode = eNode->next() )
463
			{
464
				switch ( eNode->Data()->iType )
465
				{
466
					case DIFFTYPE_ADDITION:
160 cycrow 467
						writeLines.pushBack(((SDiffEntryAddition *)eNode->Data())->sEntry);
1 cycrow 468
						break;
469
					case DIFFTYPE_REMOVAL:
160 cycrow 470
						writeLines.removeAt(eNode->Data()->iID);
1 cycrow 471
						break;
472
					case DIFFTYPE_CHANGE:
473
						{
160 cycrow 474
							auto strAt = writeLines.get(eNode->Data()->iID);
1 cycrow 475
							if ( strAt )
160 cycrow 476
								strAt->str = strAt->str.replaceToken(";", ((SDiffEntryChange *)eNode->Data())->iPos, ((SDiffEntryChange *)eNode->Data())->sEntry);
1 cycrow 477
						}
478
						break;
479
				}
480
			}
481
 
482
			// add our comments and info
160 cycrow 483
			writeLines.pushFront(Utils::String((long)id) + ";" + (long)writeLines.size() + ";");
484
			writeLines.pushFront("// Generated by ModDiff (SPK Version: " + Utils::String::FromFloat(GetLibraryVersion(), 2) + ")");
1 cycrow 485
 
486
			// now write the file
58 cycrow 487
			CFileIO WriteFile(m_sTempDir + "/" + CFileIO(f->sFile).filename());
160 cycrow 488
			if ( WriteFile.writeFile(&writeLines) )
1 cycrow 489
			{
58 cycrow 490
				if ( cat.AppendFile(m_sTempDir + "/" + CFileIO(f->sFile).filename(), f->sFile) )
1 cycrow 491
				{
492
					ret = true;
493
				}
58 cycrow 494
				WriteFile.remove();
1 cycrow 495
			}
496
		}
497
	}
498
 
499
	if ( ret )
500
		cat.WriteCatFile();
501
 
502
	return ret;
503
}
504
 
160 cycrow 505
bool CModDiff::_readGameFile(const Utils::String &file, Utils::CStringList &writeLines, int *id)
1 cycrow 506
{
507
	bool ret = false;
508
 
58 cycrow 509
	Utils::String sTo = m_fileSystem.ExtractGameFile(file, m_sTempDir + "/" + CFileIO(file).filename());
510
	if ( !sTo.empty() ) {
511
		CFileIO File(sTo);
1 cycrow 512
 
160 cycrow 513
		Utils::CStringList *lines = File.readLinesStr();
1 cycrow 514
		if ( lines )
515
		{
516
			int entries = -1;
160 cycrow 517
			for(auto itr = lines->begin(); itr != lines->end(); itr++)
1 cycrow 518
			{
160 cycrow 519
				Utils::String l = (*itr)->str;
520
				l.removeFirstSpace();
521
				if ( l.empty() ) continue;
522
				if ( l.left(1) == "/" ) continue;
1 cycrow 523
 
524
				if ( entries == -1 )
525
				{
160 cycrow 526
					entries = l.token(":", 2).toInt();
1 cycrow 527
					if ( id )
160 cycrow 528
						(*id) = l.token(":", 1).toInt();
1 cycrow 529
				}
530
				else
160 cycrow 531
					writeLines.pushBack(l);
1 cycrow 532
			}
533
			delete lines;
534
			ret = true;
535
		}
58 cycrow 536
		File.close();
52 cycrow 537
		File.remove();
1 cycrow 538
	}
539
 
540
	return ret;
541
}
542
 
543
 
58 cycrow 544
bool CModDiff::_validFile(const Utils::String &file)
1 cycrow 545
{
546
	return CModDiff::CanBeDiffed(file);
547
}
548
 
58 cycrow 549
bool CModDiff::CanBeDiffed(const Utils::String &file)
1 cycrow 550
{
58 cycrow 551
	Utils::String checkFile = file;
102 cycrow 552
	checkFile = CFileIO(file).dir() + "/" + CFileIO(file).baseName();
1 cycrow 553
 
554
	// all t files are the same format
58 cycrow 555
	if ( checkFile.left(7).Compare("types/T") )
1 cycrow 556
		return true;
557
 
558
	return false;
35 cycrow 559
}