diff --git a/launcher/firstLaunch/firstlaunch_moc.cpp b/launcher/firstLaunch/firstlaunch_moc.cpp index b8a8223f5..6827b60a7 100644 --- a/launcher/firstLaunch/firstlaunch_moc.cpp +++ b/launcher/firstLaunch/firstlaunch_moc.cpp @@ -371,6 +371,9 @@ void FirstLaunchView::extractGogData() QFile(fileExe).copy(tmpFileExe); QFile(fileBin).copy(tmpFileBin); + logGlobal->info("Installing exe '%s' ('%s')", tmpFileExe.toStdString(), fileExe.toStdString()); + logGlobal->info("Installing bin '%s' ('%s')", tmpFileBin.toStdString(), fileBin.toStdString()); + QString errorText{}; auto isGogGalaxyExe = [](QString fileToTest) { diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 6967b6dbc..01d483176 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -258,12 +258,15 @@ void MainWindow::manualInstallFile(QString filePath) QString fileName = QFileInfo{filePath}.fileName(); if(filePath.endsWith(".zip", Qt::CaseInsensitive)) - getModView()->downloadFile(fileName.toLower() - // mod name currently comes from zip file -> remove suffixes from github zip download - .replace(QRegularExpression("-[0-9a-f]{40}"), "") - .replace(QRegularExpression("-vcmi-.+\\.zip"), ".zip") - .replace("-main.zip", ".zip") - , QUrl::fromLocalFile(filePath), "mods"); + { + QString filenameClean = fileName.toLower() + // mod name currently comes from zip file -> remove suffixes from github zip download + .replace(QRegularExpression("-[0-9a-f]{40}"), "") + .replace(QRegularExpression("-vcmi-.+\\.zip"), ".zip") + .replace("-main.zip", ".zip"); + + getModView()->downloadFile(filenameClean, QUrl::fromLocalFile(filePath), "mods"); + } else if(filePath.endsWith(".json", Qt::CaseInsensitive)) { QDir configDir(QString::fromStdString(VCMIDirs::get().userConfigPath().string())); diff --git a/launcher/modManager/chroniclesextractor.cpp b/launcher/modManager/chroniclesextractor.cpp index a8a4ccd62..c15e621c7 100644 --- a/launcher/modManager/chroniclesextractor.cpp +++ b/launcher/modManager/chroniclesextractor.cpp @@ -41,37 +41,25 @@ void ChroniclesExtractor::removeTempDir() tempDir.removeRecursively(); } -int ChroniclesExtractor::getChronicleNo(QFile & file) +int ChroniclesExtractor::getChronicleNo() { - if(!file.open(QIODevice::ReadOnly)) - { - QMessageBox::critical(parent, tr("The file cannot be opened"), file.errorString()); - return 0; - } + QStringList appDirCandidates = tempDir.entryList({"app"}, QDir::Filter::Dirs); - QByteArray magic{"MZ"}; - QByteArray magicFile = file.read(magic.length()); - if(!magicFile.startsWith(magic)) + if (!appDirCandidates.empty()) { - QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select a gog installer file!")); - return 0; - } + QDir appDir = tempDir.filePath(appDirCandidates.front()); - QByteArray dataBegin = file.read(1'000'000); - int chronicle = 0; - for (const auto& kv : chronicles) { - if(dataBegin.contains(kv.second)) + for (size_t i = 1; i < chronicles.size(); ++i) { - chronicle = kv.first; - break; + QString chronicleName = chronicles.at(i); + QStringList chroniclesDirCandidates = appDir.entryList({chronicleName}, QDir::Filter::Dirs); + + if (!chroniclesDirCandidates.empty()) + return i; } } - if(!chronicle) - { - QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select a Heroes Chronicles installer file!")); - return 0; - } - return chronicle; + QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select a Heroes Chronicles installer file!")); + return 0; } bool ChroniclesExtractor::extractGogInstaller(QString file) @@ -147,14 +135,13 @@ void ChroniclesExtractor::createChronicleMod(int no) dir.removeRecursively(); dir.mkpath("."); - QByteArray tmpChronicles = chronicles.at(no); - tmpChronicles.replace('\0', ""); + QString tmpChronicles = chronicles.at(no); QJsonObject mod { { "modType", "Expansion" }, - { "name", QString::number(no) + " - " + QString(tmpChronicles) }, - { "description", tr("Heroes Chronicles") + " - " + QString::number(no) + " - " + QString(tmpChronicles) }, + { "name", QString("%1 - %2").arg(no).arg(tmpChronicles) }, + { "description", tr("Heroes Chronicles %1 - %2").arg(no).arg(tmpChronicles) }, { "author", "3DO" }, { "version", "1.0" }, { "contact", "vcmi.eu" }, @@ -171,8 +158,7 @@ void ChroniclesExtractor::createChronicleMod(int no) void ChroniclesExtractor::extractFiles(int no) const { - QByteArray tmpChronicles = chronicles.at(no); - tmpChronicles.replace('\0', ""); + QString tmpChronicles = chronicles.at(no); std::string chroniclesDir = "chronicles_" + std::to_string(no); QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front()); @@ -228,29 +214,46 @@ void ChroniclesExtractor::extractFiles(int no) const void ChroniclesExtractor::installChronicles(QStringList exe) { + logGlobal->info("Installing Chronicles"); + extractionFile = -1; fileCount = exe.size(); for(QString f : exe) { extractionFile++; + logGlobal->info("Creating temporary directory"); if(!createTempDir()) continue; + logGlobal->info("Copying offline installer"); + // FIXME: this is required at the moment for Android (and possibly iOS) + // Incoming file names are in content URI form, e.g. content://media/internal/chronicles.exe + // Qt can handle those like it does regular files + // however, innoextract fails to open such files + // so make a copy in directory to which vcmi always has full access and operate on it QString filepath = tempDir.filePath("chr.exe"); QFile(f).copy(filepath); QFile file(filepath); - int chronicleNo = getChronicleNo(file); + logGlobal->info("Extracting offline installer"); + if(!extractGogInstaller(filepath)) + continue; + + logGlobal->info("Detecting Chronicle"); + int chronicleNo = getChronicleNo(); if(!chronicleNo) continue; - if(!extractGogInstaller(filepath)) - continue; - + logGlobal->info("Creating base Chronicle mod"); createBaseMod(); + + logGlobal->info("Creating Chronicle mod"); createChronicleMod(chronicleNo); + logGlobal->info("Removing temporary directory"); removeTempDir(); } + + logGlobal->info("Chronicles installed"); } diff --git a/launcher/modManager/chroniclesextractor.h b/launcher/modManager/chroniclesextractor.h index f9e1c1fc4..2e55a7677 100644 --- a/launcher/modManager/chroniclesextractor.h +++ b/launcher/modManager/chroniclesextractor.h @@ -24,21 +24,22 @@ class ChroniclesExtractor : public QObject bool createTempDir(); void removeTempDir(); - int getChronicleNo(QFile & file); + int getChronicleNo(); bool extractGogInstaller(QString filePath); void createBaseMod() const; void createChronicleMod(int no); void extractFiles(int no) const; - const std::map chronicles = { - {1, QByteArray{reinterpret_cast(u"Warlords of the Wasteland"), 50}}, - {2, QByteArray{reinterpret_cast(u"Conquest of the Underworld"), 52}}, - {3, QByteArray{reinterpret_cast(u"Masters of the Elements"), 46}}, - {4, QByteArray{reinterpret_cast(u"Clash of the Dragons"), 40}}, - {5, QByteArray{reinterpret_cast(u"The World Tree"), 28}}, - {6, QByteArray{reinterpret_cast(u"The Fiery Moon"), 28}}, - {7, QByteArray{reinterpret_cast(u"Revolt of the Beastmasters"), 52}}, - {8, QByteArray{reinterpret_cast(u"The Sword of Frost"), 36}} + const QStringList chronicles = { + {}, // fake 0th "chronicle", to create 1-based list + "Warlords of the Wasteland", + "Conquest of the Underworld", + "Masters of the Elements", + "Clash of the Dragons", + "The World Tree", + "The Fiery Moon", + "Revolt of the Beastmasters", + "The Sword of Frost", }; public: void installChronicles(QStringList exe); diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 974c4062f..d59238eb7 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -817,6 +817,8 @@ void CModListView::installFiles(QStringList files) { ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; }); ce.installChronicles(exe); + modStateModel->reloadLocalState(); + modModel->reloadRepositories(); enableModByName("chronicles"); return true; }); diff --git a/launcher/startGame/StartGameTab.cpp b/launcher/startGame/StartGameTab.cpp index bf3e6c3f4..238acdf4a 100644 --- a/launcher/startGame/StartGameTab.cpp +++ b/launcher/startGame/StartGameTab.cpp @@ -219,7 +219,10 @@ void StartGameTab::on_buttonImportFiles_clicked() QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns, gog files) to install..."), QDir::homePath(), filter); for(const auto & file : files) + { + logGlobal->info("Importing file %s", file.toStdString()); getMainWindow()->manualInstallFile(file); + } }; // iOS can't display modal dialogs when called directly on button press diff --git a/launcher/translation/chinese.ts b/launcher/translation/chinese.ts index 2258fc1e4..473c6929a 100644 --- a/launcher/translation/chinese.ts +++ b/launcher/translation/chinese.ts @@ -1086,6 +1086,11 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Heroes Chronicles 英雄无敌历代记 + + + Heroes Chronicles %1 - %2 + 英雄无敌历代记 %1 - %2 + File size diff --git a/launcher/translation/czech.ts b/launcher/translation/czech.ts index e5b7ceb73..0e7c7003e 100644 --- a/launcher/translation/czech.ts +++ b/launcher/translation/czech.ts @@ -832,6 +832,11 @@ Režim celé obrazovky - hra pokryje celou vaši obrazovku a použije vybrané r Heroes Chronicles Heroes Chronicles + + + Heroes Chronicles %1 - %2 + Heroes Chronicles %1 - %2 + File size diff --git a/launcher/translation/french.ts b/launcher/translation/french.ts index e2d17a8f0..a2f0fb890 100644 --- a/launcher/translation/french.ts +++ b/launcher/translation/french.ts @@ -1055,6 +1055,11 @@ Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écra Heroes Chronicles + + + Heroes Chronicles %1 - %2 + + File size diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index ce45eb0ea..89bcf032a 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -1080,6 +1080,11 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend Heroes Chronicles Heroes Chronicles + + + Heroes Chronicles %1 - %2 + Heroes Chronicles %1 - %2 + File size diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 04fecd027..83112dcce 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -1080,6 +1080,11 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane Heroes Chronicles Heroes Kroniki + + + Heroes Chronicles %1 - %2 + Heroes Kroniki %1 - %2 + File size diff --git a/launcher/translation/portuguese.ts b/launcher/translation/portuguese.ts index ecda0a2df..e0935ff74 100644 --- a/launcher/translation/portuguese.ts +++ b/launcher/translation/portuguese.ts @@ -898,6 +898,11 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu Heroes Chronicles Heroes Chronicles + + + Heroes Chronicles %1 - %2 + + File size diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index 57469830c..7b835d383 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -918,6 +918,11 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and Heroes Chronicles + + + Heroes Chronicles %1 - %2 + + File size diff --git a/launcher/translation/spanish.ts b/launcher/translation/spanish.ts index 6512c65f0..8d39c658b 100644 --- a/launcher/translation/spanish.ts +++ b/launcher/translation/spanish.ts @@ -949,6 +949,11 @@ Pantalla completa - el juego cubrirá la totalidad de la pantalla y utilizará l Heroes Chronicles + + + Heroes Chronicles %1 - %2 + + File size diff --git a/launcher/translation/swedish.ts b/launcher/translation/swedish.ts index 9d013274d..5c0bb329a 100644 --- a/launcher/translation/swedish.ts +++ b/launcher/translation/swedish.ts @@ -1074,6 +1074,11 @@ Exklusivt helskärmsläge - spelet kommer att täcka hela skärmen och använda Heroes Chronicles Heroes Chronicles + + + Heroes Chronicles %1 - %2 + Heroes Chronicles %1 - %2 + File size diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index cf5349dcb..3a9bcfec0 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -1056,6 +1056,11 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Heroes Chronicles Хроніки Героїв + + + Heroes Chronicles %1 - %2 + Хроніки Героїв %1 - %2 + File size diff --git a/launcher/translation/vietnamese.ts b/launcher/translation/vietnamese.ts index adac3a14b..de08a8eee 100644 --- a/launcher/translation/vietnamese.ts +++ b/launcher/translation/vietnamese.ts @@ -934,6 +934,11 @@ Toàn màn hình riêng biệt - Trò chơi chạy toàn màn hình và dùng đ Heroes Chronicles + + + Heroes Chronicles %1 - %2 + + File size