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