1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-08 00:39:47 +02:00

Slightly rework Chronicles installer to make it work on Android

This commit is contained in:
Ivan Savenko 2024-12-16 14:14:49 +00:00
parent a9dfd9b7f4
commit 80f5aa8336
6 changed files with 66 additions and 53 deletions

View File

@ -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) {

View File

@ -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()));

View File

@ -41,37 +41,23 @@ void ChroniclesExtractor::removeTempDir()
tempDir.removeRecursively();
}
int ChroniclesExtractor::getChronicleNo(QFile & file)
int ChroniclesExtractor::getChronicleNo()
{
if(!file.open(QIODevice::ReadOnly))
for (size_t i = 1; i < chronicles.size(); ++i)
{
QMessageBox::critical(parent, tr("The file cannot be opened"), file.errorString());
return 0;
QString chronicleName = chronicles.at(i);
QStringList appDirCandidates = tempDir.entryList({"app"}, QDir::Filter::Dirs);
QDir appDir = tempDir.filePath(appDirCandidates.front());
QStringList chroniclesDirCandidates = appDir.entryList({chronicleName}, QDir::Filter::Dirs);
if (!chroniclesDirCandidates.empty())
return i;
}
QByteArray magic{"MZ"};
QByteArray magicFile = file.read(magic.length());
if(!magicFile.startsWith(magic))
{
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select a gog installer file!"));
return 0;
}
QByteArray dataBegin = file.read(1'000'000);
int chronicle = 0;
for (const auto& kv : chronicles) {
if(dataBegin.contains(kv.second))
{
chronicle = kv.first;
break;
}
}
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 +133,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::number(no) + " - " + tmpChronicles },
{ "description", tr("Heroes Chronicles") + " - " + QString::number(no) + " - " + tmpChronicles },
{ "author", "3DO" },
{ "version", "1.0" },
{ "contact", "vcmi.eu" },
@ -171,8 +156,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 +212,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");
}

View File

@ -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<int, QByteArray> chronicles = {
{1, QByteArray{reinterpret_cast<const char*>(u"Warlords of the Wasteland"), 50}},
{2, QByteArray{reinterpret_cast<const char*>(u"Conquest of the Underworld"), 52}},
{3, QByteArray{reinterpret_cast<const char*>(u"Masters of the Elements"), 46}},
{4, QByteArray{reinterpret_cast<const char*>(u"Clash of the Dragons"), 40}},
{5, QByteArray{reinterpret_cast<const char*>(u"The World Tree"), 28}},
{6, QByteArray{reinterpret_cast<const char*>(u"The Fiery Moon"), 28}},
{7, QByteArray{reinterpret_cast<const char*>(u"Revolt of the Beastmasters"), 52}},
{8, QByteArray{reinterpret_cast<const char*>(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);

View File

@ -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;
});

View File

@ -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