// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 A-La-Karte Contributors #include "heroicimporter.h" #include #include #include #include #include #include HeroicImporter::HeroicImporter(QObject *parent) : PlatformImporter(parent) { } QString HeroicImporter::platformName() const { return QStringLiteral("Heroic"); } QString HeroicImporter::platformId() const { return QStringLiteral("heroic"); } bool HeroicImporter::isAvailable() const { return directoryExists(findHeroicConfigPath()); } QString HeroicImporter::findHeroicConfigPath() const { QStringList possiblePaths = {expandPath(QStringLiteral("~/.config/heroic")), expandPath(QStringLiteral("~/.var/app/com.heroicgameslauncher.hgl/config/heroic"))}; for (const QString &path : possiblePaths) { if (directoryExists(path)) { return path; } } return {}; } QUrl HeroicImporter::findCoverImage(const QString &appName, const QString &store) const { QString configPath = findHeroicConfigPath(); if (configPath.isEmpty()) { return {}; } // Check images cache QStringList imageDirs = {configPath + QStringLiteral("/images-cache"), configPath + QStringLiteral("/images")}; QStringList extensions = {QStringLiteral(".jpg"), QStringLiteral(".png"), QStringLiteral(".webp")}; QStringList prefixes = {appName, appName + QStringLiteral("_cover"), appName + QStringLiteral("_library")}; for (const QString &imageDir : imageDirs) { QDir dir(imageDir); if (!dir.exists()) continue; for (const QString &prefix : prefixes) { for (const QString &ext : extensions) { QString imagePath = imageDir + QStringLiteral("/") + prefix + ext; if (QFile::exists(imagePath)) { return QUrl::fromLocalFile(imagePath); } } } // Also try looking in subdirectories QStringList subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QString &subDir : subDirs) { for (const QString &prefix : prefixes) { for (const QString &ext : extensions) { QString imagePath = imageDir + QStringLiteral("/") + subDir + QStringLiteral("/") + prefix + ext; if (QFile::exists(imagePath)) { return QUrl::fromLocalFile(imagePath); } } } } } return {}; } QList HeroicImporter::importGames() { QList games; games.append(importEpicGames()); games.append(importGOGGames()); games.append(importAmazonGames()); return games; } QList HeroicImporter::importEpicGames() { QList games; QString configPath = findHeroicConfigPath(); if (configPath.isEmpty()) { return games; } // Check for installed.json QString installedPath = configPath + QStringLiteral("/legendaryConfig/legendary/installed.json"); QFile installedFile(installedPath); if (!installedFile.open(QIODevice::ReadOnly)) { // Try alternative path installedPath = configPath + QStringLiteral("/store_cache/legendary_library.json"); installedFile.setFileName(installedPath); if (!installedFile.open(QIODevice::ReadOnly)) { return games; } } QJsonDocument doc = QJsonDocument::fromJson(installedFile.readAll()); if (doc.isObject()) { QJsonObject obj = doc.object(); int current = 0; int total = obj.keys().count(); for (const QString &appName : obj.keys()) { QJsonObject gameObj = obj[appName].toObject(); QString title = gameObj[QStringLiteral("title")].toString(); if (title.isEmpty()) { title = appName; } QString installPath = gameObj[QStringLiteral("install_path")].toString(); bool isInstalled = !installPath.isEmpty(); Game *game = new Game(QStringLiteral("heroic-epic-%1").arg(appName), title); game->setPlatform(QStringLiteral("Heroic (Epic)")); game->setPlatformId(appName); game->setLaunchCommand(QStringLiteral("heroic --launch %1").arg(appName)); game->setWorkingDirectory(installPath); game->setInstalled(isInstalled); // Developer/Publisher info QString developer = gameObj[QStringLiteral("developer")].toString(); QString publisher = gameObj[QStringLiteral("publisher")].toString(); game->setDeveloper(developer); game->setPublisher(publisher); QUrl coverUrl = findCoverImage(appName, QStringLiteral("epic")); if (coverUrl.isValid()) { game->setCoverUrl(coverUrl); } games.append(game); current++; Q_EMIT importProgress(current, total); } } else if (doc.isArray()) { QJsonArray arr = doc.array(); int current = 0; int total = arr.count(); for (const QJsonValue &value : arr) { QJsonObject gameObj = value.toObject(); QString appName = gameObj[QStringLiteral("app_name")].toString(); QString title = gameObj[QStringLiteral("title")].toString(); if (appName.isEmpty() || title.isEmpty()) continue; bool isInstalled = gameObj[QStringLiteral("is_installed")].toBool(); Game *game = new Game(QStringLiteral("heroic-epic-%1").arg(appName), title); game->setPlatform(QStringLiteral("Heroic (Epic)")); game->setPlatformId(appName); game->setLaunchCommand(QStringLiteral("heroic --launch %1").arg(appName)); game->setInstalled(isInstalled); QUrl coverUrl = findCoverImage(appName, QStringLiteral("epic")); if (coverUrl.isValid()) { game->setCoverUrl(coverUrl); } games.append(game); current++; Q_EMIT importProgress(current, total); } } return games; } QList HeroicImporter::importGOGGames() { QList games; QString configPath = findHeroicConfigPath(); if (configPath.isEmpty()) { return games; } // Check for GOG installed games QString installedPath = configPath + QStringLiteral("/gog_store/installed.json"); QFile installedFile(installedPath); if (!installedFile.open(QIODevice::ReadOnly)) { // Try library file installedPath = configPath + QStringLiteral("/store_cache/gog_library.json"); installedFile.setFileName(installedPath); if (!installedFile.open(QIODevice::ReadOnly)) { return games; } } QJsonDocument doc = QJsonDocument::fromJson(installedFile.readAll()); auto parseGames = [this, &games](const QJsonArray &arr) { for (const QJsonValue &value : arr) { QJsonObject gameObj = value.toObject(); QString appName = gameObj[QStringLiteral("app_name")].toString(); if (appName.isEmpty()) { appName = gameObj[QStringLiteral("appName")].toString(); } QString title = gameObj[QStringLiteral("title")].toString(); if (title.isEmpty()) { title = gameObj[QStringLiteral("name")].toString(); } if (appName.isEmpty() || title.isEmpty()) continue; bool isInstalled = gameObj[QStringLiteral("is_installed")].toBool() || gameObj[QStringLiteral("isInstalled")].toBool(); Game *game = new Game(QStringLiteral("heroic-gog-%1").arg(appName), title); game->setPlatform(QStringLiteral("Heroic (GOG)")); game->setPlatformId(appName); game->setLaunchCommand(QStringLiteral("heroic --launch %1").arg(appName)); game->setInstalled(isInstalled); QUrl coverUrl = findCoverImage(appName, QStringLiteral("gog")); if (coverUrl.isValid()) { game->setCoverUrl(coverUrl); } games.append(game); } }; if (doc.isArray()) { parseGames(doc.array()); } else if (doc.isObject()) { QJsonObject obj = doc.object(); if (obj.contains(QStringLiteral("games"))) { parseGames(obj[QStringLiteral("games")].toArray()); } } return games; } QList HeroicImporter::importAmazonGames() { QList games; QString configPath = findHeroicConfigPath(); if (configPath.isEmpty()) { return games; } QString libraryPath = configPath + QStringLiteral("/store_cache/nile_library.json"); QFile libraryFile(libraryPath); if (!libraryFile.open(QIODevice::ReadOnly)) { return games; } QJsonDocument doc = QJsonDocument::fromJson(libraryFile.readAll()); if (!doc.isArray()) { return games; } QJsonArray arr = doc.array(); for (const QJsonValue &value : arr) { QJsonObject gameObj = value.toObject(); QString appName = gameObj[QStringLiteral("app_name")].toString(); QString title = gameObj[QStringLiteral("title")].toString(); if (appName.isEmpty() || title.isEmpty()) continue; bool isInstalled = gameObj[QStringLiteral("is_installed")].toBool(); Game *game = new Game(QStringLiteral("heroic-amazon-%1").arg(appName), title); game->setPlatform(QStringLiteral("Heroic (Amazon)")); game->setPlatformId(appName); game->setLaunchCommand(QStringLiteral("heroic --launch %1").arg(appName)); game->setInstalled(isInstalled); QUrl coverUrl = findCoverImage(appName, QStringLiteral("amazon")); if (coverUrl.isValid()) { game->setCoverUrl(coverUrl); } games.append(game); } return games; }