1 #include "lc_global.h"
2 #include "lc_library.h"
3 #include "lc_zipfile.h"
4 #include "lc_file.h"
5 #include "pieceinf.h"
6 #include "lc_colors.h"
7 #include "lc_texture.h"
8 #include "lc_category.h"
9 #include "lc_application.h"
10 #include "lc_context.h"
11 #include "lc_glextensions.h"
12 #include "lc_synth.h"
13 #include "project.h"
14 #include "lc_profile.h"
15 #include "lc_meshloader.h"
16 #include <ctype.h>
17 #include <locale.h>
18 #include <zlib.h>
19 #include <QtConcurrent>
20 
21 #if MAX_MEM_LEVEL >= 8
22 #  define DEF_MEM_LEVEL 8
23 #else
24 #  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
25 #endif
26 
27 #define LC_LIBRARY_CACHE_VERSION   0x0109
28 #define LC_LIBRARY_CACHE_ARCHIVE   0x0001
29 #define LC_LIBRARY_CACHE_DIRECTORY 0x0002
30 
lcPiecesLibrary()31 lcPiecesLibrary::lcPiecesLibrary()
32 	: mLoadMutex(QMutex::Recursive)
33 {
34 	QStringList cachePathList = QStandardPaths::standardLocations(QStandardPaths::CacheLocation);
35 	mCachePath = cachePathList.first();
36 
37 	QDir Dir;
38 	Dir.mkpath(mCachePath);
39 
40 	mNumOfficialPieces = 0;
41 	mBuffersDirty = false;
42 	mHasUnofficial = false;
43 	mCancelLoading = false;
44 	mStudStyle = static_cast<lcStudStyle>(lcGetProfileInt(LC_PROFILE_STUD_STYLE));
45 }
46 
~lcPiecesLibrary()47 lcPiecesLibrary::~lcPiecesLibrary()
48 {
49 	mLoadMutex.lock();
50 	mLoadQueue.clear();
51 	mLoadMutex.unlock();
52 	mCancelLoading = true;
53 	WaitForLoadQueue();
54 	Unload();
55 	ReleaseBuffers();
56 }
57 
Unload()58 void lcPiecesLibrary::Unload()
59 {
60 	for (const auto& PieceIt : mPieces)
61 		delete PieceIt.second;
62 	mPieces.clear();
63 
64 	mSources.clear();
65 
66 	for (lcTexture* Texture : mTextures)
67 		delete Texture;
68 	mTextures.clear();
69 
70 	mNumOfficialPieces = 0;
71 
72 	for (std::unique_ptr<lcZipFile>& ZipFile : mZipFiles)
73 		ZipFile.reset();
74 }
75 
RemoveTemporaryPieces()76 void lcPiecesLibrary::RemoveTemporaryPieces()
77 {
78 	QMutexLocker LoadLock(&mLoadMutex);
79 
80 	for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end();)
81 	{
82 		PieceInfo* Info = PieceIt->second;
83 
84 		if (Info->IsTemporary() && Info->GetRefCount() == 0)
85 		{
86 			PieceIt = mPieces.erase(PieceIt);
87 			delete Info;
88 		}
89 		else
90 			PieceIt++;
91 	}
92 }
93 
RemovePiece(PieceInfo * Info)94 void lcPiecesLibrary::RemovePiece(PieceInfo* Info)
95 {
96 	for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); PieceIt++)
97 	{
98 		if (PieceIt->second == Info)
99 		{
100 			mPieces.erase(PieceIt);
101 			break;
102 		}
103 	}
104 	delete Info;
105 }
106 
RenamePiece(PieceInfo * Info,const char * NewName)107 void lcPiecesLibrary::RenamePiece(PieceInfo* Info, const char* NewName)
108 {
109 	for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); PieceIt++)
110 	{
111 		if (PieceIt->second == Info)
112 		{
113 			mPieces.erase(PieceIt);
114 			break;
115 		}
116 	}
117 
118 	strncpy(Info->mFileName, NewName, sizeof(Info->mFileName));
119 	Info->mFileName[sizeof(Info->mFileName) - 1] = 0;
120 	strncpy(Info->m_strDescription, NewName, sizeof(Info->m_strDescription));
121 	Info->m_strDescription[sizeof(Info->m_strDescription) - 1] = 0;
122 
123 	char PieceName[LC_PIECE_NAME_LEN];
124 	strcpy(PieceName, Info->mFileName);
125 	strupr(PieceName);
126 
127 	mPieces[PieceName] = Info;
128 }
129 
FindPiece(const char * PieceName,Project * CurrentProject,bool CreatePlaceholder,bool SearchProjectFolder)130 PieceInfo* lcPiecesLibrary::FindPiece(const char* PieceName, Project* CurrentProject, bool CreatePlaceholder, bool SearchProjectFolder)
131 {
132 	QString ProjectPath;
133 	if (SearchProjectFolder)
134 	{
135 		QString FileName = CurrentProject->GetFileName();
136 
137 		if (!FileName.isEmpty())
138 			ProjectPath = QFileInfo(FileName).absolutePath();
139 	}
140 
141 	char CleanName[LC_PIECE_NAME_LEN];
142 	const char* Src = PieceName;
143 	char* Dst = CleanName;
144 
145 	while (*Src && Dst - CleanName != sizeof(CleanName))
146 	{
147 		if (*Src == '\\')
148 			*Dst = '/';
149 		else if (*Src >= 'a' && *Src <= 'z')
150 			*Dst = *Src + 'A' - 'a';
151 		else
152 			*Dst = *Src;
153 
154 		Src++;
155 		Dst++;
156 	}
157 	*Dst = 0;
158 
159 	const auto PieceIt = mPieces.find(CleanName);
160 
161 	if (PieceIt != mPieces.end())
162 	{
163 		PieceInfo* Info = PieceIt->second;
164 
165 		if ((!CurrentProject || !Info->IsModel() || CurrentProject->GetModels().FindIndex(Info->GetModel()) != -1) && (!ProjectPath.isEmpty() || !Info->IsProject()))
166 			return Info;
167 	}
168 
169 	if (!ProjectPath.isEmpty())
170 	{
171 		QFileInfo ProjectFile = QFileInfo(ProjectPath + QDir::separator() + PieceName);
172 
173 		if (ProjectFile.isFile())
174 		{
175 			Project* NewProject = new Project();
176 
177 			if (NewProject->Load(ProjectFile.absoluteFilePath(), false))
178 			{
179 				PieceInfo* Info = new PieceInfo();
180 
181 				Info->CreateProject(NewProject, PieceName);
182 				mPieces[CleanName] = Info;
183 
184 				return Info;
185 			}
186 			else
187 				delete NewProject;
188 		}
189 	}
190 
191 	if (CreatePlaceholder)
192 	{
193 		PieceInfo* Info = new PieceInfo();
194 
195 		Info->CreatePlaceholder(PieceName);
196 		mPieces[CleanName] = Info;
197 
198 		return Info;
199 	}
200 
201 	return nullptr;
202 }
203 
FindTexture(const char * TextureName,Project * CurrentProject,bool SearchProjectFolder)204 lcTexture* lcPiecesLibrary::FindTexture(const char* TextureName, Project* CurrentProject, bool SearchProjectFolder)
205 {
206 	for (lcTexture* Texture : mTextures)
207 		if (!strcmp(TextureName, Texture->mName))
208 			return Texture;
209 
210 	QString ProjectPath;
211 	if (SearchProjectFolder)
212 	{
213 		QString FileName = CurrentProject->GetFileName();
214 
215 		if (!FileName.isEmpty())
216 			ProjectPath = QFileInfo(FileName).absolutePath();
217 	}
218 
219 	if (!ProjectPath.isEmpty())
220 	{
221 		QFileInfo TextureFile = QFileInfo(ProjectPath + QDir::separator() + TextureName + ".png");
222 
223 		if (TextureFile.isFile())
224 		{
225 			lcTexture* Texture = lcLoadTexture(TextureFile.absoluteFilePath(), LC_TEXTURE_WRAPU | LC_TEXTURE_WRAPV);
226 
227 			if (Texture)
228 			{
229 				mTextures.push_back(Texture);
230 				return Texture;
231 			}
232 		}
233 	}
234 
235 	return nullptr;
236 }
237 
Load(const QString & LibraryPath,bool ShowProgress)238 bool lcPiecesLibrary::Load(const QString& LibraryPath, bool ShowProgress)
239 {
240 	Unload();
241 
242 	if (OpenArchive(LibraryPath, lcZipFileType::Official))
243 	{
244 		LoadColors();
245 
246 		mLibraryDir = QFileInfo(LibraryPath).absoluteDir();
247 		QString UnofficialFileName = mLibraryDir.absoluteFilePath(QLatin1String("ldrawunf.zip"));
248 
249 		if (!OpenArchive(UnofficialFileName, lcZipFileType::Unofficial))
250 			UnofficialFileName.clear();
251 
252 		ReadArchiveDescriptions(LibraryPath, UnofficialFileName);
253 	}
254 	else
255 	{
256 		mLibraryDir.setPath(LibraryPath);
257 
258 		if (OpenDirectory(mLibraryDir, ShowProgress))
259 			LoadColors();
260 		else
261 			return false;
262 	}
263 
264 	UpdateStudStyleSource();
265 	lcLoadDefaultCategories();
266 	lcSynthInit();
267 
268 	return true;
269 }
270 
LoadColors()271 void lcPiecesLibrary::LoadColors()
272 {
273 	QString CustomColorsPath = lcGetProfileString(LC_PROFILE_COLOR_CONFIG);
274 
275 	if (!CustomColorsPath.isEmpty())
276 	{
277 		lcDiskFile ColorFile(CustomColorsPath);
278 
279 		if (ColorFile.Open(QIODevice::ReadOnly) && lcLoadColorFile(ColorFile, mStudStyle))
280 		{
281 			emit ColorsLoaded();
282 			return;
283 		}
284 	}
285 
286 	if (mZipFiles[static_cast<int>(lcZipFileType::Official)])
287 	{
288 		lcMemFile ColorFile;
289 
290 		if (!mZipFiles[static_cast<int>(lcZipFileType::Official)]->ExtractFile("ldraw/ldconfig.ldr", ColorFile) || !lcLoadColorFile(ColorFile, mStudStyle))
291 			lcLoadDefaultColors(mStudStyle);
292 	}
293 	else
294 	{
295 		lcDiskFile ColorFile(mLibraryDir.absoluteFilePath(QLatin1String("ldconfig.ldr")));
296 
297 		if (!ColorFile.Open(QIODevice::ReadOnly) || !lcLoadColorFile(ColorFile, mStudStyle))
298 		{
299 			ColorFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String("LDConfig.ldr")));
300 
301 			if (!ColorFile.Open(QIODevice::ReadOnly) || !lcLoadColorFile(ColorFile, mStudStyle))
302 				lcLoadDefaultColors(mStudStyle);
303 		}
304 	}
305 
306 	emit ColorsLoaded();
307 }
308 
IsStudPrimitive(const char * FileName)309 bool lcPiecesLibrary::IsStudPrimitive(const char* FileName)
310 {
311 	return memcmp(FileName, "STU", 3) == 0;
312 }
313 
IsStudStylePrimitive(const char * FileName)314 bool lcPiecesLibrary::IsStudStylePrimitive(const char* FileName)
315 {
316 	constexpr std::array<const char*, 15> StudStylePrimitives =
317 	{
318 		"2-4STUD4.DAT", "STUD.DAT", "STUD2.DAT", "STUD2A.DAT", "STUD3.DAT", "STUD4.DAT", "STUD4A.DAT", "STUD4H.DAT",
319 		"8/STUD.DAT", "8/STUD2.DAT", "8/STUD2A.DAT", "8/STUD3.DAT", "8/STUD4.DAT", "8/STUD4A.DAT", "8/STUD4H.DAT"
320 	};
321 
322 	for (const char* StudStylePrimitive : StudStylePrimitives)
323 		if (!strcmp(StudStylePrimitive, FileName))
324 			return true;
325 
326 	return false;
327 }
328 
UpdateStudStyleSource()329 void lcPiecesLibrary::UpdateStudStyleSource()
330 {
331 	if (!mSources.empty() && mSources.front()->Type == lcLibrarySourceType::StudStyle)
332 		mSources.erase(mSources.begin());
333 
334 	mZipFiles[static_cast<int>(lcZipFileType::StudStyle)].reset();
335 
336 	if (mStudStyle == lcStudStyle::Plain)
337 		return;
338 
339 	const QLatin1String FileNames[] =
340 	{
341 		QLatin1String(""),                                // Plain
342 		QLatin1String(":/resources/studlogo1.zip"),       // ThinLinesLogo
343 		QLatin1String(":/resources/studlogo2.zip"),       // OutlineLogo
344 		QLatin1String(":/resources/studlogo3.zip"),       // SharpTopLogo
345 		QLatin1String(":/resources/studlogo4.zip"),       // RoundedTopLogo
346 		QLatin1String(":/resources/studlogo5.zip"),       // FlattenedLogo
347 		QLatin1String(":/resources/studslegostyle1.zip"), // HighContrast
348 		QLatin1String(":/resources/studslegostyle2.zip")  // HighContrastLogo
349 	};
350 
351 	LC_ARRAY_SIZE_CHECK(FileNames, lcStudStyle::Count);
352 
353 	std::unique_ptr<lcDiskFile> StudStyleFile(new lcDiskFile(FileNames[static_cast<int>(mStudStyle)]));
354 
355 	if (StudStyleFile->Open(QIODevice::ReadOnly))
356 		OpenArchive(std::move(StudStyleFile), lcZipFileType::StudStyle);
357 }
358 
OpenArchive(const QString & FileName,lcZipFileType ZipFileType)359 bool lcPiecesLibrary::OpenArchive(const QString& FileName, lcZipFileType ZipFileType)
360 {
361 	std::unique_ptr<lcDiskFile> File(new lcDiskFile(FileName));
362 
363 	if (!File->Open(QIODevice::ReadOnly))
364 		return false;
365 
366 	return OpenArchive(std::move(File), ZipFileType);
367 }
368 
OpenArchive(std::unique_ptr<lcFile> File,lcZipFileType ZipFileType)369 bool lcPiecesLibrary::OpenArchive(std::unique_ptr<lcFile> File, lcZipFileType ZipFileType)
370 {
371 	std::unique_ptr<lcZipFile> ZipFile(new lcZipFile());
372 
373 	if (!ZipFile->OpenRead(std::move(File)))
374 		return false;
375 
376 	std::unique_ptr<lcLibrarySource> Source(new lcLibrarySource);
377 	Source->Type = ZipFileType != lcZipFileType::StudStyle ? lcLibrarySourceType::Library : lcLibrarySourceType::StudStyle;
378 
379 	for (int FileIdx = 0; FileIdx < ZipFile->mFiles.GetSize(); FileIdx++)
380 	{
381 		lcZipFileInfo& FileInfo = ZipFile->mFiles[FileIdx];
382 		char NameBuffer[LC_PIECE_NAME_LEN];
383 		char* Name = NameBuffer;
384 
385 		const char* Src = FileInfo.file_name;
386 		char* Dst = Name;
387 
388 		while (*Src && Dst - Name < LC_PIECE_NAME_LEN)
389 		{
390 			if (*Src >= 'a' && *Src <= 'z')
391 				*Dst = *Src + 'A' - 'a';
392 			else if (*Src == '\\')
393 				*Dst = '/';
394 			else
395 				*Dst = *Src;
396 
397 			Src++;
398 			Dst++;
399 		}
400 
401 		if (Dst - Name <= 4)
402 			continue;
403 
404 		*Dst = 0;
405 		Dst -= 4;
406 		if (memcmp(Dst, ".DAT", 4))
407 		{
408 			if (!memcmp(Dst, ".PNG", 4))
409 			{
410 				if ((ZipFileType == lcZipFileType::Official && !memcmp(Name, "LDRAW/PARTS/TEXTURES/", 21)) ||
411 					(ZipFileType == lcZipFileType::Unofficial && !memcmp(Name, "PARTS/TEXTURES/", 15)))
412 				{
413 					lcTexture* Texture = new lcTexture();
414 					mTextures.push_back(Texture);
415 
416 					*Dst = 0;
417 					strncpy(Texture->mName, Name + (ZipFileType == lcZipFileType::Official ? 21 : 15), sizeof(Texture->mName)-1);
418 					Texture->mName[sizeof(Texture->mName) - 1] = 0;
419 				}
420 			}
421 
422 			continue;
423 		}
424 
425 		if (ZipFileType == lcZipFileType::Official)
426 		{
427 			if (memcmp(Name, "LDRAW/", 6))
428 				continue;
429 
430 			Name += 6;
431 		}
432 
433 		if (!memcmp(Name, "PARTS/", 6))
434 		{
435 			Name += 6;
436 
437 			if (memcmp(Name, "S/", 2))
438 			{
439 				PieceInfo* Info = FindPiece(Name, nullptr, false, false);
440 
441 				if (!Info)
442 				{
443 					Info = new PieceInfo();
444 
445 					strncpy(Info->mFileName, FileInfo.file_name + (Name - NameBuffer), sizeof(Info->mFileName)-1);
446 					Info->mFileName[sizeof(Info->mFileName) - 1] = 0;
447 
448 					mPieces[Name] = Info;
449 				}
450 
451 				Info->SetZipFile(ZipFileType, FileIdx);
452 			}
453 			else
454 				Source->Primitives[Name] = new lcLibraryPrimitive(QString(), FileInfo.file_name + (Name - NameBuffer), ZipFileType, FileIdx, false, false, true);
455 		}
456 		else if (!memcmp(Name, "P/", 2))
457 		{
458 			Name += 2;
459 
460 			Source->Primitives[Name] = new lcLibraryPrimitive(QString(), FileInfo.file_name + (Name - NameBuffer), ZipFileType, FileIdx, IsStudPrimitive(Name), IsStudStylePrimitive(Name), false);
461 		}
462 	}
463 
464 	mZipFiles[static_cast<int>(ZipFileType)] = std::move(ZipFile);
465 
466 	if (ZipFileType != lcZipFileType::StudStyle)
467 		mSources.emplace_back(std::move(Source));
468 	else
469 		mSources.insert(mSources.begin(), std::move(Source));
470 
471 	return true;
472 }
473 
ReadArchiveDescriptions(const QString & OfficialFileName,const QString & UnofficialFileName)474 void lcPiecesLibrary::ReadArchiveDescriptions(const QString& OfficialFileName, const QString& UnofficialFileName)
475 {
476 	QFileInfo OfficialInfo(OfficialFileName);
477 	QFileInfo UnofficialInfo(UnofficialFileName);
478 
479 	mArchiveCheckSum[0] = OfficialInfo.size();
480 	mArchiveCheckSum[1] = OfficialInfo.lastModified().toMSecsSinceEpoch();
481 
482 	if (!UnofficialFileName.isEmpty())
483 	{
484 		mArchiveCheckSum[2] = UnofficialInfo.size();
485 		mArchiveCheckSum[3] = UnofficialInfo.lastModified().toMSecsSinceEpoch();
486 	}
487 	else
488 	{
489 		mArchiveCheckSum[2] = 0;
490 		mArchiveCheckSum[3] = 0;
491 	}
492 
493 	QString IndexFileName = QFileInfo(QDir(mCachePath), QLatin1String("index")).absoluteFilePath();
494 
495 	if (!LoadCacheIndex(IndexFileName))
496 	{
497 		lcMemFile PieceFile;
498 
499 		for (const auto& PieceIt : mPieces)
500 		{
501 			PieceInfo* Info = PieceIt.second;
502 
503 			mZipFiles[static_cast<int>(Info->mZipFileType)]->ExtractFile(Info->mZipFileIndex, PieceFile, 256);
504 			PieceFile.Seek(0, SEEK_END);
505 			PieceFile.WriteU8(0);
506 
507 			char* Src = (char*)PieceFile.mBuffer + 2;
508 			char* Dst = Info->m_strDescription;
509 
510 			for (;;)
511 			{
512 				if (*Src != '\r' && *Src != '\n' && *Src && Dst - Info->m_strDescription < (int)sizeof(Info->m_strDescription) - 1)
513 				{
514 					*Dst++ = *Src++;
515 					continue;
516 				}
517 
518 				*Dst = 0;
519 				break;
520 			}
521 		}
522 
523 		SaveArchiveCacheIndex(IndexFileName);
524 	}
525 }
526 
OpenDirectory(const QDir & LibraryDir,bool ShowProgress)527 bool lcPiecesLibrary::OpenDirectory(const QDir& LibraryDir, bool ShowProgress)
528 {
529 	const QLatin1String BaseFolders[] = { QLatin1String(""), QLatin1String("unofficial/") };
530 	constexpr int NumBaseFolders = LC_ARRAY_COUNT(BaseFolders);
531 
532 	QFileInfoList FileLists[NumBaseFolders];
533 
534 	for (unsigned int BaseFolderIdx = 0; BaseFolderIdx < NumBaseFolders; BaseFolderIdx++)
535 	{
536 		QString ParstPath = QDir(LibraryDir.absoluteFilePath(BaseFolders[BaseFolderIdx])).absoluteFilePath(QLatin1String("parts/"));
537 		QDir Dir = QDir(ParstPath, QLatin1String("*.dat"), QDir::SortFlags(QDir::Name | QDir::IgnoreCase), QDir::Files | QDir::Hidden | QDir::Readable);
538 		FileLists[BaseFolderIdx] = Dir.entryInfoList();
539 	}
540 
541 	if (FileLists[static_cast<int>(lcLibraryFolderType::Official)].isEmpty())
542 		return false;
543 
544 	mHasUnofficial = !FileLists[static_cast<int>(lcLibraryFolderType::Unofficial)].isEmpty();
545 	ReadDirectoryDescriptions(FileLists, ShowProgress);
546 
547 	for (unsigned int BaseFolderIdx = 0; BaseFolderIdx < LC_ARRAY_COUNT(BaseFolders); BaseFolderIdx++)
548 	{
549 		std::unique_ptr<lcLibrarySource> Source(new lcLibrarySource);
550 		Source->Type = lcLibrarySourceType::Library;
551 
552 		const char* PrimitiveDirectories[] = { "p/", "parts/s/" };
553 		bool SubFileDirectories[] = { false, false, true };
554 		QDir BaseDir(LibraryDir.absoluteFilePath(QLatin1String(BaseFolders[BaseFolderIdx])));
555 
556 		for (int DirectoryIdx = 0; DirectoryIdx < (int)(LC_ARRAY_COUNT(PrimitiveDirectories)); DirectoryIdx++)
557 		{
558 			QString ChildPath = BaseDir.absoluteFilePath(QLatin1String(PrimitiveDirectories[DirectoryIdx]));
559 			QDirIterator DirIterator(ChildPath, QStringList() << QLatin1String("*.dat"), QDir::Files | QDir::Hidden | QDir::Readable, QDirIterator::Subdirectories);
560 
561 			while (DirIterator.hasNext())
562 			{
563 				char Name[LC_PIECE_NAME_LEN];
564 				QString FileName = DirIterator.next();
565 				QByteArray FileString = BaseDir.relativeFilePath(FileName).toLatin1();
566 				const char* Src = strchr(FileString, '/') + 1;
567 				char* Dst = Name;
568 
569 				while (*Src && Dst - Name < (int)sizeof(Name))
570 				{
571 					if (*Src >= 'a' && *Src <= 'z')
572 						*Dst = *Src + 'A' - 'a';
573 					else if (*Src == '\\')
574 						*Dst = '/';
575 					else
576 						*Dst = *Src;
577 
578 					Src++;
579 					Dst++;
580 				}
581 				*Dst = 0;
582 
583 				if (Dst - Name <= 4)
584 					continue;
585 
586 				Dst -= 4;
587 				if (memcmp(Dst, ".DAT", 4))
588 					continue;
589 
590 				if (BaseFolderIdx > 0 && IsPrimitive(Name))
591 					continue;
592 
593 				if (BaseFolderIdx == static_cast<int>(lcLibraryFolderType::Unofficial))
594 					mHasUnofficial = true;
595 
596 				const bool SubFile = SubFileDirectories[DirectoryIdx];
597 				Source->Primitives[Name] = new lcLibraryPrimitive(std::move(FileName), strchr(FileString, '/') + 1, lcZipFileType::Count, 0, !SubFile && IsStudPrimitive(Name), IsStudStylePrimitive(Name), SubFile);
598 			}
599 		}
600 
601 		mSources.emplace_back(std::move(Source));
602 	}
603 
604 	for (unsigned int BaseFolderIdx = 0; BaseFolderIdx < LC_ARRAY_COUNT(BaseFolders); BaseFolderIdx++)
605 	{
606 		QDir BaseDir(LibraryDir.absoluteFilePath(QLatin1String(BaseFolders[BaseFolderIdx])));
607 		QDir Dir(BaseDir.absoluteFilePath(QLatin1String("parts/textures/")), QLatin1String("*.png"), QDir::SortFlags(QDir::Name | QDir::IgnoreCase), QDir::Files | QDir::Hidden | QDir::Readable);
608 		QStringList FileList = Dir.entryList();
609 
610 		mTextures.reserve(mTextures.size() + FileList.size());
611 
612 		for (int FileIdx = 0; FileIdx < FileList.size(); FileIdx++)
613 		{
614 			char Name[LC_MAXPATH];
615 			QByteArray FileString = FileList[FileIdx].toLatin1();
616 			const char* Src = FileString;
617 			char* Dst = Name;
618 
619 			while (*Src && Dst - Name < (int)sizeof(Name))
620 			{
621 				if (*Src >= 'a' && *Src <= 'z')
622 					*Dst = *Src + 'A' - 'a';
623 				else if (*Src == '\\')
624 					*Dst = '/';
625 				else
626 					*Dst = *Src;
627 
628 				Src++;
629 				Dst++;
630 			}
631 
632 			if (Dst - Name <= 4)
633 				continue;
634 
635 			Dst -= 4;
636 			if (memcmp(Dst, ".PNG", 4))
637 				continue;
638 			*Dst = 0;
639 
640 			lcTexture* Texture = new lcTexture();
641 			mTextures.push_back(Texture);
642 
643 			strncpy(Texture->mName, Name, sizeof(Texture->mName));
644 			Texture->mName[sizeof(Texture->mName) - 1] = 0;
645 			Texture->mFileName = Dir.absoluteFilePath(FileList[FileIdx]);
646 		}
647 	}
648 
649 	return true;
650 }
651 
ReadDirectoryDescriptions(const QFileInfoList (& FileLists)[static_cast<int> (lcLibraryFolderType::Count)],bool ShowProgress)652 void lcPiecesLibrary::ReadDirectoryDescriptions(const QFileInfoList (&FileLists)[static_cast<int>(lcLibraryFolderType::Count)], bool ShowProgress)
653 {
654 	QString IndexFileName = QFileInfo(QDir(mCachePath), QLatin1String("index")).absoluteFilePath();
655 	lcMemFile IndexFile;
656 	std::vector<const char*> CachedDescriptions;
657 
658 	if (ReadDirectoryCacheFile(IndexFileName, IndexFile))
659 	{
660 		QString LibraryPath = IndexFile.ReadQString();
661 
662 		if (LibraryPath == mLibraryDir.absolutePath())
663 		{
664 			int NumDescriptions = IndexFile.ReadU32();
665 			CachedDescriptions.reserve(NumDescriptions);
666 
667 			while (NumDescriptions--)
668 			{
669 				const char* FileName = (const char*)IndexFile.mBuffer + IndexFile.GetPosition();
670 				CachedDescriptions.push_back(FileName);
671 				IndexFile.Seek(strlen(FileName) + 1, SEEK_CUR);
672 				const char* Description = (const char*)IndexFile.mBuffer + IndexFile.GetPosition();
673 				IndexFile.Seek(strlen(Description) + 1, SEEK_CUR);
674 				IndexFile.Seek(4 + 1 + 8, SEEK_CUR);
675 			}
676 		}
677 	}
678 
679 	for (int FolderIdx = 0; FolderIdx < static_cast<int>(lcLibraryFolderType::Count); FolderIdx++)
680 	{
681 		const QFileInfoList& FileList = FileLists[FolderIdx];
682 
683 		for (int FileIdx = 0; FileIdx < FileList.size(); FileIdx++)
684 		{
685 			char Name[LC_PIECE_NAME_LEN];
686 			QByteArray FileString = FileList[FileIdx].fileName().toLatin1();
687 			const char* Src = FileString;
688 			char* Dst = Name;
689 
690 			while (*Src && Dst - Name < (int)sizeof(Name))
691 			{
692 				if (*Src >= 'a' && *Src <= 'z')
693 					*Dst = *Src + 'A' - 'a';
694 				else if (*Src == '\\')
695 					*Dst = '/';
696 				else
697 					*Dst = *Src;
698 
699 				Src++;
700 				Dst++;
701 			}
702 			*Dst = 0;
703 
704 			if (FolderIdx > 0 && mPieces.find(Name) != mPieces.end())
705 				continue;
706 
707 			PieceInfo* Info = new PieceInfo();
708 
709 			strncpy(Info->mFileName, FileString, sizeof(Info->mFileName));
710 			Info->mFileName[sizeof(Info->mFileName) - 1] = 0;
711 			Info->mFolderType = FolderIdx;
712 			Info->mFolderIndex = FileIdx;
713 
714 			mPieces[Name] = Info;
715 		}
716 	}
717 
718 	QAtomicInt FilesLoaded;
719 	bool Modified = false;
720 
721 	auto ReadDescriptions = [&FileLists, &CachedDescriptions, &FilesLoaded, &Modified](const std::pair<std::string, PieceInfo*>& Entry)
722 	{
723 		PieceInfo* Info = Entry.second;
724 		FilesLoaded.ref();
725 
726 		lcDiskFile PieceFile(FileLists[Info->mFolderType][Info->mFolderIndex].absoluteFilePath());
727 		char Line[1024];
728 
729 		if (!CachedDescriptions.empty())
730 		{
731 			auto DescriptionCompare = [](const void* Key, const void* Element)
732 			{
733 				return strcmp((const char*)Key, *(const char**)Element);
734 			};
735 
736 			void* CachedDescription = bsearch(Info->mFileName, &CachedDescriptions.front(), CachedDescriptions.size(), sizeof(char*), DescriptionCompare);
737 
738 			if (CachedDescription)
739 			{
740 				const char* FileName = *(const char**)CachedDescription;
741 				const char* Description = FileName + strlen(FileName) + 1;
742 				const uint64_t CachedFileTime = *(uint64_t*)(Description + strlen(Description) + 1 + 4 + 1);
743 
744 				quint64 FileTime = FileLists[Info->mFolderType][Info->mFolderIndex].lastModified().toMSecsSinceEpoch();
745 
746 				if (FileTime == CachedFileTime)
747 				{
748 					strcpy(Info->m_strDescription, Description);
749 					return;
750 				}
751 			}
752 		}
753 
754 		if (!PieceFile.Open(QIODevice::ReadOnly) || !PieceFile.ReadLine(Line, sizeof(Line)))
755 		{
756 			strcpy(Info->m_strDescription, "Unknown");
757 			return;
758 		}
759 
760 		const char* Src = Line + 2;
761 		char* Dst = Info->m_strDescription;
762 
763 		for (;;)
764 		{
765 			if (*Src != '\r' && *Src != '\n' && *Src && Dst - Info->m_strDescription < (int)sizeof(Info->m_strDescription) - 1)
766 			{
767 				*Dst++ = *Src++;
768 				continue;
769 			}
770 
771 			*Dst = 0;
772 			break;
773 		}
774 
775 		Modified = true;
776 	};
777 
778 	QProgressDialog* ProgressDialog = new QProgressDialog(nullptr);
779 	ProgressDialog->setWindowFlags(ProgressDialog->windowFlags() & ~Qt::WindowCloseButtonHint);
780 	ProgressDialog->setWindowTitle(tr("Initializing"));
781 	ProgressDialog->setLabelText(tr("Loading Parts Library"));
782 	ProgressDialog->setMaximum((int)mPieces.size());
783 	ProgressDialog->setMinimum(0);
784 	ProgressDialog->setValue(0);
785 	ProgressDialog->setCancelButton(nullptr);
786 	ProgressDialog->setAutoReset(false);
787 	if (ShowProgress)
788 		ProgressDialog->show();
789 
790 	QFuture<void> LoadFuture = QtConcurrent::map(mPieces, ReadDescriptions);
791 
792 	while (!LoadFuture.isFinished())
793 	{
794 		ProgressDialog->setValue(FilesLoaded);
795 		QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
796 	}
797 
798 	ProgressDialog->setValue(FilesLoaded);
799 	QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
800 
801 	ProgressDialog->deleteLater();
802 
803 	if (Modified)
804 	{
805 		lcMemFile NewIndexFile;
806 
807 		NewIndexFile.WriteQString(mLibraryDir.absolutePath());
808 
809 		NewIndexFile.WriteU32((quint32)mPieces.size());
810 
811 		std::vector<PieceInfo*> SortedPieces;
812 		SortedPieces.reserve(mPieces.size());
813 		for (const auto& PieceIt : mPieces)
814 			SortedPieces.push_back(PieceIt.second);
815 
816 		auto PieceInfoCompare = [](PieceInfo* Info1, PieceInfo* Info2)
817 		{
818 			return strcmp(Info1->mFileName, Info2->mFileName) < 0;
819 		};
820 
821 		std::sort(SortedPieces.begin(), SortedPieces.end(), PieceInfoCompare);
822 
823 		for (const PieceInfo* Info : SortedPieces)
824 		{
825 			if (NewIndexFile.WriteBuffer(Info->mFileName, strlen(Info->mFileName) + 1) == 0)
826 				return;
827 
828 			if (NewIndexFile.WriteBuffer(Info->m_strDescription, strlen(Info->m_strDescription) + 1) == 0)
829 				return;
830 
831 			NewIndexFile.WriteU8(Info->mFolderType);
832 
833 			quint64 FileTime = FileLists[Info->mFolderType][Info->mFolderIndex].lastModified().toMSecsSinceEpoch();
834 
835 			NewIndexFile.WriteU64(FileTime);
836 		}
837 
838 		WriteDirectoryCacheFile(IndexFileName, NewIndexFile);
839 	}
840 }
841 
ReadArchiveCacheFile(const QString & FileName,lcMemFile & CacheFile)842 bool lcPiecesLibrary::ReadArchiveCacheFile(const QString& FileName, lcMemFile& CacheFile)
843 {
844 	QFile File(FileName);
845 
846 	if (!File.open(QIODevice::ReadOnly))
847 		return false;
848 
849 	quint32 CacheVersion, CacheFlags;
850 
851 	if (File.read((char*)&CacheVersion, sizeof(CacheVersion)) == -1 || CacheVersion != LC_LIBRARY_CACHE_VERSION)
852 		return false;
853 
854 	if (File.read((char*)&CacheFlags, sizeof(CacheFlags)) == -1 || CacheFlags != LC_LIBRARY_CACHE_ARCHIVE)
855 		return false;
856 
857 	qint64 CacheCheckSum[4];
858 
859 	if (File.read((char*)&CacheCheckSum, sizeof(CacheCheckSum)) == -1 || memcmp(CacheCheckSum, mArchiveCheckSum, sizeof(CacheCheckSum)))
860 		return false;
861 
862 	quint32 UncompressedSize;
863 
864 	if (File.read((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1)
865 		return false;
866 
867 	QByteArray CompressedData = File.readAll();
868 
869 	CacheFile.SetLength(UncompressedSize);
870 	CacheFile.Seek(0, SEEK_SET);
871 
872 	constexpr int CHUNK = 16384;
873 	int ret;
874 	unsigned have;
875 	z_stream strm;
876 	unsigned char in[CHUNK];
877 	unsigned char out[CHUNK];
878 	int pos;
879 
880 	strm.zalloc = Z_NULL;
881 	strm.zfree = Z_NULL;
882 	strm.opaque = Z_NULL;
883 	strm.avail_in = 0;
884 	strm.next_in = Z_NULL;
885 	pos = 0;
886 
887 	ret = inflateInit2(&strm, -MAX_WBITS);
888 	if (ret != Z_OK)
889 		return ret;
890 
891 	do
892 	{
893 		strm.avail_in = lcMin(CompressedData.size() - pos, CHUNK);
894 		strm.next_in = in;
895 
896 		if (strm.avail_in == 0)
897 			break;
898 
899 		memcpy(in, CompressedData.constData() + pos, strm.avail_in);
900 		pos += strm.avail_in;
901 
902 		do
903 		{
904 			strm.avail_out = CHUNK;
905 			strm.next_out = out;
906 			ret = inflate(&strm, Z_NO_FLUSH);
907 
908 			switch (ret)
909 			{
910 			case Z_NEED_DICT:
911 				ret = Z_DATA_ERROR;
912 				Q_FALLTHROUGH();
913 			case Z_DATA_ERROR:
914 				Q_FALLTHROUGH();
915 			case Z_MEM_ERROR:
916 				(void)inflateEnd(&strm);
917 				return ret;
918 			}
919 
920 			have = CHUNK - strm.avail_out;
921 			CacheFile.WriteBuffer(out, have);
922 		} while (strm.avail_out == 0);
923 	} while (ret != Z_STREAM_END);
924 
925 	(void)inflateEnd(&strm);
926 
927 	CacheFile.Seek(0, SEEK_SET);
928 
929 	return ret == Z_STREAM_END;
930 }
931 
WriteArchiveCacheFile(const QString & FileName,lcMemFile & CacheFile)932 bool lcPiecesLibrary::WriteArchiveCacheFile(const QString& FileName, lcMemFile& CacheFile)
933 {
934 	QFile File(FileName);
935 
936 	if (!File.open(QIODevice::WriteOnly))
937 		return false;
938 
939 	constexpr quint32 CacheVersion = LC_LIBRARY_CACHE_VERSION;
940 	constexpr quint32 CacheFlags = LC_LIBRARY_CACHE_ARCHIVE;
941 
942 	if (File.write((char*)&CacheVersion, sizeof(CacheVersion)) == -1)
943 		return false;
944 
945 	if (File.write((char*)&CacheFlags, sizeof(CacheFlags)) == -1)
946 		return false;
947 
948 	if (File.write((char*)&mArchiveCheckSum, sizeof(mArchiveCheckSum)) == -1)
949 		return false;
950 
951 	const quint32 UncompressedSize = (quint32)CacheFile.GetLength();
952 
953 	if (File.write((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1)
954 		return false;
955 
956 	constexpr size_t BufferSize = 16384;
957 	char WriteBuffer[BufferSize];
958 	z_stream Stream;
959 	quint32 Crc32 = 0;
960 
961 	CacheFile.Seek(0, SEEK_SET);
962 
963 	Stream.zalloc = nullptr;
964 	Stream.zfree = nullptr;
965 	Stream.opaque = nullptr;
966 
967 	if (deflateInit2(&Stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK)
968 		return false;
969 
970 	Bytef* BufferIn = CacheFile.mBuffer;
971 	int FlushMode;
972 
973 	do
974 	{
975 		uInt Read = (uInt)lcMin(CacheFile.GetLength() - (BufferIn - CacheFile.mBuffer), BufferSize);
976 		Stream.avail_in = Read;
977 		Stream.next_in = BufferIn;
978 		Crc32 = crc32(Crc32, BufferIn, Read);
979 		BufferIn += Read;
980 
981 		FlushMode = (BufferIn >= CacheFile.mBuffer + CacheFile.GetLength()) ? Z_FINISH : Z_NO_FLUSH;
982 
983 		do
984 		{
985 			Stream.avail_out = BufferSize;
986 			Stream.next_out = (Bytef*)WriteBuffer;
987 			deflate(&Stream, FlushMode);
988 			File.write(WriteBuffer, BufferSize - Stream.avail_out);
989 		} while (Stream.avail_out == 0);
990 	} while (FlushMode != Z_FINISH);
991 
992 	deflateEnd(&Stream);
993 
994 	return true;
995 }
996 
ReadDirectoryCacheFile(const QString & FileName,lcMemFile & CacheFile)997 bool lcPiecesLibrary::ReadDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile)
998 {
999 	QFile File(FileName);
1000 
1001 	if (!File.open(QIODevice::ReadOnly))
1002 		return false;
1003 
1004 	quint32 CacheVersion, CacheFlags;
1005 
1006 	if (File.read((char*)&CacheVersion, sizeof(CacheVersion)) == -1 || CacheVersion != LC_LIBRARY_CACHE_VERSION)
1007 		return false;
1008 
1009 	if (File.read((char*)&CacheFlags, sizeof(CacheFlags)) == -1 || CacheFlags != LC_LIBRARY_CACHE_DIRECTORY)
1010 		return false;
1011 
1012 	quint32 UncompressedSize;
1013 
1014 	if (File.read((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1)
1015 		return false;
1016 
1017 	QByteArray Data = qUncompress(File.readAll());
1018 	if (Data.isEmpty())
1019 		return false;
1020 
1021 	CacheFile.SetLength(Data.size());
1022 	CacheFile.Seek(0, SEEK_SET);
1023 	CacheFile.WriteBuffer(Data.constData(), Data.size());
1024 	CacheFile.Seek(0, SEEK_SET);
1025 
1026 	return true;
1027 }
1028 
WriteDirectoryCacheFile(const QString & FileName,lcMemFile & CacheFile)1029 bool lcPiecesLibrary::WriteDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile)
1030 {
1031 	QFile File(FileName);
1032 
1033 	if (!File.open(QIODevice::WriteOnly))
1034 		return false;
1035 
1036 	constexpr quint32 CacheVersion = LC_LIBRARY_CACHE_VERSION;
1037 	if (File.write((char*)&CacheVersion, sizeof(CacheVersion)) == -1)
1038 		return false;
1039 
1040 	constexpr quint32 CacheFlags = LC_LIBRARY_CACHE_DIRECTORY;
1041 	if (File.write((char*)&CacheFlags, sizeof(CacheFlags)) == -1)
1042 		return false;
1043 
1044 	const quint32 UncompressedSize = (quint32)CacheFile.GetLength();
1045 	if (File.write((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1)
1046 		return false;
1047 
1048 	File.write(qCompress(CacheFile.mBuffer, (int)CacheFile.GetLength()));
1049 
1050 	return true;
1051 }
1052 
LoadCacheIndex(const QString & FileName)1053 bool lcPiecesLibrary::LoadCacheIndex(const QString& FileName)
1054 {
1055 	lcMemFile IndexFile;
1056 
1057 	if (!ReadArchiveCacheFile(FileName, IndexFile))
1058 		return false;
1059 
1060 	quint32 NumFiles;
1061 
1062 	if (IndexFile.ReadBuffer((char*)&NumFiles, sizeof(NumFiles)) == 0 || NumFiles != mPieces.size())
1063 		return false;
1064 
1065 	for (const auto& PieceIt : mPieces)
1066 	{
1067 		PieceInfo* Info = PieceIt.second;
1068 		quint8 Length;
1069 
1070 		if (IndexFile.ReadBuffer((char*)&Length, sizeof(Length)) == 0 || Length >= sizeof(Info->m_strDescription))
1071 			return false;
1072 
1073 		if (IndexFile.ReadBuffer((char*)Info->m_strDescription, Length) == 0)
1074 			return false;
1075 
1076 		Info->m_strDescription[Length] = 0;
1077 	}
1078 
1079 	return true;
1080 }
1081 
SaveArchiveCacheIndex(const QString & FileName)1082 bool lcPiecesLibrary::SaveArchiveCacheIndex(const QString& FileName)
1083 {
1084 	lcMemFile IndexFile;
1085 
1086 	const quint32 NumFiles = (quint32)mPieces.size();
1087 
1088 	if (IndexFile.WriteBuffer((char*)&NumFiles, sizeof(NumFiles)) == 0)
1089 		return false;
1090 
1091 	for (const auto& PieceIt : mPieces)
1092 	{
1093 		const PieceInfo* Info = PieceIt.second;
1094 		const quint8 Length = (quint8)strlen(Info->m_strDescription);
1095 
1096 		if (IndexFile.WriteBuffer((char*)&Length, sizeof(Length)) == 0)
1097 			return false;
1098 
1099 		if (IndexFile.WriteBuffer((char*)Info->m_strDescription, Length) == 0)
1100 			return false;
1101 	}
1102 
1103 	return WriteArchiveCacheFile(FileName, IndexFile);
1104 }
1105 
LoadCachePiece(PieceInfo * Info)1106 bool lcPiecesLibrary::LoadCachePiece(PieceInfo* Info)
1107 {
1108 	QString FileName = QFileInfo(QDir(mCachePath), QString::fromLatin1(Info->mFileName)).absoluteFilePath();
1109 	lcMemFile MeshData;
1110 
1111 	if (!ReadArchiveCacheFile(FileName, MeshData))
1112 		return false;
1113 
1114 	qint32 Flags;
1115 	if (MeshData.ReadBuffer((char*)&Flags, sizeof(Flags)) == 0)
1116 		return false;
1117 
1118 	if (Flags != static_cast<qint32>(mStudStyle))
1119 		return false;
1120 
1121 	lcMesh* Mesh = new lcMesh;
1122 	if (Mesh->FileLoad(MeshData))
1123 	{
1124 		Info->SetMesh(Mesh);
1125 		return true;
1126 	}
1127 	else
1128 	{
1129 		delete Mesh;
1130 		return false;
1131 	}
1132 }
1133 
SaveCachePiece(PieceInfo * Info)1134 bool lcPiecesLibrary::SaveCachePiece(PieceInfo* Info)
1135 {
1136 	lcMemFile MeshData;
1137 
1138 	const qint32 Flags = static_cast<qint32>(mStudStyle);
1139 	if (MeshData.WriteBuffer((char*)&Flags, sizeof(Flags)) == 0)
1140 		return false;
1141 
1142 	if (!Info->GetMesh()->FileSave(MeshData))
1143 		return false;
1144 
1145 	QString FileName = QFileInfo(QDir(mCachePath), QString::fromLatin1(Info->mFileName)).absoluteFilePath();
1146 
1147 	return WriteArchiveCacheFile(FileName, MeshData);
1148 }
1149 
1150 class lcSleeper : public QThread
1151 {
1152 public:
msleep(unsigned long Msecs)1153 	static void msleep(unsigned long Msecs)
1154 	{
1155 		QThread::msleep(Msecs);
1156 	}
1157 };
1158 
LoadPieceInfo(PieceInfo * Info,bool Wait,bool Priority)1159 void lcPiecesLibrary::LoadPieceInfo(PieceInfo* Info, bool Wait, bool Priority)
1160 {
1161 	QMutexLocker LoadLock(&mLoadMutex);
1162 
1163 	if (Wait)
1164 	{
1165 		if (Info->AddRef() == 1)
1166 			Info->Load();
1167 		else
1168 		{
1169 			if (Info->mState == LC_PIECEINFO_UNLOADED)
1170 			{
1171 				Info->Load();
1172 				emit PartLoaded(Info);
1173 			}
1174 			else
1175 			{
1176 				LoadLock.unlock();
1177 
1178 				while (Info->mState != LC_PIECEINFO_LOADED)
1179 					lcSleeper::msleep(10);
1180 			}
1181 		}
1182 	}
1183 	else
1184 	{
1185 		if (Info->AddRef() == 1)
1186 		{
1187 			if (Priority)
1188 				mLoadQueue.prepend(Info);
1189 			else
1190 				mLoadQueue.append(Info);
1191 
1192 			mLoadFutures.append(QtConcurrent::run([this]() { LoadQueuedPiece(); }));
1193 		}
1194 	}
1195 }
1196 
ReleasePieceInfo(PieceInfo * Info)1197 void lcPiecesLibrary::ReleasePieceInfo(PieceInfo* Info)
1198 {
1199 	QMutexLocker LoadLock(&mLoadMutex);
1200 
1201 	if (Info->GetRefCount() == 0 || Info->Release() == 0)
1202 		Info->Unload();
1203 }
1204 
LoadQueuedPiece()1205 void lcPiecesLibrary::LoadQueuedPiece()
1206 {
1207 	mLoadMutex.lock();
1208 
1209 	PieceInfo* Info = nullptr;
1210 
1211 	while (!mLoadQueue.isEmpty())
1212 	{
1213 		Info = mLoadQueue.takeFirst();
1214 
1215 		if (Info->mState == LC_PIECEINFO_UNLOADED && Info->GetRefCount() > 0)
1216 		{
1217 			Info->mState = LC_PIECEINFO_LOADING;
1218 			break;
1219 		}
1220 
1221 		Info = nullptr;
1222 	}
1223 
1224 	mLoadMutex.unlock();
1225 
1226 	if (Info)
1227 		Info->Load();
1228 
1229 	emit PartLoaded(Info);
1230 }
1231 
WaitForLoadQueue()1232 void lcPiecesLibrary::WaitForLoadQueue()
1233 {
1234 	for (QFuture<void>& Future : mLoadFutures)
1235 		Future.waitForFinished();
1236 	mLoadFutures.clear();
1237 }
1238 
LoadPieceData(PieceInfo * Info)1239 bool lcPiecesLibrary::LoadPieceData(PieceInfo* Info)
1240 {
1241 	lcLibraryMeshData MeshData;
1242 	lcMeshLoader MeshLoader(MeshData, true, nullptr, false);
1243 
1244 	bool Loaded = false;
1245 	bool SaveCache = false;
1246 
1247 	if (Info->mZipFileType != lcZipFileType::Count && mZipFiles[static_cast<int>(Info->mZipFileType)])
1248 	{
1249 		if (LoadCachePiece(Info))
1250 			return true;
1251 
1252 		lcMemFile PieceFile;
1253 
1254 		if (mZipFiles[static_cast<int>(Info->mZipFileType)]->ExtractFile(Info->mZipFileIndex, PieceFile))
1255 			Loaded = MeshLoader.LoadMesh(PieceFile, LC_MESHDATA_SHARED);
1256 
1257 		SaveCache = Loaded && (Info->mZipFileType == lcZipFileType::Official);
1258 	}
1259 	else
1260 	{
1261 		char FileName[LC_MAXPATH];
1262 		lcDiskFile PieceFile;
1263 
1264 		sprintf(FileName, "parts/%s", Info->mFileName);
1265 		PieceFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName)));
1266 		if (PieceFile.Open(QIODevice::ReadOnly))
1267 			Loaded = MeshLoader.LoadMesh(PieceFile, LC_MESHDATA_SHARED);
1268 
1269 		if (mHasUnofficial && !Loaded)
1270 		{
1271 			sprintf(FileName, "unofficial/parts/%s", Info->mFileName);
1272 			PieceFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName)));
1273 			if (PieceFile.Open(QIODevice::ReadOnly))
1274 				Loaded = MeshLoader.LoadMesh(PieceFile, LC_MESHDATA_SHARED);
1275 		}
1276 	}
1277 
1278 	if (mCancelLoading)
1279 		return false;
1280 
1281 	if (Info)
1282 	{
1283 		if (Loaded)
1284 			Info->SetMesh(MeshData.CreateMesh());
1285 		else
1286 		{
1287 			lcMesh* Mesh = new lcMesh;
1288 			Mesh->CreateBox();
1289 			Info->SetMesh(Mesh);
1290 		}
1291 	}
1292 
1293 	if (SaveCache)
1294 		SaveCachePiece(Info);
1295 
1296 	return Loaded;
1297 }
1298 
GetPrimitiveFile(lcLibraryPrimitive * Primitive,std::function<void (lcFile & File)> Callback)1299 void lcPiecesLibrary::GetPrimitiveFile(lcLibraryPrimitive* Primitive, std::function<void(lcFile& File)> Callback)
1300 {
1301 	if (mZipFiles[static_cast<int>(lcZipFileType::Official)])
1302 	{
1303 		lcMemFile IncludeFile;
1304 
1305 		if (mZipFiles[static_cast<int>(Primitive->mZipFileType)]->ExtractFile(Primitive->mZipFileIndex, IncludeFile))
1306 			Callback(IncludeFile);
1307 	}
1308 	else
1309 	{
1310 		lcDiskFile IncludeFile(Primitive->mFileName);
1311 
1312 		if (IncludeFile.Open(QIODevice::ReadOnly))
1313 			Callback(IncludeFile);
1314 	}
1315 }
1316 
GetPieceFile(const char * PieceName,std::function<void (lcFile & File)> Callback)1317 void lcPiecesLibrary::GetPieceFile(const char* PieceName, std::function<void(lcFile& File)> Callback)
1318 {
1319 	const auto PieceIt = mPieces.find(PieceName);
1320 
1321 	if (PieceIt != mPieces.end())
1322 	{
1323 		PieceInfo* Info = PieceIt->second;
1324 
1325 		if (mZipFiles[static_cast<int>(lcZipFileType::Official)] && Info->mZipFileType != lcZipFileType::Count)
1326 		{
1327 			lcMemFile IncludeFile;
1328 
1329 			if (mZipFiles[static_cast<int>(Info->mZipFileType)]->ExtractFile(Info->mZipFileIndex, IncludeFile))
1330 				Callback(IncludeFile);
1331 		}
1332 		else
1333 		{
1334 			lcDiskFile IncludeFile;
1335 			char FileName[LC_MAXPATH];
1336 			bool Found = false;
1337 
1338 			sprintf(FileName, "parts/%s", Info->mFileName);
1339 			IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName)));
1340 			Found = IncludeFile.Open(QIODevice::ReadOnly);
1341 
1342 			if (mHasUnofficial && !Found)
1343 			{
1344 				sprintf(FileName, "unofficial/parts/%s", Info->mFileName);
1345 				IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName)));
1346 				Found = IncludeFile.Open(QIODevice::ReadOnly);
1347 			}
1348 
1349 			if (Found)
1350 				Callback(IncludeFile);
1351 		}
1352 	}
1353 	else
1354 	{
1355 		bool Found = false;
1356 
1357 		if (mZipFiles[static_cast<int>(lcZipFileType::Official)])
1358 		{
1359 			lcMemFile IncludeFile;
1360 
1361 			auto LoadIncludeFile = [&IncludeFile, PieceName, this](const char* Folder, lcZipFileType ZipFileType)
1362 			{
1363 				char IncludeFileName[LC_MAXPATH];
1364 				sprintf(IncludeFileName, Folder, PieceName);
1365 				return mZipFiles[static_cast<int>(ZipFileType)]->ExtractFile(IncludeFileName, IncludeFile);
1366 			};
1367 
1368 			Found = LoadIncludeFile("ldraw/parts/%s", lcZipFileType::Official);
1369 
1370 			if (!Found)
1371 				Found = LoadIncludeFile("ldraw/p/%s", lcZipFileType::Official);
1372 
1373 			if (mZipFiles[static_cast<int>(lcZipFileType::Unofficial)] && !Found)
1374 			{
1375 				Found = LoadIncludeFile("parts/%s", lcZipFileType::Unofficial);
1376 
1377 				if (!Found)
1378 					Found = LoadIncludeFile("p/%s", lcZipFileType::Unofficial);
1379 			}
1380 
1381 			if (Found)
1382 				Callback(IncludeFile);
1383 		}
1384 		else
1385 		{
1386 			lcDiskFile IncludeFile;
1387 
1388 			auto LoadIncludeFile = [&IncludeFile, PieceName, this](const QLatin1String& Folder)
1389 			{
1390 				const QString IncludeFileName = Folder + PieceName;
1391 				IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(IncludeFileName));
1392 				if (IncludeFile.Open(QIODevice::ReadOnly))
1393 					return true;
1394 
1395 #if defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
1396 				// todo: search the parts/primitive lists and get the file name from there instead of using toLower
1397 				IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(IncludeFileName.toLower()));
1398 				return IncludeFile.Open(QIODevice::ReadOnly);
1399 #else
1400 				return false;
1401 #endif
1402 			};
1403 
1404 			Found = LoadIncludeFile(QLatin1String("parts/"));
1405 
1406 			if (!Found)
1407 				Found = LoadIncludeFile(QLatin1String("p/"));
1408 
1409 			if (mHasUnofficial && !Found)
1410 			{
1411 				Found = LoadIncludeFile(QLatin1String("unofficial/parts/"));
1412 
1413 				if (!Found)
1414 					Found = LoadIncludeFile(QLatin1String("unofficial/p/"));
1415 			}
1416 
1417 			if (Found)
1418 				Callback(IncludeFile);
1419 		}
1420 	}
1421 }
1422 
ReleaseBuffers()1423 void lcPiecesLibrary::ReleaseBuffers()
1424 {
1425 	lcContext* Context = lcContext::GetGlobalOffscreenContext();
1426 
1427 	Context->MakeCurrent();
1428 	Context->DestroyVertexBuffer(mVertexBuffer);
1429 	Context->DestroyIndexBuffer(mIndexBuffer);
1430 
1431 	mBuffersDirty = true;
1432 }
1433 
UpdateBuffers(lcContext * Context)1434 void lcPiecesLibrary::UpdateBuffers(lcContext* Context)
1435 {
1436 	if (!gSupportsVertexBufferObject || !mBuffersDirty)
1437 		return;
1438 
1439 	int VertexDataSize = 0;
1440 	int IndexDataSize = 0;
1441 	std::vector<lcMesh*> Meshes;
1442 
1443 	for (const auto& PieceIt : mPieces)
1444 	{
1445 		const PieceInfo* const Info = PieceIt.second;
1446 		lcMesh* Mesh = Info->GetMesh();
1447 
1448 		if (!Mesh)
1449 			continue;
1450 
1451 		if (Mesh->mVertexDataSize > 16 * 1024 * 1024 || Mesh->mIndexDataSize > 16 * 1024 * 1024)
1452 			continue;
1453 
1454 		VertexDataSize += Mesh->mVertexDataSize;
1455 		IndexDataSize += Mesh->mIndexDataSize;
1456 
1457 		Meshes.push_back(Mesh);
1458 	}
1459 
1460 	Context->DestroyVertexBuffer(mVertexBuffer);
1461 	Context->DestroyIndexBuffer(mIndexBuffer);
1462 
1463 	if (!VertexDataSize || !IndexDataSize)
1464 		return;
1465 
1466 	void* VertexData = malloc(VertexDataSize);
1467 	void* IndexData = malloc(IndexDataSize);
1468 
1469 	VertexDataSize = 0;
1470 	IndexDataSize = 0;
1471 
1472 	for (lcMesh* Mesh : Meshes)
1473 	{
1474 		Mesh->mVertexCacheOffset = VertexDataSize;
1475 		Mesh->mIndexCacheOffset = IndexDataSize;
1476 
1477 		memcpy((char*)VertexData + VertexDataSize, Mesh->mVertexData, Mesh->mVertexDataSize);
1478 		memcpy((char*)IndexData + IndexDataSize, Mesh->mIndexData, Mesh->mIndexDataSize);
1479 
1480 		VertexDataSize += Mesh->mVertexDataSize;
1481 		IndexDataSize += Mesh->mIndexDataSize;
1482 	}
1483 
1484 	mVertexBuffer = Context->CreateVertexBuffer(VertexDataSize, VertexData);
1485 	mIndexBuffer = Context->CreateIndexBuffer(IndexDataSize, IndexData);
1486 	mBuffersDirty = false;
1487 
1488 	free(VertexData);
1489 	free(IndexData);
1490 }
1491 
UnloadUnusedParts()1492 void lcPiecesLibrary::UnloadUnusedParts()
1493 {
1494 	QMutexLocker LoadLock(&mLoadMutex);
1495 
1496 	for (const auto& PieceIt : mPieces)
1497 	{
1498 		PieceInfo* Info = PieceIt.second;
1499 		if (Info->GetRefCount() == 0 && Info->mState != LC_PIECEINFO_UNLOADED)
1500 			ReleasePieceInfo(Info);
1501 	}
1502 }
1503 
LoadTexture(lcTexture * Texture)1504 bool lcPiecesLibrary::LoadTexture(lcTexture* Texture)
1505 {
1506 	QMutexLocker Lock(&mTextureMutex);
1507 	char FileName[2*LC_MAXPATH];
1508 
1509 	if (mZipFiles[static_cast<int>(lcZipFileType::Official)])
1510 	{
1511 		lcMemFile TextureFile;
1512 
1513 		sprintf(FileName, "ldraw/parts/textures/%s.png", Texture->mName);
1514 
1515 		if (!mZipFiles[static_cast<int>(lcZipFileType::Official)]->ExtractFile(FileName, TextureFile))
1516 		{
1517 			sprintf(FileName, "parts/textures/%s.png", Texture->mName);
1518 
1519 			if (!mZipFiles[static_cast<int>(lcZipFileType::Unofficial)] || !mZipFiles[static_cast<int>(lcZipFileType::Unofficial)]->ExtractFile(FileName, TextureFile))
1520 				return false;
1521 		}
1522 
1523 		return Texture->Load(TextureFile);
1524 	}
1525 	else
1526 		return Texture->Load(Texture->mFileName);
1527 }
1528 
ReleaseTexture(lcTexture * Texture)1529 void lcPiecesLibrary::ReleaseTexture(lcTexture* Texture)
1530 {
1531 	QMutexLocker LoadLock(&mLoadMutex);
1532 
1533 	if (Texture->Release() == 0 && Texture->IsTemporary())
1534 	{
1535 		std::vector<lcTexture*>::iterator TextureIt = std::find(mTextures.begin(), mTextures.end(), Texture);
1536 		if (TextureIt != mTextures.end())
1537 			mTextures.erase(TextureIt);
1538 		delete Texture;
1539 	}
1540 }
1541 
SupportsStudStyle() const1542 bool lcPiecesLibrary::SupportsStudStyle() const
1543 {
1544 	return true;
1545 }
1546 
SetStudStyle(lcStudStyle StudStyle,bool Reload)1547 void lcPiecesLibrary::SetStudStyle(lcStudStyle StudStyle, bool Reload)
1548 {
1549 	if (mStudStyle == StudStyle)
1550 		return;
1551 
1552 	mStudStyle = StudStyle;
1553 
1554 	LoadColors();
1555 	UpdateStudStyleSource();
1556 
1557 	mLoadMutex.lock();
1558 
1559 	for (const std::unique_ptr<lcLibrarySource>& Source : mSources)
1560 	{
1561 		for (const auto& PrimitiveIt : Source->Primitives)
1562 		{
1563 			lcLibraryPrimitive* Primitive = PrimitiveIt.second;
1564 
1565 			if (Primitive->mStudStyle || Primitive->mMeshData.mHasStyleStud)
1566 				Primitive->Unload();
1567 		}
1568 	}
1569 
1570 	mLoadMutex.unlock();
1571 
1572 	if (Reload)
1573 	{
1574 		mLoadMutex.lock();
1575 
1576 		for (const auto& PieceIt : mPieces)
1577 		{
1578 			PieceInfo* Info = PieceIt.second;
1579 
1580 			if (Info->mState == LC_PIECEINFO_LOADED && Info->GetMesh() && Info->GetMesh()->mFlags & lcMeshFlag::HasStyleStud)
1581 			{
1582 				Info->Unload();
1583 				mLoadQueue.append(Info);
1584 				mLoadFutures.append(QtConcurrent::run([this]() { LoadQueuedPiece(); }));
1585 			}
1586 		}
1587 
1588 		mLoadMutex.unlock();
1589 
1590 		WaitForLoadQueue();
1591 	}
1592 }
1593 
IsPrimitive(const char * Name) const1594 bool lcPiecesLibrary::IsPrimitive(const char* Name) const
1595 {
1596 	for (const std::unique_ptr<lcLibrarySource>& Source : mSources)
1597 		if (Source->Primitives.find(Name) != Source->Primitives.end())
1598 			return true;
1599 
1600 	return false;
1601 }
1602 
FindPrimitive(const char * Name) const1603 lcLibraryPrimitive* lcPiecesLibrary::FindPrimitive(const char* Name) const
1604 {
1605 	for (const std::unique_ptr<lcLibrarySource>& Source : mSources)
1606 	{
1607 		const auto PrimitiveIt = Source->Primitives.find(Name);
1608 
1609 		if (PrimitiveIt != Source->Primitives.end())
1610 			return PrimitiveIt->second;
1611 	}
1612 
1613 	return  nullptr;
1614 }
1615 
LoadPrimitive(lcLibraryPrimitive * Primitive)1616 bool lcPiecesLibrary::LoadPrimitive(lcLibraryPrimitive* Primitive)
1617 {
1618 	mLoadMutex.lock();
1619 
1620 	if (Primitive->mState == lcPrimitiveState::NotLoaded)
1621 		Primitive->mState = lcPrimitiveState::Loading;
1622 	else
1623 	{
1624 		mLoadMutex.unlock();
1625 
1626 		while (Primitive->mState == lcPrimitiveState::Loading)
1627 			lcSleeper::msleep(5);
1628 
1629 		return Primitive->mState == lcPrimitiveState::Loaded;
1630 	}
1631 
1632 	mLoadMutex.unlock();
1633 
1634 	lcMeshLoader MeshLoader(Primitive->mMeshData, true, nullptr, false);
1635 
1636 	if (mZipFiles[static_cast<int>(lcZipFileType::Official)])
1637 	{
1638 		lcLibraryPrimitive* LowPrimitive = nullptr;
1639 
1640 		lcMemFile PrimFile;
1641 
1642 		if (Primitive->mStud && !Primitive->mStudStyle)
1643 		{
1644 			if (strncmp(Primitive->mName, "8/", 2)) // todo: this is currently the only place that uses mName so use mFileName instead. this should also be done for the loose file libraries.
1645 			{
1646 				char Name[LC_PIECE_NAME_LEN];
1647 				strcpy(Name, "8/");
1648 				strcat(Name, Primitive->mName);
1649 				strupr(Name);
1650 
1651 				LowPrimitive = FindPrimitive(Name); // todo: low primitives don't work with studlogo, because the low stud gets added as shared
1652 			}
1653 		}
1654 
1655 		if (!mZipFiles[static_cast<int>(Primitive->mZipFileType)]->ExtractFile(Primitive->mZipFileIndex, PrimFile))
1656 			return false;
1657 
1658 		if (!LowPrimitive)
1659 		{
1660 			if (!MeshLoader.LoadMesh(PrimFile, LC_MESHDATA_SHARED))
1661 				return false;
1662 		}
1663 		else
1664 		{
1665 			if (!MeshLoader.LoadMesh(PrimFile, LC_MESHDATA_HIGH))
1666 				return false;
1667 
1668 			if (!mZipFiles[static_cast<int>(LowPrimitive->mZipFileType)]->ExtractFile(LowPrimitive->mZipFileIndex, PrimFile))
1669 				return false;
1670 
1671 			if (!MeshLoader.LoadMesh(PrimFile, LC_MESHDATA_LOW))
1672 				return false;
1673 		}
1674 	}
1675 	else
1676 	{
1677 		if (Primitive->mZipFileType == lcZipFileType::Count)
1678 		{
1679 			lcDiskFile PrimFile(Primitive->mFileName);
1680 
1681 			if (!PrimFile.Open(QIODevice::ReadOnly) || !MeshLoader.LoadMesh(PrimFile, LC_MESHDATA_SHARED)) // todo: LOD like the zip files
1682 				return false;
1683 		}
1684 		else
1685 		{
1686 			lcMemFile PrimFile;
1687 
1688 			if (!mZipFiles[static_cast<int>(Primitive->mZipFileType)]->ExtractFile(Primitive->mZipFileIndex, PrimFile))
1689 				return false;
1690 
1691 			if (!MeshLoader.LoadMesh(PrimFile, LC_MESHDATA_SHARED))
1692 				return false;
1693 		}
1694 	}
1695 
1696 	mLoadMutex.lock();
1697 	Primitive->mState = lcPrimitiveState::Loaded;
1698 	mLoadMutex.unlock();
1699 
1700 	return true;
1701 }
1702 
PieceInCategory(PieceInfo * Info,const char * CategoryKeywords) const1703 bool lcPiecesLibrary::PieceInCategory(PieceInfo* Info, const char* CategoryKeywords) const
1704 {
1705 	if (Info->IsTemporary())
1706 		return false;
1707 
1708 	const char* PieceName;
1709 	if (Info->m_strDescription[0] == '~' || Info->m_strDescription[0] == '_')
1710 		PieceName = Info->m_strDescription + 1;
1711 	else
1712 		PieceName = Info->m_strDescription;
1713 
1714 	return lcMatchCategory(PieceName, CategoryKeywords);
1715 }
1716 
GetCategoryEntries(int CategoryIndex,bool GroupPieces,lcArray<PieceInfo * > & SinglePieces,lcArray<PieceInfo * > & GroupedPieces)1717 void lcPiecesLibrary::GetCategoryEntries(int CategoryIndex, bool GroupPieces, lcArray<PieceInfo*>& SinglePieces, lcArray<PieceInfo*>& GroupedPieces)
1718 {
1719 	if (CategoryIndex >= 0 && CategoryIndex < static_cast<int>(gCategories.size()))
1720 		GetCategoryEntries(gCategories[CategoryIndex].Keywords.constData(), GroupPieces, SinglePieces, GroupedPieces);
1721 }
1722 
GetCategoryEntries(const char * CategoryKeywords,bool GroupPieces,lcArray<PieceInfo * > & SinglePieces,lcArray<PieceInfo * > & GroupedPieces)1723 void lcPiecesLibrary::GetCategoryEntries(const char* CategoryKeywords, bool GroupPieces, lcArray<PieceInfo*>& SinglePieces, lcArray<PieceInfo*>& GroupedPieces)
1724 {
1725 	SinglePieces.RemoveAll();
1726 	GroupedPieces.RemoveAll();
1727 
1728 	for (const auto& PieceIt : mPieces)
1729 	{
1730 		PieceInfo* Info = PieceIt.second;
1731 
1732 		if (!PieceInCategory(Info, CategoryKeywords))
1733 			continue;
1734 
1735 		if (!GroupPieces)
1736 		{
1737 			SinglePieces.Add(Info);
1738 			continue;
1739 		}
1740 
1741 		// Check if it's a patterned piece.
1742 		if (Info->IsPatterned())
1743 		{
1744 			PieceInfo* Parent;
1745 
1746 			// Find the parent of this patterned piece.
1747 			char ParentName[LC_PIECE_NAME_LEN];
1748 			strcpy(ParentName, Info->mFileName);
1749 			*strchr(ParentName, 'P') = '\0';
1750 			strcat(ParentName, ".dat");
1751 
1752 			Parent = FindPiece(ParentName, nullptr, false, false);
1753 
1754 			if (Parent)
1755 			{
1756 				// Check if the parent was added as a single piece.
1757 				int Index = SinglePieces.FindIndex(Parent);
1758 
1759 				if (Index != -1)
1760 					SinglePieces.RemoveIndex(Index);
1761 
1762 				Index = GroupedPieces.FindIndex(Parent);
1763 
1764 				if (Index == -1)
1765 					GroupedPieces.Add(Parent);
1766 			}
1767 			else
1768 			{
1769 				// Patterned pieces should have a parent but in case they don't just add them anyway.
1770 				SinglePieces.Add(Info);
1771 			}
1772 		}
1773 		else
1774 		{
1775 			// Check if this piece has already been added to this category by one of its children.
1776 			const int Index = GroupedPieces.FindIndex(Info);
1777 
1778 			if (Index == -1)
1779 				SinglePieces.Add(Info);
1780 		}
1781 	}
1782 }
1783 
GetPatternedPieces(PieceInfo * Parent,lcArray<PieceInfo * > & Pieces) const1784 void lcPiecesLibrary::GetPatternedPieces(PieceInfo* Parent, lcArray<PieceInfo*>& Pieces) const
1785 {
1786 	char Name[LC_PIECE_NAME_LEN];
1787 	strcpy(Name, Parent->mFileName);
1788 	char* Ext = strchr(Name, '.');
1789 	if (Ext)
1790 		*Ext = 0;
1791 	strcat(Name, "P");
1792 	strupr(Name);
1793 
1794 	Pieces.RemoveAll();
1795 
1796 	for (const auto& PieceIt : mPieces)
1797 		if (strncmp(Name, PieceIt.first.c_str(), strlen(Name)) == 0)
1798 			Pieces.Add(PieceIt.second);
1799 
1800 	// Sometimes pieces with A and B versions don't follow the same convention (for example, 3040Pxx instead of 3040BPxx).
1801 	if (Pieces.GetSize() == 0)
1802 	{
1803 		strcpy(Name, Parent->mFileName);
1804 		Ext = strchr(Name, '.');
1805 		if (Ext)
1806 			*Ext = 0;
1807 		size_t Len = strlen(Name);
1808 		if (Name[Len-1] < '0' || Name[Len-1] > '9')
1809 			Name[Len-1] = 'P';
1810 
1811 		for (const auto& PieceIt : mPieces)
1812 			if (strncmp(Name, PieceIt.first.c_str(), strlen(Name)) == 0)
1813 				Pieces.Add(PieceIt.second);
1814 	}
1815 }
1816 
GetParts(lcArray<PieceInfo * > & Parts) const1817 void lcPiecesLibrary::GetParts(lcArray<PieceInfo*>& Parts) const
1818 {
1819 	Parts.SetSize(0);
1820 	Parts.AllocGrow(mPieces.size());
1821 
1822 	for (const auto& PartIt : mPieces)
1823 		Parts.Add(PartIt.second);
1824 }
1825 
GetPartsFromSet(const std::vector<std::string> & PartIds) const1826 std::vector<PieceInfo*> lcPiecesLibrary::GetPartsFromSet(const std::vector<std::string>& PartIds) const
1827 {
1828 	std::vector<PieceInfo*> Parts;
1829 	Parts.reserve(PartIds.size());
1830 
1831 	for (const std::string& PartId : PartIds)
1832 	{
1833 		std::map<std::string, PieceInfo*>::const_iterator PartIt = mPieces.find(PartId);
1834 
1835 		if (PartIt != mPieces.end())
1836 			Parts.push_back(PartIt->second);
1837 	}
1838 
1839 	return Parts;
1840 }
1841 
GetPartId(const PieceInfo * Info) const1842 std::string lcPiecesLibrary::GetPartId(const PieceInfo* Info) const
1843 {
1844 	std::map<std::string, PieceInfo*>::const_iterator PartIt = std::find_if(mPieces.begin(), mPieces.end(), [Info](const std::pair<std::string, PieceInfo*>& PartIt)
1845 	{
1846 		return PartIt.second == Info;
1847 	});
1848 
1849 	if (PartIt != mPieces.end())
1850 		return PartIt->first;
1851 	else
1852 		return std::string();
1853 }
1854 
LoadBuiltinPieces()1855 bool lcPiecesLibrary::LoadBuiltinPieces()
1856 {
1857 	std::unique_ptr<lcDiskFile> File(new lcDiskFile(":/resources/library.zip"));
1858 
1859 	if (!File->Open(QIODevice::ReadOnly) || !OpenArchive(std::move(File), lcZipFileType::Official))
1860 		return false;
1861 
1862 	lcMemFile PieceFile;
1863 
1864 	for (const auto& PieceIt : mPieces)
1865 	{
1866 		PieceInfo* Info = PieceIt.second;
1867 
1868 		mZipFiles[static_cast<int>(Info->mZipFileType)]->ExtractFile(Info->mZipFileIndex, PieceFile, 256);
1869 		PieceFile.Seek(0, SEEK_END);
1870 		PieceFile.WriteU8(0);
1871 
1872 		char* Src = (char*)PieceFile.mBuffer + 2;
1873 		char* Dst = Info->m_strDescription;
1874 
1875 		for (;;)
1876 		{
1877 			if (*Src != '\r' && *Src != '\n' && *Src && Dst - Info->m_strDescription < (int)sizeof(Info->m_strDescription) - 1)
1878 			{
1879 				*Dst++ = *Src++;
1880 				continue;
1881 			}
1882 
1883 			*Dst = 0;
1884 			break;
1885 		}
1886 	}
1887 
1888 	lcLoadDefaultColors(lcStudStyle::Plain);
1889 	lcLoadDefaultCategories(true);
1890 	lcSynthInit();
1891 
1892 	return true;
1893 }
1894