/* SPDX-FileCopyrightText: 2026 Project Tick * SPDX-FileContributor: Project Tick * SPDX-License-Identifier: GPL-3.0-or-later * * MeshMC - A Custom Launcher for Minecraft * Copyright (C) 2026 Project Tick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "GoUpdate.h" #include #include #include #include #include "net/Download.h" #include "net/ChecksumValidator.h" namespace GoUpdate { bool parseVersionInfo(const QByteArray& data, VersionFileList& list, QString& error) { QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); if (jsonError.error != QJsonParseError::NoError) { error = QString("Failed to parse version info JSON: %1 at %2") .arg(jsonError.errorString()) .arg(jsonError.offset); qCritical() << error; return false; } QJsonObject json = jsonDoc.object(); qDebug() << data; qDebug() << "Loading version info from JSON."; QJsonArray filesArray = json.value("Files").toArray(); for (QJsonValue fileValue : filesArray) { QJsonObject fileObj = fileValue.toObject(); QString file_path = fileObj.value("Path").toString(); VersionFileEntry file{ file_path, fileObj.value("Perms").toVariant().toInt(), FileSourceList(), fileObj.value("MD5").toString(), }; qDebug() << "File" << file.path << "with perms" << file.mode; QJsonArray sourceArray = fileObj.value("Sources").toArray(); for (QJsonValue val : sourceArray) { QJsonObject sourceObj = val.toObject(); QString type = sourceObj.value("SourceType").toString(); if (type == "http") { file.sources.append( FileSource("http", sourceObj.value("Url").toString())); } else { qWarning() << "Unknown source type" << type << "ignored."; } } qDebug() << "Loaded info for" << file.path; list.append(file); } return true; } bool processFileLists(const VersionFileList& currentVersion, const VersionFileList& newVersion, const QString& rootPath, const QString& tempPath, NetJob::Ptr job, OperationList& ops) { // First, if we've loaded the current version's file list, we need to // iterate through it and delete anything in the current one version's // list that isn't in the new version's list. for (VersionFileEntry entry : currentVersion) { QFileInfo toDelete(FS::PathCombine(rootPath, entry.path)); if (!toDelete.exists()) { qCritical() << "Expected file " << toDelete.absoluteFilePath() << " doesn't exist!"; } bool keep = false; // for (VersionFileEntry newEntry : newVersion) { if (newEntry.path == entry.path) { qDebug() << "Not deleting" << entry.path << "because it is still present in the new version."; keep = true; break; } } // If the loop reaches the end and we didn't find a match, delete // the file. if (!keep) { if (toDelete.exists()) ops.append(Operation::DeleteOp(entry.path)); } } // Next, check each file in MeshMC's folder and see if we need to update // them. for (VersionFileEntry entry : newVersion) { // TODO: Let's not MD5sum a ton of files on the GUI thread. We // should probably find a way to do this in the background. QString fileMD5; QString realEntryPath = FS::PathCombine(rootPath, entry.path); QFile entryFile(realEntryPath); QFileInfo entryInfo(realEntryPath); bool needs_upgrade = false; if (!entryFile.exists()) { needs_upgrade = true; } else { bool pass = true; if (!entryInfo.isReadable()) { qCritical() << "File " << realEntryPath << " is not readable."; pass = false; } if (!entryInfo.isWritable()) { qCritical() << "File " << realEntryPath << " is not writable."; pass = false; } if (!entryFile.open(QFile::ReadOnly)) { qCritical() << "File " << realEntryPath << " cannot be opened for reading."; pass = false; } if (!pass) { ops.clear(); return false; } } if (!needs_upgrade) { QCryptographicHash hash(QCryptographicHash::Md5); auto foo = entryFile.readAll(); hash.addData(foo); fileMD5 = hash.result().toHex(); if ((fileMD5 != entry.md5)) { qDebug() << "MD5Sum does not match!"; qDebug() << "Expected:'" << entry.md5 << "'"; qDebug() << "Got: '" << fileMD5 << "'"; needs_upgrade = true; } } // skip file. it doesn't need an upgrade. if (!needs_upgrade) { qDebug() << "File" << realEntryPath << " does not need updating."; continue; } // yep. this file actually needs an upgrade. PROCEED. qDebug() << "Found file" << realEntryPath << " that needs updating."; // Go through the sources list and find one to use. // TODO: Make a NetAction that takes a source list and tries each of // them until one works. For now, we'll just use the first http one. for (FileSource source : entry.sources) { if (source.type != "http") continue; qDebug() << "Will download" << entry.path << "from" << source.url; // Download it to updatedir/- where filepath is // the file's path with slashes replaced by underscores. QString dlPath = FS::PathCombine( tempPath, QString(entry.path).replace("/", "_")); // We need to download the file to the updatefiles folder and // add a task to copy it to its install path. auto download = Net::Download::makeFile(source.url, dlPath); auto rawMd5 = QByteArray::fromHex(entry.md5.toLatin1()); download->addValidator(new Net::ChecksumValidator( QCryptographicHash::Md5, rawMd5)); job->addNetAction(download); ops.append(Operation::CopyOp(dlPath, entry.path, entry.mode)); } } return true; } } // namespace GoUpdate