// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 A-La-Karte Contributors #include "lutrisimporter.h" #include #include #include #include #include #include #include #include LutrisImporter::LutrisImporter(QObject *parent) : PlatformImporter(parent) { } QString LutrisImporter::platformName() const { return QStringLiteral("Lutris"); } QString LutrisImporter::platformId() const { return QStringLiteral("lutris"); } bool LutrisImporter::isAvailable() const { return !findExecutable(QStringLiteral("lutris")).isEmpty() || directoryExists(findLutrisDataPath()); } QString LutrisImporter::findLutrisDataPath() const { QStringList possiblePaths = {expandPath(QStringLiteral("~/.local/share/lutris")), expandPath(QStringLiteral("~/.var/app/net.lutris.Lutris/data/lutris"))}; for (const QString &path : possiblePaths) { if (directoryExists(path)) { return path; } } return {}; } QString LutrisImporter::findLutrisCachePath() const { QStringList possiblePaths = {expandPath(QStringLiteral("~/.cache/lutris")), expandPath(QStringLiteral("~/.var/app/net.lutris.Lutris/cache/lutris"))}; for (const QString &path : possiblePaths) { if (directoryExists(path)) { return path; } } return {}; } QUrl LutrisImporter::findCoverImage(const QString &slug) const { QString cachePath = findLutrisCachePath(); if (cachePath.isEmpty()) { return {}; } // Check coverart directory QString coverartDir = cachePath + QStringLiteral("/coverart"); QStringList extensions = {QStringLiteral(".jpg"), QStringLiteral(".png"), QStringLiteral(".webp")}; for (const QString &ext : extensions) { QString coverPath = coverartDir + QStringLiteral("/") + slug + ext; if (QFile::exists(coverPath)) { return QUrl::fromLocalFile(coverPath); } } // Check banners directory as fallback QString bannersDir = cachePath + QStringLiteral("/banners"); for (const QString &ext : extensions) { QString bannerPath = bannersDir + QStringLiteral("/") + slug + ext; if (QFile::exists(bannerPath)) { return QUrl::fromLocalFile(bannerPath); } } return {}; } QList LutrisImporter::importGames() { QList games; // Try using lutris CLI first QString lutrisPath = findExecutable(QStringLiteral("lutris")); if (!lutrisPath.isEmpty()) { QProcess process; process.start(lutrisPath, {QStringLiteral("-lo"), QStringLiteral("--json")}); if (process.waitForFinished(30000)) { QByteArray output = process.readAllStandardOutput(); QJsonDocument doc = QJsonDocument::fromJson(output); if (doc.isArray()) { QJsonArray gamesArray = doc.array(); int current = 0; int total = gamesArray.count(); for (const QJsonValue &value : gamesArray) { if (!value.isObject()) continue; QJsonObject obj = value.toObject(); QString id = obj[QStringLiteral("id")].toString(); QString name = obj[QStringLiteral("name")].toString(); QString slug = obj[QStringLiteral("slug")].toString(); QString runner = obj[QStringLiteral("runner")].toString(); QString directory = obj[QStringLiteral("directory")].toString(); if (name.isEmpty()) continue; Game *game = new Game(QStringLiteral("lutris-%1").arg(slug.isEmpty() ? id : slug), name); game->setPlatform(platformName()); game->setPlatformId(slug.isEmpty() ? id : slug); game->setLaunchCommand(QStringLiteral("lutris lutris:rungameid/%1").arg(id)); game->setWorkingDirectory(directory); game->setInstalled(true); // Find cover QUrl coverUrl = findCoverImage(slug); if (coverUrl.isValid()) { game->setCoverUrl(coverUrl); } games.append(game); current++; Q_EMIT importProgress(current, total); } } } } // If CLI didn't work, try reading the database directly if (games.isEmpty()) { QString dataPath = findLutrisDataPath(); if (dataPath.isEmpty()) { return games; } QString gamesDir = dataPath + QStringLiteral("/games"); QDir dir(gamesDir); if (!dir.exists()) { return games; } QStringList yamlFiles = dir.entryList({QStringLiteral("*.yml")}, QDir::Files); int current = 0; int total = yamlFiles.count(); for (const QString &file : yamlFiles) { QFile gameFile(gamesDir + QStringLiteral("/") + file); if (!gameFile.open(QIODevice::ReadOnly | QIODevice::Text)) { continue; } // Simple YAML parsing for game config QString content = QString::fromUtf8(gameFile.readAll()); QString name, slug, runner; QStringList lines = content.split(QLatin1Char('\n')); for (const QString &line : lines) { if (line.startsWith(QLatin1String("name:"))) { name = line.mid(5).trimmed(); } else if (line.startsWith(QLatin1String("slug:"))) { slug = line.mid(5).trimmed(); } else if (line.startsWith(QLatin1String("runner:"))) { runner = line.mid(7).trimmed(); } } if (name.isEmpty()) { // Use filename without extension name = QFileInfo(file).baseName(); } if (slug.isEmpty()) { slug = QFileInfo(file).baseName(); } Game *game = new Game(QStringLiteral("lutris-%1").arg(slug), name); game->setPlatform(platformName()); game->setPlatformId(slug); game->setLaunchCommand(QStringLiteral("lutris lutris:rungame/%1").arg(slug)); game->setInstalled(true); QUrl coverUrl = findCoverImage(slug); if (coverUrl.isValid()) { game->setCoverUrl(coverUrl); } games.append(game); current++; Q_EMIT importProgress(current, total); } } return games; }