Improve mod parsing

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2025-07-18 18:24:54 +03:00
parent 94f3fd28b9
commit 428f69b387
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
3 changed files with 103 additions and 141 deletions

View File

@ -244,35 +244,23 @@ void World::readFromZip(const QFileInfo& file)
{ {
MMCZip::ArchiveReader r(file.absoluteFilePath()); MMCZip::ArchiveReader r(file.absoluteFilePath());
if (m_isValid = r.collectFiles(); !m_isValid) { m_isValid = false;
return; r.parse([this](MMCZip::ArchiveReader::File* file, bool& stop) {
} const QString levelDat = "level.dat";
auto filePath = file->filename();
QString path;
const QString levelDat = "level.dat";
for (auto filePath : r.getFiles()) {
QFileInfo fi(filePath); QFileInfo fi(filePath);
if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) { if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) {
m_containerOffsetPath = filePath.chopped(levelDat.length()); m_containerOffsetPath = filePath.chopped(levelDat.length());
path = filePath; if (!m_containerOffsetPath.isEmpty()) {
break; return false;
}
m_levelDatTime = file->dateTime();
loadFromLevelDat(file->readAll());
m_isValid = true;
stop = true;
} }
} return true;
});
if (m_isValid = !m_containerOffsetPath.isEmpty(); !m_isValid) {
return;
}
auto zippedFile = r.goToFile(path);
if (m_isValid = !!zippedFile; !m_isValid) {
return;
}
// read the install profile
m_levelDatTime = zippedFile->dateTime();
if (!m_isValid) {
return;
}
loadFromLevelDat(zippedFile->readAll());
} }
bool World::install(const QString& to, const QString& name) bool World::install(const QString& to, const QString& name)

View File

@ -1,6 +1,7 @@
#include "LocalModParseTask.h" #include "LocalModParseTask.h"
#include <qdcss.h> #include <qdcss.h>
#include <qstringview.h>
#include <toml++/toml.h> #include <toml++/toml.h>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
@ -13,6 +14,7 @@
#include "Json.h" #include "Json.h"
#include "archive/ArchiveReader.h" #include "archive/ArchiveReader.h"
#include "minecraft/mod/ModDetails.h" #include "minecraft/mod/ModDetails.h"
#include "modplatform/ModIndex.h"
#include "settings/INIFile.h" #include "settings/INIFile.h"
static const QRegularExpression s_newlineRegex("\r\n|\n|\r"); static const QRegularExpression s_newlineRegex("\r\n|\n|\r");
@ -470,35 +472,32 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
ModDetails details; ModDetails details;
MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); MMCZip::ArchiveReader zip(mod.fileinfo().filePath());
if (!zip.collectFiles())
return false;
auto isForge = zip.exists("META-INF/mods.toml"); bool baseForgePopulated = false;
if (isForge || zip.exists("META-INF/neoforge.mods.toml")) { bool isNilMod = false;
{ bool isValid = false;
std::unique_ptr<MMCZip::ArchiveReader::File> file; QString manifestVersion = {};
if (isForge) { QByteArray nilData = {};
file = zip.goToFile("META-INF/mods.toml"); QString nilFilePath = {};
} else {
file = zip.goToFile("META-INF/neoforge.mods.toml");
}
if (!file) {
return false;
}
details = ReadMCModTOML(file->readAll()); if (!zip.parse([&details, &baseForgePopulated, &manifestVersion, &isValid, &nilData, &isNilMod, &nilFilePath](
} MMCZip::ArchiveReader::File* file, bool& stop) {
auto filePath = file->filename();
// to replace ${file.jarVersion} with the actual version, as needed if (filePath == "META-INF/mods.toml" || filePath == "META-INF/neoforge.mods.toml") {
if (details.version == "${file.jarVersion}") { details = ReadMCModTOML(file->readAll());
if (zip.exists("META-INF/MANIFEST.MF")) { isValid = true;
auto file = zip.goToFile("META-INF/MANIFEST.MF"); if (details.version == "${file.jarVersion}" && !manifestVersion.isEmpty()) {
if (!file) { details.version = manifestVersion;
return false;
} }
stop = details.version != "${file.jarVersion}";
baseForgePopulated = true;
return true;
}
if (filePath == "META-INF/MANIFEST.MF") {
// quick and dirty line-by-line parser // quick and dirty line-by-line parser
auto manifestLines = QString(file->readAll()).split(s_newlineRegex); auto manifestLines = QString(file->readAll()).split(s_newlineRegex);
QString manifestVersion = ""; manifestVersion = "";
for (auto& line : manifestLines) { for (auto& line : manifestLines) {
if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) { if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) {
manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive); manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive);
@ -511,79 +510,64 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") { if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") {
manifestVersion = "NONE"; manifestVersion = "NONE";
} }
if (baseForgePopulated) {
details.version = manifestVersion; details.version = manifestVersion;
stop = true;
}
return true;
}
if (filePath == "mcmod.info") {
details = ReadMCModInfo(file->readAll());
isValid = true;
stop = true;
return true;
}
if (filePath == "quilt.mod.json") {
details = ReadQuiltModInfo(file->readAll());
isValid = true;
stop = true;
return true;
}
if (filePath == "fabric.mod.json") {
details = ReadFabricModInfo(file->readAll());
isValid = true;
stop = true;
return true;
}
if (filePath == "forgeversion.properties") {
details = ReadForgeInfo(file->readAll());
isValid = true;
stop = true;
return true;
}
if (filePath == "META-INF/nil/mappings.json") {
// nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename
// thankfully, there is a good file to use as a canary so we don't look for nil meta all the time
isNilMod = true;
stop = !nilFilePath.isEmpty();
file->skip();
return true;
} }
}
mod.setDetails(details);
return true;
} else if (zip.exists("mcmod.info")) {
auto file = zip.goToFile("mcmod.info");
if (!file) {
return false;
}
details = ReadMCModInfo(file->readAll());
mod.setDetails(details);
return true;
} else if (zip.exists("quilt.mod.json")) {
auto file = zip.goToFile("quilt.mod.json");
if (!file) {
return false;
}
details = ReadQuiltModInfo(file->readAll());
mod.setDetails(details);
return true;
} else if (zip.exists("fabric.mod.json")) {
auto file = zip.goToFile("fabric.mod.json");
if (!file) {
return false;
}
details = ReadFabricModInfo(file->readAll());
mod.setDetails(details);
return true;
} else if (zip.exists("forgeversion.properties")) {
auto file = zip.goToFile("forgeversion.properties");
if (!file) {
return false;
}
details = ReadForgeInfo(file->readAll());
mod.setDetails(details);
return true;
} else if (zip.exists("META-INF/nil/mappings.json")) {
// nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename
// thankfully, there is a good file to use as a canary so we don't look for nil meta all the time
QString foundNilMeta;
for (auto& fname : zip.getFiles()) {
// nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file
if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") { if (filePath.endsWith(".nilmod.css") && filePath != "nilloader.nilmod.css") {
foundNilMeta = fname; nilData = file->readAll();
break; nilFilePath = filePath;
stop = isNilMod;
return true;
} }
} file->skip();
if (zip.exists(foundNilMeta)) {
auto file = zip.goToFile(foundNilMeta);
if (!file) {
return false;
}
details = ReadNilModInfo(file->readAll(), foundNilMeta);
mod.setDetails(details);
return true; return true;
} })) {
return false;
}
if (isNilMod) {
details = ReadNilModInfo(nilData, nilFilePath);
isValid = true;
}
if (isValid) {
mod.setDetails(details);
return true;
} }
return false; // no valid mod found in archive return false; // no valid mod found in archive
} }

View File

@ -90,35 +90,25 @@ bool processZIP(TexturePack& pack, ProcessingLevel level)
Q_ASSERT(pack.type() == ResourceType::ZIPFILE); Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); MMCZip::ArchiveReader zip(pack.fileinfo().filePath());
bool packProcessed = false;
bool iconProcessed = false;
{ return zip.parse([&packProcessed, &iconProcessed, &pack, level](MMCZip::ArchiveReader::File* file, bool& stop) {
auto file = zip.goToFile("pack.txt"); if (!packProcessed && file->filename() == "pack.txt") {
if (file) { packProcessed = true;
auto data = file->readAll(); auto data = file->readAll();
stop = packProcessed && (iconProcessed || level == ProcessingLevel::BasicInfoOnly);
bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data)); return TexturePackUtils::processPackTXT(pack, std::move(data));
if (!packTXT_result) {
return false;
}
} }
} if (!iconProcessed && file->filename() == "pack.png") {
iconProcessed = true;
if (level == ProcessingLevel::BasicInfoOnly) { auto data = file->readAll();
stop = packProcessed && iconProcessed;
return TexturePackUtils::processPackPNG(pack, std::move(data));
}
file->skip();
return true; return true;
} });
auto file = zip.goToFile("pack.png");
if (file) {
auto data = file->readAll();
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
if (!packPNG_result) {
return false;
}
}
return true;
} }
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data) bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)