1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00

Merge pull request #5101 from IvanSavenko/android_chronicle

Attempt to fix Chronicles installation on Android
This commit is contained in:
Ivan Savenko 2024-12-17 00:12:57 +02:00 committed by GitHub
commit 604aa9a6b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 120 additions and 50 deletions

View File

@ -371,6 +371,9 @@ void FirstLaunchView::extractGogData()
QFile(fileExe).copy(tmpFileExe); QFile(fileExe).copy(tmpFileExe);
QFile(fileBin).copy(tmpFileBin); 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{}; QString errorText{};
auto isGogGalaxyExe = [](QString fileToTest) { auto isGogGalaxyExe = [](QString fileToTest) {

View File

@ -258,12 +258,15 @@ void MainWindow::manualInstallFile(QString filePath)
QString fileName = QFileInfo{filePath}.fileName(); QString fileName = QFileInfo{filePath}.fileName();
if(filePath.endsWith(".zip", Qt::CaseInsensitive)) if(filePath.endsWith(".zip", Qt::CaseInsensitive))
getModView()->downloadFile(fileName.toLower() {
// mod name currently comes from zip file -> remove suffixes from github zip download QString filenameClean = fileName.toLower()
.replace(QRegularExpression("-[0-9a-f]{40}"), "") // mod name currently comes from zip file -> remove suffixes from github zip download
.replace(QRegularExpression("-vcmi-.+\\.zip"), ".zip") .replace(QRegularExpression("-[0-9a-f]{40}"), "")
.replace("-main.zip", ".zip") .replace(QRegularExpression("-vcmi-.+\\.zip"), ".zip")
, QUrl::fromLocalFile(filePath), "mods"); .replace("-main.zip", ".zip");
getModView()->downloadFile(filenameClean, QUrl::fromLocalFile(filePath), "mods");
}
else if(filePath.endsWith(".json", Qt::CaseInsensitive)) else if(filePath.endsWith(".json", Qt::CaseInsensitive))
{ {
QDir configDir(QString::fromStdString(VCMIDirs::get().userConfigPath().string())); QDir configDir(QString::fromStdString(VCMIDirs::get().userConfigPath().string()));

View File

@ -41,37 +41,25 @@ void ChroniclesExtractor::removeTempDir()
tempDir.removeRecursively(); tempDir.removeRecursively();
} }
int ChroniclesExtractor::getChronicleNo(QFile & file) int ChroniclesExtractor::getChronicleNo()
{ {
if(!file.open(QIODevice::ReadOnly)) QStringList appDirCandidates = tempDir.entryList({"app"}, QDir::Filter::Dirs);
{
QMessageBox::critical(parent, tr("The file cannot be opened"), file.errorString());
return 0;
}
QByteArray magic{"MZ"}; if (!appDirCandidates.empty())
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!")); QDir appDir = tempDir.filePath(appDirCandidates.front());
return 0;
}
QByteArray dataBegin = file.read(1'000'000); for (size_t i = 1; i < chronicles.size(); ++i)
int chronicle = 0;
for (const auto& kv : chronicles) {
if(dataBegin.contains(kv.second))
{ {
chronicle = kv.first; QString chronicleName = chronicles.at(i);
break; 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;
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select a Heroes Chronicles installer file!"));
return 0;
}
return chronicle;
} }
bool ChroniclesExtractor::extractGogInstaller(QString file) bool ChroniclesExtractor::extractGogInstaller(QString file)
@ -147,14 +135,13 @@ void ChroniclesExtractor::createChronicleMod(int no)
dir.removeRecursively(); dir.removeRecursively();
dir.mkpath("."); dir.mkpath(".");
QByteArray tmpChronicles = chronicles.at(no); QString tmpChronicles = chronicles.at(no);
tmpChronicles.replace('\0', "");
QJsonObject mod QJsonObject mod
{ {
{ "modType", "Expansion" }, { "modType", "Expansion" },
{ "name", QString::number(no) + " - " + QString(tmpChronicles) }, { "name", QString("%1 - %2").arg(no).arg(tmpChronicles) },
{ "description", tr("Heroes Chronicles") + " - " + QString::number(no) + " - " + QString(tmpChronicles) }, { "description", tr("Heroes Chronicles %1 - %2").arg(no).arg(tmpChronicles) },
{ "author", "3DO" }, { "author", "3DO" },
{ "version", "1.0" }, { "version", "1.0" },
{ "contact", "vcmi.eu" }, { "contact", "vcmi.eu" },
@ -171,8 +158,7 @@ void ChroniclesExtractor::createChronicleMod(int no)
void ChroniclesExtractor::extractFiles(int no) const void ChroniclesExtractor::extractFiles(int no) const
{ {
QByteArray tmpChronicles = chronicles.at(no); QString tmpChronicles = chronicles.at(no);
tmpChronicles.replace('\0', "");
std::string chroniclesDir = "chronicles_" + std::to_string(no); std::string chroniclesDir = "chronicles_" + std::to_string(no);
QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front()); 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) void ChroniclesExtractor::installChronicles(QStringList exe)
{ {
logGlobal->info("Installing Chronicles");
extractionFile = -1; extractionFile = -1;
fileCount = exe.size(); fileCount = exe.size();
for(QString f : exe) for(QString f : exe)
{ {
extractionFile++; extractionFile++;
logGlobal->info("Creating temporary directory");
if(!createTempDir()) if(!createTempDir())
continue; 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"); QString filepath = tempDir.filePath("chr.exe");
QFile(f).copy(filepath); QFile(f).copy(filepath);
QFile file(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) if(!chronicleNo)
continue; continue;
if(!extractGogInstaller(filepath)) logGlobal->info("Creating base Chronicle mod");
continue;
createBaseMod(); createBaseMod();
logGlobal->info("Creating Chronicle mod");
createChronicleMod(chronicleNo); createChronicleMod(chronicleNo);
logGlobal->info("Removing temporary directory");
removeTempDir(); removeTempDir();
} }
logGlobal->info("Chronicles installed");
} }

View File

@ -24,21 +24,22 @@ class ChroniclesExtractor : public QObject
bool createTempDir(); bool createTempDir();
void removeTempDir(); void removeTempDir();
int getChronicleNo(QFile & file); int getChronicleNo();
bool extractGogInstaller(QString filePath); bool extractGogInstaller(QString filePath);
void createBaseMod() const; void createBaseMod() const;
void createChronicleMod(int no); void createChronicleMod(int no);
void extractFiles(int no) const; void extractFiles(int no) const;
const std::map<int, QByteArray> chronicles = { const QStringList chronicles = {
{1, QByteArray{reinterpret_cast<const char*>(u"Warlords of the Wasteland"), 50}}, {}, // fake 0th "chronicle", to create 1-based list
{2, QByteArray{reinterpret_cast<const char*>(u"Conquest of the Underworld"), 52}}, "Warlords of the Wasteland",
{3, QByteArray{reinterpret_cast<const char*>(u"Masters of the Elements"), 46}}, "Conquest of the Underworld",
{4, QByteArray{reinterpret_cast<const char*>(u"Clash of the Dragons"), 40}}, "Masters of the Elements",
{5, QByteArray{reinterpret_cast<const char*>(u"The World Tree"), 28}}, "Clash of the Dragons",
{6, QByteArray{reinterpret_cast<const char*>(u"The Fiery Moon"), 28}}, "The World Tree",
{7, QByteArray{reinterpret_cast<const char*>(u"Revolt of the Beastmasters"), 52}}, "The Fiery Moon",
{8, QByteArray{reinterpret_cast<const char*>(u"The Sword of Frost"), 36}} "Revolt of the Beastmasters",
"The Sword of Frost",
}; };
public: public:
void installChronicles(QStringList exe); void installChronicles(QStringList exe);

View File

@ -817,6 +817,8 @@ void CModListView::installFiles(QStringList files)
{ {
ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; }); ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; });
ce.installChronicles(exe); ce.installChronicles(exe);
modStateModel->reloadLocalState();
modModel->reloadRepositories();
enableModByName("chronicles"); enableModByName("chronicles");
return true; 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); QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns, gog files) to install..."), QDir::homePath(), filter);
for(const auto & file : files) for(const auto & file : files)
{
logGlobal->info("Importing file %s", file.toStdString());
getMainWindow()->manualInstallFile(file); getMainWindow()->manualInstallFile(file);
}
}; };
// iOS can't display modal dialogs when called directly on button press // iOS can't display modal dialogs when called directly on button press

View File

@ -1086,6 +1086,11 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished"> %1 - %2</translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -832,6 +832,11 @@ Režim celé obrazovky - hra pokryje celou vaši obrazovku a použije vybrané r
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation>Heroes Chronicles</translation> <translation>Heroes Chronicles</translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished">Heroes Chronicles %1 - %2</translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -1055,6 +1055,11 @@ Mode exclusif plein écran - le jeu couvrira l&quot;intégralité de votre écra
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -1080,6 +1080,11 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation>Heroes Chronicles</translation> <translation>Heroes Chronicles</translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished">Heroes Chronicles %1 - %2</translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -1080,6 +1080,11 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation>Heroes Kroniki</translation> <translation>Heroes Kroniki</translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished">Heroes Kroniki %1 - %2</translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -898,6 +898,11 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation>Heroes Chronicles</translation> <translation>Heroes Chronicles</translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -918,6 +918,11 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -949,6 +949,11 @@ Pantalla completa - el juego cubrirá la totalidad de la pantalla y utilizará l
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -1074,6 +1074,11 @@ Exklusivt helskärmsläge - spelet kommer att täcka hela skärmen och använda
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation>Heroes Chronicles</translation> <translation>Heroes Chronicles</translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished">Heroes Chronicles %1 - %2</translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -1056,6 +1056,11 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation>Хроніки Героїв</translation> <translation>Хроніки Героїв</translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished">Хроніки Героїв %1 - %2</translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>

View File

@ -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 đ
<source>Heroes Chronicles</source> <source>Heroes Chronicles</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
<source>Heroes Chronicles %1 - %2</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>File size</name> <name>File size</name>