mirror of
https://github.com/MultiMC/MultiMC5.git
synced 2025-01-26 21:35:46 +00:00
NOISSUE Some work on JRE downloading
This commit is contained in:
parent
4bd7c4d5df
commit
3ad173da71
@ -271,3 +271,9 @@ add_subdirectory(buildconfig)
|
||||
|
||||
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
|
||||
add_subdirectory(launcher)
|
||||
|
||||
option(BUILD_TOOLS "Build tools" OFF)
|
||||
|
||||
if(BUILD_TOOLS)
|
||||
add_subdirectory(tools)
|
||||
endif()
|
||||
|
@ -343,8 +343,26 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/AssetsUtils.h
|
||||
minecraft/AssetsUtils.cpp
|
||||
|
||||
mojang/Path.h
|
||||
mojang/Path.cpp
|
||||
mojang/PackageManifest.h
|
||||
mojang/PackageManifest.cpp
|
||||
mojang/PackageInstallTask.h
|
||||
mojang/PackageInstallTask.cpp
|
||||
mojang/ComponentsManifest.h
|
||||
mojang/ComponentsManifest.cpp
|
||||
)
|
||||
|
||||
add_unit_test(PackageManifest
|
||||
SOURCES mojang/PackageManifest_test.cpp
|
||||
LIBS Launcher_logic
|
||||
DATA mojang/testdata
|
||||
)
|
||||
|
||||
add_unit_test(ComponentsManifest
|
||||
SOURCES mojang/ComponentsManifest_test.cpp
|
||||
LIBS Launcher_logic
|
||||
DATA mojang/testdata
|
||||
)
|
||||
|
||||
add_unit_test(GradleSpecifier
|
||||
@ -352,21 +370,21 @@ add_unit_test(GradleSpecifier
|
||||
LIBS Launcher_logic
|
||||
)
|
||||
|
||||
add_executable(PackageManifest
|
||||
mojang/PackageManifest_test.cpp
|
||||
)
|
||||
target_link_libraries(PackageManifest
|
||||
Launcher_logic
|
||||
Qt5::Test
|
||||
)
|
||||
target_include_directories(PackageManifest
|
||||
PRIVATE ../cmake/UnitTest/
|
||||
)
|
||||
add_test(
|
||||
NAME PackageManifest
|
||||
COMMAND PackageManifest
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
# add_executable(PackageManifest
|
||||
# mojang/PackageManifest_test.cpp
|
||||
# )
|
||||
# target_link_libraries(PackageManifest
|
||||
# Launcher_logic
|
||||
# Qt5::Test
|
||||
# )
|
||||
# target_include_directories(PackageManifest
|
||||
# PRIVATE ../cmake/UnitTest/
|
||||
# )
|
||||
# add_test(
|
||||
# NAME PackageManifest
|
||||
# COMMAND PackageManifest
|
||||
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
# )
|
||||
|
||||
add_unit_test(MojangVersionFormat
|
||||
SOURCES minecraft/MojangVersionFormat_test.cpp
|
||||
@ -896,6 +914,7 @@ endif()
|
||||
|
||||
# Add executable
|
||||
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
||||
target_include_directories(Launcher_logic PUBLIC .)
|
||||
target_link_libraries(Launcher_logic
|
||||
systeminfo
|
||||
Launcher_quazip
|
||||
|
@ -24,7 +24,7 @@ bool JavaInstall::operator<(const JavaInstall &rhs)
|
||||
|
||||
bool JavaInstall::operator==(const JavaInstall &rhs)
|
||||
{
|
||||
return arch == rhs.arch && id == rhs.id && path == rhs.path;
|
||||
return arch == rhs.arch && id == rhs.id && path == rhs.path && remote == rhs.remote;
|
||||
}
|
||||
|
||||
bool JavaInstall::operator>(const JavaInstall &rhs)
|
||||
|
@ -34,6 +34,10 @@ struct JavaInstall : public BaseVersion
|
||||
Sys::Architecture arch;
|
||||
QString path;
|
||||
bool recommended = false;
|
||||
|
||||
bool remote = false;
|
||||
QString url;
|
||||
QString installRoot;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "java/JavaUtils.h"
|
||||
#include "MMCStrings.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
#include "sys.h"
|
||||
|
||||
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
|
||||
{
|
||||
@ -191,16 +192,79 @@ void JavaListLoadTask::javaCheckerFinished()
|
||||
}
|
||||
}
|
||||
|
||||
auto kernelInfo = Sys::getKernelInfo();
|
||||
|
||||
// FIXME: data-drive
|
||||
// FIXME: limited.
|
||||
JavaInstallPtr remoteJava;
|
||||
if(kernelInfo.kernelName == "Windows")
|
||||
{
|
||||
remoteJava.reset(new JavaInstall());
|
||||
remoteJava->id = "1.8.0_51";
|
||||
if(Sys::systemArchitecture().is64())
|
||||
{
|
||||
remoteJava->arch = Sys::Architecture(Sys::ArchitectureType::AMD64);
|
||||
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/ddc568a50326d2cf85765abb61e752aab191c366/manifest.json";
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteJava->arch = Sys::Architecture(Sys::ArchitectureType::X86);
|
||||
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/baa62193c2785f54d877d871d9859c67d65f08ba/manifest.json";
|
||||
}
|
||||
}
|
||||
|
||||
if(kernelInfo.kernelName == "Linux")
|
||||
{
|
||||
remoteJava.reset(new JavaInstall());
|
||||
remoteJava->id = "1.8.0_202";
|
||||
if(Sys::systemArchitecture().is64())
|
||||
{
|
||||
remoteJava->arch = Sys::Architecture(Sys::ArchitectureType::AMD64);
|
||||
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/a1c15cc788f8893fba7e988eb27404772f699a84/manifest.json";
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteJava->arch = Sys::Architecture(Sys::ArchitectureType::X86);
|
||||
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/64c6a0b8e3427c6c3f3ce82729aada8b2634a955/manifest.json";
|
||||
}
|
||||
}
|
||||
|
||||
if(kernelInfo.kernelName == "Darwin")
|
||||
{
|
||||
if(Sys::systemArchitecture().is64())
|
||||
{
|
||||
remoteJava.reset(new JavaInstall());
|
||||
remoteJava->id = "1.8.0_74";
|
||||
remoteJava->arch = Sys::Architecture(Sys::ArchitectureType::AMD64);
|
||||
remoteJava->url = "https://launchermeta.mojang.com/v1/packages/341663b48a0d4e1c448dc789463fced6ba0962e1/manifest.json";
|
||||
}
|
||||
}
|
||||
|
||||
if(remoteJava) {
|
||||
remoteJava->remote = true;
|
||||
remoteJava->recommended = true;
|
||||
|
||||
// Example: "runtimes/Windows/64/1.8.0_51"
|
||||
auto rootPath = QString("runtimes/%1/%2/%3").arg(kernelInfo.kernelName, remoteJava->arch.serialize(), remoteJava->id.toString());
|
||||
remoteJava->installRoot = rootPath;
|
||||
|
||||
if(kernelInfo.kernelName == "Windows") {
|
||||
remoteJava->path = rootPath + "/data/bin/javaw.exe";
|
||||
}
|
||||
else if (kernelInfo.kernelName == "Darwin") {
|
||||
remoteJava->path = rootPath + "/data/jre.bundle/Contents/Home/bin/java";
|
||||
}
|
||||
else if (kernelInfo.kernelName == "Linux") {
|
||||
remoteJava->path = rootPath + "/data/bin/java";
|
||||
}
|
||||
candidates.append(remoteJava);
|
||||
}
|
||||
|
||||
|
||||
QList<BaseVersionPtr> javas_bvp;
|
||||
for (auto java : candidates)
|
||||
{
|
||||
//qDebug() << java->id << java->arch << " at " << java->path;
|
||||
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
|
||||
|
||||
if (bp_java)
|
||||
{
|
||||
javas_bvp.append(java);
|
||||
}
|
||||
javas_bvp.append(java);
|
||||
}
|
||||
|
||||
m_list->updateListData(javas_bvp);
|
||||
|
113
launcher/mojang/ComponentsManifest.cpp
Normal file
113
launcher/mojang/ComponentsManifest.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2023 Petr Mrázek
|
||||
*
|
||||
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||
* Please see the COPYING.md file for more information.
|
||||
*/
|
||||
|
||||
#include "ComponentsManifest.h"
|
||||
|
||||
#include <Json.h>
|
||||
#include <QDebug>
|
||||
|
||||
// https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json
|
||||
|
||||
namespace mojang_files {
|
||||
|
||||
namespace {
|
||||
void fromJson(QJsonDocument & doc, AllPlatformsManifest & out) {
|
||||
if (!doc.isObject())
|
||||
{
|
||||
throw JSONValidationError("file manifest is not an object");
|
||||
}
|
||||
QJsonObject root = doc.object();
|
||||
|
||||
auto platformIter = root.begin();
|
||||
while (platformIter != root.end()) {
|
||||
QString platformName = platformIter.key();
|
||||
auto platformValue = platformIter.value();
|
||||
platformIter++;
|
||||
if (!platformValue.isObject())
|
||||
{
|
||||
throw JSONValidationError("platform entry inside manifest is not an object: " + platformName);
|
||||
}
|
||||
auto platformObject = platformValue.toObject();
|
||||
auto componentIter = platformObject.begin();
|
||||
ComponentsPlatform outPlatform;
|
||||
while (componentIter != platformObject.end()) {
|
||||
QString componentName = componentIter.key();
|
||||
auto componentValue = componentIter.value();
|
||||
componentIter++;
|
||||
if (!componentValue.isArray())
|
||||
{
|
||||
throw JSONValidationError("component version list inside manifest is not an array: " + componentName);
|
||||
}
|
||||
auto versionArray = componentValue.toArray();
|
||||
VersionList outVersionList;
|
||||
int i = 0;
|
||||
for (auto versionValue: versionArray) {
|
||||
if (!versionValue.isObject())
|
||||
{
|
||||
throw JSONValidationError("version is not an object: " + componentName + "[" + QString::number(i) + "]");
|
||||
}
|
||||
i++;
|
||||
auto versionObject = versionValue.toObject();
|
||||
ComponentVersion outVersion;
|
||||
auto availaibility = Json::requireObject(versionObject, "availability");
|
||||
outVersion.availability_group = Json::requireInteger(availaibility, "group");
|
||||
outVersion.availability_progress = Json::requireInteger(availaibility, "progress");
|
||||
auto manifest = Json::requireObject(versionObject, "manifest");
|
||||
outVersion.manifest_sha1 = Json::requireString(manifest, "sha1");
|
||||
outVersion.manifest_size = Json::requireInteger(manifest, "size");
|
||||
outVersion.manifest_url = Json::requireUrl(manifest, "url");
|
||||
auto version = Json::requireObject(versionObject, "version");
|
||||
outVersion.version_name = Json::requireString(version, "name");
|
||||
outVersion.version_released = Json::requireDateTime(version, "released");
|
||||
outVersionList.versions.push_back(outVersion);
|
||||
}
|
||||
if(outVersionList.versions.size()) {
|
||||
outPlatform.components[componentName] = std::move(outVersionList);
|
||||
}
|
||||
}
|
||||
if(outPlatform.components.size()) {
|
||||
out.platforms[platformName] = outPlatform;
|
||||
}
|
||||
}
|
||||
out.valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
AllPlatformsManifest AllPlatformsManifest::fromManifestContents(const QByteArray& contents)
|
||||
{
|
||||
AllPlatformsManifest out;
|
||||
try
|
||||
{
|
||||
auto doc = Json::requireDocument(contents, "AllPlatformsManifest");
|
||||
fromJson(doc, out);
|
||||
return out;
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
qDebug() << QString("Unable to parse manifest: %1").arg(e.cause());
|
||||
out.valid = false;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
AllPlatformsManifest AllPlatformsManifest::fromManifestFile(const QString & filename) {
|
||||
AllPlatformsManifest out;
|
||||
try
|
||||
{
|
||||
auto doc = Json::requireDocument(filename, filename);
|
||||
fromJson(doc, out);
|
||||
return out;
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause());
|
||||
out.valid = false;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
55
launcher/mojang/ComponentsManifest.h
Normal file
55
launcher/mojang/ComponentsManifest.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2023 Petr Mrázek
|
||||
*
|
||||
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||
* Please see the COPYING.md file for more information.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <QStringList>
|
||||
#include <QDateTime>
|
||||
#include <QUrl>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
|
||||
#include "Path.h"
|
||||
|
||||
namespace mojang_files {
|
||||
|
||||
struct ComponentVersion {
|
||||
int availability_group = 0;
|
||||
int availability_progress = 0;
|
||||
|
||||
QString manifest_sha1;
|
||||
size_t manifest_size = 0;
|
||||
QUrl manifest_url;
|
||||
|
||||
QString version_name;
|
||||
QDateTime version_released;
|
||||
};
|
||||
|
||||
struct VersionList {
|
||||
std::vector<ComponentVersion> versions;
|
||||
};
|
||||
|
||||
struct ComponentsPlatform {
|
||||
std::map<QString, VersionList> components;
|
||||
};
|
||||
|
||||
struct AllPlatformsManifest {
|
||||
static AllPlatformsManifest fromManifestFile(const QString &path);
|
||||
static AllPlatformsManifest fromManifestContents(const QByteArray& contents);
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
|
||||
std::map<QString, ComponentsPlatform> platforms;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
}
|
110
launcher/mojang/ComponentsManifest_test.cpp
Normal file
110
launcher/mojang/ComponentsManifest_test.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2023 Petr Mrázek
|
||||
*
|
||||
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||
* Please see the COPYING.md file for more information.
|
||||
*/
|
||||
|
||||
#include <QTest>
|
||||
#include <QDebug>
|
||||
#include "TestUtil.h"
|
||||
|
||||
#include "mojang/ComponentsManifest.h"
|
||||
|
||||
using namespace mojang_files;
|
||||
|
||||
class ComponentsManifestTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void test_parse();
|
||||
void test_parse_file();
|
||||
};
|
||||
|
||||
namespace {
|
||||
QByteArray basic_manifest = R"END(
|
||||
{
|
||||
"gamecore": {
|
||||
"java-runtime-alpha": [],
|
||||
"jre-legacy": [],
|
||||
"minecraft-java-exe": []
|
||||
},
|
||||
"linux": {
|
||||
"java-runtime-alpha": [{
|
||||
"availability": {
|
||||
"group": 5851,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "e968e71afd3360e5032deac19e1c14d7aa32f5bb",
|
||||
"size": 81882,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/e968e71afd3360e5032deac19e1c14d7aa32f5bb/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "16.0.1.9.1",
|
||||
"released": "2021-05-10T16:43:02+00:00"
|
||||
}
|
||||
}],
|
||||
"jre-legacy": [{
|
||||
"availability": {
|
||||
"group": 6513,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "a1c15cc788f8893fba7e988eb27404772f699a84",
|
||||
"size": 125581,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/a1c15cc788f8893fba7e988eb27404772f699a84/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "8u202",
|
||||
"released": "2020-11-17T19:26:25+00:00"
|
||||
}
|
||||
}],
|
||||
"minecraft-java-exe": []
|
||||
}
|
||||
}
|
||||
)END";
|
||||
}
|
||||
|
||||
void ComponentsManifestTest::test_parse()
|
||||
{
|
||||
auto manifest = AllPlatformsManifest::fromManifestContents(basic_manifest);
|
||||
QVERIFY(manifest.valid == true);
|
||||
QVERIFY(manifest.platforms.count("gamecore") == 0);
|
||||
QVERIFY(manifest.platforms.count("linux") == 1);
|
||||
/*
|
||||
QVERIFY(manifest.files.size() == 1);
|
||||
QVERIFY(manifest.files.count(Path("a/b.txt")));
|
||||
auto &file = manifest.files[Path("a/b.txt")];
|
||||
QVERIFY(file.executable == true);
|
||||
QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
||||
QVERIFY(file.size == 0);
|
||||
QVERIFY(manifest.folders.size() == 4);
|
||||
QVERIFY(manifest.folders.count(Path(".")));
|
||||
QVERIFY(manifest.folders.count(Path("a")));
|
||||
QVERIFY(manifest.folders.count(Path("a/b")));
|
||||
QVERIFY(manifest.folders.count(Path("a/b/c")));
|
||||
QVERIFY(manifest.symlinks.size() == 1);
|
||||
auto symlinkPath = Path("a/b/c.txt");
|
||||
QVERIFY(manifest.symlinks.count(symlinkPath));
|
||||
auto &symlink = manifest.symlinks[symlinkPath];
|
||||
QVERIFY(symlink == Path("../b.txt"));
|
||||
QVERIFY(manifest.sources.size() == 1);
|
||||
*/
|
||||
}
|
||||
|
||||
void ComponentsManifestTest::test_parse_file() {
|
||||
auto path = QFINDTESTDATA("testdata/all.json");
|
||||
auto manifest = AllPlatformsManifest::fromManifestFile(path);
|
||||
QVERIFY(manifest.valid == true);
|
||||
QVERIFY(manifest.platforms.count("gamecore") == 0);
|
||||
QVERIFY(manifest.platforms.count("linux") == 1);
|
||||
/*
|
||||
QVERIFY(manifest.sources.count("c725183c757011e7ba96c83c1e86ee7e8b516a2b") == 1);
|
||||
*/
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ComponentsManifestTest)
|
||||
|
||||
#include "ComponentsManifest_test.moc"
|
254
launcher/mojang/PackageInstallTask.cpp
Normal file
254
launcher/mojang/PackageInstallTask.cpp
Normal file
@ -0,0 +1,254 @@
|
||||
#include "PackageInstallTask.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QRunnable>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <net/NetJob.h>
|
||||
#include <net/ChecksumValidator.h>
|
||||
|
||||
#include "PackageManifest.h"
|
||||
#include <FileSystem.h>
|
||||
|
||||
using Package = mojang_files::Package;
|
||||
using UpdateOperations = mojang_files::UpdateOperations;
|
||||
|
||||
struct PackageInstallTaskData
|
||||
{
|
||||
QString root;
|
||||
QString version;
|
||||
QString packageURL;
|
||||
Net::Mode netmode;
|
||||
QFuture<Package> inspectionFuture;
|
||||
QFutureWatcher<Package> inspectionWatcher;
|
||||
Package inspectedPackage;
|
||||
|
||||
NetJob::Ptr manifestDownloadJob;
|
||||
bool manifestDone = false;
|
||||
Package downloadedPackage;
|
||||
|
||||
UpdateOperations updateOps;
|
||||
NetJob::Ptr mainDownloadJob;
|
||||
shared_qobject_ptr<QNetworkAccessManager> network;
|
||||
};
|
||||
|
||||
namespace {
|
||||
class InspectFolder
|
||||
{
|
||||
public:
|
||||
InspectFolder(const QString & folder) : folder(folder) {}
|
||||
Package operator()()
|
||||
{
|
||||
return Package::fromInspectedFolder(folder);
|
||||
}
|
||||
private:
|
||||
QString folder;
|
||||
};
|
||||
|
||||
class ParsingValidator : public Net::Validator
|
||||
{
|
||||
public: /* con/des */
|
||||
ParsingValidator(Package &package) : m_package(package)
|
||||
{
|
||||
}
|
||||
virtual ~ParsingValidator() = default;
|
||||
|
||||
public: /* methods */
|
||||
bool init(QNetworkRequest &) override
|
||||
{
|
||||
m_package.valid = false;
|
||||
return true;
|
||||
}
|
||||
bool write(QByteArray & data) override
|
||||
{
|
||||
this->data.append(data);
|
||||
return true;
|
||||
}
|
||||
bool abort() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool validate(QNetworkReply &) override
|
||||
{
|
||||
m_package = Package::fromManifestContents(data);
|
||||
return m_package.valid;
|
||||
}
|
||||
|
||||
private: /* data */
|
||||
QByteArray data;
|
||||
Package &m_package;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
PackageInstallTask::PackageInstallTask(
|
||||
shared_qobject_ptr<QNetworkAccessManager> network,
|
||||
Net::Mode netmode,
|
||||
QString version,
|
||||
QString packageURL,
|
||||
QString targetPath,
|
||||
QObject* parent
|
||||
) : Task(parent) {
|
||||
d.reset(new PackageInstallTaskData);
|
||||
d->network = network;
|
||||
d->netmode = netmode;
|
||||
d->root = targetPath;
|
||||
d->packageURL = packageURL;
|
||||
d->version = version;
|
||||
}
|
||||
|
||||
PackageInstallTask::~PackageInstallTask() {
|
||||
}
|
||||
|
||||
void PackageInstallTask::executeTask() {
|
||||
// inspect the data folder in a thread
|
||||
d->inspectionFuture = QtConcurrent::run(QThreadPool::globalInstance(), InspectFolder(FS::PathCombine(d->root, "data")));
|
||||
connect(&d->inspectionWatcher, &QFutureWatcher<Package>::finished, this, &PackageInstallTask::inspectionFinished);
|
||||
d->inspectionWatcher.setFuture(d->inspectionFuture);
|
||||
|
||||
// while inspecting, grab the manifest from remote
|
||||
d->manifestDownloadJob.reset(new NetJob(QObject::tr("Download of package manifest %1").arg(d->packageURL), d->network));
|
||||
auto url = d->packageURL;
|
||||
auto dl = Net::Download::makeFile(url, FS::PathCombine(d->root, "manifest.json"));
|
||||
/*
|
||||
* The validator parses the file and loads it into the object.
|
||||
* If that fails, the file is not written to storage.
|
||||
*/
|
||||
dl->addValidator(new ParsingValidator(d->downloadedPackage));
|
||||
d->manifestDownloadJob->addNetAction(dl);
|
||||
auto job = d->manifestDownloadJob.get();
|
||||
connect(job, &NetJob::finished, this, &PackageInstallTask::manifestFetchFinished);
|
||||
d->manifestDownloadJob->start();
|
||||
}
|
||||
|
||||
void PackageInstallTask::inspectionFinished() {
|
||||
d->inspectedPackage = d->inspectionWatcher.result();
|
||||
processInputs();
|
||||
}
|
||||
|
||||
void PackageInstallTask::manifestFetchFinished()
|
||||
{
|
||||
d->manifestDone = true;
|
||||
d->manifestDownloadJob.reset();
|
||||
processInputs();
|
||||
}
|
||||
|
||||
void PackageInstallTask::processInputs()
|
||||
{
|
||||
if(!d->manifestDone) {
|
||||
return;
|
||||
}
|
||||
if(!d->inspectionFuture.isFinished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!d->downloadedPackage.valid) {
|
||||
emitFailed("Downloading package manifest failed...");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!d->inspectedPackage.valid) {
|
||||
emitFailed("Inspecting local data folder failed...");
|
||||
return;
|
||||
}
|
||||
|
||||
d->updateOps = UpdateOperations::resolve(d->inspectedPackage, d->downloadedPackage);
|
||||
|
||||
if(!d->updateOps.valid) {
|
||||
emitFailed("Unable to determine update actions...");
|
||||
return;
|
||||
}
|
||||
|
||||
if(d->updateOps.empty()) {
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
auto dataRoot = FS::PathCombine(d->root, "data");
|
||||
|
||||
// first, ensure data path exists
|
||||
QDir temp;
|
||||
temp.mkpath(dataRoot);
|
||||
|
||||
for(auto & rm: d->updateOps.deletes) {
|
||||
auto filePath = FS::PathCombine(dataRoot, rm.toString());
|
||||
qDebug() << "RM" << filePath;
|
||||
QFile::remove(filePath);
|
||||
}
|
||||
|
||||
for(auto & rmdir: d->updateOps.rmdirs) {
|
||||
auto folderPath = FS::PathCombine(dataRoot, rmdir.toString());
|
||||
qDebug() << "RMDIR" << folderPath;
|
||||
QDir dir;
|
||||
dir.rmdir(folderPath);
|
||||
}
|
||||
|
||||
for(auto & mkdir: d->updateOps.mkdirs) {
|
||||
auto folderPath = FS::PathCombine(dataRoot, mkdir.toString());
|
||||
qDebug() << "MKDIR" << folderPath;
|
||||
QDir dir;
|
||||
dir.mkdir(folderPath);
|
||||
}
|
||||
|
||||
for(auto & mklink: d->updateOps.mklinks) {
|
||||
auto linkPath = FS::PathCombine(dataRoot, mklink.first.toString());
|
||||
auto linkTarget = mklink.second.toString();
|
||||
qDebug() << "MKLINK" << linkPath << "->" << linkTarget;
|
||||
QFile::link(linkTarget, linkPath);
|
||||
}
|
||||
|
||||
for(auto & fix: d->updateOps.executable_fixes) {
|
||||
const auto &path = fix.first;
|
||||
bool executable = fix.second;
|
||||
auto targetPath = FS::PathCombine(dataRoot, path.toString());
|
||||
qDebug() << "FIX_EXEC" << targetPath << "->" << (executable ? "EXECUTABLE" : "REGULAR");
|
||||
|
||||
auto perms = QFile::permissions(targetPath);
|
||||
if(executable) {
|
||||
perms |= QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
|
||||
}
|
||||
else {
|
||||
perms &= ~(QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
}
|
||||
QFile::setPermissions(targetPath, perms);
|
||||
}
|
||||
|
||||
if(!d->updateOps.downloads.size()) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
||||
// we download.
|
||||
d->manifestDownloadJob.reset(new NetJob(QObject::tr("Download of files for %1").arg(d->packageURL), d->network));
|
||||
connect(d->manifestDownloadJob.get(), &NetJob::succeeded, this, &PackageInstallTask::downloadsSucceeded);
|
||||
connect(d->manifestDownloadJob.get(), &NetJob::failed, this, &PackageInstallTask::downloadsFailed);
|
||||
connect(d->manifestDownloadJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
using Option = Net::Download::Option;
|
||||
for(auto & download: d->updateOps.downloads) {
|
||||
const auto &path = download.first;
|
||||
const auto &object = download.second;
|
||||
auto targetPath = FS::PathCombine(dataRoot, path.toString());
|
||||
auto dl = Net::Download::makeFile(object.url, targetPath, object.executable ? Option::SetExecutable : Option::NoOptions);
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, QByteArray::fromHex(object.hash.toLatin1())));
|
||||
dl->m_total_progress = object.size;
|
||||
d->manifestDownloadJob->addNetAction(dl);
|
||||
qDebug() << "DOWNLOAD" << object.url << "to" << targetPath << (object.executable ? "(EXECUTABLE)" : "(REGULAR)");
|
||||
}
|
||||
d->manifestDownloadJob->start();
|
||||
}
|
||||
|
||||
void PackageInstallTask::downloadsFailed(QString reason)
|
||||
{
|
||||
d->manifestDownloadJob.reset();
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void PackageInstallTask::downloadsSucceeded()
|
||||
{
|
||||
d->manifestDownloadJob.reset();
|
||||
emitSucceeded();
|
||||
}
|
49
launcher/mojang/PackageInstallTask.h
Normal file
49
launcher/mojang/PackageInstallTask.h
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "net/Mode.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
struct PackageInstallTaskData;
|
||||
|
||||
class PackageInstallTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
Launch,
|
||||
Resolution
|
||||
};
|
||||
|
||||
public:
|
||||
explicit PackageInstallTask(
|
||||
shared_qobject_ptr<QNetworkAccessManager> network,
|
||||
Net::Mode netmode,
|
||||
QString version,
|
||||
QString packageURL,
|
||||
QString targetPath,
|
||||
QObject *parent = 0
|
||||
);
|
||||
virtual ~PackageInstallTask();
|
||||
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
||||
private slots:
|
||||
void inspectionFinished();
|
||||
void manifestFetchFinished();
|
||||
|
||||
void processInputs();
|
||||
|
||||
void downloadsSucceeded();
|
||||
void downloadsFailed(QString reason);
|
||||
|
||||
private:
|
||||
std::unique_ptr<PackageInstallTaskData> d;
|
||||
};
|
||||
|
||||
|
@ -74,8 +74,8 @@ void Package::addLink(const Path& path, const Path& target) {
|
||||
symlinks[path] = target;
|
||||
}
|
||||
|
||||
void Package::addSource(const FileSource& source) {
|
||||
sources[source.hash] = source;
|
||||
void Package::addSource(const Hash & rawHash, const FileSource& source) {
|
||||
sources[rawHash] = source;
|
||||
}
|
||||
|
||||
|
||||
@ -131,6 +131,8 @@ void fromJson(QJsonDocument & doc, Package & out) {
|
||||
}
|
||||
else if (compression == "lzma") {
|
||||
source.compression = Compression::Lzma;
|
||||
// FIXME: remove this line when we implement LZMA filter for downloads again
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
@ -141,11 +143,10 @@ void fromJson(QJsonDocument & doc, Package & out) {
|
||||
throw JSONValidationError("No valid compression method for file " + iter.key());
|
||||
}
|
||||
out.addFile(objectPath, file);
|
||||
out.addSource(bestSource);
|
||||
out.addSource(file.hash, bestSource);
|
||||
}
|
||||
else if(type == "link") {
|
||||
auto target = Json::requireString(fileObject, "target");
|
||||
out.symlinks[objectPath] = target;
|
||||
out.addLink(objectPath, target);
|
||||
}
|
||||
else {
|
||||
@ -245,7 +246,8 @@ Package Package::fromInspectedFolder(const QString& folderPath)
|
||||
iterator.next();
|
||||
|
||||
auto fileInfo = iterator.fileInfo();
|
||||
auto relPath = root.relativeFilePath(fileInfo.filePath());
|
||||
auto itemPath = fileInfo.absoluteFilePath();
|
||||
auto relPath = root.relativeFilePath(itemPath);
|
||||
// FIXME: this is probably completely busted on Windows anyway, so just disable it.
|
||||
// Qt makes shit up and doesn't understand the platform details
|
||||
// TODO: Actually use a filesystem library that isn't terrible and has decen license.
|
||||
@ -253,7 +255,7 @@ Package Package::fromInspectedFolder(const QString& folderPath)
|
||||
#ifndef Q_OS_WIN32
|
||||
if(fileInfo.isSymLink()) {
|
||||
Path targetPath;
|
||||
if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) {
|
||||
if(!actually_read_symlink_target(fileInfo.absoluteFilePath(), targetPath)) {
|
||||
qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
|
||||
out.valid = false;
|
||||
}
|
||||
@ -371,12 +373,21 @@ UpdateOperations UpdateOperations::resolve(const Package& from, const Package& t
|
||||
}
|
||||
}
|
||||
for(auto iter = to.files.begin(); iter != to.files.end(); iter++) {
|
||||
auto path = iter->first;
|
||||
auto & path = iter->first;
|
||||
auto & file = iter->second;
|
||||
auto & fileHash = file.hash;
|
||||
auto executable = file.executable;
|
||||
if(!to.sources.count(fileHash)) {
|
||||
out.valid = false;
|
||||
return out;
|
||||
}
|
||||
auto & source = to.sources.at(fileHash);
|
||||
// it wasn't there before, it is there now... therefore we fill it in
|
||||
if(!from.files.count(path)) {
|
||||
out.downloads.emplace(
|
||||
std::pair<Path, FileDownload>{
|
||||
path,
|
||||
FileDownload(to.sources.at(iter->second.hash), iter->second.executable)
|
||||
FileDownload(source, executable)
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -418,13 +429,14 @@ UpdateOperations UpdateOperations::resolve(const Package& from, const Package& t
|
||||
const auto &new_target = iter2->second;
|
||||
if (current_target != new_target) {
|
||||
out.deletes.push_back(path);
|
||||
out.mklinks[path] = iter2->second;
|
||||
out.mklinks[path] = new_target;
|
||||
}
|
||||
}
|
||||
for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) {
|
||||
auto path = iter->first;
|
||||
const auto &new_target = iter->second;
|
||||
if(!from.symlinks.count(path)) {
|
||||
out.mklinks[path] = iter->second;
|
||||
out.mklinks[path] = new_target;
|
||||
}
|
||||
}
|
||||
out.valid = true;
|
||||
|
@ -12,98 +12,13 @@
|
||||
#include <set>
|
||||
#include <QStringList>
|
||||
#include "tasks/Task.h"
|
||||
#include "Path.h"
|
||||
|
||||
namespace mojang_files {
|
||||
|
||||
using Hash = QString;
|
||||
extern const Hash empty_hash;
|
||||
|
||||
// simple-ish path implementation. assumes always relative and does not allow '..' entries
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
using parts_type = QStringList;
|
||||
|
||||
Path() = default;
|
||||
Path(QString string) {
|
||||
auto parts_in = string.split('/');
|
||||
for(auto & part: parts_in) {
|
||||
if(part.isEmpty() || part == ".") {
|
||||
continue;
|
||||
}
|
||||
if(part == "..") {
|
||||
if(parts.size()) {
|
||||
parts.pop_back();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
parts.push_back(part);
|
||||
}
|
||||
}
|
||||
|
||||
bool has_parent_path() const
|
||||
{
|
||||
return parts.size() > 0;
|
||||
}
|
||||
|
||||
Path parent_path() const
|
||||
{
|
||||
if (parts.empty())
|
||||
return Path();
|
||||
return Path(parts.begin(), std::prev(parts.end()));
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return parts.empty();
|
||||
}
|
||||
|
||||
int length() const
|
||||
{
|
||||
return parts.length();
|
||||
}
|
||||
|
||||
bool operator==(const Path & rhs) const {
|
||||
return parts == rhs.parts;
|
||||
}
|
||||
|
||||
bool operator!=(const Path & rhs) const {
|
||||
return parts != rhs.parts;
|
||||
}
|
||||
|
||||
inline bool operator<(const Path& rhs) const
|
||||
{
|
||||
return compare(rhs) < 0;
|
||||
}
|
||||
|
||||
parts_type::const_iterator begin() const
|
||||
{
|
||||
return parts.begin();
|
||||
}
|
||||
|
||||
parts_type::const_iterator end() const
|
||||
{
|
||||
return parts.end();
|
||||
}
|
||||
|
||||
QString toString() const {
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
private:
|
||||
Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
|
||||
auto cursor = start;
|
||||
while(cursor != end) {
|
||||
parts.push_back(*cursor);
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
int compare(const Path& p) const;
|
||||
|
||||
parts_type parts;
|
||||
};
|
||||
|
||||
|
||||
enum class Compression {
|
||||
Raw,
|
||||
Lzma,
|
||||
@ -146,7 +61,7 @@ struct Package {
|
||||
void addFolder(Path folder);
|
||||
void addFile(const Path & path, const File & file);
|
||||
void addLink(const Path & path, const Path & target);
|
||||
void addSource(const FileSource & source);
|
||||
void addSource(const Hash & rawHash, const FileSource & source);
|
||||
|
||||
std::map<Hash, FileSource> sources;
|
||||
bool valid = true;
|
||||
@ -173,6 +88,19 @@ struct UpdateOperations {
|
||||
std::map<Path, FileDownload> downloads;
|
||||
std::map<Path, Path> mklinks;
|
||||
std::map<Path, bool> executable_fixes;
|
||||
|
||||
bool empty() const {
|
||||
if(!valid) {
|
||||
return true;
|
||||
}
|
||||
return
|
||||
deletes.empty() &&
|
||||
rmdirs.empty() &&
|
||||
mkdirs.empty() &&
|
||||
downloads.empty() &&
|
||||
mklinks.empty() &&
|
||||
executable_fixes.empty();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ void PackageManifestTest::test_parse_file() {
|
||||
auto path = QFINDTESTDATA("testdata/1.8.0_202-x64.json");
|
||||
auto manifest = Package::fromManifestFile(path);
|
||||
QVERIFY(manifest.valid == true);
|
||||
QVERIFY(manifest.sources.count("c725183c757011e7ba96c83c1e86ee7e8b516a2b") == 1);
|
||||
}
|
||||
|
||||
|
||||
|
9
launcher/mojang/Path.cpp
Normal file
9
launcher/mojang/Path.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright 2023 Petr Mrázek
|
||||
*
|
||||
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||
* Please see the COPYING.md file for more information.
|
||||
*/
|
||||
|
||||
// NOOP
|
||||
|
100
launcher/mojang/Path.h
Normal file
100
launcher/mojang/Path.h
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2023 Petr Mrázek
|
||||
*
|
||||
* This source is subject to the Microsoft Permissive License (MS-PL).
|
||||
* Please see the COPYING.md file for more information.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace mojang_files {
|
||||
|
||||
// simple-ish path implementation. assumes always relative and does not allow '..' entries
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
using parts_type = QStringList;
|
||||
|
||||
Path() = default;
|
||||
Path(QString string) {
|
||||
auto parts_in = string.split('/');
|
||||
for(auto & part: parts_in) {
|
||||
if(part.isEmpty() || part == ".") {
|
||||
continue;
|
||||
}
|
||||
if(part == "..") {
|
||||
if(parts.size()) {
|
||||
parts.pop_back();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
parts.push_back(part);
|
||||
}
|
||||
}
|
||||
|
||||
bool has_parent_path() const
|
||||
{
|
||||
return parts.size() > 0;
|
||||
}
|
||||
|
||||
Path parent_path() const
|
||||
{
|
||||
if (parts.empty())
|
||||
return Path();
|
||||
return Path(parts.begin(), std::prev(parts.end()));
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return parts.empty();
|
||||
}
|
||||
|
||||
int length() const
|
||||
{
|
||||
return parts.length();
|
||||
}
|
||||
|
||||
bool operator==(const Path & rhs) const {
|
||||
return parts == rhs.parts;
|
||||
}
|
||||
|
||||
bool operator!=(const Path & rhs) const {
|
||||
return parts != rhs.parts;
|
||||
}
|
||||
|
||||
inline bool operator<(const Path& rhs) const
|
||||
{
|
||||
return compare(rhs) < 0;
|
||||
}
|
||||
|
||||
parts_type::const_iterator begin() const
|
||||
{
|
||||
return parts.begin();
|
||||
}
|
||||
|
||||
parts_type::const_iterator end() const
|
||||
{
|
||||
return parts.end();
|
||||
}
|
||||
|
||||
QString toString() const {
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
private:
|
||||
Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
|
||||
auto cursor = start;
|
||||
while(cursor != end) {
|
||||
parts.push_back(*cursor);
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
int compare(const Path& p) const;
|
||||
|
||||
parts_type parts;
|
||||
};
|
||||
|
||||
}
|
186
launcher/mojang/testdata/all.json
vendored
Normal file
186
launcher/mojang/testdata/all.json
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
{
|
||||
"gamecore": {
|
||||
"java-runtime-alpha": [],
|
||||
"jre-legacy": [],
|
||||
"minecraft-java-exe": []
|
||||
},
|
||||
"linux": {
|
||||
"java-runtime-alpha": [{
|
||||
"availability": {
|
||||
"group": 5851,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "e968e71afd3360e5032deac19e1c14d7aa32f5bb",
|
||||
"size": 81882,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/e968e71afd3360e5032deac19e1c14d7aa32f5bb/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "16.0.1.9.1",
|
||||
"released": "2021-05-10T16:43:02+00:00"
|
||||
}
|
||||
}],
|
||||
"jre-legacy": [{
|
||||
"availability": {
|
||||
"group": 6513,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "a1c15cc788f8893fba7e988eb27404772f699a84",
|
||||
"size": 125581,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/a1c15cc788f8893fba7e988eb27404772f699a84/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "8u202",
|
||||
"released": "2020-11-17T19:26:25+00:00"
|
||||
}
|
||||
}],
|
||||
"minecraft-java-exe": []
|
||||
},
|
||||
"linux-i386": {
|
||||
"java-runtime-alpha": [],
|
||||
"jre-legacy": [{
|
||||
"availability": {
|
||||
"group": 4119,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "64c6a0b8e3427c6c3f3ce82729aada8b2634a955",
|
||||
"size": 126498,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/64c6a0b8e3427c6c3f3ce82729aada8b2634a955/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "8u202",
|
||||
"released": "2020-11-17T19:28:39+00:00"
|
||||
}
|
||||
}],
|
||||
"minecraft-java-exe": []
|
||||
},
|
||||
"mac-os": {
|
||||
"java-runtime-alpha": [{
|
||||
"availability": {
|
||||
"group": 3212,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "5a480fde2214534ab0b51ae78c70455ffd7c0e6a",
|
||||
"size": 95323,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/5a480fde2214534ab0b51ae78c70455ffd7c0e6a/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "16.0.1.9.1_3",
|
||||
"released": "2021-05-11T15:03:25+00:00"
|
||||
}
|
||||
}],
|
||||
"jre-legacy": [{
|
||||
"availability": {
|
||||
"group": 8727,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "341663b48a0d4e1c448dc789463fced6ba0962e1",
|
||||
"size": 77382,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/341663b48a0d4e1c448dc789463fced6ba0962e1/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "8u74",
|
||||
"released": "2020-11-17T19:06:44+00:00"
|
||||
}
|
||||
}],
|
||||
"minecraft-java-exe": []
|
||||
},
|
||||
"windows-x64": {
|
||||
"java-runtime-alpha": [{
|
||||
"availability": {
|
||||
"group": 4219,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "0586267ba40236e176925da17ca0d29dead3d30d",
|
||||
"size": 145480,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/0586267ba40236e176925da17ca0d29dead3d30d/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "16.0.1.9.1",
|
||||
"released": "2021-05-10T16:48:07+00:00"
|
||||
}
|
||||
}],
|
||||
"jre-legacy": [{
|
||||
"availability": {
|
||||
"group": 3654,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "ddc568a50326d2cf85765abb61e752aab191c366",
|
||||
"size": 78978,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/ddc568a50326d2cf85765abb61e752aab191c366/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "8u51",
|
||||
"released": "2020-11-17T19:12:13+00:00"
|
||||
}
|
||||
}],
|
||||
"minecraft-java-exe": [{
|
||||
"availability": {
|
||||
"group": 23,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "956d6567682740ab6327a1e8357658d8f7b86421",
|
||||
"size": 455,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/956d6567682740ab6327a1e8357658d8f7b86421/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "14",
|
||||
"released": "2021-03-25T22:47:03+00:00"
|
||||
}
|
||||
}]
|
||||
},
|
||||
"windows-x86": {
|
||||
"java-runtime-alpha": [{
|
||||
"availability": {
|
||||
"group": 3889,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "85c8e2653b5197bbb0fc9257932b670c630e1c77",
|
||||
"size": 143786,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/85c8e2653b5197bbb0fc9257932b670c630e1c77/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "16.0.1.9.1",
|
||||
"released": "2021-05-10T16:45:24+00:00"
|
||||
}
|
||||
}],
|
||||
"jre-legacy": [{
|
||||
"availability": {
|
||||
"group": 4866,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "baa62193c2785f54d877d871d9859c67d65f08ba",
|
||||
"size": 80613,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/baa62193c2785f54d877d871d9859c67d65f08ba/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "8u51",
|
||||
"released": "2020-11-17T19:10:36+00:00"
|
||||
}
|
||||
}],
|
||||
"minecraft-java-exe": [{
|
||||
"availability": {
|
||||
"group": 5779,
|
||||
"progress": 100
|
||||
},
|
||||
"manifest": {
|
||||
"sha1": "5d5cf8b579ccf4a3567eef481b12479ee8c35369",
|
||||
"size": 455,
|
||||
"url": "https://launchermeta.mojang.com/v1/packages/5d5cf8b579ccf4a3567eef481b12479ee8c35369/manifest.json"
|
||||
},
|
||||
"version": {
|
||||
"name": "12",
|
||||
"released": "2021-03-25T22:47:02+00:00"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ public: /* methods */
|
||||
{
|
||||
if(m_expected.size() && m_expected != hash())
|
||||
{
|
||||
qWarning() << "Checksum mismatch, download is bad.";
|
||||
qWarning() << "Checksum mismatch. expected:" << m_expected << "got:" << hash();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -52,4 +52,4 @@ private: /* data */
|
||||
QCryptographicHash m_checksum;
|
||||
QByteArray m_expected;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ Download::Ptr Download::makeFile(QUrl url, QString path, Options options)
|
||||
Download * dl = new Download();
|
||||
dl->m_url = url;
|
||||
dl->m_options = options;
|
||||
dl->m_sink.reset(new FileSink(path));
|
||||
dl->m_sink.reset(new FileSink(path, options & Option::SetExecutable));
|
||||
return dl;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,8 @@ public: /* types */
|
||||
enum class Option
|
||||
{
|
||||
NoOptions = 0,
|
||||
AcceptLocalFiles = 1
|
||||
AcceptLocalFiles = 1,
|
||||
SetExecutable = 2
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
namespace Net {
|
||||
|
||||
FileSink::FileSink(QString filename)
|
||||
:m_filename(filename)
|
||||
FileSink::FileSink(QString filename, bool setExecutable)
|
||||
:m_filename(filename), setExecutable(setExecutable)
|
||||
{
|
||||
// nil
|
||||
}
|
||||
@ -94,6 +94,10 @@ JobStatus FileSink::finalize(QNetworkReply& reply)
|
||||
m_output_file->cancelWriting();
|
||||
return Job_Failed;
|
||||
}
|
||||
if(setExecutable) {
|
||||
auto permissions = QFile::permissions(m_filename);
|
||||
QFile::setPermissions(m_filename, permissions | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
}
|
||||
}
|
||||
// then get rid of the save file
|
||||
m_output_file.reset();
|
||||
|
@ -6,7 +6,7 @@ namespace Net {
|
||||
class FileSink : public Sink
|
||||
{
|
||||
public: /* con/des */
|
||||
FileSink(QString filename);
|
||||
FileSink(QString filename, bool setExecutable = false);
|
||||
virtual ~FileSink();
|
||||
|
||||
public: /* methods */
|
||||
@ -24,5 +24,6 @@ protected: /* data */
|
||||
QString m_filename;
|
||||
bool wroteAnyData = false;
|
||||
std::unique_ptr<QSaveFile> m_output_file;
|
||||
bool setExecutable = false;
|
||||
};
|
||||
}
|
||||
|
1
tools/CMakeLists.txt
Normal file
1
tools/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(grabjre)
|
12
tools/grabjre/CMakeLists.txt
Normal file
12
tools/grabjre/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
add_executable(GrabJRE GrabJRE.cpp)
|
||||
target_link_libraries(GrabJRE Launcher_logic Qt5::Core)
|
||||
|
||||
if(DEFINED Launcher_BINARY_RPATH)
|
||||
SET_TARGET_PROPERTIES(GrabJRE PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
|
||||
endif()
|
||||
|
||||
install(TARGETS GrabJRE
|
||||
BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime
|
||||
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
|
||||
)
|
60
tools/grabjre/GrabJRE.cpp
Normal file
60
tools/grabjre/GrabJRE.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <QTextStream>
|
||||
#include <QCoreApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
#include "mojang/PackageManifest.h"
|
||||
#include "mojang/PackageInstallTask.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
QCoreApplication app(argc, argv);
|
||||
QCoreApplication::setApplicationName("GrabJRE");
|
||||
QCoreApplication::setApplicationVersion("1.0");
|
||||
shared_qobject_ptr<QNetworkAccessManager> network(new QNetworkAccessManager());
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Stupid thing that grabs a piston package and updates a local folder with it");
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
parser.addPositionalArgument("url", "Source URL");
|
||||
parser.addPositionalArgument("version", "Source version");
|
||||
parser.addPositionalArgument("destination", "Destination folder to update");
|
||||
|
||||
parser.process(app);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 3) {
|
||||
parser.showHelp(1);
|
||||
}
|
||||
|
||||
// Run like ./GrabJRE "https://launchermeta.mojang.com/v1/packages/a1c15cc788f8893fba7e988eb27404772f699a84/manifest.json" "1.8.0_202" "test"
|
||||
|
||||
auto url = args[0];
|
||||
auto version = args[1];
|
||||
auto destination = args[2];
|
||||
PackageInstallTask installTask(
|
||||
network,
|
||||
Net::Mode::Online,
|
||||
version,
|
||||
url,
|
||||
destination
|
||||
);
|
||||
installTask.start();
|
||||
QCoreApplication::connect(&installTask, &PackageInstallTask::progress, [&](qint64 now, qint64 total) {
|
||||
static int percentage = 0;
|
||||
if(total > 0) {
|
||||
int newPercentage = (now * 100.0f) / double(total);
|
||||
if(newPercentage != percentage) {
|
||||
percentage = newPercentage;
|
||||
QTextStream(stdout) << "Downloading: " << percentage << "% done\n";
|
||||
}
|
||||
}
|
||||
});
|
||||
QCoreApplication::connect(&installTask, &PackageInstallTask::finished, [&]() {
|
||||
app.exit(installTask.wasSuccessful() ? 0 : 1);
|
||||
});
|
||||
app.exec();
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user